From c033f577d908832feede17c0b2f4ca11b3c2c15b Mon Sep 17 00:00:00 2001 From: Nikita Yadav Date: Thu, 28 Oct 2021 12:16:56 +0530 Subject: [PATCH] saving changes --- README.md | 2 +- lanforge/lanforge-scripts/.gitignore | 7 + .../lanforge-scripts/LANforge/Endpoint.pm | 2155 +++ lanforge/lanforge-scripts/LANforge/GuiJson.pm | 268 + .../lanforge-scripts/LANforge/JsonUtils.pm | 197 + lanforge/lanforge-scripts/LANforge/Port.pm | 1079 ++ lanforge/lanforge-scripts/LANforge/Test.pm | 80 + lanforge/lanforge-scripts/LANforge/Utils.pm | 1229 ++ lanforge/lanforge-scripts/LANforge/csv.pm | 123 + lanforge/lanforge-scripts/LICENSE | 29 + lanforge/lanforge-scripts/README.md | 296 + lanforge/lanforge-scripts/WlanPro.desktop | 15 + .../lanforge-scripts/add-dhcp-hostname.pl | 57 + lanforge/lanforge-scripts/adjust_apache.pl | 516 + lanforge/lanforge-scripts/adjust_ice.sh | 203 + .../antenna_stations_traffic.sh | 83 + lanforge/lanforge-scripts/ap_ctl.py | 498 + lanforge/lanforge-scripts/arp-flood.sh | 138 + lanforge/lanforge-scripts/associate_loop.sh | 158 + .../lanforge-scripts/attenuator_series.pl | 357 + .../attenuator_series_example.csv | 15 + lanforge/lanforge-scripts/auto-install-gui.py | 127 + lanforge/lanforge-scripts/brent_showport.sh | 61 + lanforge/lanforge-scripts/calc_autn.pl | 59 + lanforge/lanforge-scripts/cert-builder.sh | 36 + .../lanforge-scripts/check_large_files.bash | 1051 ++ lanforge/lanforge-scripts/connectTest.py | 509 + lanforge/lanforge-scripts/countdown.bash | 19 + lanforge/lanforge-scripts/cpu_stats.py | 149 + lanforge/lanforge-scripts/create-mounts.sh | 101 + .../create_file_assortment.bash | 25 + .../lanforge-scripts/create_group_matching.sh | 67 + lanforge/lanforge-scripts/db_sorter.sh | 28 + lanforge/lanforge-scripts/dhcp-lease-list.pl | 221 + lanforge/lanforge-scripts/emailHelper.py | 37 + lanforge/lanforge-scripts/every_vrf.sh | 27 + lanforge/lanforge-scripts/fanctl_lf0312.pl | 68 + lanforge/lanforge-scripts/four_million.pl | 92 + lanforge/lanforge-scripts/four_million.sh | 31 + lanforge/lanforge-scripts/ftp-upload.pl | 105 + lanforge/lanforge-scripts/genia-stations.sh | 55 + .../gnuradio-dependencies.bash | 96 + .../gnuradio-offline-install.bash | 62 + .../lanforge-scripts/gnuradio_urls_file.txt | 150 + lanforge/lanforge-scripts/gui/README.txt | 65 + .../gui/basic_regression.bash | 408 + lanforge/lanforge-scripts/gui/cicd/README.txt | 104 + .../cicd/ferndale-basic-01/TESTBED_INFO.txt | 7 + lanforge/lanforge-scripts/gui/cicd/jfrog.pl | 379 + .../lanforge-scripts/gui/cicd/testbed_poll.pl | 449 + lanforge/lanforge-scripts/gui/default.plot | 11 + .../lanforge-scripts/gui/default_group.plot | 11 + .../lanforge-scripts/gui/index_template.html | 58 + lanforge/lanforge-scripts/gui/kpi.java | 1386 ++ .../gui/lf_gui_report_summary.pl | 322 + lanforge/lanforge-scripts/gui/mini.plot | 17 + lanforge/lanforge-scripts/gui/mini_group.plot | 17 + .../gui/test_configs/64_sta_scenario.txt | 12 + .../AP-Auto-ap-auto-32-64-dual.txt | 171 + .../gui/test_configs/WCT-64sta.txt | 126 + .../gui/test_configs/dpt-pkt-sz.txt | 55 + .../gui/testbed_template.html | 70 + .../gui/testbeds/README_OPENWRT.txt | 23 + .../gui/testbeds/ferndale-basic-01/NOTES.txt | 10 + .../OpenWrt-overlay/etc/config/wireless | 57 + .../testbeds/ferndale-basic-01/ap-auto.txt | 107 + .../testbeds/ferndale-basic-01/dpt-pkt-sz.txt | 55 + .../testbeds/ferndale-basic-01/run_basic.bash | 71 + .../ferndale-basic-01/run_basic_fast.bash | 81 + .../testbeds/ferndale-basic-01/scenario.txt | 15 + .../ferndale-basic-01/scenario_small.txt | 15 + .../ferndale-basic-01/test_bed_cfg.bash | 61 + .../gui/testbeds/ferndale-basic-01/wct.txt | 323 + lanforge/lanforge-scripts/har-to-portal.pl | 213 + lanforge/lanforge-scripts/hires_cxreport.pl | 130 + lanforge/lanforge-scripts/hostap_timestamp.pl | 29 + lanforge/lanforge-scripts/imix.pl | 460 + .../influxgrafanaghost_fedora_install.sh | 46 + .../influxgrafanaghost_ubuntu_install.sh | 52 + lanforge/lanforge-scripts/jbr-test.sh | 26 + lanforge/lanforge-scripts/jlanpro_test.pl | 1043 ++ lanforge/lanforge-scripts/json/check_ports.pl | 78 + lanforge/lanforge-scripts/json/cx_test.pl | 318 + .../lanforge-scripts/json/cx_test_allsta.pl | 191 + .../json/cx_test_allsta_para.pl | 94 + .../lanforge-scripts/json/cx_test_helper.pl | 113 + lanforge/lanforge-scripts/json/dut_test.pl | 193 + .../lanforge-scripts/json/json_create_sta2.sh | 108 + .../lanforge-scripts/json/json_port_test.sh | 252 + .../lanforge-scripts/json/json_port_test2.sh | 104 + .../lanforge-scripts/json/json_port_test3.sh | 151 + lanforge/lanforge-scripts/json/json_test4.sh | 65 + lanforge/lanforge-scripts/json/l4_test.pl | 419 + lanforge/lanforge-scripts/json/port_test.pl | 331 + .../lanforge-scripts/json/port_toggle_test.pl | 206 + .../lanforge-scripts/json/set_port_aliases.pl | 183 + .../lanforge-scripts/json/show-chamber.sh | 13 + .../lanforge-scripts/json/test_sta_mode.pl | 62 + .../json/vap_stations_example.pl | 70 + lanforge/lanforge-scripts/l3_vid_group.pl | 454 + lanforge/lanforge-scripts/l3_video_em.pl | 913 ++ .../lanforge-scripts/label-printer/README.md | 48 + .../label-printer/label-printer-test.bash | 10 + .../label-printer/label-printer.py | 283 + .../label-printer/label-printer.service | 14 + lanforge/lanforge-scripts/lcheck.sh | 36 + lanforge/lanforge-scripts/lf_associate_ap.pl | 1811 +++ lanforge/lanforge-scripts/lf_attenmod.pl | 127 + lanforge/lanforge-scripts/lf_auto_wifi_cap.pl | 345 + lanforge/lanforge-scripts/lf_cmc_macvlan.pl | 802 + lanforge/lanforge-scripts/lf_create_bcast.pl | 304 + lanforge/lanforge-scripts/lf_curl.sh | 143 + .../lanforge-scripts/lf_cycle_wanlinks.pl | 52 + lanforge/lanforge-scripts/lf_endp_script.pl | 255 + lanforge/lanforge-scripts/lf_firemod.pl | 852 + lanforge/lanforge-scripts/lf_generic_ping.pl | 409 + lanforge/lanforge-scripts/lf_gui_cmd.pl | 241 + lanforge/lanforge-scripts/lf_ice.pl | 375 + lanforge/lanforge-scripts/lf_icemod.pl | 275 + lanforge/lanforge-scripts/lf_l4_auth.pl | 285 + .../lanforge-scripts/lf_l4_random_speeds.bash | 84 + lanforge/lanforge-scripts/lf_l4_reset.sh | 128 + lanforge/lanforge-scripts/lf_log_parse.pl | 22 + lanforge/lanforge-scripts/lf_loop_traffic.sh | 70 + lanforge/lanforge-scripts/lf_macvlan.pl | 1509 ++ lanforge/lanforge-scripts/lf_macvlan2.pl | 654 + lanforge/lanforge-scripts/lf_macvlan3.pl | 630 + lanforge/lanforge-scripts/lf_macvlan_l4.pl | 813 + .../lanforge-scripts/lf_macvlan_streams.pl | 723 + lanforge/lanforge-scripts/lf_mail.py | 100 + lanforge/lanforge-scripts/lf_many_conn.pl | 466 + lanforge/lanforge-scripts/lf_many_conn2.pl | 423 + lanforge/lanforge-scripts/lf_many_vphy.pl | 32 + .../lanforge-scripts/lf_max_cxs_v1_3000.pl | 1749 ++ lanforge/lanforge-scripts/lf_mcast.bash | 39 + lanforge/lanforge-scripts/lf_monitor.pl | 259 + lanforge/lanforge-scripts/lf_netoptics.pl | 762 + lanforge/lanforge-scripts/lf_nfs_io.pl | 1064 ++ .../lanforge-scripts/lf_parse_tshark_log.pl | 87 + lanforge/lanforge-scripts/lf_port_walk.pl | 279 + lanforge/lanforge-scripts/lf_portmod.pl | 700 + lanforge/lanforge-scripts/lf_show_events.pl | 83 + lanforge/lanforge-scripts/lf_sniff.py | 287 + lanforge/lanforge-scripts/lf_sta_name.pl | 190 + lanforge/lanforge-scripts/lf_staggered_dl.sh | 299 + lanforge/lanforge-scripts/lf_stress1.pl | 257 + lanforge/lanforge-scripts/lf_stress2.pl | 234 + lanforge/lanforge-scripts/lf_stress3.pl | 297 + lanforge/lanforge-scripts/lf_stress4.pl | 230 + lanforge/lanforge-scripts/lf_testmod.pl | 206 + lanforge/lanforge-scripts/lf_tos_plus_test.py | 836 + lanforge/lanforge-scripts/lf_tos_test.py | 549 + lanforge/lanforge-scripts/lf_tx_power.py | 2197 +++ lanforge/lanforge-scripts/lf_verify.pl | 817 + lanforge/lanforge-scripts/lf_voip.pl | 882 + lanforge/lanforge-scripts/lf_voip_test.pl | 1121 ++ lanforge/lanforge-scripts/lf_vue_mod.sh | 383 + lanforge/lanforge-scripts/lf_wifi_dot1x.bash | 212 + lanforge/lanforge-scripts/lf_wifi_fire.bash | 197 + lanforge/lanforge-scripts/lf_wifi_portal.bash | 129 + .../lanforge-scripts/lf_wifi_rest_example.pl | 597 + lanforge/lanforge-scripts/lf_zlt_binary.pl | 394 + lanforge/lanforge-scripts/lib_vrf.bash | 51 + lanforge/lanforge-scripts/license.txt | 26 + lanforge/lanforge-scripts/list_phy_sta.sh | 52 + lanforge/lanforge-scripts/mem-info.sh | 14 + .../lanforge-scripts/min_max_ave_station.pl | 257 + lanforge/lanforge-scripts/multi_endp.bash | 36 + lanforge/lanforge-scripts/multi_routers.pl | 181 + lanforge/lanforge-scripts/ocean-text.csv | 154 + lanforge/lanforge-scripts/openwrt_ctl.py | 366 + lanforge/lanforge-scripts/portal-check.pl | 235 + lanforge/lanforge-scripts/print_udev.sh | 31 + lanforge/lanforge-scripts/pulse_detect.py | 204 + .../py-dashboard/GhostRequest.py | 600 + .../py-dashboard/GrafanaRequest.py | 469 + .../py-dashboard/InfluxRequest.py | 119 + lanforge/lanforge-scripts/py-json/.gitignore | 3 + .../py-json/LANforge/LFRequest.py | 427 + .../py-json/LANforge/LFUtils.py | 808 + .../py-json/LANforge/__init__.py | 0 .../py-json/LANforge/add_dut.py | 93 + .../py-json/LANforge/add_file_endp.py | 77 + .../py-json/LANforge/add_l4_endp.py | 21 + .../py-json/LANforge/add_monitor.py | 7 + .../py-json/LANforge/add_sta.py | 54 + .../py-json/LANforge/add_vap.py | 30 + .../py-json/LANforge/lf_json_autogen.py | 13335 ++++++++++++++++ .../py-json/LANforge/lfcli_base.py | 659 + .../py-json/LANforge/pandas_extensions.py | 118 + .../py-json/LANforge/set_port.py | 122 + .../py-json/LANforge/set_wifi_radio.py | 17 + lanforge/lanforge-scripts/py-json/README.md | 181 + lanforge/lanforge-scripts/py-json/TODO.txt | 20 + lanforge/lanforge-scripts/py-json/__init__.py | 0 .../lanforge-scripts/py-json/base_profile.py | 122 + .../py-json/create_wanlink.py | 238 + .../py-json/cv_dut_profile.py | 148 + .../py-json/cv_test_manager.py | 534 + .../py-json/cv_test_reports.py | 17 + .../py-json/dataplane_test_profile.py | 46 + .../lanforge-scripts/py-json/dut_profile.py | 124 + .../py-json/fio_endp_profile.py | 194 + .../lanforge-scripts/py-json/gen_cxprofile.py | 608 + .../lanforge-scripts/py-json/http_profile.py | 202 + .../lanforge-scripts/py-json/l3_cxprofile.py | 586 + .../lanforge-scripts/py-json/l3_cxprofile2.py | 678 + .../lanforge-scripts/py-json/l4_cxprofile.py | 318 + .../lanforge-scripts/py-json/lf_attenmod.py | 78 + .../lanforge-scripts/py-json/lf_cv_base.py | 82 + lanforge/lanforge-scripts/py-json/lfdata.py | 91 + .../py-json/mac_vlan_profile.py | 197 + .../py-json/multicast_profile.py | 196 + .../py-json/old-examples/create_sta.py | 370 + .../py-json/old-examples/generic_cx.py | 77 + .../py-json/old-examples/test_l4.py | 58 + .../py-json/old-examples/wct-example.py | 160 + .../lanforge-scripts/py-json/port_utils.py | 45 + .../lanforge-scripts/py-json/qvlan_profile.py | 171 + lanforge/lanforge-scripts/py-json/realm.py | 1010 ++ .../lanforge-scripts/py-json/realm_test.py | 139 + .../lanforge-scripts/py-json/show_ports.py | 36 + .../py-json/station_profile.py | 568 + .../lanforge-scripts/py-json/test_base.py | 68 + .../py-json/test_group_profile.py | 87 + .../py-json/test_histogram.py | 44 + .../lanforge-scripts/py-json/test_utility.py | 327 + .../lanforge-scripts/py-json/vap_profile.py | 372 + .../lanforge-scripts/py-json/vr_profile2.py | 772 + .../py-json/wifi_monitor_profile.py | 135 + .../py-json/wlan_theoretical_sta.py | 2231 +++ .../py-json/ws-sta-monitor.py | 361 + .../py-json/ws_generic_monitor.py | 25 + .../lanforge-scripts/py-scripts/.gitignore | 2 + .../py-scripts/CT_US_001.bash | 74 + .../lanforge-scripts/py-scripts/README.md | 268 + .../lanforge-scripts/py-scripts/__init__.py | 0 .../CandelaLogo2-90dpi-200x90-trans.png | Bin 0 -> 9122 bytes .../py-scripts/artifacts/banner.png | Bin 0 -> 204159 bytes .../artifacts/candela_swirl_small-72h.png | Bin 0 -> 12989 bytes .../py-scripts/artifacts/report.css | 376 + .../py-scripts/check_argparse.py | 57 + .../py-scripts/cicd_TipIntegration.py | 547 + .../py-scripts/cicd_testrail.py | 203 + .../py-scripts/cicd_testrailAndInfraSetup.py | 400 + .../py-scripts/connection_test.py | 278 + .../py-scripts/create_bond.py | 98 + .../py-scripts/create_bridge.py | 143 + .../py-scripts/create_chamberview.py | 218 + .../py-scripts/create_chamberview_dut.py | 191 + .../lanforge-scripts/py-scripts/create_l3.py | 129 + .../py-scripts/create_l3_stations.py | 198 + .../lanforge-scripts/py-scripts/create_l4.py | 188 + .../py-scripts/create_macvlan.py | 192 + .../py-scripts/create_qvlan.py | 161 + .../py-scripts/create_station.py | 179 + .../py-scripts/create_station_from_df.py | 139 + .../lanforge-scripts/py-scripts/create_vap.py | 200 + .../lanforge-scripts/py-scripts/create_vr.py | 185 + .../py-scripts/create_wanlink.py | 314 + .../py-scripts/csv_convert.py | 119 + .../py-scripts/csv_processor.py | 154 + .../py-scripts/csv_to_grafana.py | 269 + .../py-scripts/csv_to_influx.py | 63 + .../py-scripts/ct_002_igg.json | 38 + .../py-scripts/ct_004_igg.json | 39 + .../lanforge-scripts/py-scripts/ct_igg.json | 39 + .../py-scripts/ct_us_001_orig.json | 587 + .../py-scripts/ct_us_001_tests_igg.json | 455 + .../py-scripts/ct_us_002_orig.json | 341 + .../py-scripts/ct_us_003_orig.json | 72 + .../py-scripts/ct_us_004_orig.json | 472 + .../cv_examples/ferndale_ucentral.bash | 78 + .../lanforge-scripts/py-scripts/cv_manager.py | 49 + .../py-scripts/cv_to_grafana.py | 343 + .../lanforge-scripts/py-scripts/docstrings.py | 47 + .../py-scripts/download_test.py | 85 + .../py-scripts/event_breaker.py | 133 + .../py-scripts/event_flood.py | 116 + .../example-configs/mesh-ferndale-cfg.txt | 47 + .../example-configs/tr398-ferndale-ac-cfg.txt | 43 + .../tr398v2-ferndale-ac-cfg.txt | 193 + .../py-scripts/example_security_connection.py | 134 + .../lanforge-scripts/py-scripts/ftp_html.py | 380 + .../py-scripts/ghost_profile.py | 220 + .../py-scripts/grafana_profile.py | 151 + .../py-scripts/html_template.py | 1005 ++ .../lanforge-scripts/py-scripts/influx.py | 82 + .../lanforge-scripts/py-scripts/influx2.py | 86 + .../py-scripts/influxgrafanaghost.sh | 61 + .../py-scripts/layer3_test.py | 168 + .../py-scripts/layer4_test.py | 293 + .../py-scripts/lf_ap_auto_test.py | 379 + .../py-scripts/lf_atten_mod_test.py | 84 + .../lanforge-scripts/py-scripts/lf_check.json | 61 + .../py-scripts/lf_check_95_6.json | 135 + .../py-scripts/lf_check_config_template.ini | 187 + .../py-scripts/lf_check_ct_us_001.json | 60 + .../py-scripts/lf_check_orig.py | 1219 ++ .../lanforge-scripts/py-scripts/lf_csv.py | 80 + .../py-scripts/lf_dataplane_test.py | 410 + .../py-scripts/lf_dfs_test.py | 2845 ++++ .../py-scripts/lf_dut_sta_vap_test.py | 481 + .../lanforge-scripts/py-scripts/lf_ftp.py | 859 + .../py-scripts/lf_ftp_test.py | 557 + .../lanforge-scripts/py-scripts/lf_graph.py | 469 + .../py-scripts/lf_header_template.py | 21 + .../py-scripts/lf_influx_db.json | 12 + .../py-scripts/lf_mesh_test.py | 286 + .../py-scripts/lf_multipsk.py | 403 + .../lanforge-scripts/py-scripts/lf_report.py | 608 + .../py-scripts/lf_report_test.py | 217 + .../py-scripts/lf_rvr_test.py | 284 + .../py-scripts/lf_rx_sensitivity_config.json | 19 + .../py-scripts/lf_rx_sensitivity_test.py | 390 + .../py-scripts/lf_sniff_radio.py | 126 + .../py-scripts/lf_snp_test.py | 2578 +++ .../py-scripts/lf_tr398_test.py | 344 + .../py-scripts/lf_tr398v2_test.py | 446 + .../lanforge-scripts/py-scripts/lf_webpage.py | 802 + .../py-scripts/lf_wifi_capacity_test.py | 582 + .../py-scripts/measure_station_time_up.py | 194 + .../py-scripts/modify_station.py | 159 + .../lanforge-scripts/py-scripts/modify_vap.py | 153 + .../lanforge-scripts/py-scripts/port_probe.py | 157 + .../py-scripts/recordinflux.py | 87 + .../py-scripts/regression_test.rc.example | 17 + .../py-scripts/regression_test.sh | 568 + .../py-scripts/run_cv_scenario.py | 218 + .../py-scripts/rvr_scenario.py | 228 + .../py-scripts/sandbox/__init__.py | 0 .../py-scripts/sandbox/csv_sqlite.py | 79 + .../py-scripts/sandbox/ct_001_igg.json | 39 + .../py-scripts/sandbox/ct_id_102.json | 139 + .../py-scripts/sandbox/ct_test_lf_check.json | 30 + .../py-scripts/sandbox/ct_us_001.json | 687 + .../py-scripts/sandbox/ct_us_001_igg.json | 126 + .../py-scripts/sandbox/ct_us_001_rig.json | 56 + .../py-scripts/sandbox/ct_us_002.json | 346 + .../py-scripts/sandbox/ct_us_003.json | 167 + .../py-scripts/sandbox/ct_us_004.json | 481 + .../py-scripts/sandbox/jbr_jag_test.py | 250 + .../py-scripts/sandbox/kpi_3.py | 54 + .../py-scripts/sandbox/kpi_csv_sq.py | 281 + .../py-scripts/sandbox/kpi_sqlite.py | 77 + .../py-scripts/sandbox/lf_check_ap.py | 134 + .../py-scripts/sandbox/lf_check_igg.py | 1541 ++ .../py-scripts/sandbox/lf_hackrf3.py | 257 + .../sandbox/lf_json_autogen_test.py | 94 + .../sandbox/test_ipv4_variable_time2.py | 297 + .../py-scripts/sandbox/update_dut.py | 180 + .../lanforge-scripts/py-scripts/scenario.py | 80 + .../scripts_deprecated/csv_to_influx.py | 170 + .../py-scripts/sta_connect.py | 556 + .../py-scripts/sta_connect2.py | 498 + .../py-scripts/sta_connect_bssid_mac.py | 130 + .../py-scripts/sta_connect_example.py | 82 + .../py-scripts/sta_connect_multi_example.py | 84 + .../py-scripts/sta_scan_test.py | 164 + .../py-scripts/station_layer3.py | 109 + .../py-scripts/stations_connected.py | 58 + .../py-scripts/test_1k_clients_jedtest.py | 284 + .../py-scripts/test_all_scripts.sh | 100 + .../py-scripts/test_client_admission.py | 127 + .../py-scripts/test_fileio.py | 788 + .../py-scripts/test_generic.py | 364 + .../py-scripts/test_ip_connection.py | 294 + .../py-scripts/test_ip_variable_time.py | 520 + .../py-scripts/test_ipv4_connection.py | 228 + .../py-scripts/test_ipv4_l4.py | 248 + .../py-scripts/test_ipv4_l4_ftp_upload.py | 240 + .../test_ipv4_l4_ftp_urls_per_ten.py | 251 + .../py-scripts/test_ipv4_l4_ftp_wifi.py | 254 + .../py-scripts/test_ipv4_l4_urls_per_ten.py | 299 + .../py-scripts/test_ipv4_l4_wifi.py | 246 + .../py-scripts/test_ipv4_ps.py | 250 + .../py-scripts/test_ipv4_ttls.py | 413 + .../py-scripts/test_ipv4_variable_time.py | 496 + .../py-scripts/test_ipv6_connection.py | 224 + .../py-scripts/test_ipv6_variable_time.py | 309 + .../py-scripts/test_l3_WAN_LAN.py | 251 + .../py-scripts/test_l3_longevity.py | 1681 ++ .../py-scripts/test_l3_powersave_traffic.py | 173 + .../py-scripts/test_l3_scenario_throughput.py | 335 + .../py-scripts/test_l3_unicast_traffic_gen.py | 411 + .../lanforge-scripts/py-scripts/test_l4.py | 402 + .../py-scripts/test_status_msg.py | 345 + .../py-scripts/test_wanlink.py | 112 + .../py-scripts/test_wpa_passphrases.py | 40 + .../lanforge-scripts/py-scripts/testgroup.py | 167 + .../lanforge-scripts/py-scripts/testgroup2.py | 325 + .../tip-cicd-sanity/Nightly_Sanity.py | 2034 +++ .../py-scripts/tip-cicd-sanity/README.md | 66 + .../tip-cicd-sanity/Throughput_Test.py | 561 + .../py-scripts/tip-cicd-sanity/ap_ssh.py | 117 + .../py-scripts/tip-cicd-sanity/cloudsdk.py | 299 + .../py-scripts/tip-cicd-sanity/eap_connect.py | 375 + .../py-scripts/tip-cicd-sanity/lab_ap_info.py | 1384 ++ .../reports/report_template.php | 1838 +++ .../tip-cicd-sanity/sanity_status.json | 1 + .../single_client_throughput.py | 1066 ++ .../templates/ap_profile_template.json | 1 + .../templates/radius_profile_template.json | 1 + .../templates/ssid_profile_template.json | 1 + .../tip-cicd-sanity/testrail_api.py | 194 + .../tip-cicd-sanity/throughput_profiles.py | 220 + .../py-scripts/tip_station_powersave.py | 414 + .../py-scripts/tools/ct_001_AX88U_dut.json | 26 + .../py-scripts/tools/ct_002_AX12_dut.json | 28 + .../py-scripts/tools/ct_003_LANforge_dut.json | 26 + .../py-scripts/tools/ct_004_AX88U_dut.json | 26 + .../py-scripts/tools/ct_us_001_rig.json | 52 + .../py-scripts/tools/ct_us_001_scripts.json | 70 + .../py-scripts/tools/ct_us_001_tests.json | 502 + .../py-scripts/tools/ct_us_002_rig.json | 53 + .../py-scripts/tools/ct_us_002_tests.json | 298 + .../py-scripts/tools/ct_us_003_rig.json | 46 + .../py-scripts/tools/ct_us_004_rig.json | 57 + .../py-scripts/tools/ct_us_004_tests.json | 611 + .../py-scripts/tools/lf_check.py | 2016 +++ .../py-scripts/tools/lf_dash.py | 273 + .../py-scripts/tools/lf_qa.py | 607 + .../py-scripts/update_dependencies.py | 57 + .../py-scripts/vap_stations_example.py | 53 + .../py-scripts/video_rates.py | 186 + .../py-scripts/wifi_cap_to_grafana.sh | 61 + .../wifi_capacity_dataplane_ghost.sh | 51 + .../py-scripts/wlan_capacity_calculator.py | 250 + .../py-scripts/ws_generic_monitor_test.py | 48 + lanforge/lanforge-scripts/quick-copy.sh | 8 + lanforge/lanforge-scripts/rand_nc.pl | 85 + lanforge/lanforge-scripts/rand_nmap.pl | 70 + .../lanforge-scripts/reconfigure_apache.pl | 137 + lanforge/lanforge-scripts/rvr-helper.pl | 33 + lanforge/lanforge-scripts/script_test.pl | 650 + lanforge/lanforge-scripts/sensorz.pl | 49 + .../lanforge-scripts/setup-concentrator.sh | 358 + .../lanforge-scripts/setup-concentrator2.sh | 466 + .../lanforge-scripts/show-port-from-json.pl | 50 + lanforge/lanforge-scripts/sixtyk-udp.pl | 55 + lanforge/lanforge-scripts/speedtest-cli | 2000 +++ lanforge/lanforge-scripts/station-toggle.sh | 46 + .../lanforge-scripts/stationStressTest.py | 422 + lanforge/lanforge-scripts/strongswan-config | 10 + lanforge/lanforge-scripts/sysmon.sh | 80 + .../lanforge-scripts/telnet_expect_wrapper.pl | 9 + lanforge/lanforge-scripts/ten_sta_tos.bash | 157 + lanforge/lanforge-scripts/test_perl_json.sh | 63 + lanforge/lanforge-scripts/test_refcnt.pl | 123 + lanforge/lanforge-scripts/timed_ice_pause.sh | 189 + lanforge/lanforge-scripts/to_pip.sh | 392 + lanforge/lanforge-scripts/topmon.sh | 31 + lanforge/lanforge-scripts/tos_plus_auto.py | 276 + lanforge/lanforge-scripts/track_call.sh | 163 + lanforge/lanforge-scripts/track_call_end.sh | 230 + lanforge/lanforge-scripts/updateTest.bash | 192 + .../lanforge-scripts/upgrade_over_dut.bash | 48 + lanforge/lanforge-scripts/wait_on_ports.pl | 330 + lanforge/lanforge-scripts/wifi-event-histo.sh | 70 + .../lanforge-scripts/wifi-roaming-times.pl | 131 + .../lanforge-scripts/wifi_ctl_9800_3504.py | 1496 ++ lanforge/lanforge-scripts/wifi_diag/Packet.pm | 419 + .../lanforge-scripts/wifi_diag/PeerConn.pm | 181 + lanforge/lanforge-scripts/wifi_diag/Tid.pm | 401 + .../wifi_diag/do_wifi_diag.bash | 95 + .../wifi_diag/wifi_diag_python/Dataplot.py | 42 + .../wifi_diag/wifi_diag_python/htmlText.py | 241 + .../wifi_diag/wifi_diag_python/scratch.py | 832 + .../wifi_diag/wifi_pcap_diag.pl | 1011 ++ .../lanforge-scripts/wifi_log_separator.bash | 24 + lanforge/lanforge-scripts/wlanpro_test.pl | 1030 ++ lanforge/lanforge-scripts/wpro.sh | 37 + libs/controller/controller_1x/controller.py | 4 +- .../CandelaLogo2-90dpi-200x90-trans.png | Bin 0 -> 9122 bytes .../CenturyGothic.woff | Bin 0 -> 70128 bytes .../candela_swirl_small-72h.png | Bin 0 -> 12989 bytes .../canvil.ico | Bin 0 -> 766 bytes .../custom.css | 11 + .../index-print.html | 146 + .../index.html | 147 + .../logo.png | Bin 0 -> 9122 bytes ...te-vs-range-report-2021-10-26-03-39-37.pdf | Bin 0 -> 248403 bytes .../report.css | 256 + .../report_banner-1000x205.jpg | Bin 0 -> 30826 bytes .../CandelaLogo2-90dpi-200x90-trans.png | Bin 0 -> 9122 bytes .../CenturyGothic.woff | Bin 0 -> 70128 bytes .../candela_swirl_small-72h.png | Bin 0 -> 12989 bytes .../canvil.ico | Bin 0 -> 766 bytes .../custom.css | 11 + .../index-print.html | 146 + .../index.html | 147 + .../logo.png | Bin 0 -> 9122 bytes ...te-vs-range-report-2021-10-26-04-00-20.pdf | Bin 0 -> 247519 bytes .../report.css | 256 + .../report_banner-1000x205.jpg | Bin 0 -> 30826 bytes .../CandelaLogo2-90dpi-200x90-trans.png | Bin 0 -> 9122 bytes .../CenturyGothic.woff | Bin 0 -> 70128 bytes .../candela_swirl_small-72h.png | Bin 0 -> 12989 bytes .../canvil.ico | Bin 0 -> 766 bytes .../custom.css | 11 + .../index-print.html | 146 + .../index.html | 147 + .../logo.png | Bin 0 -> 9122 bytes ...te-vs-range-report-2021-10-26-10-33-02.pdf | Bin 0 -> 248168 bytes .../report.css | 256 + .../report_banner-1000x205.jpg | Bin 0 -> 30826 bytes .../CandelaLogo2-90dpi-200x90-trans.png | Bin 0 -> 9122 bytes .../CenturyGothic.woff | Bin 0 -> 70128 bytes .../candela_swirl_small-72h.png | Bin 0 -> 12989 bytes .../canvil.ico | Bin 0 -> 766 bytes .../chart-2-print.png | Bin 0 -> 19007 bytes .../chart-2.png | Bin 0 -> 19592 bytes .../chart-3-print.png | Bin 0 -> 12523 bytes .../chart-3.png | Bin 0 -> 12836 bytes .../chart-4-print.png | Bin 0 -> 12164 bytes .../chart-4.png | Bin 0 -> 14381 bytes .../chart-5-print.png | Bin 0 -> 12849 bytes .../chart-5.png | Bin 0 -> 13226 bytes .../chart-6-print.png | Bin 0 -> 13312 bytes .../chart-6.png | Bin 0 -> 13698 bytes .../chart-7-print.png | Bin 0 -> 15065 bytes .../chart-7.png | Bin 0 -> 16069 bytes .../chart-8-print.png | Bin 0 -> 12620 bytes .../chart-8.png | Bin 0 -> 13234 bytes ...a-Endpoint_RX_Packet_Loss_Percentage-1.csv | 63 + .../csv-data/data-Realtime_Throughput-1.csv | 74 + .../csv-data/data-Rx_Errors-1.csv | 63 + ...ta-TX_Completion_vs_Calculated_Power-1.csv | 3 + .../data-TX_Completion_vs_LANforge_RSSI-1.csv | 3 + ...data-Throughput_vs_Calculated_Signal-1.csv | 3 + .../data-Throughput_vs_LANforge_RSSI-1.csv | 3 + ...-WiFi_Link_Rate_vs_Calculated_Signal-1.csv | 3 + ...data-WiFi_Link_Rate_vs_LANforge_RSSI-1.csv | 3 + .../csv-data/meta-info.txt | 18 + .../custom.css | 11 + .../index-print.html | 535 + .../index.html | 540 + .../kpi-chart-0-print.png | Bin 0 -> 12585 bytes .../kpi-chart-0.png | Bin 0 -> 13175 bytes .../kpi-chart-1-print.png | Bin 0 -> 12173 bytes .../kpi-chart-1.png | Bin 0 -> 14657 bytes .../rate-vs-range-2021-10-26-11-01-44/kpi.csv | 2 + .../logo.png | Bin 0 -> 9122 bytes ...te-vs-range-report-2021-10-26-11-00-32.pdf | Bin 0 -> 1965544 bytes .../report.css | 256 + .../report_banner-1000x205.jpg | Bin 0 -> 30826 bytes .../text-csv-0.csv | 2 + .../text-csv-1.csv | 2 + .../text-tab-0.csv | 2 + .../text-tab-1.csv | 2 + .../chart-2-print.png | Bin 0 -> 19213 bytes .../chart-2.png | Bin 0 -> 19724 bytes .../chart-5.png | Bin 0 -> 13226 bytes .../chart-6-print.png | Bin 0 -> 13312 bytes .../chart-7-print.png | Bin 0 -> 15377 bytes ...a-Endpoint_RX_Packet_Loss_Percentage-1.csv | 63 + .../data-TX_Completion_vs_LANforge_RSSI-1.csv | 3 + ...-WiFi_Link_Rate_vs_Calculated_Signal-1.csv | 0 .../csv-data/meta-info.txt | 18 + .../rate-vs-range-2021-10-27-12-09-32/kpi.csv | 2 + .../logo.png | Bin 0 -> 9122 bytes tests/configuration.py | 45 +- .../wpa2_personal/test_bridge_spatial.py | 77 +- tests/e2e/basic/README.md | 2 +- 564 files changed, 154848 insertions(+), 8 deletions(-) create mode 100644 lanforge/lanforge-scripts/.gitignore create mode 100644 lanforge/lanforge-scripts/LANforge/Endpoint.pm create mode 100644 lanforge/lanforge-scripts/LANforge/GuiJson.pm create mode 100644 lanforge/lanforge-scripts/LANforge/JsonUtils.pm create mode 100644 lanforge/lanforge-scripts/LANforge/Port.pm create mode 100644 lanforge/lanforge-scripts/LANforge/Test.pm create mode 100644 lanforge/lanforge-scripts/LANforge/Utils.pm create mode 100644 lanforge/lanforge-scripts/LANforge/csv.pm create mode 100644 lanforge/lanforge-scripts/LICENSE create mode 100644 lanforge/lanforge-scripts/README.md create mode 100755 lanforge/lanforge-scripts/WlanPro.desktop create mode 100755 lanforge/lanforge-scripts/add-dhcp-hostname.pl create mode 100755 lanforge/lanforge-scripts/adjust_apache.pl create mode 100755 lanforge/lanforge-scripts/adjust_ice.sh create mode 100755 lanforge/lanforge-scripts/antenna_stations_traffic.sh create mode 100755 lanforge/lanforge-scripts/ap_ctl.py create mode 100755 lanforge/lanforge-scripts/arp-flood.sh create mode 100755 lanforge/lanforge-scripts/associate_loop.sh create mode 100755 lanforge/lanforge-scripts/attenuator_series.pl create mode 100644 lanforge/lanforge-scripts/attenuator_series_example.csv create mode 100755 lanforge/lanforge-scripts/auto-install-gui.py create mode 100755 lanforge/lanforge-scripts/brent_showport.sh create mode 100755 lanforge/lanforge-scripts/calc_autn.pl create mode 100755 lanforge/lanforge-scripts/cert-builder.sh create mode 100755 lanforge/lanforge-scripts/check_large_files.bash create mode 100755 lanforge/lanforge-scripts/connectTest.py create mode 100755 lanforge/lanforge-scripts/countdown.bash create mode 100644 lanforge/lanforge-scripts/cpu_stats.py create mode 100755 lanforge/lanforge-scripts/create-mounts.sh create mode 100755 lanforge/lanforge-scripts/create_file_assortment.bash create mode 100755 lanforge/lanforge-scripts/create_group_matching.sh create mode 100755 lanforge/lanforge-scripts/db_sorter.sh create mode 100755 lanforge/lanforge-scripts/dhcp-lease-list.pl create mode 100755 lanforge/lanforge-scripts/emailHelper.py create mode 100755 lanforge/lanforge-scripts/every_vrf.sh create mode 100755 lanforge/lanforge-scripts/fanctl_lf0312.pl create mode 100755 lanforge/lanforge-scripts/four_million.pl create mode 100755 lanforge/lanforge-scripts/four_million.sh create mode 100755 lanforge/lanforge-scripts/ftp-upload.pl create mode 100755 lanforge/lanforge-scripts/genia-stations.sh create mode 100755 lanforge/lanforge-scripts/gnuradio-dependencies.bash create mode 100755 lanforge/lanforge-scripts/gnuradio-offline-install.bash create mode 100644 lanforge/lanforge-scripts/gnuradio_urls_file.txt create mode 100644 lanforge/lanforge-scripts/gui/README.txt create mode 100755 lanforge/lanforge-scripts/gui/basic_regression.bash create mode 100644 lanforge/lanforge-scripts/gui/cicd/README.txt create mode 100644 lanforge/lanforge-scripts/gui/cicd/ferndale-basic-01/TESTBED_INFO.txt create mode 100755 lanforge/lanforge-scripts/gui/cicd/jfrog.pl create mode 100755 lanforge/lanforge-scripts/gui/cicd/testbed_poll.pl create mode 100644 lanforge/lanforge-scripts/gui/default.plot create mode 100644 lanforge/lanforge-scripts/gui/default_group.plot create mode 100644 lanforge/lanforge-scripts/gui/index_template.html create mode 100644 lanforge/lanforge-scripts/gui/kpi.java create mode 100755 lanforge/lanforge-scripts/gui/lf_gui_report_summary.pl create mode 100644 lanforge/lanforge-scripts/gui/mini.plot create mode 100644 lanforge/lanforge-scripts/gui/mini_group.plot create mode 100644 lanforge/lanforge-scripts/gui/test_configs/64_sta_scenario.txt create mode 100644 lanforge/lanforge-scripts/gui/test_configs/AP-Auto-ap-auto-32-64-dual.txt create mode 100644 lanforge/lanforge-scripts/gui/test_configs/WCT-64sta.txt create mode 100644 lanforge/lanforge-scripts/gui/test_configs/dpt-pkt-sz.txt create mode 100644 lanforge/lanforge-scripts/gui/testbed_template.html create mode 100644 lanforge/lanforge-scripts/gui/testbeds/README_OPENWRT.txt create mode 100644 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/NOTES.txt create mode 100644 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/OpenWrt-overlay/etc/config/wireless create mode 100644 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/ap-auto.txt create mode 100644 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/dpt-pkt-sz.txt create mode 100755 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/run_basic.bash create mode 100755 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/run_basic_fast.bash create mode 100644 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/scenario.txt create mode 100644 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/scenario_small.txt create mode 100644 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/test_bed_cfg.bash create mode 100644 lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/wct.txt create mode 100755 lanforge/lanforge-scripts/har-to-portal.pl create mode 100755 lanforge/lanforge-scripts/hires_cxreport.pl create mode 100755 lanforge/lanforge-scripts/hostap_timestamp.pl create mode 100755 lanforge/lanforge-scripts/imix.pl create mode 100755 lanforge/lanforge-scripts/influxgrafanaghost_fedora_install.sh create mode 100755 lanforge/lanforge-scripts/influxgrafanaghost_ubuntu_install.sh create mode 100755 lanforge/lanforge-scripts/jbr-test.sh create mode 100755 lanforge/lanforge-scripts/jlanpro_test.pl create mode 100755 lanforge/lanforge-scripts/json/check_ports.pl create mode 100755 lanforge/lanforge-scripts/json/cx_test.pl create mode 100755 lanforge/lanforge-scripts/json/cx_test_allsta.pl create mode 100755 lanforge/lanforge-scripts/json/cx_test_allsta_para.pl create mode 100755 lanforge/lanforge-scripts/json/cx_test_helper.pl create mode 100755 lanforge/lanforge-scripts/json/dut_test.pl create mode 100755 lanforge/lanforge-scripts/json/json_create_sta2.sh create mode 100755 lanforge/lanforge-scripts/json/json_port_test.sh create mode 100755 lanforge/lanforge-scripts/json/json_port_test2.sh create mode 100755 lanforge/lanforge-scripts/json/json_port_test3.sh create mode 100755 lanforge/lanforge-scripts/json/json_test4.sh create mode 100755 lanforge/lanforge-scripts/json/l4_test.pl create mode 100755 lanforge/lanforge-scripts/json/port_test.pl create mode 100755 lanforge/lanforge-scripts/json/port_toggle_test.pl create mode 100755 lanforge/lanforge-scripts/json/set_port_aliases.pl create mode 100755 lanforge/lanforge-scripts/json/show-chamber.sh create mode 100755 lanforge/lanforge-scripts/json/test_sta_mode.pl create mode 100755 lanforge/lanforge-scripts/json/vap_stations_example.pl create mode 100755 lanforge/lanforge-scripts/l3_vid_group.pl create mode 100755 lanforge/lanforge-scripts/l3_video_em.pl create mode 100644 lanforge/lanforge-scripts/label-printer/README.md create mode 100755 lanforge/lanforge-scripts/label-printer/label-printer-test.bash create mode 100755 lanforge/lanforge-scripts/label-printer/label-printer.py create mode 100644 lanforge/lanforge-scripts/label-printer/label-printer.service create mode 100755 lanforge/lanforge-scripts/lcheck.sh create mode 100755 lanforge/lanforge-scripts/lf_associate_ap.pl create mode 100755 lanforge/lanforge-scripts/lf_attenmod.pl create mode 100755 lanforge/lanforge-scripts/lf_auto_wifi_cap.pl create mode 100755 lanforge/lanforge-scripts/lf_cmc_macvlan.pl create mode 100755 lanforge/lanforge-scripts/lf_create_bcast.pl create mode 100755 lanforge/lanforge-scripts/lf_curl.sh create mode 100755 lanforge/lanforge-scripts/lf_cycle_wanlinks.pl create mode 100755 lanforge/lanforge-scripts/lf_endp_script.pl create mode 100755 lanforge/lanforge-scripts/lf_firemod.pl create mode 100755 lanforge/lanforge-scripts/lf_generic_ping.pl create mode 100755 lanforge/lanforge-scripts/lf_gui_cmd.pl create mode 100755 lanforge/lanforge-scripts/lf_ice.pl create mode 100755 lanforge/lanforge-scripts/lf_icemod.pl create mode 100755 lanforge/lanforge-scripts/lf_l4_auth.pl create mode 100755 lanforge/lanforge-scripts/lf_l4_random_speeds.bash create mode 100755 lanforge/lanforge-scripts/lf_l4_reset.sh create mode 100755 lanforge/lanforge-scripts/lf_log_parse.pl create mode 100755 lanforge/lanforge-scripts/lf_loop_traffic.sh create mode 100755 lanforge/lanforge-scripts/lf_macvlan.pl create mode 100755 lanforge/lanforge-scripts/lf_macvlan2.pl create mode 100755 lanforge/lanforge-scripts/lf_macvlan3.pl create mode 100755 lanforge/lanforge-scripts/lf_macvlan_l4.pl create mode 100755 lanforge/lanforge-scripts/lf_macvlan_streams.pl create mode 100755 lanforge/lanforge-scripts/lf_mail.py create mode 100755 lanforge/lanforge-scripts/lf_many_conn.pl create mode 100755 lanforge/lanforge-scripts/lf_many_conn2.pl create mode 100755 lanforge/lanforge-scripts/lf_many_vphy.pl create mode 100755 lanforge/lanforge-scripts/lf_max_cxs_v1_3000.pl create mode 100755 lanforge/lanforge-scripts/lf_mcast.bash create mode 100755 lanforge/lanforge-scripts/lf_monitor.pl create mode 100755 lanforge/lanforge-scripts/lf_netoptics.pl create mode 100755 lanforge/lanforge-scripts/lf_nfs_io.pl create mode 100755 lanforge/lanforge-scripts/lf_parse_tshark_log.pl create mode 100755 lanforge/lanforge-scripts/lf_port_walk.pl create mode 100755 lanforge/lanforge-scripts/lf_portmod.pl create mode 100755 lanforge/lanforge-scripts/lf_show_events.pl create mode 100755 lanforge/lanforge-scripts/lf_sniff.py create mode 100755 lanforge/lanforge-scripts/lf_sta_name.pl create mode 100755 lanforge/lanforge-scripts/lf_staggered_dl.sh create mode 100755 lanforge/lanforge-scripts/lf_stress1.pl create mode 100755 lanforge/lanforge-scripts/lf_stress2.pl create mode 100755 lanforge/lanforge-scripts/lf_stress3.pl create mode 100755 lanforge/lanforge-scripts/lf_stress4.pl create mode 100755 lanforge/lanforge-scripts/lf_testmod.pl create mode 100755 lanforge/lanforge-scripts/lf_tos_plus_test.py create mode 100755 lanforge/lanforge-scripts/lf_tos_test.py create mode 100755 lanforge/lanforge-scripts/lf_tx_power.py create mode 100755 lanforge/lanforge-scripts/lf_verify.pl create mode 100755 lanforge/lanforge-scripts/lf_voip.pl create mode 100755 lanforge/lanforge-scripts/lf_voip_test.pl create mode 100755 lanforge/lanforge-scripts/lf_vue_mod.sh create mode 100755 lanforge/lanforge-scripts/lf_wifi_dot1x.bash create mode 100755 lanforge/lanforge-scripts/lf_wifi_fire.bash create mode 100755 lanforge/lanforge-scripts/lf_wifi_portal.bash create mode 100755 lanforge/lanforge-scripts/lf_wifi_rest_example.pl create mode 100755 lanforge/lanforge-scripts/lf_zlt_binary.pl create mode 100755 lanforge/lanforge-scripts/lib_vrf.bash create mode 100644 lanforge/lanforge-scripts/license.txt create mode 100755 lanforge/lanforge-scripts/list_phy_sta.sh create mode 100755 lanforge/lanforge-scripts/mem-info.sh create mode 100755 lanforge/lanforge-scripts/min_max_ave_station.pl create mode 100755 lanforge/lanforge-scripts/multi_endp.bash create mode 100755 lanforge/lanforge-scripts/multi_routers.pl create mode 100644 lanforge/lanforge-scripts/ocean-text.csv create mode 100755 lanforge/lanforge-scripts/openwrt_ctl.py create mode 100755 lanforge/lanforge-scripts/portal-check.pl create mode 100755 lanforge/lanforge-scripts/print_udev.sh create mode 100755 lanforge/lanforge-scripts/pulse_detect.py create mode 100644 lanforge/lanforge-scripts/py-dashboard/GhostRequest.py create mode 100644 lanforge/lanforge-scripts/py-dashboard/GrafanaRequest.py create mode 100644 lanforge/lanforge-scripts/py-dashboard/InfluxRequest.py create mode 100644 lanforge/lanforge-scripts/py-json/.gitignore create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/LFRequest.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/LFUtils.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/__init__.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/add_dut.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/add_file_endp.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/add_l4_endp.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/add_monitor.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/add_sta.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/add_vap.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/lf_json_autogen.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/lfcli_base.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/pandas_extensions.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/set_port.py create mode 100644 lanforge/lanforge-scripts/py-json/LANforge/set_wifi_radio.py create mode 100644 lanforge/lanforge-scripts/py-json/README.md create mode 100644 lanforge/lanforge-scripts/py-json/TODO.txt create mode 100644 lanforge/lanforge-scripts/py-json/__init__.py create mode 100644 lanforge/lanforge-scripts/py-json/base_profile.py create mode 100755 lanforge/lanforge-scripts/py-json/create_wanlink.py create mode 100644 lanforge/lanforge-scripts/py-json/cv_dut_profile.py create mode 100644 lanforge/lanforge-scripts/py-json/cv_test_manager.py create mode 100644 lanforge/lanforge-scripts/py-json/cv_test_reports.py create mode 100644 lanforge/lanforge-scripts/py-json/dataplane_test_profile.py create mode 100644 lanforge/lanforge-scripts/py-json/dut_profile.py create mode 100644 lanforge/lanforge-scripts/py-json/fio_endp_profile.py create mode 100644 lanforge/lanforge-scripts/py-json/gen_cxprofile.py create mode 100644 lanforge/lanforge-scripts/py-json/http_profile.py create mode 100644 lanforge/lanforge-scripts/py-json/l3_cxprofile.py create mode 100644 lanforge/lanforge-scripts/py-json/l3_cxprofile2.py create mode 100644 lanforge/lanforge-scripts/py-json/l4_cxprofile.py create mode 100644 lanforge/lanforge-scripts/py-json/lf_attenmod.py create mode 100644 lanforge/lanforge-scripts/py-json/lf_cv_base.py create mode 100644 lanforge/lanforge-scripts/py-json/lfdata.py create mode 100644 lanforge/lanforge-scripts/py-json/mac_vlan_profile.py create mode 100644 lanforge/lanforge-scripts/py-json/multicast_profile.py create mode 100755 lanforge/lanforge-scripts/py-json/old-examples/create_sta.py create mode 100755 lanforge/lanforge-scripts/py-json/old-examples/generic_cx.py create mode 100755 lanforge/lanforge-scripts/py-json/old-examples/test_l4.py create mode 100755 lanforge/lanforge-scripts/py-json/old-examples/wct-example.py create mode 100644 lanforge/lanforge-scripts/py-json/port_utils.py create mode 100644 lanforge/lanforge-scripts/py-json/qvlan_profile.py create mode 100755 lanforge/lanforge-scripts/py-json/realm.py create mode 100755 lanforge/lanforge-scripts/py-json/realm_test.py create mode 100644 lanforge/lanforge-scripts/py-json/show_ports.py create mode 100644 lanforge/lanforge-scripts/py-json/station_profile.py create mode 100644 lanforge/lanforge-scripts/py-json/test_base.py create mode 100644 lanforge/lanforge-scripts/py-json/test_group_profile.py create mode 100755 lanforge/lanforge-scripts/py-json/test_histogram.py create mode 100644 lanforge/lanforge-scripts/py-json/test_utility.py create mode 100644 lanforge/lanforge-scripts/py-json/vap_profile.py create mode 100644 lanforge/lanforge-scripts/py-json/vr_profile2.py create mode 100644 lanforge/lanforge-scripts/py-json/wifi_monitor_profile.py create mode 100755 lanforge/lanforge-scripts/py-json/wlan_theoretical_sta.py create mode 100755 lanforge/lanforge-scripts/py-json/ws-sta-monitor.py create mode 100644 lanforge/lanforge-scripts/py-json/ws_generic_monitor.py create mode 100644 lanforge/lanforge-scripts/py-scripts/.gitignore create mode 100755 lanforge/lanforge-scripts/py-scripts/CT_US_001.bash create mode 100644 lanforge/lanforge-scripts/py-scripts/README.md create mode 100755 lanforge/lanforge-scripts/py-scripts/__init__.py create mode 100755 lanforge/lanforge-scripts/py-scripts/artifacts/CandelaLogo2-90dpi-200x90-trans.png create mode 100755 lanforge/lanforge-scripts/py-scripts/artifacts/banner.png create mode 100755 lanforge/lanforge-scripts/py-scripts/artifacts/candela_swirl_small-72h.png create mode 100644 lanforge/lanforge-scripts/py-scripts/artifacts/report.css create mode 100644 lanforge/lanforge-scripts/py-scripts/check_argparse.py create mode 100755 lanforge/lanforge-scripts/py-scripts/cicd_TipIntegration.py create mode 100755 lanforge/lanforge-scripts/py-scripts/cicd_testrail.py create mode 100755 lanforge/lanforge-scripts/py-scripts/cicd_testrailAndInfraSetup.py create mode 100755 lanforge/lanforge-scripts/py-scripts/connection_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_bond.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_bridge.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_chamberview.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_chamberview_dut.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_l3.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_l3_stations.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_l4.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_macvlan.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_qvlan.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_station.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_station_from_df.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_vap.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_vr.py create mode 100755 lanforge/lanforge-scripts/py-scripts/create_wanlink.py create mode 100755 lanforge/lanforge-scripts/py-scripts/csv_convert.py create mode 100755 lanforge/lanforge-scripts/py-scripts/csv_processor.py create mode 100755 lanforge/lanforge-scripts/py-scripts/csv_to_grafana.py create mode 100755 lanforge/lanforge-scripts/py-scripts/csv_to_influx.py create mode 100644 lanforge/lanforge-scripts/py-scripts/ct_002_igg.json create mode 100644 lanforge/lanforge-scripts/py-scripts/ct_004_igg.json create mode 100644 lanforge/lanforge-scripts/py-scripts/ct_igg.json create mode 100644 lanforge/lanforge-scripts/py-scripts/ct_us_001_orig.json create mode 100644 lanforge/lanforge-scripts/py-scripts/ct_us_001_tests_igg.json create mode 100644 lanforge/lanforge-scripts/py-scripts/ct_us_002_orig.json create mode 100644 lanforge/lanforge-scripts/py-scripts/ct_us_003_orig.json create mode 100644 lanforge/lanforge-scripts/py-scripts/ct_us_004_orig.json create mode 100755 lanforge/lanforge-scripts/py-scripts/cv_examples/ferndale_ucentral.bash create mode 100755 lanforge/lanforge-scripts/py-scripts/cv_manager.py create mode 100755 lanforge/lanforge-scripts/py-scripts/cv_to_grafana.py create mode 100755 lanforge/lanforge-scripts/py-scripts/docstrings.py create mode 100644 lanforge/lanforge-scripts/py-scripts/download_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/event_breaker.py create mode 100755 lanforge/lanforge-scripts/py-scripts/event_flood.py create mode 100644 lanforge/lanforge-scripts/py-scripts/example-configs/mesh-ferndale-cfg.txt create mode 100644 lanforge/lanforge-scripts/py-scripts/example-configs/tr398-ferndale-ac-cfg.txt create mode 100644 lanforge/lanforge-scripts/py-scripts/example-configs/tr398v2-ferndale-ac-cfg.txt create mode 100755 lanforge/lanforge-scripts/py-scripts/example_security_connection.py create mode 100644 lanforge/lanforge-scripts/py-scripts/ftp_html.py create mode 100755 lanforge/lanforge-scripts/py-scripts/ghost_profile.py create mode 100755 lanforge/lanforge-scripts/py-scripts/grafana_profile.py create mode 100644 lanforge/lanforge-scripts/py-scripts/html_template.py create mode 100755 lanforge/lanforge-scripts/py-scripts/influx.py create mode 100644 lanforge/lanforge-scripts/py-scripts/influx2.py create mode 100755 lanforge/lanforge-scripts/py-scripts/influxgrafanaghost.sh create mode 100755 lanforge/lanforge-scripts/py-scripts/layer3_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/layer4_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_ap_auto_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_atten_mod_test.py create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_check.json create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_check_95_6.json create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_check_config_template.ini create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_check_ct_us_001.json create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_check_orig.py create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_csv.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_dataplane_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_dfs_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_dut_sta_vap_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_ftp.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_ftp_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_graph.py create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_header_template.py create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_influx_db.json create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_mesh_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_multipsk.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_report.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_report_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_rvr_test.py create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_rx_sensitivity_config.json create mode 100644 lanforge/lanforge-scripts/py-scripts/lf_rx_sensitivity_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_sniff_radio.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_snp_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_tr398_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_tr398v2_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_webpage.py create mode 100755 lanforge/lanforge-scripts/py-scripts/lf_wifi_capacity_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/measure_station_time_up.py create mode 100755 lanforge/lanforge-scripts/py-scripts/modify_station.py create mode 100755 lanforge/lanforge-scripts/py-scripts/modify_vap.py create mode 100755 lanforge/lanforge-scripts/py-scripts/port_probe.py create mode 100755 lanforge/lanforge-scripts/py-scripts/recordinflux.py create mode 100644 lanforge/lanforge-scripts/py-scripts/regression_test.rc.example create mode 100755 lanforge/lanforge-scripts/py-scripts/regression_test.sh create mode 100755 lanforge/lanforge-scripts/py-scripts/run_cv_scenario.py create mode 100755 lanforge/lanforge-scripts/py-scripts/rvr_scenario.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/__init__.py create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/csv_sqlite.py create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_001_igg.json create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_id_102.json create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_test_lf_check.json create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001.json create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001_igg.json create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001_rig.json create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_002.json create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_003.json create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_004.json create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/jbr_jag_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/kpi_3.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/kpi_csv_sq.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/kpi_sqlite.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/lf_check_ap.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/lf_check_igg.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/lf_hackrf3.py create mode 100644 lanforge/lanforge-scripts/py-scripts/sandbox/lf_json_autogen_test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/test_ipv4_variable_time2.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sandbox/update_dut.py create mode 100755 lanforge/lanforge-scripts/py-scripts/scenario.py create mode 100755 lanforge/lanforge-scripts/py-scripts/scripts_deprecated/csv_to_influx.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sta_connect.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sta_connect2.py create mode 100644 lanforge/lanforge-scripts/py-scripts/sta_connect_bssid_mac.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sta_connect_example.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sta_connect_multi_example.py create mode 100755 lanforge/lanforge-scripts/py-scripts/sta_scan_test.py create mode 100644 lanforge/lanforge-scripts/py-scripts/station_layer3.py create mode 100755 lanforge/lanforge-scripts/py-scripts/stations_connected.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_1k_clients_jedtest.py create mode 100644 lanforge/lanforge-scripts/py-scripts/test_all_scripts.sh create mode 100755 lanforge/lanforge-scripts/py-scripts/test_client_admission.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_fileio.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_generic.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ip_connection.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ip_variable_time.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_connection.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_l4.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_upload.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_urls_per_ten.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_wifi.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_urls_per_ten.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_wifi.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_ps.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_ttls.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv4_variable_time.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv6_connection.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_ipv6_variable_time.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_l3_WAN_LAN.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_l3_longevity.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_l3_powersave_traffic.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_l3_scenario_throughput.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_l3_unicast_traffic_gen.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_l4.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_status_msg.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_wanlink.py create mode 100755 lanforge/lanforge-scripts/py-scripts/test_wpa_passphrases.py create mode 100755 lanforge/lanforge-scripts/py-scripts/testgroup.py create mode 100755 lanforge/lanforge-scripts/py-scripts/testgroup2.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/Nightly_Sanity.py create mode 100644 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/README.md create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/Throughput_Test.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/ap_ssh.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/cloudsdk.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/eap_connect.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/lab_ap_info.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/reports/report_template.php create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/sanity_status.json create mode 100755 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/single_client_throughput.py create mode 100644 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/templates/ap_profile_template.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/templates/radius_profile_template.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/templates/ssid_profile_template.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/testrail_api.py create mode 100644 lanforge/lanforge-scripts/py-scripts/tip-cicd-sanity/throughput_profiles.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tip_station_powersave.py create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_001_AX88U_dut.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_002_AX12_dut.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_003_LANforge_dut.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_004_AX88U_dut.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_us_001_rig.json create mode 100755 lanforge/lanforge-scripts/py-scripts/tools/ct_us_001_scripts.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_us_001_tests.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_us_002_rig.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_us_002_tests.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_us_003_rig.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_us_004_rig.json create mode 100644 lanforge/lanforge-scripts/py-scripts/tools/ct_us_004_tests.json create mode 100755 lanforge/lanforge-scripts/py-scripts/tools/lf_check.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tools/lf_dash.py create mode 100755 lanforge/lanforge-scripts/py-scripts/tools/lf_qa.py create mode 100755 lanforge/lanforge-scripts/py-scripts/update_dependencies.py create mode 100755 lanforge/lanforge-scripts/py-scripts/vap_stations_example.py create mode 100644 lanforge/lanforge-scripts/py-scripts/video_rates.py create mode 100755 lanforge/lanforge-scripts/py-scripts/wifi_cap_to_grafana.sh create mode 100755 lanforge/lanforge-scripts/py-scripts/wifi_capacity_dataplane_ghost.sh create mode 100755 lanforge/lanforge-scripts/py-scripts/wlan_capacity_calculator.py create mode 100755 lanforge/lanforge-scripts/py-scripts/ws_generic_monitor_test.py create mode 100755 lanforge/lanforge-scripts/quick-copy.sh create mode 100755 lanforge/lanforge-scripts/rand_nc.pl create mode 100755 lanforge/lanforge-scripts/rand_nmap.pl create mode 100755 lanforge/lanforge-scripts/reconfigure_apache.pl create mode 100755 lanforge/lanforge-scripts/rvr-helper.pl create mode 100755 lanforge/lanforge-scripts/script_test.pl create mode 100755 lanforge/lanforge-scripts/sensorz.pl create mode 100755 lanforge/lanforge-scripts/setup-concentrator.sh create mode 100755 lanforge/lanforge-scripts/setup-concentrator2.sh create mode 100644 lanforge/lanforge-scripts/show-port-from-json.pl create mode 100644 lanforge/lanforge-scripts/sixtyk-udp.pl create mode 100755 lanforge/lanforge-scripts/speedtest-cli create mode 100755 lanforge/lanforge-scripts/station-toggle.sh create mode 100755 lanforge/lanforge-scripts/stationStressTest.py create mode 100644 lanforge/lanforge-scripts/strongswan-config create mode 100755 lanforge/lanforge-scripts/sysmon.sh create mode 100755 lanforge/lanforge-scripts/telnet_expect_wrapper.pl create mode 100755 lanforge/lanforge-scripts/ten_sta_tos.bash create mode 100755 lanforge/lanforge-scripts/test_perl_json.sh create mode 100755 lanforge/lanforge-scripts/test_refcnt.pl create mode 100755 lanforge/lanforge-scripts/timed_ice_pause.sh create mode 100755 lanforge/lanforge-scripts/to_pip.sh create mode 100755 lanforge/lanforge-scripts/topmon.sh create mode 100755 lanforge/lanforge-scripts/tos_plus_auto.py create mode 100755 lanforge/lanforge-scripts/track_call.sh create mode 100644 lanforge/lanforge-scripts/track_call_end.sh create mode 100755 lanforge/lanforge-scripts/updateTest.bash create mode 100755 lanforge/lanforge-scripts/upgrade_over_dut.bash create mode 100755 lanforge/lanforge-scripts/wait_on_ports.pl create mode 100755 lanforge/lanforge-scripts/wifi-event-histo.sh create mode 100755 lanforge/lanforge-scripts/wifi-roaming-times.pl create mode 100755 lanforge/lanforge-scripts/wifi_ctl_9800_3504.py create mode 100644 lanforge/lanforge-scripts/wifi_diag/Packet.pm create mode 100644 lanforge/lanforge-scripts/wifi_diag/PeerConn.pm create mode 100644 lanforge/lanforge-scripts/wifi_diag/Tid.pm create mode 100755 lanforge/lanforge-scripts/wifi_diag/do_wifi_diag.bash create mode 100644 lanforge/lanforge-scripts/wifi_diag/wifi_diag_python/Dataplot.py create mode 100644 lanforge/lanforge-scripts/wifi_diag/wifi_diag_python/htmlText.py create mode 100644 lanforge/lanforge-scripts/wifi_diag/wifi_diag_python/scratch.py create mode 100755 lanforge/lanforge-scripts/wifi_diag/wifi_pcap_diag.pl create mode 100755 lanforge/lanforge-scripts/wifi_log_separator.bash create mode 100755 lanforge/lanforge-scripts/wlanpro_test.pl create mode 100755 lanforge/lanforge-scripts/wpro.sh create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/CandelaLogo2-90dpi-200x90-trans.png create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/CenturyGothic.woff create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/candela_swirl_small-72h.png create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/canvil.ico create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/custom.css create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/index-print.html create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/index.html create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/logo.png create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/rate-vs-range-report-2021-10-26-03-39-37.pdf create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/report.css create mode 100644 reports/rate-vs-range-2021-10-26-03-39-37/report_banner-1000x205.jpg create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/CandelaLogo2-90dpi-200x90-trans.png create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/CenturyGothic.woff create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/candela_swirl_small-72h.png create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/canvil.ico create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/custom.css create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/index-print.html create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/index.html create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/logo.png create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/rate-vs-range-report-2021-10-26-04-00-20.pdf create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/report.css create mode 100644 reports/rate-vs-range-2021-10-26-04-00-21/report_banner-1000x205.jpg create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/CandelaLogo2-90dpi-200x90-trans.png create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/CenturyGothic.woff create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/candela_swirl_small-72h.png create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/canvil.ico create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/custom.css create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/index-print.html create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/index.html create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/logo.png create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/rate-vs-range-report-2021-10-26-10-33-02.pdf create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/report.css create mode 100644 reports/rate-vs-range-2021-10-26-10-33-02/report_banner-1000x205.jpg create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/CandelaLogo2-90dpi-200x90-trans.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/CenturyGothic.woff create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/candela_swirl_small-72h.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/canvil.ico create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-2-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-2.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-3-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-3.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-4-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-4.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-5-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-5.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-6-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-6.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-7-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-7.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-8-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/chart-8.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-Endpoint_RX_Packet_Loss_Percentage-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-Realtime_Throughput-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-Rx_Errors-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-TX_Completion_vs_Calculated_Power-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-TX_Completion_vs_LANforge_RSSI-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-Throughput_vs_Calculated_Signal-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-Throughput_vs_LANforge_RSSI-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-WiFi_Link_Rate_vs_Calculated_Signal-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/data-WiFi_Link_Rate_vs_LANforge_RSSI-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/csv-data/meta-info.txt create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/custom.css create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/index-print.html create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/index.html create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/kpi-chart-0-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/kpi-chart-0.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/kpi-chart-1-print.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/kpi-chart-1.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/kpi.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/logo.png create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/rate-vs-range-report-2021-10-26-11-00-32.pdf create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/report.css create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/report_banner-1000x205.jpg create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/text-csv-0.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/text-csv-1.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/text-tab-0.csv create mode 100644 reports/rate-vs-range-2021-10-26-11-01-44/text-tab-1.csv create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/chart-2-print.png create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/chart-2.png create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/chart-5.png create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/chart-6-print.png create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/chart-7-print.png create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/csv-data/data-Endpoint_RX_Packet_Loss_Percentage-1.csv create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/csv-data/data-TX_Completion_vs_LANforge_RSSI-1.csv create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/csv-data/data-WiFi_Link_Rate_vs_Calculated_Signal-1.csv create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/csv-data/meta-info.txt create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/kpi.csv create mode 100644 reports/rate-vs-range-2021-10-27-12-09-32/logo.png diff --git a/README.md b/README.md index 7e8c03cbe..40c76208a 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ All code must be written in python 3 and conform to PEP 8 style guide. The test ├── mdu |── mesh |── scale - |── README.md -/* Pytest framework and testcases information */ + |── README.md -/* Pytest spatial_consist and testcases information */ ``` ### Setup Instructions diff --git a/lanforge/lanforge-scripts/.gitignore b/lanforge/lanforge-scripts/.gitignore new file mode 100644 index 000000000..5fc2ef0e9 --- /dev/null +++ b/lanforge/lanforge-scripts/.gitignore @@ -0,0 +1,7 @@ +*.bash.txt +*.sh.txt +*.pl.txt +*~ +*.iml +**/*.iml +.idea diff --git a/lanforge/lanforge-scripts/LANforge/Endpoint.pm b/lanforge/lanforge-scripts/LANforge/Endpoint.pm new file mode 100644 index 000000000..5c9f94ff7 --- /dev/null +++ b/lanforge/lanforge-scripts/LANforge/Endpoint.pm @@ -0,0 +1,2155 @@ +#!/usr/bin/perl + +package LANforge::Endpoint; +use strict; + +################################################## +## the object constructor ## +## To use: $ep = LANforge::Endpoint->new(); ## +## or: $ep2 = $ep->new(); ## +################################################## + +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + + $self->{name} = undef; + + bless( $self, $class ); + + initDataMembers(); + + return $self; +} + +sub initDataMembers { + my $self = shift; + + $self->{payload} = undef; + $self->{shelf_id} = undef; + $self->{card_id} = undef; + $self->{port_id} = undef; + $self->{endp_id} = undef; + $self->{endp_type} = undef; + $self->{pattern} = undef; + $self->{ip_port} = undef; + $self->{ip_tos} = undef; + $self->{ip_addr} = undef; + $self->{dst_ip_port} = undef; + $self->{dst_ip_addr} = undef; + $self->{dst_mac} = undef; + $self->{src_addr} = undef; + $self->{role} = undef; + $self->{ep_flags} = undef; + $self->{min_pkt_size} = undef; + $self->{max_pkt_size} = undef; + $self->{min_tx_rate} = undef; + $self->{max_tx_rate} = undef; + $self->{report_timer} = undef; + $self->{start_time} = undef; + $self->{stop_time} = undef; + $self->{cx_detected_dropped_rx} = undef; + $self->{last_rpt} = undef; + $self->{rx_pkts} = undef; + $self->{tx_pkts} = undef; + $self->{tx_failed_pkts} = undef; + $self->{tx_failed_bytes} = undef; + $self->{rx_bytes} = undef; + $self->{tx_bytes} = undef; + $self->{rx_dropped_pkts} = undef; # figure out by looking at gaps in pkt ids + $self->{rx_dup_pkts} = undef; + $self->{rx_ooo_pkts} = undef; + $self->{rx_wrong_dev} = undef; + $self->{rx_crc_failed} = undef; + $self->{connection_dropped} = undef; + $self->{real_tx_rate} = undef; + $self->{real_rx_rate} = undef; + $self->{counters} = undef; + $self->{avg_latency} = undef; + $self->{ttl} = undef; + $self->{filename} = undef; + $self->{send_bad_crc} = undef; + $self->{rx_drop_cx} = undef; + $self->{rx_drop_seq} = undef; + $self->{rx_bit_errors} = undef; + $self->{conn_estab} = undef; + $self->{tcp_retrans} = undef; + + # WanLink (LANforge ICE) fields. + $self->{cfg_latency} = undef; + $self->{max_jitter} = undef; + $self->{drop_freq} = undef; + $self->{dup_freq} = undef; + $self->{jitter_freq} = undef; + $self->{reord_freq} = undef; + $self->{max_buf} = undef; + $self->{extra_buf} = undef; + $self->{wan_paths} = undef; + $self->{record_q} = undef; + $self->{dump_file} = undef; + $self->{min_drop_amt} = undef; + $self->{max_drop_amt} = undef; + + # VOIP fields. + $self->{proxy_ip} = undef; + $self->{phone_num} = undef; + $self->{peer_phone_num} = undef; + $self->{min_rtp_port} = undef; + $self->{max_rtp_port} = undef; + $self->{reg_expire_timer} = undef; + $self->{sound_dev} = undef; + $self->{tx_sound_file} = undef; + $self->{rx_sound_file} = undef; + $self->{fc_delay} = undef; + $self->{min_ic_gap} = undef; + $self->{max_ic_gap} = undef; + $self->{loop_calls} = undef; + $self->{loop_wav_files} = undef; + $self->{call_setup_dist} = undef; + $self->{last_call_setup_time} = undef; + $self->{state_change_in} = undef; + $self->{min_call_duration} = undef; + $self->{max_call_duration} = undef; + $self->{register_state} = undef; + $self->{call_state} = undef; + $self->{msg_proto} = undef; + $self->{rtp_encoding} = undef; + $self->{latency} = undef; + $self->{rt_latency} = undef; + $self->{jitter} = undef; + $self->{calls_attempted} = undef; + $self->{calls_completed} = undef; + $self->{calls_answered} = undef; + $self->{calls_connected} = undef; + $self->{calls_RHUP} = undef; + $self->{calls_failed} = undef; + $self->{calls_failed_404} = undef; + $self->{calls_failed_no_answer} = undef; + $self->{calls_failed_busy} = undef; + $self->{ringing_timer} = undef; + $self->{rcvd_487_cancel} = undef; + + # Armageddon fields - Added by Adam + $self->{udp_src_min} = undef; + $self->{udp_src_max} = undef; + $self->{udp_dst_min} = undef; + $self->{udp_dst_max} = undef; + $self->{pps} = undef; + $self->{pkts_to_send} = undef; + $self->{arm_flags} = undef; + $self->{src_mac_cnt} = undef; + $self->{dst_mac_cnt} = undef; + $self->{multi_pkt} = undef; + $self->{min_src_ip} = undef; + $self->{max_src_ip} = undef; + $self->{min_dst_ip} = undef; + $self->{max_dst_ip} = undef; + +} #initDataMembers + +# VOIP endpoints look like this: +#VoipEndp [voip1-B] (RUNNING, UDP_TRANSPORT, SAVE_RX_PCM, PLAY_AUDIO, RCV_CALL_ONLY) +# Shelf: 1, Card: 2 Port: 0 Endpoint: 27 Type: VOIP +# ProxyIP: 192.168.1.24 PhoneNum: 2102 PeerPhone: 2103 +# MinRtpPort: 10000 MaxRtpPort: 10002 RegExpireTimer: 300 +# SoundDev: /dev/dsp TxSoundFile: /tmp/fvoice.wav RxSoundFile: /tmp/pcm_rx.wav +# FC-Delay: 5 MinInterCallGap: 5 MaxInterCallGap: 5 +# LoopCalls: FOREVER LoopWaveFiles: 1 MinCallDuration: 30 Max: 30 +# RegisterState: REGISTERED CallState: CALL_IN_PROGRESS Protocol: SIP/G711U +# RingingTimer: 10000ms LastCallSetup: 12ms StateChangeIn: 4s +# RptTimer: 5000ms RunningFor: 150446s StopIn: 206276387s +# LastRpt: 0.000 secs ago RealWriteRate: 62044bps RealReadRate: 62044bps +# Latency: -32 -:-24:- -10 [ 9 1 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (5) +# RT-Latency: 0 -:73:- 1760 [ 5 0 1 0 0 0 0 2 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (5) +# Jitter: -20 -:-1:- 2010 [ 891 1 1968 95 0 0 1 0 5 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (5) +# CallSetup: 0 -:13:- 37 [ 0 0 49 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (5) +# CallsAttempted: Total: 0 Time: 300000ms Current: 0 +# CallsCompleted: Total: 501 Time: 300000ms Current: 1 +# CallsAnswered Total: 502 Time: 300000ms Current: 1 +# CallsConnected Total: 0 Time: 300000ms Current: 0 +# CallsRemoteHUP Total: 501 Time: 300000ms Current: 1 +# CallsFailed: Total: 0 Time: 300000ms Current: 0 +# RTP Pkts Tx: Total: 7292502 Time: 300000ms Current: 14361 +# RTP Pkts Rx: Total: 7292442 Time: 300000ms Current: 14361 +# RTP Bytes Tx: Total: 1166800320 Time: 300000ms Current: 2297760 +# RTP Bytes Rx: Total: 1166790720 Time: 300000ms Current: 2297760 +# RTP Pkts Dropped: Total: 0 Time: 300000ms Current: 0 +# RTP Pkts Dup: Total: 0 Time: 300000ms Current: 0 +# RTP Pkts OOO: Total: 0 Time: 300000ms Current: 0 +# CallsFailed-404 Total: 0 Time: 300000ms Current: 0 +# CF 408 (No-Answer) Total: 3 Time: 300000ms Current: 3 +# CallsFailed-busy Total: 0 Time: 300000ms Current: 0 +# Rcvd 487 (Cancel) Total: 4 Time: 300000ms Current: 4 + +# A WanLink (LANforge ICE) endpoint's output from the CLI looks something like this +#WanLink [wl1-B] (NOT_RUNNING) +# Shelf: 1, Card: 1 Port: 2 Endpoint: 10 Type: WAN_LINK +# MaxTxRate: 56000bps Latency: 0ms MaxJitter: 0ms +# DropFreq: 0 DupFreq: 0 ReorderFreq: 0 ExtraBuf: 64KB +# RptTimer: 5000ms RunningFor: 0s StopIn: 0s MaxBuf: 67050B +# Cur Backlog: 0 Real Tx Rate: 0bps WanPaths: 1 +# Rx Pkts: Total: 0 Time: 300000ms Current: 0 +# Rx Bytes: Total: 0 Time: 300000ms Current: 0 +# Tx OOO Pkts: Total: 0 Time: 300000ms Current: 0 +# Rx Dropped Pkts: Total: 0 Time: 300000ms Current: 0 +# Rx Dropped Bytes: Total: 0 Time: 300000ms Current: 0 +# Tx Duplicate Pkts: Total: 0 Time: 300000ms Current: 0 +# Tx Pkts: Total: 0 Time: 300000ms Current: 0 +# Tx Bytes: Total: 0 Time: 300000ms Current: 0 +# Tx Failed Pkts: Total: 0 Time: 300000ms Current: 0 +# Tx Failed Bytes: Total: 0 Time: 300000ms Current: 0 +# +# Name RxPkts RxBytes Dropped MaxRate(bps) Latency Backlog TxPkts TxBytes +# wp1 0 0 0 56000 6 0 0 0 + +# A Data-Generating (LANforge FIRE) endpoint's output from the CLI looks something like this +#Endpoint [endp-399-RX] (NOT_RUNNING, FIXED_PLD_SIZE, PHANTOM, RATE_CONSTANT, IP_PORT_AUTO) +# Shelf: 1, Card: 1 Port: 409 Endpoint: 400 Type: LANFORGE_TCP Pattern: INCREASING +# MinTxRate: 9600bps MaxTxRate: 9600bps MinPktSize: 1472B MaxPktSize: 1472B +# DestMAC: 00 00 00 00 00 00 DestIpAddr: 0.0.0.0 DestIpPort: 0 Quiesce: 3 +# SrcMAC: 00 00 00 00 00 00 SrcIp: 0.0.0.0 IpPort: 0 IpTOS: DONT-SET Priority: 0 +# Role: ACCEPT RptTimer: 1000ms RunningFor: 0s StopIn: 0s +# Latency: 0 -:0:- 0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) +# Last Rpt: 0.000 secs ago RealTxRate: 0bps RealRxRate: 0bps TTL: 0 +# FileName: SendBadCrc: 0 RcvBuf: 0 SndBuf: 0 CWND: 0 +# RxDrop%-SEQ: 0.0000 RxDrop%-CX: 0.0000 Conn-Timer: -1ms +# Rx Pkts: Total: 0 Time: 60s Cur: 0 0/s +# Rx Bytes: Total: 0 Time: 60s Cur: 0 0/s +# Rx OOO Pkts: Total: 0 Time: 60s Cur: 0 0/s +# RX Wrong Dev: Total: 0 Time: 60s Cur: 0 0/s +# RX CRC Failed: Total: 0 Time: 60s Cur: 0 0/s +# RX Bit Errors: Total: 0 Time: 3s Cur: 0 0/s +# Rx Dropped Pkts: Total: 0 Time: 3s Cur: 0 0/s +# Cx Detected: 0 +# Rx Duplicate Pkts: Total: 0 Time: 60s Cur: 0 0/s +# Tx Pkts: Total: 0 Time: 60s Cur: 0 0/s +# Tx Bytes: Total: 0 Time: 60s Cur: 0 0/s +# Tx Failed Pkts: Total: 0 Time: 60s Cur: 0 0/s +# Tx Failed Bytes: Total: 0 Time: 60s Cur: 0 0/s +# Conn Established: Total: 0 Time: 30s Cur: 0 0/s +# TCP Retransmits: Total: 0 Time: 3s Cur: 0 0/s + +sub decode { + my $self = shift; + my $txt = shift; + my @ta = split( /\n/, $txt ); + my $i = -1; + my $got_endp = 0; + my $got_wl = 0; + my $got_voip = 0; + my $got_arm = 0; + + print "Endpoint::decode, txt -:$txt:-\n"; + + foreach my $ln (@ta) { + $i++; + + next if ($ln =~ /^\s*$/); + next if ($ln =~ /^\s*>>RSLT:/); + next if ($ln =~ /(admin|default)\@btbits>>/i); + #print "Line: -:$ln:-\n"; + + #Endpoint [endp-34-TX] (NOT_RUNNING, RND_PLD_SIZE, RATE_BURSTY) + #print STDERR "NAME:".$self->{name}."\n"; + if ( !defined( $self->{name} ) ) { + if ( $ln =~ /Endpoint\s+\[(.*)\]\s+\((.*)\)/ ) { + print "line has Endpoint\n"; + $self->name($1); # set our name + $self->ep_flags($2); + $got_endp = 1; + next; + } + elsif ( $ln =~ /WanLink\s+\[(.*)\]\s+\((.*)\)/ ) { + print "line has Wanlink\n"; + $self->name($1); # set our name + $self->ep_flags($2); + $got_wl = 1; + next; + } + elsif ( $ln =~ /VoipEndp\s+\[(.*)\]\s+\((.*)\)/ ) { + print "line has Voip\n"; + $self->name($1); # set our name + $self->ep_flags($2); + $got_voip = 1; + next; + } + elsif ( $ln =~ /ArmEndp\s+\[(.*)\]\s+\((.*)\)/ ) { + # added by Adam 8-17-04 + print "line has Armg\n"; + $self->name($1); # set our name + $self->ep_flags($2); + $got_arm = 1; + next; + } + else { + warn "$0: Don't know about this endpoint: $ln\n"; + } + } + elsif (($got_endp + $got_wl + $got_arm) == 0) { + my $nm = $self->{name}; + if ( $ln =~ /Endpoint\s+\[$nm\]\s+\((.*)\)/ ) { + $self->ep_flags($1); + #print "Set flags -:" . $self->ep_flags() . ":- orig -:$1:-\n"; + $got_endp = 1; + next; + } + elsif ( $ln =~ /WanLink\s+\[$nm\]\s+\((.*)\)/ ) { + $self->ep_flags($1); + #print "Set flags -:" . $self->ep_flags() . ":- orig -:$1:-\n"; + $got_wl = 1; + next; + } + elsif ( $ln =~ /ArmEndp\s+\[(.*)\]\s+\((.*)\)/ ) { + $self->ep_flags($2); + #print "Set flags -:" . $self->ep_flags() . ":- orig -:$2:-\n"; + $got_arm = 1; + next; + } + else { + warn "$0: Don't know about this endpoint, nm: $nm, ln: $ln\n"; + } + next; + } + +# Shelf: 1, Card: 1 Port: 3 Endpoint: 15 Type: CUSTOM_TCP Pattern: CUSTOM + if ($got_endp) { + + if ( $ln =~ +/Shelf:\s+(\d+)\,*\s+Card:\s+(\d+)\s+Port:\s+(\d+)\s+Endpoint:\s+(\d+)\s+Type:\s+(\S+)\s+Pattern:\s+(\S+)/ + ) + { + $self->shelf_id($1); + $self->card_id($2); + $self->port_id($3); + $self->ep_id($4); + $self->ep_type($5); + $self->pattern($6); + next; + } + +# MinTxRate: 512000bps MaxTxRate: 1024000bps MinPktSize: 128B MaxPktSize: 128B + if ( $ln =~ +/MinTxRate:\s+(\d+)bps\s+MaxTxRate:\s+(\d+)bps\s+MinPktSize:\s+(\d+)B\s+MaxPktSize:\s+(\d+)B/ + ) + { + $self->min_tx_rate($1); + $self->max_tx_rate($2); + $self->min_pkt_size($3); + $self->max_pkt_size($4); + next; + } + + # DestMAC: 00 90 27 a2 9e 54 DestIpAddr: 172.1.2.201 DestIpPort: 33011 + if ( $ln =~ +/DestMAC:\s+(([0-9a-fA-F]{2}[: ]){5}([0-9a-fA-F]{2}))\s+DestIpAddr:\s+(\S+)\s+DestIpPort:\s+(\d+)/ + ) + { + $self->dest_mac($1); + $self->dest_ip_addr($2); + $self->dest_ip_port($3); + next; + } + +# SrcMAC: 00 c0 95 e2 4c 0c SrcIp: 172.1.2.200 IpPort: 33011 IpTOS: 0x8 + if ( $ln =~ +/SrcMAC:\s+(([0-9a-fA-F]{2}[: ]){5}([0-9a-fA-F]{2}))\s+SrcIp:\s+(\S+)\s+IpPort:\s+(\d+(-\d+)?)\s+IpTOS:\s+(\S+)/ + ) + { + $self->src_mac($1); + $self->ip_addr($2); + $self->ip_port($3); + $self->ip_tos($4); + next; + } + +# Role: CONNECT RptTimer: 3000ms RunningFor: 1271256573s StopIn: 0s + if ( $ln =~ +/Role:\s+(\S+)\s+RptTimer:\s+(\d+)ms\s+RunningFor:\s+(\d+)s\s+StopIn:\s+(\d+)s/ + ) + { + $self->role($1); + $self->report_timer($2); + $self->running_for($3); + $self->stop_in($4); + next; + } + + if ( $ln =~ /PktsToSend:\s+(\d+)/) { + $self->pkts_to_send($4); + next; + } + +# Latency: 0 -:0:- 0 [ 0 0 0 0 0 0 0 0 ] (0) + if ( $ln =~ /Latency:\s+(\S+) -:(\S+):- (\S+)\s+\[ (.*) \]\s+\((\S+)\)/ ) { + $self->min_lat($1); + $self->avg_latency($2); + $self->max_lat($3); + $self->lat_buckets($4); + $self->lat_bucket_size($5); + next; + } + +# Last Rpt: 0.000 secs ago RealTxRate: 0bps RealRxRate: 5bps TTL: 0 + if ( $ln =~ +/Last[- ]Rpt:\s+(\S+) secs ago\s+RealTxRate:\s+(\d+)bps\s+RealRxRate:\s+(\d+)bps\s+TTL:\s+(\S+)/ + ) + { + $self->last_rpt($1); + $self->real_tx_rate($2); + $self->real_rx_rate($3); + $self->ttl($4); + next; + } + +# FileName: + if ( $ln =~ /FileName:\s+(\S+)\s+SendBadCrc:\s+(\S+)/ ) { + $self->filename($1); + $self->send_bad_crc($2); + next; + } + elsif ( $ln =~ /FileName:\s+SendBadCrc:\s+(\S+)/ ) { + $self->filename(""); + $self->send_bad_crc($1); + next; + } + +# RxDrop%-SEQ: 0.0000 RxDrop%-CX: 12.1819 + if ($ln =~ /RxDrop\%-SEQ:\s+(\S+)\s+RxDrop\%-CX:\s+(\S+)/ ) { + $self->rx_drop_seq($1); + $self->rx_drop_cx($2); + next; + } + +# Multi-Conn: 0 Active-Connections: 0 +# Rx Pkts: Total: 0 Time: 300000ms Current: 0 + if ($ln =~ /Rx Pkts:\s+Total:\s+(\d+)/ ) { + $self->rx_pkts($1); + next; + } + +# Rx Bytes: Total: 2455112960 Time: 300000ms Current: 0 + if ($ln =~ /Rx Bytes:\s+Total:\s+(\d+)/ ) { + $self->rx_bytes($1); + next; + } + +# Rx OOO Pkts: Total: 0 Time: 300000ms Current: 0 + if ($ln =~ /Rx OOO Pkts:\s+Total:\s+(\d+)/ ) { + $self->rx_ooo_pkts($1); + next; + } + +# RX Wrong Dev: Total: 0 Time: 300000ms Current: 0 +# RX CRC Failed: Total: 0 Time: 300000ms Current: 0 +# RX Bit Errors: Total: 0 Time: 3s Cur: 0 0/s + if ( $ln =~ /RX Bit Errors:\s+Total:\s+(\d+)/ ) { + $self->rx_bit_errors($1); + next; + } + +# Rx Dropped Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Rx Dropped Pkts:\s+Total:\s+(\d+)/ ) { + $self->rx_dropped_pkts($1); + next; + } + +# Tx Pkts: Total: 30288945 Time: 300000ms Current: 0 + + if ( $ln =~ /Tx Pkts:\s+Total:\s+(\d+)/ ) { + $self->tx_pkts($1); + next; + } + +# Tx Bytes: Total: 3876984960 Time: 300000ms Current: 0 + if ( $ln =~ /Tx Bytes:\s+Total:\s+(\d+)/ ) { + $self->tx_bytes($1); + next; + } + +# Conn Established: Total: 0 Time: 30s Cur: 0 0/s + if ( $ln =~ /Conn Established:\s+Total:\s+(\d+)/ ) { + $self->conn_estab($1); + next; + } + +# TCP Retransmits: Total: 0 Time: 3s Cur: 0 0/s + if ( $ln =~ /TCP Retransmits:\s+Total:\s+(\d+)/ ) { + $self->tcp_retrans($1); + next; + } + + next if ($ln =~ /RX Wrong Dev:/ ); + next if ($ln =~ /RX CRC Failed:/); + next if ($ln =~ /Multi-Conn:/ ); + next if ($ln =~ /Cx Detected:/); + next if ($ln =~ /Rx Duplicate Pkts:/); + next if ($ln =~ /Tx Failed Pkts:/); + next if ($ln =~ /Tx Failed Bytes:/); + next if ( $ln =~ /Pkt-Gaps:/ ); + next if ( $ln =~ /First-Rx:/); + next if ( $ln =~ /RunningInGroup:/); + next if ( $ln =~ /[RT]x Pkts .On Wire./); + next if ( $ln =~ /[RT]x Bytes .On Wire./); + next if ( $ln =~ /Conn Timeouts:/); + + + warn( "Could not parse line -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n" ); + return; + } + + # If we were a LANforge-FIRE endpoint. + elsif ($got_wl) { + print "PARSING WANLINK: $ln\n"; + if ( $ln =~ +/Shelf:\s+(\d+)\,\s+Card:\s+(\d+)\s+Port:\s+(\d+)\s+Endpoint:\s+(\d+)\s+Type:\s+(\S+)/ + ) + { + # Shelf: 1, Card: 1 Port: 3 Endpoint: 4 Type: WAN_LINK + $self->shelf_id($1); + $self->card_id($2); + $self->port_id($3); + $self->ep_id($4); + $self->ep_type($5); + next; + } + + # Description: + $i++; + $ln = $ta[$i]; + if ( $ln =~ /Description:/ ) { # ignore + next; + } + + # MaxTxRate: 4000000bps Latency: 10ms MaxJitter: 0ms + if ( $ln =~ + /MaxTxRate:\s+(\d+)bps\s+Latency:\s+(\d+)ms\s+MaxJitter:\s+(\d+)ms/ + ) + { + $self->max_tx_rate($1); + $self->cfg_latency($2); + $self->max_jitter($3); + next; + } + + # Case where MaxTxRate < 0. It shouldn't happen, but i added it + # so the regression script wouldn't die - Adam. + elsif ( $ln =~ + /MaxTxRate:\s+-(\d+)bps\s+Latency:\s+(\d+)ms\s+MaxJitter:\s+(\d+)ms/ + ) + { + my $tmp = "-" . $1; + $self->max_tx_rate($tmp); + $self->cfg_latency($2); + $self->max_jitter($3); + next; + } + + # DropFreq: 0 DupFreq: 0 ReorderFreq: 0 ExtraBuf: 100KB + if ( $ln =~ +/DropFreq:\s+(\d+)\s+DupFreq:\s+(\d+)\s+ReorderFreq:\s+(\d+)\s+ExtraBuf:\s+(\d+)KB/ + ) + { + $self->drop_freq($1); + $self->dup_freq($2); + $self->reord_freq($3); + $self->extra_buf($4); + next; + } + + # RptTimer: 5000ms RunningFor: 277602940s StopIn: 0s MaxBuf: 103914B + if ( $ln =~ +/RptTimer:\s+(\d+)ms\s+RunningFor:\s+(\d+)s\s+StopIn:\s+(\d+)s\s+MaxBuf:\s+(\d+)B/ + ) + { + $self->report_timer($1); + $self->running_for($2); + $self->stop_in($3); + $self->max_buf($4); + next; + } + + # Cur Backlog: 0 Real Tx Rate: 0bps WanPaths: 1 + $i++; + $ln = $ta[$i]; + if ( $ln =~ +/Cur Backlog:\s+(\d+)\s+Real Tx Rate:\s+(\d+)bps\s+WanPaths:\s+(\d+)/ + ) + { + $self->cur_backlog($1); + $self->real_tx_rate($2); + $self->wan_paths($3); + next; + } + + # JitterFreq: 1000 RecordQ: 0 Dump File: + if ( $ln =~ + /JitterFreq:\s+(\d+)\s+RecordQ:\s+(\d+)\s+Dump File:\s+(\S*)/ ) + { + $self->jitter_freq($1); + $self->record_q($2); + $self->dump_file($3); + next; + } + + # MinDropAmt: 0 MaxDropAmt: 0 + $ln = $ta[ ++$i ]; + if ( $ln =~ /MinDropAmt:\s+(\d+)\s+MaxDropAmt:\s+(\d+)/ ) { + $self->min_drop_amt($1); + $self->max_drop_amt($2); + next; + } + + if ( $ln =~ /\s+QueueDiscipline:/ ) { # ignore + next; + } + + # Rx Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /\s+Rx Pkts:\s+Total:\s+(\d+)/ ) { + $self->rx_pkts($1); + next; + } + + # Adam - I am just matching the fields in these sections for now because I am + # not using this data. Will complete when the data is needed + + # Rx Bytes: Total: 2455112960 Time: 300000ms Current: 0 + if ( $ln =~ /Rx Bytes:\s+Total:\s+(\d+)/ ) { + $self->rx_bytes($1); + next; + } + + # Tx OOO Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Tx OOO Pkts:\s+Total:\s+(\d+)/ ) { + #$self->tx_ooo_pkts($1); + next; + } + + # Rx Dropped Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Rx Dropped Pkts:\s+Total:\s+(\d+)/ ) { + $self->rx_dropped_pkts($1); + next; + } + + # Rx Dropped Bytes: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Rx Dropped Bytes:\s+Total:\s+(\d+)/ ) { + # $self->rx_dropped_bytes($1); + next; + } + + # Tx Duplicate Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Tx Duplicate Pkts:\s+Total:\s+(\d+)/ ) { + #$self->tx_duplicate_pkts($1); + next; + } + + # Tx Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Tx Pkts:\s+Total:\s+(\d+)/ ) { + #$self->tx_pkts_pkts($1); + next; + } + + # Tx Bytes: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Tx Bytes:\s+Total:\s+(\d+)/ ) { + $self->tx_bytes($1); + next; + } + + # Tx Failed Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Tx Failed Pkts:\s+Total:\s+(\d+)/ ) { + #$self->tx_failed_pkts($1); + next; + } + + # Tx Failed Late Pkts: Total: 5 Time: 60s Cur: 5 0/s + if ( $ln =~ /Tx Failed Late Pkts:\s+Total:\s+(\d+)/ ) { # ignore + next; + } + + # Tx Failed Bytes: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Tx Failed Bytes:\s+Total:\s+(\d+)/ ) { + next; + #$self->tx_bytes($1); + } + + # Tx Failed Late Pkts: Total: 5 Time: 60s Cur: 5 0/s + if ( $ln =~ /Tx Failed Late Bytes:\s+Total:\s+(\d+)/ ) { # ignore + next; + } + + # Recorded Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Recorded Pkts:/ ) { + next; + } + + # Recorded Bytes: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Recorded Bytes:/ ) { + next; + } + + # Rcrd Dropped Pkts: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Rcrd Dropped Pkts:/ ) { + next; + } + + # Rcrd Dropped Bytes: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /Rcrd Dropped Bytes:/ ) { + next; + } + else { + warn( "LAST LINE? Could not parse line -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n" ); + } + return; + # return for now, the rest of wanlink stuff not completely implemented - Adam + + # TODO: Adam - redo the rest of this wanpath stuff + + # WanPaths +# my $j = 0; +# my $wpaths = ""; +# for ( $j = 0 ; $j < $self->wan_paths() ; $j++ ) { +# +# # Name RxPkts RxBytes Dropped MaxRate(bps) Latency Backlog TxPkts TxBytes +# # wp1 0 0 0 56000 6 0 0 0 +# $i++; +# $ln = $ta[$i]; +# if ( $ln =~ +#/\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d)\s+(\d+)\s+(\d+)/ +# ) +# { +# $wpaths .= $ln; +# } +# else { +# warn( "Could not parse line -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n" ); +# } +# $self->wan_path_rpts($wpaths); +# } +# return; + } # If we were a LANforge-ICE endpoint. + + elsif ($got_voip) { + # Skip first line, we ignore VOIP flags for now. + if ( $ln =~ +/Shelf:\s+(\d+)\,\s+Card:\s+(\d+)\s+Port:\s+(\d+)\s+Endpoint:\s+(\d+)\s+Type:\s+(\S+)/ + ) + { + + # Shelf: 1, Card: 1 Port: 3 Endpoint: 4 Type: VOIP + $self->shelf_id($1); + $self->card_id($2); + $self->port_id($3); + $self->ep_id($4); + $self->ep_type($5); + next; + } + + # ProxyIP: 192.168.1.24 PhoneNum: 2102 PeerPhone: 2103 + if ( $ln =~ +/ProxyIP:\s+(\S+)\s+SipPort:\s+\S+\s+PhoneNum:\s+(\S+)\s+PeerPhone:\s+(\S+)/ + ) + { + $self->proxy_ip($1); + $self->phone_num($2); + $self->peer_phone_num($3); + next; + } + +# MinRtpPort: 10000 MaxRtpPort: 10002 RegExpireTimer: 300 + if ( $ln =~ +/MinRtpPort:\s+(\S+)\s+MaxRtpPort:\s+(\S+)\s+RegExpireTimer:\s+(\S+)/ + ) + { + $self->min_rtp_port($1); + $self->max_rtp_port($2); + $self->reg_expire_timer($3); + next; + } + +# SoundDev: /dev/dsp TxSoundFile: /tmp/graceland.wav RxSoundFile: /tmp/pcm_rx.wav + if ( $ln =~ /SoundDev:\s+(\S+)\s+TxSoundFile:\s+(.*\S)\s+JB-Bufs/ ) { + $self->sound_dev($1); + $self->tx_sound_file($2); + next; + } + + if ( $ln =~ /RxSoundFile:\s+(.*) PESQ-Server/ ) { + $self->rx_sound_file($3); + next; + } + + # FC-Delay: 5 MinInterCallGap: 5 MaxInterCallGap: 5 + if ( $ln =~ +/FC-Delay:\s+(\S+)\s+MinInterCallGap:\s+(\S+)\s+MaxInterCallGap:\s+(\S+)/ + ) + { + $self->fc_delay($1); + $self->min_ic_gap($2); + $self->max_ic_gap($3); + next; + } + + # LoopCalls: FOREVER LoopWaveFiles: 1 MinCallDuration: 0 + if ( $ln =~ +/LoopCalls:\s+(\S+)\s+LoopWaveFiles:\s+(\S+)\s+MinCallDuration:\s+(\S+)\s+Max:\s+(\S+)/ + ) + { + $self->loop_calls($1); + $self->loop_wav_files($2); + $self->min_call_duration($3); + $self->max_call_duration($4); + next; + } + +# RegisterState: REGISTERED CallState: CALL_IN_PROGRESS Protocol: SIP/G711U + if ( $ln =~ +/RegisterState:\s+(\S+)\s+CallState:\s+(\S+)\s+Protocol:\s+(\S+)\/(\S+)/ + ) + { + $self->register_state($1); + $self->call_state($2); + $self->msg_proto($3); + $self->rtp_encoding($4); + next; + } + + # RingingTimer: 10000ms LastCallSetup: 12ms StateChangeIn: 4s + if ( $ln =~ +/RingingTimer:\s+(\S+)\s+LastCallSetup:\s+(\S+)ms\s+StateChangeIn:\s+(\S+)s/ + ) + { + $self->ringing_timer($1); + $self->last_call_setup_time($2); + $self->state_change_in($3); + next; + } + + # RptTimer: 5000ms RunningFor: 150446s StopIn: 206276387s + if ( $ln =~ + /RptTimer:\s+(\d+)ms\s+RunningFor:\s+(\d+)s\s+StopIn:\s+(\d+)s/ ) + { + $self->report_timer($1); + $self->running_for($2); + $self->stop_in($3); + next; + } + +# LastRpt: 0.000 secs ago RealWriteRate: 62044bps RealReadRate: 62044bps + if ( $ln =~ +/LastRpt:\s+(\S+) secs ago\s+RealWriteRate:\s+(\d+)bps\s+RealReadRate:\s+(\d+)bps/ + ) + { + $self->last_rpt($1); + $self->real_tx_rate($2); + $self->real_rx_rate($3); + next; + } + + # Skip VAD settings + +# Latency: -32 -:-24:- -10 [ 9 1 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (5) + if ($ln =~ /\s+Latency:/) { + $self->latency($ln); + next; + } + +# RT-Latency: 0 -:73:- 1760 [ 5 0 1 0 0 0 0 2 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (5) + if ($ln =~ /RT-Latency:/) { + $self->rt_latency($ln); + next; + } + +# Jitter: -20 -:-1:- 2010 [ 891 1 1968 95 0 0 1 0 5 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (5) + if ($ln =~ /Jitter:/) { + $self->jitter($ln); + next; + } + +# CallSetup: 0 -:13:- 37 [ 0 0 49 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (5) + if ($ln =~ /CallSetup:/) { + $self->call_setup_dist($ln); + next; + } + + # CallsAttempted: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /CallsAttempted:\s+Total:\s+(\d+)/ ) { + $self->calls_attempted($1); + next; + } + + # CallsCompleted: Total: 501 Time: 300000ms Current: 1 + if ( $ln =~ /CallsCompleted:\s+Total:\s+(\d+)/ ) { + $self->calls_completed($1); + next; + } + + # CallsAnswered Total: 502 Time: 300000ms Current: 1 + if ( $ln =~ /CallsAnswered.*\s+Total:\s+(\d+)/ ) { + $self->calls_answered($1); + next; + } + + # CallsConnected Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /CallsConnected.*\s+Total:\s+(\d+)/ ) { + $self->calls_connected($1); + next; + } + + # CallsRemoteHUP Total: 501 Time: 300000ms Current: 1 + if ( $ln =~ /CallsRemoteHUP.*\s+Total:\s+(\d+)/ ) { + $self->calls_RHUP($1); + next; + } + + # CallsFailed: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /CallsFailed:\s+Total:\s+(\d+)/ ) { + $self->calls_failed($1); + next; + } + + # RTP Pkts Tx: Total: 7292502 Time: 300000ms Current: 14361 + if ( $ln =~ /RTP Pkts Tx:\s+Total:\s+(\d+)/ ) { + $self->tx_pkts($1); + next; + } + + if ($ln =~ /VAD:/) { + next; + } + + # RTP Pkts Rx: Total: 7292442 Time: 300000ms Current: 14361 + if ( $ln =~ /RTP Pkts Rx:\s+Total:\s+(\d+)/ ) { + $self->rx_pkts($1); + next; + } + + # RTP Bytes Tx: Total: 1166800320 Time: 300000ms Current: 2297760 + if ( $ln =~ /RTP Bytes Tx:\s+Total:\s+(\d+)/ ) { + $self->tx_bytes($1); + next; + } + + # RTP Bytes Rx: Total: 1166790720 Time: 300000ms Current: 2297760 + if ( $ln =~ /RTP Bytes Rx:\s+Total:\s+(\d+)/ ) { + $self->rx_bytes($1); + next; + } + + # RTP Pkts Dropped: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /RTP Pkts Dropped:\s+Total:\s+(\d+)/ ) { + $self->rx_dropped_pkts($1); + next; + } + + # RTP Pkts Dup: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /RTP Pkts Dup:\s+Total:\s+(\d+)/ ) { + $self->rx_dup_pkts($1); + next; + } + + # RTP Pkts Dropped: Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /RTP Pkts OOO:\s+Total:\s+(\d+)/ ) { + $self->rx_ooo_pkts($1); + next; + } + + # Skip JB silence played, overruns, underruns + #$i += 3; + + # CallsFailed-404 Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /CallsFailed-404\s+Total:\s+(\d+)/ ) { + $self->calls_failed_404($1); + next; + } + + # CF 408 (No-Answer) Total: 3 Time: 300000ms Current: 3 + if ( $ln =~ /CF 408 \(No-Answer\)\s+Total:\s+(\d+)/ ) { + $self->calls_failed_no_answer($1); + next; + } + else { + warn( "Could not parse line -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n" ); + } + + # CallsFailed-busy Total: 0 Time: 300000ms Current: 0 + if ( $ln =~ /CallsFailed-busy\s+Total:\s+(\d+)/ ) { + $self->calls_failed_busy($1); + next; + } + + # Rcvd 487 (Cancel) Total: 4 Time: 300000ms Current: 4 + if ( $ln =~ /Rcvd 487 \(Cancel\)\s+Total:\s+(\d+)/ ) { + $self->rcvd_487_cancel($1); + next; + } + warn( "Could not parse line -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n" ); + return; + } + elsif ($got_arm) { + + if ( $ln =~ +/Shelf:\s+(\d+)\,\s+Card:\s+(\d+)\s+Port:\s+(\d+)\s+Endpoint:\s+(\d+)\s+Type:\s+(\S+)/ + ) + { #Shelf: 1, Card: 1 Port: 1 Endpoint: 125 Type: ARM_UDP + $self->shelf_id($1); + $self->card_id($2); + $self->port_id($3); + $self->ep_id($4); + $self->ep_type($5); + next; + } + if ( $ln =~ +/MinPktSz:\s+(\d+)B\s+MaxPktSz:\s+(\d+)B\s+Pps:\s+(\d+)/ + ) + { #MinPktSz: 128B MaxPktSz: 128B Pps: 1 PktsToSend: 0 + $self->min_pkt_size($1); + $self->max_pkt_size($2); + $self->pps($3); + } + if ( $ln =~ /PktsToSend:\s+(\d+)/) { + $self->pkts_to_send($4); + next; + } + + if ( $ln =~ +/UdpSrcMin:\s+(\d+)\s+UdpSrcMax:\s+(\d+)\s+UdpDstMin:\s+(\d+)\s+UdpDstMax:\s+(\d+)/ + ) + { #UdpSrcMin: 9 UdpSrcMax: 9 UdpDstMin: 9 UdpDstMax: 9 + $self->udp_src_min($1); + $self->udp_src_max($2); + $self->udp_dst_min($3); + $self->udp_dst_max($4); + next; + } + if ( $ln =~ +/TcpSrcMin:\s+(\d+.\d+.\d+.\d+)\s+TcpSrcMax:\s+(\d+.\d+.\d+.\d+)\s+TcpDstMin:\s+(\d+.\d+.\d+.\d+)\s+TcpDstMax:\s+(\d+.\d+.\d+.\d+)/ + ) + { #TcpSrcMin: 192.168.99.2 TcpSrcMax: 192.168.99.2 TcpDstMin: 192.168.99.3 TcpDstMax: 192.168.99.3 + $self->min_src_ip($1); + $self->max_src_ip($2); + $self->min_dst_ip($3); + $self->max_dst_ip($4); + next; + } + if ( $ln =~ +/SrcMac:\s+(\S\S \S\S \S\S \S\S \S\S \S\S)\s+SrcMacCount:\s+(\d+)\s+RepeatedPkts:\s+(\d+)/ + ) + { #SrcMac: 00 e4 14 d5 e7 14 SrcMacCount: 0 RepeatedPkts: 0 + $self->src_mac($1); + $self->src_mac_cnt($2); + $self->multi_pkt($3); + next; + } + if ( $ln =~ +/DstMac:\s+(\S\S \S\S \S\S \S\S \S\S \S\S)\s+DstMacCount:\s+(\d+)\s+PeerRepeatedPkts:\s+(\d+)/ + ) + { #DstMac: 00 30 f7 03 c5 4a DstMacCount: 0 PeerRepeatedPkts: 0 + $self->dest_mac($1); + $self->dst_mac_cnt($2); + next; + + #$self->peer_multi_pkt($3); + + } + if ( $ln =~ +/RptTimer:\s+(\d+)ms\s+RunningFor:\s+(\d+)s\s+StopIn:\s+(\d+)s\s+ArmFlags:\s+\S+/ + ) + { #RptTimer: 156746ms RunningFor: 0s StopIn: 0s ArmFlags: 0x0 + $self->report_timer($1); + $self->running_for($2); + $self->stop_in($3); + $self->arm_flags($4); + next; + } + if ( $ln =~ + /LastRpt:\s+(\d+)\s+RealTxRate:\s+(\d+)bps\s+RealRxRate:\s+(\d+)bps/ + ) + { #LastRpt: 137608640 RealTxRate: 0bps RealRxRate: 0bps + # print "FOUND LINE 8\n"; + next; + } + + warn( "Could not parse line -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n" ); + } + else { + warn( "Could not parse line -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n" ); + } + } #for all the lines in our buffer +} #decode + +sub decodePayload { + my $self = shift; + + my $txt = shift; + + my @ta = split( /\n/, $txt ); + my $i; + my $got_one = 0; + + #print "Endpoint::decodePayload, txt -:$txt:-\n"; + my $pld = ""; + + for ( $i = 0 ; $i < @ta ; $i++ ) { + my $ln = $ta[$i]; + + #print "Line: -:$ln:-\n"; + + #Endpoint [endp-34-TX] (NOT_RUNNING, RND_PLD_SIZE, RATE_BURSTY) + my $nm = $self->{name}; + if ( $ln =~ /$nm payload\,/ ) { + $got_one = 1; + $i++; + $ln = $ta[$i]; + } + +# Shelf: 1, Card: 1 Port: 3 Endpoint: 15 Type: CUSTOM_TCP Pattern: CUSTOM + if ($got_one) { + + if ( $ln =~ /(\S.*\S\s)\s\s.*/ ) { + $pld .= "$1"; + } + else { + last; #done + } + } + } #for + + # Trim off remaining white-space + if ( $pld =~ /\s*(\S.*\S)\s*/ ) { + $self->payload($1); + } + else { + $self->payload(""); + } + +} #decodePayload + +sub toStringBrief { + my $self = shift; + + return "Endpoint: " . $self->name() . " (" . $self->ep_id() . ")"; + my $rv = ""; +} #toStringBrief + +sub isCustom { + my $self = shift; + my $tp = $self->ep_type(); + if (!defined $tp || ($tp eq "")) { + return 0; + } + if ( ( $tp =~ /custom/ ) || ( $tp =~ /CUSTOM/ ) ) { + return 1; + } + return 0; +} + +sub usesIP() { + my $self = shift; + my $tp = $self->ep_type(); + if ( ( $tp =~ /tcp/ ) || ( $tp =~ /udp/ ) ) { + return 1; + } + return 0; +} #usesIP + +sub isOfType { + my $self = shift; + my $tp = shift; + + my $_tp = $self->ep_type(); + if ( $_tp eq $tp ) { + return 1; + } + + # Deal with special (**puke**) cases + if ( $_tp eq "LANFORGE" ) { + return ( ( $tp eq "lf" ) || ( $tp eq "LF" ) ); + } + + if ( $_tp eq "LANFORGE_UDP" ) { + return ( ( $tp eq "lf_udp" ) || ( $tp eq "LF_UDP" ) ); + } + + if ( $_tp eq "LANFORGE_TCP" ) { + return ( ( $tp eq "lf_tcp" ) || ( $tp eq "LF_TCP" ) ); + } + + if ( $_tp eq "CUSTOM_ETHER" ) { + return ( $tp eq "custom_ether" ); + } + + if ( $_tp eq "CUSTOM_TCP" ) { + return ( $tp eq "custom_tcp" ); + } + + if ( $_tp eq "CUSTOM_UDP" ) { + return ( $tp eq "custom_udp" ); + } + + if ( $_tp eq "WAN_LINK" ) { + return ( $tp eq "wan_link" ); + } + + return 0; +} #isOfType + +# Not sure this works, and I know it does not deal well with WAN_LINK endpoints. +# TODO: Support WAN_LINK +sub toString { + my $self = shift; + + my $rv = ""; + $rv = <<__END_TS; +Endpoint [$self->{name}] ($self->{ep_flags}) + Shelf: $self->{shelf_id}, Card: $self->{card_id} Port: $self->{port_id} Endpoint: self->{endp_id} Type: self->{endp_type} Pattern: $self->{pattern} + MinTxRate: self->{min_tx_rate}bps MaxTxRate: self->{max_tx_rate}bps MinPktSize: self->min_pkt_sizeB MaxPktSize: self->{max_pkt_size}B + DestMAC: self->{dst_mac} DestIpAddr: self->{dest_ip_addr} DestIpPort: self->{dst_ip_port} + SrcMAC: self->{src_mac} SrcIP: self->{src_ip_addr} IpPort: self->{src_ip_port} IpTOS: self->{ip_tos} + Role: self->{role} ReportTimer: self->{report_timer}ms RunningFor: self->{running_for}s StopIn: self->{stop_in}s + Last Rpt: self->{last_rpt} secs ago Real Tx Rate: self->{real_tx_rate}bps +self->{counters} + +__END_TS + + return $rv; +} + +sub getSetCmds { + my $self = shift; + + my @rslt = (); + my $i = 0; + + if ( $self->ep_type() eq "WAN_LINK" ) { + $rslt[$i] = + ( "add_wl_endp " + . $self->name() . " " + . $self->shelf_id() . " " + . $self->card_id() . " " + . $self->port_id() . " " + . $self->cfg_latency() . " " + . $self->max_tx_rate() ); + $i++; + + $rslt[$i] = + ( "set_wanlink_info " + . $self->name() . " " + . $self->max_tx_rate() . " " + . $self->cfg_latency() . " " . " " + . $self->max_jitter() . " " + . $self->reord_freq() . " " + . $self->extra_buffer() . " " + . $self->drop_freq . " " + . $self->dup_freq() ); + $i++; + } + + # TODO: Support other types of endpoints. + #elsif ($self->ep_type() eq "WAN_LINK") { + else { + $rslt[$i] = + ( "add_endp " + . $self->name() . " " + . $self->shelf_id() . " " + . $self->card_id() . " " + . $self->port_id() . " " + . $self->getSetType() . " " + . $self->getSetIpPort() . " " + . $self->getBursty() . " " + . $self->min_tx_rate() . " " + . $self->max_tx_rate() . " " + . $self->size_random() . " " + . $self->min_pkt_size() . " " + . $self->max_pkt_size() . " " + . $self->pattern() . " " + . $self->checksum() ); + $i++; + + $rslt[$i] = ( "set_endp_tos " . $self->name() . " " . $self->ip_tos() ); + $i++; + } + + return @rslt; + +} #getSetCmds + +sub getSetPayloadCmd { + my $self = shift; + + return "set_endp_pay " . $self->name() . " custom " . $self->payload(); +} #getPayloadSetCmd + +sub getSetType { + my $self = shift; + + my $tp = $self->ep_type(); + if (!defined $tp || ($tp eq "")) { + print STDERR "Endpoint type not defined\n"; + return ""; + } + if ( $tp eq "LANFORGE" ) { + return "lf"; + } + if ( $tp eq "LANFORGE_UDP" ) { + return "lf_udp"; + } + if ( $tp eq "LANFORGE_TCP" ) { + return "lf_tcp"; + } + return $tp; +} #getSetType + +sub getSetIpPort { + my $self = shift; + + if ( $self->isFlagSet("IP_PORT_AUTO") ) { + return "-1"; + } + return $self->ip_port(); +} + +############################################## +## methods to access per-object data ## +## ## +## With args, they set the value. Without ## +## any, they only retrieve it/them. ## +############################################## + +sub name { + my $self = shift; + if (@_) { $self->{name} = shift } + return $self->{name}; +} + +sub setRandom { + my $self = shift; + my $val = shift; + if ( $val eq "YES" ) { + $self->ensureFlagSet("RND_PLD_SIZE"); + $self->ensureFlagNotSet("FIXED_PLD_SIZE"); + } + else { + $self->ensureFlagNotSet("RND_PLD_SIZE"); + $self->ensureFlagSet("FIXED_PLD_SIZE"); + } + + #print "Endpoint::setBursty -:$val:- flags: " . $self->ep_flags() . "\n"; +} #setBursty + +sub size_random { + my $self = shift; + + if ( $self->isFlagSet("RND_PLD_SIZE") ) { + return "YES"; + } + return "NO"; +} + +sub checksum { + my $self = shift; + + if ( $self->isFlagSet("CHECKSUM") ) { + return "YES"; + } + return "NO"; +} + +sub setBursty { + my $self = shift; + my $val = shift; + if ( $val eq "YES" ) { + $self->ensureFlagSet("RATE_BURSTY"); + } + else { + $self->ensureFlagNotSet("RATE_BURSTY"); + } + + #print "Endpoint::setBursty -:$val:- flags: " . $self->ep_flags() . "\n"; +} #setBursty + +sub getBursty { + my $self = shift; + + #print "Endpoint::getBursty flags: " . $self->ep_flags() . "\n"; + + if ( $self->isFlagSet("RATE_BURSTY") ) { + return "YES"; + } + return "NO"; +} + +sub isRunning { + my $self = shift; + + #print "Endpoint::getBursty flags: " . $self->ep_flags() . "\n"; + + if ( $self->isFlagSet("NOT_RUNNING") ) { + return "NO"; + } + return "YES"; +} + +sub ensureFlagSet { + my $self = shift; + my $flg = shift; + my $flgs = $self->ep_flags(); + if ( $flgs =~ /$flg/ ) { + return; + } + else { + $flgs .= "$flg "; + $self->ep_flags($flgs); + } +} #ensureFlagSet + +sub ensureFlagNotSet { + my $self = shift; + my $flg = shift; + my $flgs = $self->ep_flags(); + if ( $flgs =~ /$flg/ ) { + $flgs =~ s/$flg//; + $self->ep_flags($flgs); + } + else { + return; + } +} #ensureFlagNotSet + +sub isFlagSet { + my $self = shift; + my $flg = shift; + my $cur_flgs = $self->ep_flags(); + + #print "Endpoint::isFlagSet, flags -:$cur_flgs:- flg -:$flg:-\n"; + if ( $cur_flgs =~ /$flg/ ) { + return 1; + } + return 0; +} #isFlagSet + +sub ep_flags { + my $self = shift; + if (@_) { $self->{ep_flags} = shift } + return $self->{ep_flags}; +} + +sub tx_bytes { + my $self = shift; + if (@_) { $self->{tx_bytes} = shift } + return $self->{tx_bytes}; +} + +sub rx_bytes { + my $self = shift; + if (@_) { $self->{rx_bytes} = shift } + return $self->{rx_bytes}; +} + +sub rx_pkts { + my $self = shift; + if (@_) { $self->{rx_pkts} = shift } + return $self->{rx_pkts}; +} + +sub tx_pkts { + my $self = shift; + if (@_) { $self->{tx_pkts} = shift } + return $self->{tx_pkts}; +} + +sub rx_dropped_pkts { + my $self = shift; + if (@_) { $self->{rx_dropped_pkts} = shift } + return $self->{rx_dropped_pkts}; +} + +sub ttl { + my $self = shift; + if (@_) { $self->{ttl} = shift } + return $self->{ttl}; +} + +sub rx_ooo_pkts { + my $self = shift; + if (@_) { $self->{rx_ooo_pkts} = shift } + return $self->{rx_ooo_pkts}; +} + +sub rx_dup_pkts { + my $self = shift; + if (@_) { $self->{rx_dup_pkts} = shift } + return $self->{rx_dup_pkts}; +} + +sub shelf_id { + my $self = shift; + if (@_) { $self->{shelf_id} = shift } + return $self->{shelf_id}; +} + +sub card_id { + my $self = shift; + if (@_) { $self->{card_id} = shift } + return $self->{card_id}; +} + +sub port_id { + my $self = shift; + if (@_) { $self->{port_id} = shift } + return $self->{port_id}; +} + +sub ep_id { + my $self = shift; + if (@_) { $self->{endp_id} = shift } + return $self->{endp_id}; +} + +sub ep_type { + my $self = shift; + if (@_) { $self->{endp_type} = shift } + return $self->{endp_type}; +} + +sub payload { + my $self = shift; + if (@_) { $self->{payload} = shift } + return $self->{payload}; +} + +sub pattern { + my $self = shift; + if (@_) { $self->{pattern} = shift } + return $self->{pattern}; +} + +sub min_tx_rate { + my $self = shift; + if (@_) { $self->{min_tx_rate} = shift } + return $self->{min_tx_rate}; +} + +sub max_tx_rate { + my $self = shift; + if (@_) { $self->{max_tx_rate} = shift } + return $self->{max_tx_rate}; +} + +sub min_pkt_size { + my $self = shift; + if (@_) { $self->{min_pkt_size} = shift } + return $self->{min_pkt_size}; +} + +sub max_pkt_size { + my $self = shift; + if (@_) { $self->{max_pkt_size} = shift } + return $self->{max_pkt_size}; +} + +sub dest_mac { + my $self = shift; + if (@_) { $self->{dst_mac} = shift } + return $self->{dst_mac}; +} + +sub dest_ip_addr { + my $self = shift; + if (@_) { $self->{dst_ip_addr} = shift } + return $self->{dst_ip_addr}; +} + +sub dest_ip_port { + my $self = shift; + if (@_) { $self->{dst_ip_port} = shift } + return $self->{dst_ip_port}; +} + +sub src_mac { + my $self = shift; + if (@_) { $self->{src_mac} = shift } + return $self->{src_mac}; +} + +sub ip_addr { + my $self = shift; + if (@_) { $self->{ip_addr} = shift } + return $self->{ip_addr}; +} + +sub ip_port { + my $self = shift; + if (@_) { $self->{ip_port} = shift } + return $self->{ip_port}; +} + +sub ip_tos { + my $self = shift; + if (@_) { $self->{ip_tos} = shift } + return $self->{ip_tos}; +} + +sub role { + my $self = shift; + if (@_) { $self->{role} = shift } + return $self->{role}; +} + +sub report_timer { + my $self = shift; + if (@_) { $self->{report_timer} = shift } + return $self->{report_timer}; +} + +sub running_for { + my $self = shift; + if (@_) { $self->{running_for} = shift } + return $self->{running_for}; +} + +sub stop_in { + my $self = shift; + if (@_) { $self->{stop_in} = shift } + return $self->{stop_in}; +} + +sub last_rpt { + my $self = shift; + if (@_) { $self->{last_rpt} = shift } + return $self->{last_rpt}; +} + +sub real_tx_rate { + my $self = shift; + if (@_) { $self->{real_tx_rate} = shift } + return $self->{real_tx_rate}; +} + +sub real_rx_rate { + my $self = shift; + if (@_) { $self->{real_rx_rate} = shift } + return $self->{real_rx_rate}; +} + +sub cur_backlog { + my $self = shift; + if (@_) { $self->{cur_backlog} = shift } + return $self->{cur_backlog}; +} + +sub avg_latency { + my $self = shift; + if (@_) { $self->{avg_latency} = shift } + return $self->{avg_latency}; +} + +sub filename { + my $self = shift; + if (@_) { $self->{filename} = shift } + return $self->{filename}; +} + +sub send_bad_crc { + my $self = shift; + if (@_) { $self->{send_bad_crc} = shift } + return $self->{send_bad_crc}; +} + +sub rx_drop_seq { + my $self = shift; + if (@_) { $self->{rx_drop_seq} = shift } + return $self->{rx_drop_seq}; +} + +sub rx_drop_cx { + my $self = shift; + if (@_) { $self->{rx_drop_cx} = shift } + return $self->{rx_drop_cx}; +} + +sub rx_bit_errors { + my $self = shift; + if (@_) { $self->{rx_bit_errors} = shift } + return $self->{rx_bit_errors}; +} + +sub conn_estab { + my $self = shift; + if (@_) { $self->{conn_estab} = shift } + return $self->{conn_estab}; +} + +sub tcp_retrans { + my $self = shift; + if (@_) { $self->{tcp_retrans} = shift } + return $self->{tcp_retrans}; +} + +sub min_lat { + my $self = shift; + if (@_) { $self->{min_lat} = shift } + return $self->{min_lat}; +} + +sub max_lat { + my $self = shift; + if (@_) { $self->{max_lat} = shift } + return $self->{max_lat}; +} + +sub lat_buckets { + my $self = shift; + if (@_) { $self->{lat_buckets} = shift } + return $self->{lat_buckets}; +} + +sub lat_bucket_size { + my $self = shift; + if (@_) { $self->{lat_bucket_size} = shift } + return $self->{lat_bucket_size}; +} + +sub cfg_latency { + my $self = shift; + if (@_) { $self->{cfg_latency} = shift } + return $self->{cfg_latency}; +} + +sub max_jitter { + my $self = shift; + if (@_) { $self->{max_jitter} = shift } + return $self->{max_jitter}; +} + +sub wan_paths { + my $self = shift; + if (@_) { $self->{wan_paths} = shift } + return $self->{wan_paths}; +} + +sub drop_freq { + my $self = shift; + if (@_) { $self->{drop_freq} = shift } + return $self->{drop_freq}; +} + +sub dup_freq { + my $self = shift; + if (@_) { $self->{dup_freq} = shift } + return $self->{dup_freq}; +} + +sub jitter_freq { + my $self = shift; + if (@_) { $self->{jitter_freq} = shift } + return $self->{jitter_freq}; +} + +sub record_q { + my $self = shift; + if (@_) { $self->{record_q} = shift } + return $self->{record_q}; +} + +sub dump_file { + my $self = shift; + if (@_) { $self->{dump_file} = shift } + return $self->{dump_file}; +} + +sub min_drop_amt { + my $self = shift; + if (@_) { $self->{min_drop_amt} = shift } + return $self->{min_drop_amt}; +} + +sub max_drop_amt { + my $self = shift; + if (@_) { $self->{max_drop_amt} = shift } + return $self->{max_drop_amt}; +} + +sub reord_freq { + my $self = shift; + if (@_) { $self->{reord_freq} = shift } + return $self->{reord_freq}; +} + +sub max_buf { + my $self = shift; + if (@_) { $self->{max_buf} = shift } + return $self->{max_buf}; +} + +sub extra_buf { + my $self = shift; + if (@_) { $self->{extra_buf} = shift } + return $self->{extra_buf}; +} + +sub counters { + my $self = shift; + if (@_) { $self->{counters} = shift } + return $self->{counters}; +} + +sub proxy_ip { + my $self = shift; + if (@_) { $self->{proxy_ip} = shift } + return $self->{proxy_ip}; +} + +sub phone_num { + my $self = shift; + if (@_) { $self->{phone_num} = shift } + return $self->{phone_num}; +} + +sub peer_phone_num { + my $self = shift; + if (@_) { $self->{peer_phone_num} = shift } + return $self->{peer_phone_num}; +} + +sub min_rtp_port { + my $self = shift; + if (@_) { $self->{min_rtp_port} = shift } + return $self->{min_rtp_port}; +} + +sub max_rtp_port { + my $self = shift; + if (@_) { $self->{max_rtp_port} = shift } + return $self->{max_rtp_port}; +} + +sub reg_expire_timer { + my $self = shift; + if (@_) { $self->{reg_expire_timer} = shift } + return $self->{reg_expire_timer}; +} + +sub sound_dev { + my $self = shift; + if (@_) { $self->{sound_dev} = shift } + return $self->{sound_dev}; +} + +sub tx_sound_file { + my $self = shift; + if (@_) { $self->{tx_sound_file} = shift } + return $self->{tx_sound_file}; +} + +sub rx_sound_file { + my $self = shift; + if (@_) { $self->{rx_sound_file} = shift } + return $self->{rx_sound_file}; +} + +sub fc_delay { + my $self = shift; + if (@_) { $self->{fc_delay} = shift } + return $self->{fc_delay}; +} + +sub min_ic_gap { + my $self = shift; + if (@_) { $self->{min_ic_gap} = shift } + return $self->{min_ic_gap}; +} + +sub max_ic_gap { + my $self = shift; + if (@_) { $self->{max_ic_gap} = shift } + return $self->{max_ic_gap}; +} + +sub loop_calls { + my $self = shift; + if (@_) { $self->{loop_calls} = shift } + return $self->{loop_calls}; +} + +sub loop_wav_files { + my $self = shift; + if (@_) { $self->{loop_wav_files} = shift } + return $self->{loop_wav_files}; +} + +sub min_call_duration { + my $self = shift; + if (@_) { $self->{min_call_duration} = shift } + return $self->{min_call_duration}; +} + +sub state_change_in { + my $self = shift; + if (@_) { $self->{state_change_in} = shift } + return $self->{state_change_in}; +} + +sub call_setup_dist { + my $self = shift; + if (@_) { $self->{call_setup_dist} = shift } + return $self->{call_setup_dist}; +} + +sub last_call_setup_time { + my $self = shift; + if (@_) { $self->{last_call_setup_time} = shift } + return $self->{last_call_setup_time}; +} + +sub max_call_duration { + my $self = shift; + if (@_) { $self->{max_call_duration} = shift } + return $self->{max_call_duration}; +} + +sub register_state { + my $self = shift; + if (@_) { $self->{register_state} = shift } + return $self->{register_state}; +} + +sub call_state { + my $self = shift; + if (@_) { $self->{call_state} = shift } + return $self->{call_state}; +} + +sub msg_proto { + my $self = shift; + if (@_) { $self->{msg_proto} = shift } + return $self->{msg_proto}; +} + +sub rtp_encoding { + my $self = shift; + if (@_) { $self->{rtp_encoding} = shift } + return $self->{rtp_encoding}; +} + +sub latency { + my $self = shift; + if (@_) { $self->{latency} = shift } + return $self->{latency}; +} + +sub rt_latency { + my $self = shift; + if (@_) { $self->{rt_latency} = shift } + return $self->{rt_latency}; +} + +sub jitter { + my $self = shift; + if (@_) { $self->{jitter} = shift } + return $self->{jitter}; +} + +sub calls_attempted { + my $self = shift; + if (@_) { $self->{calls_attempted} = shift } + return $self->{calls_attempted}; +} + +sub calls_completed { + my $self = shift; + if (@_) { $self->{calls_completed} = shift } + return $self->{calls_completed}; +} + +sub calls_answered { + my $self = shift; + if (@_) { $self->{calls_answered} = shift } + return $self->{calls_answered}; +} + +sub calls_connected { + my $self = shift; + if (@_) { $self->{calls_connected} = shift } + return $self->{calls_connected}; +} + +sub calls_RHUP { + my $self = shift; + if (@_) { $self->{calls_RHUP} = shift } + return $self->{calls_RHUP}; +} + +sub calls_failed { + my $self = shift; + if (@_) { $self->{calls_failed} = shift } + return $self->{calls_failed}; +} + +sub calls_failed_404 { + my $self = shift; + if (@_) { $self->{calls_failed_404} = shift } + return $self->{calls_failed_404}; +} + +sub calls_failed_busy { + my $self = shift; + if (@_) { $self->{calls_failed_busy} = shift } + return $self->{calls_failed_busy}; +} + +sub calls_failed_no_answer { + my $self = shift; + if (@_) { $self->{calls_failed_no_answer} = shift } + return $self->{calls_failed_no_answer}; +} + +sub rcvd_487_cancel { + my $self = shift; + if (@_) { $self->{rcvd_487_cancel} = shift } + return $self->{rcvd_487_cancel}; +} + +sub ringing_timer { + my $self = shift; + if (@_) { $self->{ringing_timer} = shift } + return $self->{ringing_timer}; +} + +# +# Armageddon get/set functions - Added by Adam +# + +sub udp_src_min { + my $self = shift; + if (@_) { $self->{udp_src_min} = shift } + return $self->{udp_src_min}; +} + +sub udp_src_max { + my $self = shift; + if (@_) { $self->{udp_src_max} = shift } + return $self->{udp_src_max}; +} + +sub udp_dst_min { + my $self = shift; + if (@_) { $self->{udp_dst_min} = shift } + return $self->{udp_dst_min}; +} + +sub udp_dst_max { + my $self = shift; + if (@_) { $self->{udp_dst_max} = shift } + return $self->{udp_dst_max}; +} + +sub pps { + my $self = shift; + if (@_) { $self->{pps} = shift } + return $self->{pps}; +} + +sub pkts_to_send { + my $self = shift; + if (@_) { $self->{pkts_to_send} = shift } + return $self->{pkts_to_send}; +} + +sub arm_flags { + my $self = shift; + if (@_) { $self->{arm_flags} = shift } + return $self->{arm_flags}; +} + +sub src_mac_cnt { + my $self = shift; + if (@_) { $self->{src_mac_cnt} = shift } + return $self->{src_mac_cnt}; +} + +sub dst_mac_cnt { + my $self = shift; + if (@_) { $self->{dst_mac_cnt} = shift } + return $self->{dst_mac_cnt}; +} + +sub multi_pkt { + my $self = shift; + if (@_) { $self->{multi_pkt} = shift } + return $self->{multi_pkt}; +} + +sub min_src_ip { + my $self = shift; + if (@_) { $self->{min_src_ip} = shift } + return $self->{min_src_ip}; +} + +sub max_src_ip { + my $self = shift; + if (@_) { $self->{max_src_ip} = shift } + return $self->{max_src_ip}; +} + +sub min_dst_ip { + my $self = shift; + if (@_) { $self->{min_dst_ip} = shift } + return $self->{min_dst_ip}; +} + +sub max_dst_ip { + my $self = shift; + if (@_) { $self->{max_dst_ip} = shift } + return $self->{max_dst_ip}; +} + +1; # So the require or use succeeds (perl stuff) +__END__ + + +# Plain Old Documentation (POD) + +=head1 NAME + Endpoint - class to implement LANforge Endpoints + +=head1 SYNOPSIS + + use LANforge::Endpoint + + ################# + # class methods # + ################# + $ob = LANforge::Endpoint->new; + + ####################### + # object data methods # + ####################### + + ### get versions ### + $name = $ob->name; + + ### set versions ### + $ob->name("endp-2-TX"); + + ######################## + # other object methods # + ######################## + + $ob->decode("CLI output that contains this Endpoint's output"); + +=head1 DESCRIPTION + + The Endpoint class gives you some clever access into the Endpoint + objects as sent by the LANforge CLI. + +=head1 AUTHOR + Ben Greear (greearb@candelatech.com) + + Copyright (c) 2001 Candela Technologies. All rights reserved. + This program is free software; you can redistribute it and/or + modify it under the same terms as Perl itself. + + +=head1 VERSION + Version 0.0.1 May 26, 2001 + +=end diff --git a/lanforge/lanforge-scripts/LANforge/GuiJson.pm b/lanforge/lanforge-scripts/LANforge/GuiJson.pm new file mode 100644 index 000000000..e255b3b0a --- /dev/null +++ b/lanforge/lanforge-scripts/LANforge/GuiJson.pm @@ -0,0 +1,268 @@ +package LANforge::GuiJson; +use strict; +use warnings; +use JSON; +#use Exporter 'import'; +use Scalar::Util 'blessed'; + +if (defined $ENV{'DEBUG'}) { + use Data::Dumper; + use diagnostics; + use Carp; + $SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +} + +our $NL="\n"; + +our @EXPORT_OK=qw(new); +our $refs_example = q( \@portnames or ["sta1", "sta2"] not ("sta1", "sta2")); + +sub new { + my $this = { + 'url' => undef, + 'handler' => undef, + 'uri' => undef, + 'header' => undef, + 'data' => undef, + 'headermap' => {}, + }; # Create an anonymous hash, and #self points to it. + + bless $this; # Connect the hash to the package Cocoa. + return $this; # Return the reference to the hash. +} + +=pod +=head1 GuiResponseToHash +=cut +sub GuiResponseToHash { + my $self = shift; + my $response = shift; + my $ra_data = JSON::decode($response); + my $rh_data = {}; + $rh_data->{'handler'} = $ra_data->[0]->{'handler'}; + $rh_data->{'uri'} = $ra_data->[1]->{'uri'}; + $rh_data->{'header'} = $ra_data->[2]->{'header'}; + $rh_data->{'data'} = $ra_data->[3]->{'data'}; + #print Dumper($rh_data); + return $rh_data; +} + +sub Request { + my $self = shift; + $self->{'url'} = shift; + if (!defined $self->{'url'}) { + die("Request wants url; example 'http://localhost:8080/PortTab')"); + } + my $json = JSON->new; + my $ra_data = $json->decode(`curl -s $self->{'url'}`); + #print "---------------------------------------------------------------------\n"; + #print Dumper($ra_data); + #print "---------------------------------------------------------------------\n"; + + $self->{'handler'} = @$ra_data[0]->{'handler'}; + die("GuiJson response missing 'handler'") if (!defined $self->{'handler'}); + + $self->{'uri'} = @$ra_data[1]->{'uri'}; + die("GuiJson response missing 'uri'") if (!defined $self->{'uri'}); + + $self->{'header'} = @$ra_data[2]->{'header'}; + die("GuiJson response missing 'header'") if (!defined $self->{'header'}); + + $self->{'data'} = @$ra_data[3]->{'data'}; + die("GuiJson response missing 'data'") if (!defined $self->{'data'}); + + $self->MakeHeaderMap(); +} # ~Request + +sub MakeHeaderMap { + my $self = shift; + $self->{'headermap'} = {}; + if (!defined $self->{'header'}) { + print STDERR Dumper($self); + die("MakeHeaderMap: self->{'header'} unset\n"); + } + my $index = 0; + for my $headername (@{$self->{'header'}}) { + $self->{'headermap'}->{$headername} = $index; + $index++; + } +} + +sub GetHeaderMap { + my $self = shift; + return $self->{'headermap'}; +} + +=pod +=head1 GetRecordsMatching +GetRecordsMatching expects results of GetGuiResponseToHash and a list of port EIDs or names +$ra_ports = GetRecordsMatching($rh_data, $header_name, $value) +=cut +sub GetRecordsMatching { + my $self = shift; + my $header_name = shift; + my $ra_needles = shift; + my $ra_results = []; + + if (!defined $header_name || $header_name eq '') { + print STDERR "GetRecordsMatching wants arg1: header name\n"; + return $ra_results; + } + + if (!defined $ra_needles || ref($ra_needles) ne 'ARRAY') { + print Dumper($ra_needles); + my $example = q( \@portnames or ["sta1", "sta2"] not ("sta1", "sta2")); + print STDERR "GetRecordsMatching wants arg3: list values to match against <$header_name>.\nPass array references, eg:\n$example\n"; + return $ra_results; + } + + my $value = undef; + my @matches = undef; + for my $ra_port (@{$self->{'data'}}) { + $value = $ra_port->[ $self->HeaderIdx($header_name)]; + #print "$header_name: $value\n"; + @matches = grep { /$value/ } @$ra_needles; + if (@matches) { + push(@$ra_results, $ra_port); + } + } + return $ra_results; +} # ~GetRecordsMatching + +=pod +=head1 HeaderTrans +HeaderTrans($name) is used to resolve header regex to a field +name. HeaderTrans uses $headermap keys if they match exactly, +even if the $name passed in looks like a regex. Field names +Not found in $self->headertrans hash are then resolved as +regexes using grep { /$name/ } @fieldnames. Only the first +match is cached. + +$fieldname = HeaderIdx( "No CX (us)") + # plain return 'No CX (us)' + +$idx = HeaderIdx( "No CX.*") + # regex evaluated only if 'No CX.*' doesn't exist + # as a literal key in $self->headertrans +=cut +sub HeaderTrans { + my $self = shift; + my $headername = shift; + my %headermap = %{$self->{'headermap'}}; + $self->{'headertrans'} = {} + if (!defined $self->{'headertrans'}); + + if (!defined $headername || "$headername" eq "") { + die("HeaderTrans: Header name is empty or unset, bye\n"); + return -1; + } + my %headertrans = %{$self->{'headertrans'}}; + + if (defined $headertrans{$headername}) { + return $headertrans{$headername}; + } + if (defined $headermap{$headername}) { + $headertrans{$headername} = $headername; + return $headername; + } + # look for regex matches next + my @matches = grep { /$headername/ } keys %{$self->{'headermap'}}; + if (@matches < 1) { + print STDERR "HeaderTrans: Headermap name <$headername> unmached, you get -1.\n"; + $headertrans{$headername} = -1; + return -1; + } + my $a = $matches[0]; + $headertrans{$headername} = $a; + if (@matches > 1) { + print STDERR "Headermap name <$headername> has multiple matches, you get $a.\n"; + } + return $a; +} + +=pod +=head1 HeaderIdx +HeaderIdx($name) is used to resolve header name to index in +array holding record data. HeaderIdx uses HeaderTrans() to +map names ore regexes to resolved field names and only do +regex lookups once per pattern. +$idx = HeaderIdx( "Alias") # plain name +$idx = HeaderIdx( "No CX.*") # regex +=cut +sub HeaderIdx { + my $self = shift; + my $headername = shift; + my %headermap = %{$self->{'headermap'}}; + + if (!defined $headername || "$headername" eq "") { + die("Header name is empty or unset, bye\n"); + return -1; + } + my $key = $self->HeaderTrans($headername); + + if (defined $headermap{$key}) { + return $headermap{$key}; + } + print STDERR "headermap{$key} undefined, you get -1\n"; + return -1; +} # ~HeaderIdx + +=pod +=head1 GetFields +Returns matching fields from a record; +$ra_needles are an array of strings to match to select records +$ra_field_names are field names to return from those records +$rh = GetFields($header_name, $ra_needles, $ra_field_names) +=cut +sub GetFields { + my $self = shift; + my $header_name = shift; + my $ra_needles = shift; + my $ra_field_names = shift; + my $ra_records = []; + my $rh_field_values = {}; + + if (!defined $header_name || $header_name eq '') { + print STDERR "GetFields wants arg2: header name\n"; + return $rh_field_values; + } + + if (!defined $ra_needles || ref($ra_needles) ne 'ARRAY') { + print Dumper($ra_needles); + + print STDERR "GetFields wants arg3: list values to match against <$header_name>.\nPass array references, eg:\n$::refs_example\n"; + return $rh_field_values; + } + if (!defined $ra_field_names || ref($ra_field_names) ne 'ARRAY') { + my $arg_str = join(", ", @$ra_needles); + print STDERR "GetFields wants arg4: list field names to return if <$header_name> matches <$arg_str>\nPass array references, eg:\n$::refs_example\n"; + return $rh_field_values; + } + + $ra_records = $self->GetRecordsMatching($header_name, $ra_needles); + return $rh_field_values if (@$ra_records < 1); + + for my $ra_record (@$ra_records) { + next if (@$ra_record < 1); + next if (! defined @$ra_record[$self->HeaderIdx($header_name)]); + my $record_name = @$ra_record[$self->HeaderIdx($header_name)]; + next if (!defined $record_name || "$record_name" eq ""); + #print "record name[$record_name]\n"; + #print Dumper($ra_record); + my $rh_record_vals = {}; + $rh_field_values->{$record_name} = $rh_record_vals; + #print Dumper($ra_field_names); + + for my $field_name (@$ra_field_names) { + next if (!defined $field_name || "$field_name" eq ""); + my $xl_name = $self->HeaderTrans($field_name); + my $field_idx = $self->HeaderIdx($xl_name); + next if (!defined @$ra_record[$field_idx]); + #print "Field Name $field_name [".@$ra_record[$field_idx]."] "; + $rh_record_vals->{$xl_name} = @$ra_record[$field_idx]; + } + #print Dumper($rh_record_vals); + } + return $rh_field_values; +} +1; diff --git a/lanforge/lanforge-scripts/LANforge/JsonUtils.pm b/lanforge/lanforge-scripts/LANforge/JsonUtils.pm new file mode 100644 index 000000000..25743e366 --- /dev/null +++ b/lanforge/lanforge-scripts/LANforge/JsonUtils.pm @@ -0,0 +1,197 @@ +# JsonUtils +package LANforge::JsonUtils; +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use JSON; +use Data::Dumper; + +if (defined $ENV{'DEBUG'}) { + use Data::Dumper; + use diagnostics; + use Carp; + $SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +} + +our $NL="\n"; +use Exporter 'import'; +our @EXPORT_OK=qw(err logg xpand json_request get_links_from get_thru json_post get_port_names flatten_list); + +sub err { + my $i; + for $i (@_) { + print STDERR "$i"; + } + print STDERR $NL; +} + +sub logg { + my $i; + for $i (@_) { + print STDOUT "$i "; + } + # print STDOUT $NL; +} + +sub xpand { + my ($rrl) = @_; + die("Will not expand a blank URI") if ("" eq $rrl || $rrl =~ m/^\s*$/); + return $rrl if ($rrl =~ /^http/); + return $rrl if ($rrl =~ m{^$main::HostUri/}); + return "${main::HostUri}$rrl" if ($rrl =~ m{^/}); + return "${main::HostUri}/$rrl"; +} + +sub json_request { + my ($uri) = @_; + my $url = xpand($uri); + #logg("$uri becomes $url\n"); + my $req = new HTTP::Request("GET" => $url); + $req->header("Accept" => "application/json"); + my $response = $::Web->request($req); + if ($response->code != 200) { + err("Status ".$response->code.": ".$response->content."\n"); + if ($response->content =~ /(Can't connect|Connection refused)/) { + exit(1); + } + return {}; + } + #print Dumper($response->content); + return $::Decoder->decode($response->content); +} + +sub json_post { + my ($uri, $rh_data) = @_; + my $url = xpand($uri); + #print STDERR "URI $uri\n"; + my $req = HTTP::Request->new("POST" => $url); + $req->header('Accept' => 'application/json'); + $req->header('Content-Type' => 'application/json; charset=UTF-8'); + $req->content(encode_json($rh_data)); + #print "json_post: ".Dumper($rh_data); + #print Dumper($req); + my $response = $::Web->request($req); + #print Dumper($response); + if ($response->code != 200) { + err("Status ".$response->code.": ".$response->content."\n"); + if ($response->content =~ /(Can't connect|Connection refused)/) { + exit(1); + } + return {}; + } + my $rh_response = $::Decoder->decode($response->content); + + print Dumper($rh_response) + if ( defined $rh_response->{"Resource"} + && defined $rh_response->{"Resource"}->{"warnings"}); + print Dumper($rh_response) + if ( defined $rh_response->{"errors"} + || defined $rh_response->{"error_list"}); + return $rh_response; +} + +# use this to create a flat hash of $eid ->{result data} when given +# [ { $eid->{data}}, {}, {} ] which is harder to navigate +sub flatten_list { + my $rh_list = shift; + my $list_name = shift; + my $rh_irefs = {}; + return if (!defined $rh_list); + #print "\n- FF ------------------------------------------------------------\n"; + #print Dumper($rh_list); + #print "\n~ FF ------------------------------------------------------------\n"; + if (!defined $rh_list->{$list_name}) { + print "flatten_list: $list_name not found\n"; + return; + } + if (ref $rh_list->{$list_name} eq "HASH") { + return if ( (keys %{$rh_list->{$list_name}} < 1)); + } + if (ref $rh_list->{$list_name} ne "ARRAY") { + print "flatten_list: $list_name not Array Ref:\n"; + print "-------------------------------------------------\n"; + print Dumper($rh_list); + print "-------------------------------------------------\n"; + return; + } + #print "\n- FG -------------------------------------------------------------------\n"; + #print Dumper($rh_list->{$list_name}); + #print "\n~ FG -------------------------------------------------------------------\n"; + my $v = @{$rh_list->{$list_name}}; + my @k = (@{$rh_list->{$list_name}}); + for (my $i=0; $i < $v; $i++) { + my @rh_k = keys %{$k[$i]}; + my @rh_v = values %{$k[$i]}; + my $rh_id = $rh_k[0]; + #print "\n- FG -------------------------------------------------------------------\n"; + #print Dumper($rh_id); + #print "\n~ FG -------------------------------------------------------------------\n"; + $rh_irefs->{$rh_id} = $rh_v[0]; + } + #print "\n- FG -------------------------------------------------------------------\n"; + #print Dumper($rh_irefs); + #print "\n~ FG -------------------------------------------------------------------\n"; + $rh_list->{"flat_list"} = $rh_irefs; +} + +sub get_port_names { + my ($rh_gpn, $arrayname) = @_; + my $ra_gpn2 = $rh_gpn->{$arrayname}; + my $ra_gpn_links2 = []; + #print Dumper($ra_gpn2); + for my $rh_gpn2 (@$ra_gpn2) { + #print Dumper($rh_gpn2); + for my $key (keys %$rh_gpn2) { + my $v = $rh_gpn2->{$key}; + next if (!(defined $v->{'_links'})); + my $rh_i = { + 'uri' => $v->{'_links'}, + 'alias' => $v->{'alias'} + }; + if (defined $v->{'device'}) { + $rh_i->{'device'} = $v->{'device'}; + } + push(@$ra_gpn_links2, $rh_i); + } + } + #print Dumper($ra_links2); + return $ra_gpn_links2; +} + +sub get_links_from { + my ($rh_glf, $arrayname) = @_; + my $ra_glf2 = $rh_glf->{$arrayname}; + my $ra_glf_links2 = []; + for my $rh_glf2 (@$ra_glf2) { + for my $key (keys %$rh_glf2) { + my $v = $rh_glf2->{$key}; + next if (!(defined $v->{'_links'})); + push(@$ra_glf_links2, $v->{'_links'}); + } + } + #print Dumper($ra_links2); + return $ra_glf_links2; +} + +# eg get_thru( 'interface', 'device' ) +sub get_thru { + my ($inner, $key, $rh_top) = @_; + if (!(defined $rh_top->{$inner})) { + print Dumper($rh_top); + return -1; + } + my $rh_inner = $rh_top->{$inner}; + return $rh_inner->{$key}; +} +1; diff --git a/lanforge/lanforge-scripts/LANforge/Port.pm b/lanforge/lanforge-scripts/LANforge/Port.pm new file mode 100644 index 000000000..478e7055b --- /dev/null +++ b/lanforge/lanforge-scripts/LANforge/Port.pm @@ -0,0 +1,1079 @@ +package LANforge::Port; +use strict; + +################################################## +## the object constructor ## +## To use: $ep = LANforge::Port->new(); ## +## or: $ep2 = $ep->new(); ## +################################################## + +# Must be kept in sync with Port.h, the GUI, gnuforge (gui_msgs.h), etc... +# Hopefully it won't change too much! + +my $IF_down = 0; +my $IF_10bt_HD = 1; +my $IF_10bt_FD = 2; +my $IF_100bt_HD = 3; +my $IF_100bt_FD = 4; +my $IF_1000_HD = 5; +my $IF_1000_FD = 6; +my $IF_100bt4 = 7; +my $IF_auto_negotiate = 8; +my $IF_link_OK = 9; +my $IF_flow_control = 10; +my $IF_negotiation_complete = 11; +my $IF_remote_fault = 12; +my $IF_link_jabber = 13; +my $IF_802_3X_flow_control = 14; +my $IF_10bt_link = 15; +my $IF_100bt_link = 16; +my $IF_PHANTOM = 17; +my $IF_ADMIN_DOWN = 18; +my $IF_MII_PROBE_ERROR = 19; +my $IF_ADV_10bt_HD = 20; # What to advertise when in auto-negotiate mode. +my $IF_ADV_10bt_FD = 21; +my $IF_ADV_100bt_HD = 22; +my $IF_ADV_100bt_FD = 23; +my $IF_ADV_1000_HD = 24; +my $IF_ADV_1000_FD = 25; +my $IF_ADV_100bt4 = 26; +my $IF_ADV_flow_control = 27; +my $IF_PROMISC = 28; + +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + + $self->{shelf_id} = undef; + $self->{card_id} = undef; + $self->{port_id} = undef; + + bless( $self, $class ); + + $self->initDataMembers(); + + return $self; +} #new + +sub initDataMembers { + my $self = shift; + + $self->{pps_tx} = undef; + $self->{pps_rx} = undef; + $self->{bps_tx} = undef; + $self->{bps_rx} = undef; + $self->{port_type} = undef; + $self->{cur_flags} = undef; + $self->{parent} = undef; + $self->{supported_flags} = undef; + $self->{partner_flags} = undef; + $self->{advert_flags} = undef; + $self->{ip_addr} = undef; + $self->{ip_mask} = undef; + $self->{ip_gw} = undef; + $self->{ipv6_global} = undef; + $self->{ipv6_link} = undef; + $self->{ipv6_gw} = undef; + $self->{dns_servers} = undef; + $self->{mac_addr} = undef; + $self->{dev} = undef; + $self->{mtu} = undef; + $self->{tx_q_len} = undef; + $self->{rx_pkts} = undef; + $self->{tx_pkts} = undef; + $self->{rx_bytes} = undef; + $self->{tx_bytes} = undef; + $self->{rx_errors} = undef; + $self->{tx_errors} = undef; + $self->{rx_drop} = undef; + $self->{tx_drop} = undef; + $self->{multicasts} = undef; + $self->{collisions} = undef; + $self->{rx_len_err} = undef; + $self->{rx_overflow} = undef; + $self->{rx_crc} = undef; + $self->{rx_frame} = undef; + $self->{rx_fifo} = undef; + $self->{rx_missed} = undef; + $self->{tx_abort} = undef; + $self->{tx_carrier} = undef; + $self->{tx_fifo} = undef; + $self->{tx_heartbeat} = undef; + $self->{tx_window} = undef; + $self->{tx_bytes_ll} = undef; + $self->{tx_bytes_ll} = undef; + $self->{alias} = undef; + $self->{dhcp_client_id} = undef; + $self->{dhcp_vendor_id} = undef; +} #initDataMembers + +sub isPhantom { + my $self = shift; + my $cf = $self->{cur_flags}; + if ( $cf =~ /PHANTOM/ ) { + return 1; + } + return 0; +} + +# A Port's output from the CLI looks something like this +#Shelf: 1, Card: 1, Port: 5 Type: Ethernet Alias: dut1 +# Current: LINK-UP 100bt FULL-DUPLEX AUTO-NEGOTIATE NEG-COMPLETE +# Supported: 10bt 100bt FULL-DUPLEX AUTO-NEGOTIATE +# Partner: 10bt 100bt FULL-DUPLEX +# Advertising: 10bt-HD 10bt-FD 100bt-HD 100bt-FD FLOW-CONTROL +# IP: 0.0.0.0 MASK: 0.0.0.0 GW: 0.0.0.0 +# IPv6-Global: DELETED +# IPv6-Link: DELETED +# IPv6-Gateway: DELETED +# MAC: 00:c0:95:e2:4c:0e DEV: eth5 MTU: 1500 TX Queue Len: 400 +# Rxp: 88210 Txp: 0 Rxb: 5292600 Txb: 0 RxERR: 18338 TxERR: 0 +# RxDrop: 0 TxDrop: 0 Multi: 0 Coll: 0 RxLenERR: 0 RxOverFlow: 0 +# RxCRC: 0 RxFrame: 0 RxFifo: 0 RxMissed: 0 TxAbort: 0 TxCarrier: 0 +# TxFifo: 0 TxHeartBeat: 0 TxWindow: 0 RxBytesLL: 0 TxBytesLL: 0 + +sub decode { + my $self = shift; + my $txt = shift; + + my $dvname = ""; + + #print "Port::decode, txt -:$txt:-\n"; + if ( $txt =~ /DEV:\s+(\S+)\s+/g ) { + $dvname = $1; + } + + my @ta = split( /\n/, $txt ); + my $i; + my $got_one = 0; + for ( $i = 0 ; $i < @ta ; $i++ ) { + my $ln = $ta[$i]; + + #print "Got line -:$ln:-\n"; + + #Shelf: 1, Card: 1, Port: 5 Type: Ethernet Alias: dut1 + if ( + !( + defined( $self->{shelf_id} ) + && defined( $self->{card_id} ) + && defined( $self->{port_id} ) + ) + ) + { + if ( $ln =~ +/Shelf:\s+(\d+)\,\s+Card:\s+(\d+)\,\s+Port:\s+(\S+)\s+Type:\s+(\S+)\s+Alias:\s+(\S+)/ + ) + { + $self->shelf_id($1); + $self->card_id($2); + $self->port_id($3); + $self->port_type($4); + $self->alias($5); + $got_one = 1; + } + elsif ( $ln =~ + /Shelf:\s+(\d+)\,\s+Card:\s+(\d+)\,\s+Port:\s+(\S+)\s+Type:\s+(\S+)/ + ) + { + $self->shelf_id($1); + $self->card_id($2); + $self->port_id($3); + $self->port_type($4); + $self->alias(""); + $got_one = 1; + } + } + else { + my $sid = $self->shelf_id(); + my $cid = $self->card_id(); + my $pid = $self->port_id(); + if ( $dvname eq $pid ) { + + #print "Looking for match, ln -:$ln:-\n"; + if ( $ln =~ + /Shelf:\s+$sid\,\s+Card:\s+$cid\,\s+Port:\s+\S+\s+Type:\s+(\S+)/ + ) + { + $self->port_type($1); + $got_one = 1; + } + } + else { + + #print "Looking for match, ln -:$ln:-\n"; + if ( $ln =~ + /Shelf:\s+$sid\,\s+Card:\s+$cid\,\s+Port:\s+$pid\s+Type:\s+(\S+)/ + ) + { + $self->port_type($1); + $got_one = 1; + } + } + } + + #print "Got_one: $got_one\n"; + + if ($got_one) { + + # Current: LINK-UP 100bt FULL-DUPLEX AUTO-NEGOTIATE NEG-COMPLETE + $i++; + $ln = $ta[$i]; + + if ( $ln =~ /.*Parent\/Peer:\s+(\S+)\s+Rpt-Timer.*/ ) { + $self->parent($1); + $i++; + $ln = $ta[$i]; + } + elsif ( $ln =~ /.*Parent\/Peer:\s+Rpt-Timer.*/ ) { + $self->parent(""); + $i++; + $ln = $ta[$i]; + } + + if ( $ln =~ /\s+Current:\s+(.*)/ ) { + $self->cur_flags($1); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + # Supported: 10bt 100bt FULL-DUPLEX AUTO-NEGOTIATE + $i++; + $ln = $ta[$i]; + if ( $ln =~ /\sSupported:\s+(.*)/ ) { + $self->supported_flags($1); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + # Partner: 10bt 100bt FULL-DUPLEX + $i++; + $ln = $ta[$i]; + if ( $ln =~ /\s+Partner:\s+(.*)/ ) { + $self->partner_flags($1); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + # Advertising: 10bt-HD 10bt-FD 100bt-HD 100bt-FD FLOW-CONTROL + $i++; + $ln = $ta[$i]; + if ( $ln =~ /\s+Advertising:\s+(.*)/ ) { + $self->advert_flags($1); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + # IP: 0.0.0.0 MASK: 0.0.0.0 GW: 0.0.0.0 + $i++; + $ln = $ta[$i]; + if ( $ln =~ /\s+IP:\s+(\S+)\s+MASK:\s+(\S+)\s+GW:\s+(\S+)/ ) { + + #print "Found ip_addr: $1\n"; + $self->ip_addr($1); + + #print "After setting ip_addr: $1, " . $self->ip_addr() . "\n"; + $self->ip_mask($2); + $self->ip_gw($3); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + $i++; + $ln = $ta[$i]; + if ( $ln =~ /\s+DNS[ -]Servers:\s+(.*)/ ) { + $self->dns_servers($i); + $i++; + $ln = $ta[$i]; + } + + # IPv6-Global: DELETED + if ( $ln =~ /\s+IPv6-Global:\s+(\S+)/ ) { + + #print "Found ipv6_global: $1\n"; + $self->ipv6_global($1); + + #print "After setting ipv6_global: $1, " . $self->ipv6_global() . "\n"; + + # IPv6-Link: DELETED + $i++; + $ln = $ta[$i]; + if ( $ln =~ /\s+IPv6-Link:\s+(\S+)/ ) { + + #print "Found ipv6_link: $1\n"; + $self->ipv6_link($1); + + #print "After setting ipv6_link: $1, " . $self->ipv6_link() . "\n"; + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + # IPv6-Gateway: DELETED + $i++; + $ln = $ta[$i]; + if ( $ln =~ /\s+IPv6-Gateway:\s+(\S+)/ ) { + + #print "Found ipv6_gw: $1\n"; + $self->ipv6_gw($1); + + #print "After setting ipv6_gw: $1, " . $self->ipv6_gw() . "\n"; + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + $i++; + $ln = $ta[$i]; + } + # stuff to skip + if ( $ln =~ /\s+(Cfg|Rpt)-Secondary-IPs:/) { + $i++; + $ln = $ta[$i]; + } + if ($ln =~ /\s+IPv6-Global:/) { + $i++; + $ln = $ta[$i]; + } # IPv6-Link: + if ($ln =~ /\s+IPv6-Link:/) { + $i++; + $ln = $ta[$i]; + } # IPv6-Link: + if ($ln =~ /\s+IPv6-Global:/) { + $i++; + $ln = $ta[$i]; + } #IPv6-Gateway: + if ($ln =~ /\s+IPv6-Gateway:/) { + $i++; + $ln = $ta[$i]; + } #IPv6-Gateway: + + # MAC: 00:c0:95:e2:4c:0e DEV: eth5 MTU: 1500 TX Queue Len: 400 + if ( $ln =~ +/\s+MAC:\s+(\S+)\s+DEV:\s+(\S+)\s+MTU:\s+(\d+)\s+TX[ -]Queue[ -]Len:\s+(\d+)/ + ) + { + $self->mac_addr($1); + $self->dev($2); + $self->mtu($3); + $self->tx_q_len($4); + } + else { + die("MAC test could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); + } + #print "MAC line $i\n"; + + $i++; + $ln = $ta[$i]; + if (( $ln =~ /\s+Bridge[ -]Port-Cost:.*/ ) || + ( $ln =~ /\s+Bus-Speed:.*/ ) || + ( $ln =~ /\s+LastDHCP:.*/ )) { + # Ignore this line, skip on to the next + #print "Bridge DHCP or Bus line $i\n"; + $i++; + $ln = $ta[$i]; + } + if (( $ln =~ /\s+Bridge[ -]Port-Cost:.*/ ) || + ( $ln =~ /\s+Bus-Speed:.*/ ) || + ( $ln =~ /\s+LastDHCP:.*/ )) { + # Ignore this line, skip on to the next + #print "DHCP, Bus or Bridge line $i\n"; + $i++; + $ln = $ta[$i]; + } + if (( $ln =~ /\s+Bridge[ -]Port-Cost:.*/ ) || + ( $ln =~ /\s+Bus-Speed:.*/ ) || + ( $ln =~ /\s+LastDHCP:.*/ )) { + # Ignore this line, skip on to the next + #print "Bus DHCP or Bridge line $i\n"; + $i++; + $ln = $ta[$i]; + } + + if ($ln =~ /DHCP-Client-ID: (.*?)\s+DHCP-Vendor-ID: (.*)\s*$/) { + $self->dhcp_client_id($1); + $self->dhcp_vendor_id($2); + $i++; + $ln = $ta[$i]; + } + # IPSec-Concentartor: 0.0.0.0 IPSec-Password: NONE IPSec-Local-ID: NONE IPSec-Remote-ID: NONE + # careful, Concentrator is mis-spelled and will get corrected + if ($ln =~ +/IPSec-Con[a-z]+r:\s+([0-9.]+)\s+IPSec-Password:\s+([^ ]+)\s+IPSec-Local-ID:\s+([^ ])+\s+IPSec-Remote-ID:\s+([^ ]+)/ + ) + { + $self->{'IPSec-Concentrator'} = $1 if (defined $1); + $self->{'IPSec-Password'} = $1 if (defined $2); + $self->{'IPSec-Local-ID'} = $1 if (defined $3); + $self->{'IPSec-Remote-ID'} = $1 if (defined $4); + $i++; + $ln = $ta[$i]; + } + + # pps_tx: 0 pps_rx: 0 bps_tx: 0 bps_rx: 0 + if ( $ln =~ +/\s+pps_tx:\s+(\d+)\s+pps_rx:\s+(\d+)\s+bps_tx:\s+(\d+)\s+bps_rx:\s+(\d+)/ + ) + { + $self->pps_tx($1); + $self->pps_rx($2); + $self->bps_tx($3); + $self->bps_rx($4); + } + else { die("Could not parse pps line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + # Rxp: 88210 Txp: 0 Rxb: 5292600 Txb: 0 RxERR: 18338 TxERR: 0 + $i++; + $ln = $ta[$i]; + if ( $ln =~ +/\s+Rxp:\s+(\d+)\s+Txp:\s+(\d+)\s+Rxb:\s+(\d+)\s+Txb:\s+(\d+)\s+RxERR:\s+(\d+)\s+TxERR:\s+(\d+)/ + ) + { + $self->rx_pkts($1); + $self->tx_pkts($2); + $self->rx_bytes($3); + $self->tx_bytes($4); + $self->rx_errors($5); + $self->tx_errors($6); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + # RxDrop: 0 TxDrop: 0 Multi: 0 Coll: 0 RxLenERR: 0 RxOverFlow: 0 + $i++; + $ln = $ta[$i]; + if ( $ln =~ +/\s+RxDrop:\s+(\d+)\s+TxDrop:\s+(\d+)\s+Multi:\s+(\d+)\s+Coll:\s+(\d+)\s+RxLenERR:\s+(\d+)\s+RxOverFlow:\s+(\d+)/ + ) + { + $self->rx_drop($1); + $self->tx_drop($2); + $self->multicasts($3); + $self->collisions($4); + $self->rx_len_err($5); + $self->rx_overflow($6); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + # RxCRC: 0 RxFrame: 0 RxFifo: 0 RxMissed: 0 TxAbort: 0 TxCarrier: 0 + $i++; + $ln = $ta[$i]; + if ( $ln =~ +/\s+RxCRC:\s+(\d+)\s+RxFrame:\s+(\d+)\s+RxFifo:\s+(\d+)\s+RxMissed:\s+(\d+)\s+TxAbort:\s+(\d+)\s+TxCarrier:\s+(\d+)/ + ) + { + $self->rx_crc($1); + $self->rx_frame($2); + $self->rx_fifo($3); + $self->rx_missed($4); + $self->tx_abort($5); + $self->tx_carrier($6); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + +# TxFifo: 0 TxHeartBeat: 0 TxWindow: 0 TxCompressed: 0 RxCompr: 0 (This is pre 5.2.3 API) + $i++; + $ln = $ta[$i]; + if ( $ln =~ +/\s+TxFifo:\s+(\d+)\s+TxHeartBeat:\s+(\d+)\s+TxWindow:\s+(\d+)\s+TxCompressed:\s+(\d+)\s+RxCompr:\s+(\d+)/ + ) + { + $self->tx_fifo($1); + $self->tx_heartbeat($2); + $self->tx_window($3); + } + elsif ( $ln =~ +/\s+TxFifo:\s+(\d+)\s+TxHeartBeat:\s+(\d+)\s+TxWindow:\s+(\d+)\s+RxBytesLL:\s+(\d+)\s+TxBytesLL:\s+(\d+)/ + ) + { + $self->tx_fifo($1); + $self->tx_heartbeat($2); + $self->tx_window($3); + $self->rx_bytes_ll($4); + $self->tx_bytes_ll($5); + } + else { die("Could not parse line[$i] -:$ln:-\n ".__PACKAGE__.".".__FILE__.".".__LINE__."\n($txt)\n"); } + + return; + } #if found the beginning for the/a endpoint + } #for all the lines in our buffer +} #decode + +sub toStringBrief { + my $self = shift; + + return ( "Port: " + . $self->{shelf_id} . "." + . $self->{card_id} . "." + . $self->{port_id} . " (" + . $self->{dev} + . ")" ); +} + +sub toString { + my $self = shift; + + my $rv = ""; + $rv = <<__END_TS; +Shelf: $self->{shelf_id}, Card: $self->{card_id}, Port: $self->{port_id} Type: $self->{port_type} Alias: $self->{alias} + Parent: $self->{parent} + Current: $self->{cur_flags} + Supported: $self->{supported_flags} + Partner: $self->{partner_flags} + Advertising: $self->{advert_flags} + IP: $self->{ip_addr} MASK: $self->{ip_mask} GW: $self->{ip_gw} + DNS Servers: $self->{dns_servers} + IPv6-Global: $self->{ipv6_global} + IPv6-Link: $self->{ipv6_link} + IPv6-Gateway: $self->{ipv6_gw} + MAC: $self->{mac_addr} DEV: $self->{dev} MTU: $self->{mtu} TX Queue Len: $self->{tx_q_len} + Rxp: $self->{rx_pkts} Txp: $self->{tx_pkts} Rxb: $self->{rx_bytes} Txb: $self->{tx_bytes} RxERR: $self->{rx_errors} TxERR: $self->{tx_errors} + RxDrop: $self->{rx_drop} TxDrop: $self->{tx_drop} Multi: $self->{multicasts} Coll: $self->{collisions} RxLenERR: $self->{rx_len_err} RxOverFlow: $self->{rx_overflow} + RxCRC: $self->{rx_crc} RxFrame: $self->{rx_frame} RxFifo: $self->{rx_fifo} RxMissed: $self->{rx_missed} TxAbort: $self->{tx_abort} TxCarrier: $self->{tx_carrier} + TxFifo: $self->{tx_fifo} TxHeartBeat: $self->{tx_heartbeat} TxWindow: $self->{tx_window} RxBytesLL: $self->{rx_bytes_ll} TxBytesLL: $self->{tx_bytes_ll} + +__END_TS + + return $rv; +} + +# Sets IP & MAC information +sub getSetCmd { + my $self = shift; + my $rv = + ( "set_port " + . $self->shelf_id() . " " + . $self->card_id() . " " + . $self->port_id . " " + . $self->ip_addr() . " " + . $self->ip_mask() . " " + . $self->ip_gw() + . " NA NA " + . $self->mac_addr() + . " NA NA" ); + return $rv; +} + +sub getDeleteCmd { + my $self = shift; + my $rv; + if ( $self->dev() ) { + $rv = + ( "rm_vlan " + . $self->shelf_id() . " " + . $self->card_id() . " " + . $self->dev() ); + } + else { + $rv = + ( "rm_vlan " + . $self->shelf_id() . " " + . $self->card_id() . " " + . $self->port_id() ); + } + return $rv; +} + +sub isMacVlan { + my $self = shift; + if ( $self->{port_type} eq "MacVLAN" ) { + return 1; + } + return 0; +} + +sub is8021qVlan { + my $self = shift; + if ( $self->{port_type} eq "Vlan" ) { + return 1; + } + return 0; +} + +# Set MTU only +sub getSetMtuCmd { + my $self = shift; + my $rv = + ( "set_port " + . $self->shelf_id() . " " + . $self->card_id() . " " + . $self->port_id() + . " NA NA NA NA NA NA " + . $self->mtu() + . " NA" ); + return $rv; +} + +# Set tx-queue-length only +sub getSetTxQueueLenCmd { + my $self = shift; + my $rv = + ( "set_port " + . $self->shelf_id() . " " + . $self->card_id() . " " + . $self->port_id() + . " NA NA NA NA NA NA NA " + . $self->tx_q_len() ); + return $rv; +} + +#set rate (current flags) only +sub getSetRateCmd { + my $self = shift; + my $rv = + ( "set_port " + . $self->shelf_id() . " " + . $self->card_id() . " " + . $self->port_id() + . " NA NA NA NA " ); + my $i = 0; + if ( $self->isAutoNegotiate() ) { + $i |= ( 1 << $IF_auto_negotiate ); + } + else { + if ( $self->isCurrent("10bt-HD") ) { + $i |= ( 1 << $IF_10bt_HD ); + } + elsif ( $self->isCurrent("10bt-FD") ) { + $i |= ( 1 << $IF_10bt_FD ); + } + elsif ( $self->isCurrent("100bt-HD") ) { + $i |= ( 1 << $IF_100bt_HD ); + } + elsif ( $self->isCurrent("100bt-FD") ) { + $i |= ( 1 << $IF_100bt_FD ); + } + } + + if ( $self->isAdvertising("10bt-HD") ) { + $i |= ( 1 << $IF_ADV_10bt_HD ); + } + if ( $self->isAdvertising("10bt-FD") ) { + $i |= ( 1 << $IF_ADV_10bt_FD ); + } + if ( $self->isAdvertising("100bt-HD") ) { + $i |= ( 1 << $IF_ADV_100bt_HD ); + } + if ( $self->isAdvertising("100bt-FD") ) { + $i |= ( 1 << $IF_ADV_100bt_FD ); + } + if ( $self->isAdvertising("FLOW-CONTROL") ) { + $i |= ( 1 << $IF_ADV_flow_control ); + } + + $rv .= "$i NA NA NA"; + + return $rv; +} #getSetRateCmd + +sub setRate { + my $self = shift; + my $val = shift; + + if ( $val eq "auto" ) { + $self->ensureCurSet("AUTO-NEGOTIATE"); + } + else { + $self->cur_flags($val); + } +} #setRate + +sub ensureCurSet { + my $self = shift; + my $flg = shift; + my $flgs = $self->cur_flags(); + if ( $flgs =~ /$flg/ ) { + return; + } + else { + $flgs .= "$flg "; + $self->cur_flags($flgs); + } +} + +sub ensureCurNotSet { + my $self = shift; + my $flg = shift; + my $flgs = $self->cur_flags(); + if ( $flgs =~ /$flg/ ) { + $flgs =~ s/$flg //; + $self->cur_flags($flgs); + } + else { + return; + } +} + +sub isAutoNegotiate { + my $self = shift; + return $self->isCurrent("AUTO-NEGOTIATE"); +} + +sub isAdvertising { + my $self = shift; + my $flg = shift; + my $flgs = $self->advert_flags(); + if ( $flgs =~ /$flg/ ) { + return 1; + } + return 0; +} #isAdvertisting (flag set) + +sub isCurrent { + my $self = shift; + my $flg = shift; + my $cur_flgs = $self->cur_flags(); + if ( $cur_flgs =~ /$flg/ ) { + return 1; + } + return 0; +} #isCurrent (flag set) + +############################################## +## methods to access per-object data ## +## ## +## With args, they set the value. Without ## +## any, they only retrieve it/them. ## +############################################## + +sub pps_tx { + my $self = shift; + if (@_) { $self->{pps_tx} = shift } + return $self->{pps_tx}; +} + +sub pps_rx { + my $self = shift; + if (@_) { $self->{pps_rx} = shift } + return $self->{pps_rx}; +} + +sub bps_tx { + my $self = shift; + if (@_) { $self->{bps_tx} = shift } + return $self->{bps_tx}; +} + +sub bps_rx { + my $self = shift; + if (@_) { $self->{bps_rx} = shift } + return $self->{bps_rx}; +} + +sub shelf_id { + my $self = shift; + if (@_) { $self->{shelf_id} = shift } + return $self->{shelf_id}; +} + +sub card_id { + my $self = shift; + if (@_) { $self->{card_id} = shift } + return $self->{card_id}; +} + +sub port_id { + my $self = shift; + if (@_) { $self->{port_id} = shift } + return $self->{port_id}; +} + +sub port_type { + my $self = shift; + if (@_) { $self->{port_type} = shift } + return $self->{port_type}; +} + +sub cur_flags { + my $self = shift; + if (@_) { $self->{cur_flags} = shift } + return $self->{cur_flags}; +} + +sub parent { + my $self = shift; + if (@_) { $self->{parent} = shift } + return $self->{parent}; +} + +sub supported_flags { + my $self = shift; + if (@_) { $self->{supported_flags} = shift } + return $self->{supported_flags}; +} + +sub partner_flags { + my $self = shift; + if (@_) { $self->{partner_flags} = shift } + return $self->{partner_flags}; +} + +sub advert_flags { + my $self = shift; + if (@_) { $self->{advert_flags} = shift } + return $self->{advert_flags}; +} + +sub ip_addr { + my $self = shift; + if (@_) { $self->{ip_addr} = shift } + return $self->{ip_addr}; +} + +sub ip_mask { + my $self = shift; + if (@_) { $self->{ip_mask} = shift } + return $self->{ip_mask}; +} + +sub ip_gw { + my $self = shift; + if (@_) { $self->{ip_gw} = shift } + return $self->{ip_gw}; +} + +sub ipv6_global { + my $self = shift; + if (@_) { $self->{ipv6_global} = shift } + return $self->{ipv6_global}; +} + +sub ipv6_link { + my $self = shift; + if (@_) { $self->{ipv6_link} = shift } + return $self->{ipv6_link}; +} + +sub ipv6_gw { + my $self = shift; + if (@_) { $self->{ipv6_gw} = shift } + return $self->{ipv6_gw}; +} + +sub dns_servers { + my $self = shift; + if (@_) { $self->{dns_servers} = shift } + return $self->{dns_servers}; +} + +sub mac_addr { + my $self = shift; + if (@_) { $self->{mac_addr} = shift } + return $self->{mac_addr}; +} + +sub dev { + my $self = shift; + if (@_) { $self->{dev} = shift } + return $self->{dev}; +} + +sub mtu { + my $self = shift; + if (@_) { $self->{mtu} = shift } + return $self->{mtu}; +} + +sub tx_q_len { + my $self = shift; + if (@_) { $self->{tx_q_len} = shift } + return $self->{tx_q_len}; +} + +sub rx_pkts { + my $self = shift; + if (@_) { $self->{rx_pkts} = shift } + return $self->{rx_pkts}; +} + +sub tx_pkts { + my $self = shift; + if (@_) { $self->{tx_pkts} = shift } + return $self->{tx_pkts}; +} + +sub rx_bytes { + my $self = shift; + if (@_) { $self->{rx_bytes} = shift } + return $self->{rx_bytes}; +} + +sub tx_bytes { + my $self = shift; + if (@_) { $self->{tx_bytes} = shift } + return $self->{tx_bytes}; +} + +sub rx_errors { + my $self = shift; + if (@_) { $self->{rx_errors} = shift } + return $self->{rx_errors}; +} + +sub tx_errors { + my $self = shift; + if (@_) { $self->{tx_errors} = shift } + return $self->{tx_errors}; +} + +sub rx_drop { + my $self = shift; + if (@_) { $self->{rx_drop} = shift } + return $self->{rx_drop}; +} + +sub tx_drop { + my $self = shift; + if (@_) { $self->{tx_drop} = shift } + return $self->{tx_drop}; +} + +sub multicasts { + my $self = shift; + if (@_) { $self->{multicasts} = shift } + return $self->{multicasts}; +} + +sub collisions { + my $self = shift; + if (@_) { $self->{collisions} = shift } + return $self->{collisions}; +} + +sub rx_len_err { + my $self = shift; + if (@_) { $self->{rx_len_err} = shift } + return $self->{rx_len_err}; +} + +sub rx_overflow { + my $self = shift; + if (@_) { $self->{rx_overflow} = shift } + return $self->{rx_overflow}; +} + +sub rx_crc { + my $self = shift; + if (@_) { $self->{rx_crc} = shift } + return $self->{rx_crc}; +} + +sub rx_frame { + my $self = shift; + if (@_) { $self->{rx_frame} = shift } + return $self->{rx_frame}; +} + +sub rx_fifo { + my $self = shift; + if (@_) { $self->{rx_fifo} = shift } + return $self->{rx_fifo}; +} + +sub rx_missed { + my $self = shift; + if (@_) { $self->{rx_missed} = shift } + return $self->{rx_missed}; +} + +sub tx_abort { + my $self = shift; + if (@_) { $self->{tx_abort} = shift } + return $self->{tx_abort}; +} + +sub tx_carrier { + my $self = shift; + if (@_) { $self->{tx_carrier} = shift } + return $self->{tx_carrier}; +} + +sub tx_fifo { + my $self = shift; + if (@_) { $self->{tx_fifo} = shift } + return $self->{tx_fifo}; +} + +sub tx_heartbeat { + my $self = shift; + if (@_) { $self->{tx_heartbeat} = shift } + return $self->{tx_heartbeat}; +} + +sub tx_window { + my $self = shift; + if (@_) { $self->{tx_window} = shift } + return $self->{tx_window}; +} + +sub rx_bytes_ll { + my $self = shift; + if (@_) { $self->{rx_bytes_ll} = shift } + return $self->{rx_bytes_ll}; +} + +sub tx_bytes_ll { + my $self = shift; + if (@_) { $self->{tx_bytes_ll} = shift } + return $self->{tx_bytes_ll}; +} + +sub alias { + my $self = shift; + if (@_) { $self->{alias} = shift } + return $self->{alias}; +} + +sub dhcp_client_id { + my $self = shift; + if (@_) { $self->{dhcp_client_id} = shift } + return $self->{dhcp_client_id}; +} + +sub dhcp_vendor_id { + my $self = shift; + if (@_) { $self->{dhcp_vendor_id} = shift } + return $self->{dhcp_vendor_id}; +} + + +1; # So the require or use succeeds (perl stuff) +__END__ + + +# Plain Old Documentation (POD) + +=head1 NAME + Port - class to implement LANforge Ports + +=head1 SYNOPSIS + + use LANforge::Port + + ################# + # class methods # + ################# + $ob = LANforge::Port->new; + + ####################### + # object data methods # + ####################### + + ### get versions ### + $port_id = $ob->port_id(); + + ### set versions ### + $ob->port_id(2); + + ######################## + # other object methods # + ######################## + + $ob->decode("CLI output that contains this Port's output"); + +=head1 DESCRIPTION + + The Port class gives you some clever access into the Port + objects as sent by the LANforge CLI. + +=head1 AUTHOR + Ben Greear (greearb@candelatech.com) + + Copyright (c) 2001 Candela Technologies. All rights reserved. + This program is free software; you can redistribute it and/or + modify it under the same terms as Perl itself. + + +=head1 VERSION + Version 0.0.1 May 26, 2001 + +=end diff --git a/lanforge/lanforge-scripts/LANforge/Test.pm b/lanforge/lanforge-scripts/LANforge/Test.pm new file mode 100644 index 000000000..9c1c4671f --- /dev/null +++ b/lanforge/lanforge-scripts/LANforge/Test.pm @@ -0,0 +1,80 @@ +# test utilities for LANforge scripts +package LANforge::Test; +use strict; +use warnings; +use diagnostics; +use Carp; + +# Ubuntu: libtest2-suite-perl +use Test2::V0 qw(ok fail done_testing); +use Test2::Tools::Basic qw(plan); + + +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Data::Dumper; + +if (defined $ENV{'DEBUG'}) { + use Data::Dumper; + use diagnostics; + use Carp; + $SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +} + +require Exporter; +our @EXPORT_OK=qw(new test); + +#our $FAIL = 'fail'; +#our $OK = 'pass'; +#our $PASS = 'pass'; +our @test_errors = (); + +sub new { + my $class = shift; + my $self = {}; + my %parm = @_; + $self->{'Name'} = $parm{'Name'}; + $self->{'Desc'} = $parm{'Desc'}; + $self->{'Errors'} = []; + $self->{'ExpectedNumber'} = 1; + $self->{'Test'} = undef; + if (defined $parm{'Test'}) { + #print "new: Creating Test $self->{'Name'}\n"; + $self->{'Test'} = $parm{'Test'}; + } + if (defined $parm{'ExpectedNumber'}) { + $self->{'ExpectedNumber'} = $parm{'ExpectedNumber'}; + } + bless $self, $class; + return $self; +} + +sub run { + plan(1); + my $self = shift; + print "Run $self->{Name}\n"; + my $result = shift; + + ok($result, $self->{'Name'}) || fail($self->{'Name'}); + done_testing(); +} + +sub test { + my $self = shift; + if (! (defined $self->{'Test'})) { + print "LANforge::test lacks self->Test, please rewrite your script.\n"; + return $::FAIL; + } + return $self->{'Test'}($self, @_); +} +sub test_err { + my $self = shift; + for my $e (@_) { + my $ref = "".(caller(1))[3].":".(caller(1))[2].""; + push (@::test_errors, "$ref: $e"); + } +} +1; \ No newline at end of file diff --git a/lanforge/lanforge-scripts/LANforge/Utils.pm b/lanforge/lanforge-scripts/LANforge/Utils.pm new file mode 100644 index 000000000..e65ec0ecf --- /dev/null +++ b/lanforge/lanforge-scripts/LANforge/Utils.pm @@ -0,0 +1,1229 @@ +package LANforge::Utils; +use strict; +use warnings; +use Carp; +use Net::Telnet; +#use bigint; +use Math::BigInt; +$| = 1; +#$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +#$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +if ($ENV{DEBUG}) { + use Data::Dumper; +} + +################################################## +## the object constructor ## +## To use: $ep = LANforge::Utils->new(); ## +## or: $ep2 = $ep->new(); ## +################################################## + +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + + $self->{telnet} = undef; + $self->{cli_send_silent} = 0; + $self->{cli_rcv_silent} = 0; + $self->{error} = ""; + $self->{async_waitfor} = '/btbits>> $/'; + $self->{prompt} = '/btbits>> $/'; + + bless( $self, $class ); + return $self; +} + +sub connect { + my ($self, $host, $port) = @_; + my $t = new Net::Telnet(Prompt => '/btbits>> $/', + Timeout => 30); + $self->{telnet} = \$t; + $t->open(Host => $host, + Port => $port, + Timeout => 20); + $t->max_buffer_length(16 * 1024 * 1000); # 16 MB buffer + $t->waitfor($self->{prompt}); + $t->print("set_flag brief 0"); # If we leave it brief, RSLT prompt is not shown. + $t->waitfor($self->{prompt}); + if ($self->isQuiet()) { + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + $self->cli_send_silent(0); + $self->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); + } + else { + $self->cli_send_silent(1); # Do not show input to telnet + } + $self->cli_rcv_silent(1); # Repress output from telnet + } + else { + $self->cli_send_silent(0); # Show input to telnet + $self->cli_rcv_silent(0); # Show output from telnet + } + return ${$self->{telnet}}; +} + +sub telnet { + my $self = shift; + + die("Utils::telnet -- telnet object undefined") + if (!(defined $self->{telnet})); + my $t = ${$self->{telnet}}; + $t->max_buffer_length(50 * 1024 * 1024); + $t->print("\n"); + $t->waitfor($self->{prompt}); + + return $t; +} + +# This submits the command and returns the success/failure +# of the command. If the results from the command are not +# immediately available (say, if LANforge needs to query a remote +# resource for endpoint stats, then that results may NOT be +# in the returned string. In that case, you must wait for the +# prompt to be seen, so use the doAsyncCmd below instead. +# doCmd is good for rapidly doing lots of configuration without +# waiting for each step (port creation, for example) to fully +# complete. +sub doCmd { + my $self = shift; + my $cmd = shift; + my $nowait = shift; + my $waitfor = shift; + + if (!defined($waitfor)) { + $waitfor = '/ >>RSLT:(.*)/'; + } + + #print "CMD[[$cmd]]\n"; + my $t = ${$self->{telnet}}; + if ( !$self->cli_send_silent() || (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "")) { + $self->log_cli($cmd); + } + $t->print($cmd); + + if (defined($nowait) && ($nowait == 1)) { + return ""; + } + + my @rslt = $t->waitfor($waitfor); + if ( !$self->cli_rcv_silent() ) { + print "**************\n@rslt\n................\n\n"; + } + return join( "\n", @rslt ); +} + +# This will wait for the prompt, not just for the results. +# Use this instead of doCmd if you are unsure. +sub doAsyncCmd { + my $self = shift; + my $cmd = shift; + my $t = ${$self->{telnet}}; + my @rv = (); + + if ( !$self->cli_send_silent() || (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "")) { + $self->log_cli($cmd); + } + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + my @rslt2 = $t->waitfor( $self->async_waitfor() ); + @rv = (@rslt, @rslt2); + + if ( !$self->cli_rcv_silent() ) { + print "**************\n @rv \n................\n\n"; + } + return join( "\n", @rv ); +} # ~doAsyncCmd + +sub normalize_bucket_hdr { + my $self = shift; + my $amt = shift; + my $rv = "Min Max Avg "; + my $i; + for ($i = 0; $i<$amt; $i++) { + if ($i == 0) { + $rv .= "0 "; + } + elsif ($i == 1) { + $rv .= "1 "; + } + else { + $rv .= 2**($i-1) . "-" . (2**($i) - 1) . " "; + } + } + return $rv; +} + +# Normalize lat1, taking peer latency (lat2) into account for negative latency and such. +sub normalize_latency { + my $self = shift; + my $lat1 = shift; + my $lat2 = shift; + + #print "lat1 -:$lat1:-\n"; + #print "lat2 -:$lat2:-\n"; + + my $min1 = 0; + my $min2 = 0; + + # Looks like this: 5 -:5:- 6 [ 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) + if ($lat1 =~ /(\S+)\s+-:(\S+):-\s+(\S+)\s+\[\s+(.*)\s+\]\s+\((\S+)\)/) { + $min1 = $1; + } + if ($lat2 =~ /(\S+)\s+-:(\S+):-\s+(\S+)\s+\[\s+(.*)\s+\]\s+\((\S+)\)/) { + $min2 = $1; + } + + # For instance, min1 is -5, min2 is 25, rt-latency is 20. + # Adjust lat1 by (25 - -5) / 2 + # For instance, min1 is 25, min2 is -5, rt-latency is 20. + # Adjust lat1 by (-5 -25) / 2 + #print "min1: $min1 min2: $min2 half: " . int(($min2 - $min1) / 2) . "\n"; + # So, the above seems nice, but often we have a small negative value due to + # clock drift in one direction, and large latency in the other (due to real one-way latency) + # So, we will just adjust enough to make the smallest value positive. + my $adjust = 0; + if ($min1 < 0) { + $adjust = -$min1; + } + elsif ($min2 < 0) { + $adjust = $min2; + } + return $self->normalize_bucket($lat1, $adjust); +} + +sub normalize_bucket { + my $self = shift; + my $line = shift; + my $adjust = shift; + + #print "line -:$line:-\n"; + + # Looks like this: 5 -:5:- 6 [ 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) + if ($line =~ /(\S+)\s+-:(\S+):-\s+(\S+)\s+\[\s+(.*)\s+\]\s+\((\S+)\)/) { + my $min = $1; + my $avg = $2; + my $max = $3; + my $bks = $4; + my $width = $5; # Assumes one currently + if (!($width eq "1")) { + return $line; + } + else { + my @bkts = split(/\s+/, $bks); + @bkts = (@bkts, "0"); + my $i; + my $rv = ($min + $adjust) . " " . ($max + $adjust) . " " . ($avg + $adjust) . " "; + #print "bkts len: " . @bkts . "\n"; + my @nbkts = (0) x (@bkts); + for ($i = 0; $i<@bkts; $i++) { + # Figure out the bkt range + my $minv = 0; + my $maxv = 2 ** $i - 1; + if ($i > 0) { + $minv = 2 ** ($i - 1); + } + # Adjust by the min value, which is treated as an offset + $minv += $min; + $maxv += $min; + + # And adjust based on round-trip time to deal with clock lag + $minv += $adjust; + $maxv += $adjust; + + # And now find the normalized bucket this fits in + #print "maxv: $maxv\n"; + my $z; + my $idx = 0; + for ($z = 0; $z < 32; $z++) { + if ($maxv < (2 ** $z)) { + #print "maxv: $maxv z: $z 2^$z: " . 2 ** $z . + "\n"; + $idx = $z; + # Everything else falls in the last bucket + if ($idx >= @bkts) { + $idx = (@bkts - 1); + } + last; + } + } + + #print "idx: $idx i: $i minv: $minv maxv: $maxv min: $min adjust: $adjust\n"; + #print "nbkts: " . $nbkts[$idx]; + #print " bkts: " . $bkts[$i] . "\n"; + my $nv = $nbkts[$idx] + $bkts[$i]; + @nbkts[$idx] = $nv; + } + + for ($i = 0; $i < @nbkts; $i++) { + $rv .= ($nbkts[$i] . " "); + } + return $rv; + } + } + else { + return $line; + } +} + +# Uses cached values (so it will show Phantom ones too) +sub getPortListing { + my $self = shift; + my $shelf = shift; + my $card = shift; + + my @rv = (); + my $prts = $self->doAsyncCmd( "show_port " . $shelf . " " . $card ); + + if ( $prts =~ /Timed out waiting for/g ) { + $self->error("Partial Failure: Timed out"); + } + + my @ta = split( /\n/, $prts ); + + my $i; + for ( $i = 0 ; $i < @ta ; $i++ ) { + my $ln = $ta[$i]; + if ( $ln =~ /Shelf:\s+\d+,\s+Card:\s+\d+,\s+Port:\s+\d+\s+Type/ ) { + my $ptxt; + while ( $ln =~ /\S+/ ) { + $ptxt .= "$ln\n"; + $i++; + $ln = $ta[$i]; + } + + my $p1 = new LANforge::Port(); + $p1->decode($ptxt); + @rv = ( @rv, $p1 ); + } + } + return @rv; +} #~getPortListing + +sub updatePortRetry { + my $self = shift; + return $self->updatePort( shift, shift, shift, shift, shift, 10000 ); +} + +# Call with args: Port, (these next ones are optional): Shelf-id, Card-id, Port-Id +sub updatePort { + my $self = shift; + my $port = shift; + my $sid = shift; #shelf-id + my $max_retries = undef; + if ( defined($sid) ) { + $port->shelf_id($sid); + $port->card_id(shift); + $port->port_id(shift); + + $max_retries = shift; + } + + if ( !defined($max_retries) ) { + $max_retries = 10; + } + + # Since I use this for testing, I'm going to obliterate the port's data so that + # there will be no question as to whether or not the update worked. + $port->initDataMembers(); #Shouldn't mess with the shelf, card, or port id. + + my $cmd = + "nc_show_port " + . $port->shelf_id() . " " + . $port->card_id() . " " + . $port->port_id; + + #print "cmd -:$cmd:-\n"; + + # Use the non-caching port show. + my $prt = $self->doAsyncCmd($cmd); + +# There is a small race condition, where one report may be on the way back to the +# main server when the first request is still being sent. So, we'll ask again. This +# one will definately be up to date. + $prt = ""; + my $i = 0; + while (1) { + $prt = $self->doAsyncCmd($cmd); + if ( !$self->cli_rcv_silent() ) { # added by Adam - 8/9/2004 + print "prt: $prt\n"; + } + + if ( $i++ > $max_retries ) { + last; + } + + if ( ( $prt =~ /Could not find that Port/g ) + || ( $prt =~ /Timed out waiting/g ) + || ( !( $prt =~ /, Port:/g ) ) ) + { + sleep(5); + } + else { + last; + } + } + + if ( !$self->cli_rcv_silent() ) { # added by Adam - 8/9/2004 + print "decoding port -:$prt:-\n"; + } + $port->decode($prt); +} #updatePort + +sub updateEndpoint { + my $self = shift; + my $endp = shift; + my $name = shift; + my $fast = shift; + + if ( defined($name) ) { + $endp->name($name); + } + +# Since I use this for testing, I'm going to obliterate the Endpoint's data so that +# there will be no question as to whether or not the update worked. + $endp->initDataMembers(); #Shouldn't mess with the shelf, card, or port id. + + my $ep; + if ($fast) { + $ep = $self->doAsyncCmd( "show_endpoint " . $endp->name() ); + } + else { + # Use the non-caching endpoint show. + $ep = $self->doAsyncCmd( "nc_show_endpoint " . $endp->name() ); + +# There is a small race condition, where one report may be on the way back to the +# main server when the first request is still being sent. So, we'll ask again. This +# one will definately be up to date. + $ep = $self->doAsyncCmd( "nc_show_endpoint " . $endp->name() ); + } + + #print "EP show_endp results for cmd: " . $endp->name() . "\n-:$ep:-\n"; + + $endp->decode($ep); + + if ( $endp->isCustom() ) { + $ep = $self->doCmd( "show_endp_pay " . $endp->name() . " 5000" ); + $endp->decodePayload($ep); + } +} #updateEndpoint + +sub log_cli { + my $self = shift; + my $cmd = shift; + my $using_stdout = 0; + #print "utils::log_cli: $ENV{'LOG_CLI'}\n"; + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + if ($ENV{'LOG_CLI'} =~ /^--/) { + die("Incorrect format for LOG_CLI, it should be '1' or filename like '/tmp/cmdlog.txt'"); + } + if ($ENV{'LOG_CLI'} eq "1" || $ENV{'LOG_CLI'} =~ /STDOUT/i) { + $using_stdout = 1; + #print "STDOUT utils::log_cli: $ENV{'LOG_CLI'}\n"; + } + else { # write to a file + if ( ! -f $ENV{'LOG_CLI'}) { + print "Creating new file $ENV{'LOG_CLI'}\n"; + `touch $ENV{'LOG_CLI'}`; + chmod(0666, $ENV{'LOG_CLI'}); + } + if ( -w $ENV{'LOG_CLI'}) { + open(my $fh, ">>", $ENV{'LOG_CLI'}); + if (defined $fh) { + #print "FILE utils::log_cli: \n"; + print $fh "$cmd\n"; + close $fh; + } + else { + warn ("$ENV{'LOG_CLI'} not writable"); + $using_stdout=1; + #print "ELSE STDOUT utils::log_cli: $ENV{'LOG_CLI'}\n"; + } + } + } + } + if ($using_stdout == 1 || !isQuiet() ) { + print qq(\nCMD: \"$cmd\"\n); + } +} + +# returns 1 if we're quiet, 0 if we're verbose +# if $::quiet is undefined, we assume verbose +sub isQuiet { + my $self = shift; + return 0 + if (! defined $::quiet); + + if (length( do { no warnings "numeric"; $::quiet & "" } )) { + # we're numeric + if ($::quiet != 0) { + #print "numeric and quiet [$::quiet]\n"; + return 1; + } + #print "numeric and verbose [$::quiet]\n"; + return 0; + } + + # else we're textual + if ($::quiet =~ /(1|yes|on)/i) { + #print "textual and quiet [$::quiet]\n"; + return 1; + } + #print "textual and verbose [$::quiet]\n"; + return 0; +} + +sub async_waitfor { + my $self = shift; + if (@_) { $self->{async_waitfor} = shift } + return $self->{async_waitfor}; +} + +sub error { + my $self = shift; + if (@_) { $self->{error} = shift } + return $self->{error}; +} + +sub cli_rcv_silent { + my $self = shift; + if (@_) { $self->{cli_rcv_silent} = shift } + return $self->{cli_rcv_silent}; +} + +sub cli_send_silent { + my $self = shift; + if (@_) { $self->{cli_send_silent} = shift } + return $self->{cli_send_silent}; +} + +sub fmt_cmd { + #print Dumper(@_); + my $self = shift; + my $rv; + my $mod_hunk; + my $show_err = 0; + my $item = 1; + my $prev_item; + for my $hunk (@_) { + if (defined $hunk && $hunk eq '') { + print STDERR "\nfmt_cmd() arg $item blank, converting to NA\n"; + print STDERR " prev argument was [$prev_item]\n" if (defined $prev_item); + $show_err = 1; + } + die("rv[${rv}]\n --> fmt_cmd passed an array, bye.") if (ref($hunk) eq 'ARRAY'); + die("rv[${rv}]\n --> fmt_cmd passed a hash, bye.") if (ref($hunk) eq 'HASH'); + $mod_hunk = $hunk; + $mod_hunk = "0" if ($hunk eq "0" || $hunk eq "+0"); + + if( $hunk eq "" ) { + #print "hunk[".$hunk."] --> "; + $mod_hunk = 'NA'; + #print "hunk[".$hunk."]\n"; + #print "fmt_cmd: warning: hunk was blank, now NA. Prev hunks: $rv\n" + } + $prev_item = $hunk; + $item++; + $rv .= ( $mod_hunk =~m/ +/) ? "'$mod_hunk' " : "$mod_hunk "; + } + if (rindex($rv, ' ', length($rv)-2) > 1) { + #print STDERR "[$rv]\n"; + $rv =~ s/\s+$//g; + #print STDERR "[$rv]\n"; + } + print STDERR qq(\nFormatted cmd: "$rv"\n) if ($show_err or $::quiet ne "yes"); + return $rv; +} + +## +## Check if usleep() exists +## +our $has_usleep = 0; +if (defined &usleep) { + print("I see usleep\n"); + $LANforge::Utils::has_usleep=1; +} + + +sub sleep_ms { + my $self; + my $millis = 0; + if (@_ > 1) { + ($self, $millis) = @_; + } + else { + $millis = pop(@_); + } + return if (!(defined $millis) || ($millis == 0)); + + my $secs = $millis / 1000; + + if ($LANforge::Utils::has_usleep) { + usleep($millis); + } + else { + select(undef, undef, undef, $secs); + } +} + +sub sleep_sec { + my $self; + my $secs = 0; + if (@_ > 1) { + ($self, $secs) = @_; + } + else { + ($secs) = @_; + } + return if (!(defined $secs) || ($secs == 0)); + + if ($LANforge::Utils::has_usleep) { + usleep($secs); + } + else { + select(undef, undef, undef, $secs); + } +} + +## +## Returns ref to map of all stations maching a parent device +## EG: $rh_eid_map = $u->get_eid_map($::resource) +## + +sub get_eid_map { + my ($self, $resource) = @_; + my $rh_eid_map = {}; + my @ports_lines = split(/\r?\n/, $self->doAsyncCmd("nc_show_ports 1 $resource all")); + chomp(@ports_lines); + + my ($eid, $card, $port, $type, $mac, $dev, $parent, $ip); + foreach my $line (@ports_lines) { + # collect all stations on that radio add them to @interfaces + if ($line =~ /^Shelf: /) { + $card = undef; $port = undef; + $type = undef; $parent = undef; + $eid = undef; $mac = undef; + $dev = undef; + $ip = undef; + } + + # careful about that comma after card! + # NO EID for Shelf: 1, Card: 1, Port: 2 Type: WIFI-Radio Alias: + ($card, $port, $type) = $line =~ m/^Shelf: 1, Card: (\d+),\s+Port: (\d+)\s+Type: (\w+)/; + if ((defined $card) && ($card ne "") && (defined $port) && ($port ne "") && ($type ne "VRF")) { + $eid = "1.".$card.".".$port; + my $rh_eid = { + eid => $eid, + type => $type, + parent => undef, + dev => undef, + }; + $rh_eid_map->{$eid} = $rh_eid; + } + #elsif ($line =~ /^Shelf/) { + # #print "NO EID for $line\n"; + #} + + if (!(defined $eid) || ($eid eq "")) { + #print "NO EID for $line\n"; + next; + } + ($mac, $dev) = $line =~ / MAC: ([0-9:a-fA-F]+)\s+DEV: (\S+)/; + if ((defined $mac) && ($mac ne "")) { + #print "$eid MAC: $line\n"; + $rh_eid_map->{$eid}->{mac} = $mac; + $rh_eid_map->{$eid}->{dev} = $dev; + } + + ($parent) = $line =~ / Parent.Peer: (\S+) /; + if ((defined $parent) && ($parent ne "")) { + #print "$eid PARENT: $line\n"; + $rh_eid_map->{$eid}->{parent} = $parent; + } + + ($ip) = $line =~ m/ IP: *([^ ]+) */; + if ((defined $ip) && ($ip ne "")) { + #print "$eid IP: $line\n"; + $rh_eid_map->{$eid}->{ip} = $ip; + } + } # foreach + + #foreach $eid (keys %eid_map) { + # print "eid $eid "; + #} + return $rh_eid_map; +} + +## +## retrieve an eid/name record by name using a refrence +## to an eid_map +## +sub find_by_name { + my ($self, $rh_eid_map, $devname) = @_; + while (my ($eid, $rh_rec) = each %{$rh_eid_map}) { + #print "fbn: ".$rh_rec->{dev}."\n"; + if ((defined $rh_rec->{dev}) && ($rh_rec->{dev} eq $devname)) { + return $rh_rec; + } + } + return -1; +} + +## +## retrieve ports on radio from EID map +## EG: $ra_interfaces = $u->ports_on_radio($rh_eid_map, $radio_name); +## +sub ports_on_radio { + my ($self, $rh_rec2_map, $radio) = @_; + my $ra_ifs = []; + #print "PARENT IS $radio\n"; + + foreach my $rh_rec2 (values %{$rh_rec2_map}) { + next if (!(defined $rh_rec2->{parent})); + #print "\npor: ".$rh_rec2->{parent}.">".$rh_rec2->{dev}."\n"; + if ($rh_rec2->{parent} eq $radio) { + #print $rh_rec2->{dev}."<-".$rh_rec2->{parent}." "; + my $devn = $rh_rec2->{dev}; + push(@$ra_ifs, $devn); + } + } + return $ra_ifs; +} + +sub test_groups { + my ($self) = @_; + my @group_lines = split(/\r?\n/, $self->doAsyncCmd("show_group all")); + sleep_ms(30); + + #print Dumper(\@group_lines); + my @matches = grep {/TestGroup name:\s+/} @group_lines; + #print Dumper(\@matches); + my $ra_group_names = []; + for my $line (@matches) { + push(@$ra_group_names, ($line =~ /TestGroup name:\s+(\S+)\s+\[/)); + } + #print Dumper($ra_group_names); + + return $ra_group_names; +} + +## +sub group_items { + my ($self, $tg_name) = @_; + die("Utils::group_items wants a test group name, bye.") + if (!(defined $tg_name) || ("" eq $tg_name)); + my @lines = split(/\r?\n/, $self->doAsyncCmd( "show_group '$tg_name'")); + sleep_ms(30); + my $ra_items = []; + my $started = 0; + foreach my $line (@lines) { + $started ++ if ($line =~ /\s*Cross Connects:/); + next unless ($started); + last if ($line =~ /^\s*$/); + $line =~ s/^\s*Cross Connects:\s*//; + $line =~ s/^\s+//; + $line =~ s/\s+$//; + my @hunks = split(/\s+/, $line); + push(@$ra_items, split(/\s+/, $line)); + } + if (@$ra_items < 1) { + print STDERR "No cross connects found for test group $tg_name.\n"; + return []; + } + return $ra_items; +} + +# Generic disassembly of lines created by show +our @starting_exceptions = ( + # please keep these sorted + "Access Denied:", + "Bad Protocol:", + "Bad URL:", + "Buffers Read:", + "Buffers Written:", + "Bytes Read:", + "Bytes Read-3s:", + "Bytes Written:", + "Bytes Written-3s:", + "Command:", + "Conn Established:", + "Conn Timeouts:", + "Couldn't Connect:", + "Cx Detected:", + "DNS-Latency: ", + "DNS Servers:", + "Endpoint [", + "Files Read:", + "Files Written:", + "First-RW-Latency:", + "Flags:", + "FTP HOST Error:", + "FTP STOR Error:", + "FTP PORT Error:", + "GenericEndp [", + "HTTP RANGE Error:", + "HTTP POST Error:", + "HTTP PORT Error:", + "Latency:", + "Login Denied:", + "No Resolve Host:", + "No Resolve Proxy:", + "Not Found (404):", + "Pkt-Gaps:", + "Read CRC Failed:", + "Read Error:", + "Redirect Loop Err:", + "Results[", + ">>RSLT:", + "Rx Bytes:", + "Rx Bytes (On Wire):", + "Rx Duplicate Pkts:", + "Rx OOO Pkts:", + "Rx Pkts:", + "Rx Pkts (On Wire):", + "RX-Silence:", + "Shelf: 1,", + "SMTP-From:", + "TCP Retransmits:", + "Timed Out:", + "Total CURL Errors:", + "Tx Bytes:", + "Tx Bytes (On Wire):", + "Tx Failed Bytes:", + "Tx Failed Pkts:", + "Tx Pkts:", + "Tx Pkts (On Wire):", + "Tx-Retries:", + "URL:", + "URL-Latency:", + "URLs Processed:", + "Write Error:", + ); + +# Generic disassembly of lines created by show +our @port_starting_exceptions = ( + # please keep these sorted + "Advertising:", + "Current:", + "Missed-Beacons:", + "Partner:", + "Supported:", + "Tx-Excessive-Retry:", + "Rx-Invalid-CRYPT:", + "Rx-Invalid-MISC:", + ); + +our @one_line_keys = ( + "Latency:", + "Pkt-Gaps:", + "RX-Silence:", + "Cx Detected:", + ); +# +# examples of using this: +# $rh = u->show_as_hash($txt) +# $rh = u->show_as_hash(\$txt) +# $rh = u->show_as_hash(split(/\n/, $txt)) +# $rh = u->show_as_hash(\@lines) +# +sub show_as_hash { + my ($self, $in, $isport) = (undef, undef, 0); + if (@_ > 1) { + ($self, $in, $isport) = @_; + } + else { + $in = pop(@_); + } + my @lines = (); + + # this allows us to pass in \$txt, split(/\n/, $txt) or just $txt + if ((ref $in) eq "") { + @lines = split(/\r?\n/, $in); + } + elsif ((ref $in) eq "SCALAR") { + @lines = split(/\r?\n/, $$in); + } + elsif ((ref $in) eq "ARRAY") { + @lines = @$in; + } + + #print "show_as_hash, isport: $isport\n"; + + my $rh_pairs = {}; + my @special = (); + + # https://stackoverflow.com/questions/31724503/most-efficient-way-to-check-if-string-starts-with-needle-in-perl + my $key = undef; + my $value = undef; + my @hunks = (); + my $prefix = ""; + #print Dumper(\@lines); + chomp(@lines); + my $found_start_x = 0; + foreach my $line (@lines) { + if ($isport) { + #print "Port line -:$line:-\n"; + foreach my $start (@LANforge::Utils::port_starting_exceptions) { + # we purposefully are not wasting time trimming whitespace + my $i = index($line, $start); + if ($i >= 0) { + push(@special, $line); + $found_start_x++; + last; + } + } + } + else { + foreach my $start (@LANforge::Utils::starting_exceptions) { + # we purposefully are not wasting time trimming whitespace + my $i = index($line, $start); + if ($i >= 0) { + push(@special, $line); + $found_start_x++; + last; + } + } + } + if ($found_start_x) { + $found_start_x = 0; + next; + } + + if ($isport) { + #print "line -:$line:-\n"; + if ($line =~ /^\s+\[Configured\]/) { + #print "Prefix to cfg\n"; + $prefix = "Cfg"; + next; + } + if ($line =~ /^\s+\[Probed\]/) { + $prefix = "Probed"; + next; + } + + $line =~ s/ (dbm|[kmg]?bps)/$1/ig; + $line =~ s/DNS Servers/DNS-Servers/ig; + $line =~ s/TX Queue Len/TX-Queue-Len/ig; + $line =~ s/Missed Beacons/Missed-Beacons/ig; + #print "$i: ".$lines[$i]."\n"; + } + + # at this point, every line should be split using colons and spaces + @hunks = split(/\s+/, $line); + foreach my $hunk (@hunks) { + if (rindex($hunk, ':') == length($hunk)-1) { + $key = substr($hunk, 0, rindex($hunk, ':')); + next; + } + $value = $hunk; + if ((defined $key) && ("" ne $key)) { + my $val = (defined $value) ? $value : ""; + #print "Adding key -:$key:- val -:$val:-\n"; + $rh_pairs->{$key} = $val; + if ($prefix ne "") { + #print "Adding prefixed key -:$prefix-$key:- val -:$val:-\n"; + $rh_pairs->{"$prefix-$key"} = $val; + } + $key = undef; + $value = undef; + } + } + } + + @hunks = (); + $key = undef; + $value = undef; + foreach my $line (@special) { + #print "\nspecial: $line"; + my $rh_vals = undef; + + # special cases for certain lines + if (index($line, '>>RSLT:') >= 0) { + $rh_vals = { + 'Cmd' => substr($line, index($line, 'Cmd:')+5), + 'RSLT' => substr($line, index($line, ':')+2, (index($line, ' Cmd:') - index($line, ':')-3)), + }; + foreach my $subkey (keys %$rh_vals) { + $rh_pairs->{$subkey} = $rh_vals->{$subkey} + } + #$rh_pairs->{"RSLT"} = $rh_vals; + #print Dumper($rh_pairs->{"RSLT"}); + $rh_vals = undef; + $key = undef; + $value = undef; + next; + } + if (index($line, 'Endpoint [') >= 0) { + my $flags = substr($line, index($line, '(')+1, -1); # split(/\s+/, + my $name = substr($line, index($line, '[')+1, index($line, ']') - index($line, '[')-1); + $rh_vals = { + 'Endpoint-name' => $name, + 'Endpoint-flags' => $flags, + }; + foreach my $subkey (keys %$rh_vals) { + $rh_pairs->{$subkey} = $rh_vals->{$subkey} + } + #$rh_pairs->{"Endpoint"} = $rh_vals; + $rh_vals = undef; + $key = undef; + $value = undef; + next; + } + if (index($line, 'Shelf: 1,') >= 0) { + $line =~ s/1,/1/; + if ($line =~ /Endpoint/ ) { + my ($card, $port, $endpoint, $eptype, $patt ) = + $line =~ /Shelf:\s+1\s+Card:\s+(\d+)\s+Port:\s+(\S+)\s+Endpoint:\s+(\S+)\s+Type:\s+(\S+)\s+Pattern:\s+(\S+)$/; + $rh_pairs->{Shelf} = 1; + $rh_pairs->{Card} = $card; + $rh_pairs->{Resource} = $card; + $rh_pairs->{Port} = $port; + $rh_pairs->{Endpoint} = $endpoint; + $rh_pairs->{Type} = $eptype; + $rh_pairs->{Pattern} = $patt; + } + } + my $found_oneline = 0; + foreach my $keyv (@LANforge::Utils::one_line_keys) { + if (index($line, $keyv) >= 0) { + $found_oneline++; + if (rindex($keyv, ':') >= 1) { + $keyv = substr($keyv, 0, rindex($keyv, ':')); + } + $rh_pairs->{$keyv} = substr($line, index($line, ":")+2); + last; + } + next if ($found_oneline); + } + + # This is parsing bucket counters, maybe more + my $i = index($line, ':'); + $key = substr($line, 0, $i); + $key =~ s/^\s*//g; + $value = substr($line, $i+1); + $rh_pairs->{$key} = $value; # Add full line to hash + $value =~ s/^\s*//g; + @hunks = split(/\s+/, $value); + $rh_vals = $self->hunks_to_hashes($key, \@hunks); + foreach my $subkey (keys %$rh_vals) { + my $val = $rh_vals->{$subkey}; + #print("Adding subkey -:$subkey:- val -:$val:-\n"); + $rh_pairs->{$subkey} = $val; + } + $rh_vals = undef; + $key = undef; + $value = undef; + } + + # Add some common short-hand actions that we supported in the past. + my $val; + + $val = $rh_pairs->{"Rx-Pkts-Per-Sec"}; + if (defined($val)) { + $rh_pairs->{"rx_pps"} = $val; + } + $val = $rh_pairs->{"Tx-Pkts-Per-Sec"}; + if (defined($val)) { + $rh_pairs->{"tx_pps"} = $val; + } + $val = $rh_pairs->{"Rx-Pkts-Total"}; + if (defined($val)) { + $rh_pairs->{"rx_pkts"} = $val; + $rh_pairs->{"Rx Pkts"} = $val; + $rh_pairs->{"Rx-Pkts"} = $val; + } + $val = $rh_pairs->{"Tx-Pkts-Total"}; + if (defined($val)) { + $rh_pairs->{"tx_pkts"} = $val; + $rh_pairs->{"Tx Pkts"} = $val; + $rh_pairs->{"Tx-Pkts"} = $val; + } + $val = $rh_pairs->{"Rx-Bytes-bps"}; + if (defined($val)) { + $rh_pairs->{"rx_bps"} = $val; + } + $val = $rh_pairs->{"Tx-Bytes-bps"}; + if (defined($val)) { + $rh_pairs->{"tx_bps"} = $val; + } + + $val = $rh_pairs->{"Rx-Bytes-Total"}; + if (defined($val)) { + $rh_pairs->{"Rx-Bytes"} = $val; + $rh_pairs->{"Rx Bytes"} = $val; + } + + $val = $rh_pairs->{"Tx-Bytes-Total"}; + if (defined($val)) { + $rh_pairs->{"Tx-Bytes"} = $val; + $rh_pairs->{"Tx Bytes"} = $val; + } + + #foreach $key (sort keys %$rh_pairs) { + # print "{$key} => $rh_pairs->{$key}\n"; + #} + #die("debugging"); + return $rh_pairs; +} + +sub hunks_to_hashes { + my ($self, $prefix, $input) = (undef, undef, undef); + if (@_ > 2) { + ($self, $prefix, $input) = @_; + } + else { + $prefix = shift; + $input = shift; + } + my @hunks = (); + if (ref($input) eq "ARRAY") { + @hunks = @$input; + } + elsif (ref($input) eq "SCALAR") { + @hunks = (@$input); + } + else { + die("Utils::hunks_to_hashes() expects an array"); + } + if (index($prefix, ' ') >= 0) { + $prefix =~ s/\s+/-/g + } + + my $rh = {}; + my $key = undef; + my $value = undef; + foreach my $hunk (@hunks) { + + if (rindex($hunk, '/s') >= 1) { + $key = $prefix."-Per-Sec"; + $value = substr($hunk, 0, index($hunk, '/s')); + $rh->{$key} = (defined $value) ? $value : ""; + + # create bps not just Bps + if (index($prefix, "Bytes") >= 1) { + my $bps = (0 + $value) * 8; + $rh->{$prefix."-bps"} = $bps; + } + $key = undef; + $value = undef; + next; + } + if (rindex($hunk, ':') == length($hunk)-1) { + $key = $prefix .'-'. substr($hunk, 0, rindex($hunk, ':')); + next; + } + $value = $hunk; + if ((defined $key) && ("" ne $key)) { + $rh->{$key} = (defined $value) ? $value : ""; + if ((index($prefix, "Bytes") >= 1) + && ((index($key, "Total") >=0 ) || (index($key, "Cur") >=0 ))) { + my $bits = (0 + $value) * 8; + my $nkey = $key; + $nkey =~ s/Bytes/Bits/; + $rh->{$nkey} = $bits; + } + $key = undef; + $value = undef; + } + } + return $rh; +} + +sub expand_unit_str { + my ($self, $string) = @_; + die("Utils::expand_unit_str expects string to parse") + if (!(defined $string) || ("" eq $string)); + + return 0 if ($string =~ /^[0\.]+\s*\w+$/); + + my ($num, $suf) = $string =~ /^([\.0-9]+)\s*(\w*)$/; + if (!(defined $num) || ("" eq $num)) { + die("Utils::expand_unit_str exects something like 33Mbps or '33 Mbps', not $string"); + } + my $multiplier = 1; + #print "String[$string] => $num Suffix $suf\n"; + if (!(defined $suf) || ("" eq $suf)) { + $multiplier = 1; + print STDERR "Utils::expand_unit_str saw no suffix in [$string]\n"; + } + elsif ($suf =~ /^bps$/i) { + $multiplier = 1; + } + elsif ($suf =~ /^kbps$/i) { + $multiplier = 1000; + } + elsif ($suf =~ /^mbps$/i) { + $multiplier = 1000 * 1000; + } + elsif ($suf =~ /^gbps$/i) { + $multiplier = 1000 * 1000 * 1000; + } + return int($num) * $multiplier; +} + +sub mac_add { + my ($self, $first_mac, $second_dec) = @_; + $first_mac =~ s/[:]//g if ($first_mac =~ /[:]/); + $first_mac = "0x".$first_mac if ($first_mac !~ /^0x/); + my $newdec = Math::BigInt->new($first_mac); + $newdec->badd(0+$second_dec); + my $pad = Math::BigInt->new('0x1000000000000'); + $newdec->badd($pad); + my $newhex = "".$newdec->as_hex(); + my $rv = ""; + $newhex = substr($newhex, -12); # we begin 0x100...much bigger than we need + for (my $i = length($newhex); $i > 0; $i-=2) { + $rv = substr($newhex, $i-2, 2).":$rv"; + } + $rv =substr($rv, 0, -1); + undef($newdec); + undef($pad); + return $rv; +} + + + +#### +1; +__END__ + + +=head1 NAME + Port - class to implement various LANforge utility and helper functions. + +=head1 SYNOPSIS + + use LANforge::Utils + + ################# + # class methods # + ################# + $ob = LANforge::Utils->new; + + ####################### + # object data methods # + ####################### + + ### get versions ### + $telnet = $ob->telnet(); + + ### set versions ### + $ob->telnet($t); + + ######################## + # other object methods # + ######################## + + $ob->doCmd("$Some CLI command\n"); + $ob->doAsyncCmd("$Some Asynchronous CLI command\n"); + +=head1 DESCRIPTION + The Utils class gives you some powerful and packaged access to various + LANforge CLI objects. + +=head1 AUTHOR + Ben Greear (greearb@candelatech.com) + Copyright (c) 2020 Candela Technologies. All rights reserved. + This program is free software; you can redistribute it and/or + modify it under the same terms as Perl itself. + +=end diff --git a/lanforge/lanforge-scripts/LANforge/csv.pm b/lanforge/lanforge-scripts/LANforge/csv.pm new file mode 100644 index 000000000..039d56c42 --- /dev/null +++ b/lanforge/lanforge-scripts/LANforge/csv.pm @@ -0,0 +1,123 @@ +package LANforge::csv; +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +#use Data::Dumper; +#use Data::Dumper::Concise; + +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + $self->{'ignore_comments'} = 1; + $self->{'skip_comments'} = 0; + $self->{'trim_whitespace'} = 1; + $self->{'rows'}=(); + + bless($self, $class); + return $self; +} + +sub readFile { + my $self = shift; + my $filename = shift; + die ("readFile: no filename provided.") + if (!defined $filename || $filename eq ""); + + open(my $fh, "<", "$filename") + or die("readFile: $!"); + + my @lines = (); + while(<$fh>) { + chomp; + my @row = undef; + #print "COMMENT: $_\n" if (/^\s*?\#.*/ && $self->{ignore_comments}); + next if (/^\s*?\#.*/ && $self->{skip_comments}); + + if (/^\s*?\#.*/ && $self->{ignore_comments}) { + @row = (); + push(@{$self->{rows}}, \@row); + next; + } + else { + @row = split(/,/); + } + # trim() on all cell values + if ($self->{trim_whitespace}) { + s{^\s+|\s+$}{}g foreach @row; + } + push(@{$self->{rows}}, \@row); + } + close $fh; +} + +sub getRow { + my $self = shift; + my $row_num = shift; + die("getRow: no row number provided") + if (!defined($row_num) || $row_num eq ""); + + return undef if ($row_num >= @{$self->rows}); + return ${$self->rows}[$row_num]; +} + +sub getCell { + my $self = shift; + my $cell_num = shift; + my $row_num = shift; + my $default = (shift || 'undef'); + + die("getCell: no row number provided") + if (!defined($row_num) || $row_num eq ""); + die("getCell: no cell number provided") + if (!defined($cell_num) || $cell_num eq ""); + + if ($row_num >= @{$self->{rows}}) { + #warn Dumper(@{$self->{rows}}); + warn "row $row_num greater than number of rows(@{$self->{rows}})\n"; + return $default; + } + + my $ra_row = ${$self->{rows}}[$row_num]; + + if (!defined $ra_row) { + #warn "row $row_num unset\n"; + return $default; + } + + if ($cell_num >= @{$ra_row}) { + #warn "cell $cell_num beyond size of row (".@{$ra_row}.")\n"; + #warn Dumper($ra_row); + return $default; + } + + if (!defined $ra_row->[$cell_num]) { + #warn "value at [$cell_num,$row_num] unset\n"; + #warn Dumper($ra_row); + return $default; + } + return $ra_row->[$cell_num]; +} + +sub getRows { + my $self = shift; + return $self->{rows}; +} + +sub rows { + my $self = shift; + return $self->{rows}; +} + +sub numRows { + my $self = shift; + return 0+@{$self->{rows}}; +} + +1; +=pod +This is a simple CSV parser, please install Text::CSV or someting more sophisticated +For instance, do not embed commas or newlines into the csv cells. +=end diff --git a/lanforge/lanforge-scripts/LICENSE b/lanforge/lanforge-scripts/LICENSE new file mode 100644 index 000000000..fdd69bac8 --- /dev/null +++ b/lanforge/lanforge-scripts/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020, Telecom Infra Project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lanforge/lanforge-scripts/README.md b/lanforge/lanforge-scripts/README.md new file mode 100644 index 000000000..54ac78d74 --- /dev/null +++ b/lanforge/lanforge-scripts/README.md @@ -0,0 +1,296 @@ +# LANforge Perl, Python, and Shell Scripts # +This is a collection of scripts and scripting libraries designed to work +with LANforge systems. On your LANforge system, these scripts are +typically installed into `/home/lanforge/scripts`. The `LANforge/` sub directory holds +the perl modules (`.pm` files) that are common to the perl scripts. + +## LANforge CLI Users Guide: https://www.candelatech.com/lfcli_ug.php ## +The LANforge CLI Users Guide is a good place to start for understanding scripts + +## LANforge on system cli help and cli command composer ## +The LANforge has an on system help / system query, when on LANforge browse to +http://local_host:8080 + +The LANforge has an on system cli help and cli command composer +http://local_host:8080/help + +### Commonly Used ### +The `lf_*.pl` scripts are typically more complete and general purpose +scripts, though some are ancient and very specific. In particular, +these scripts are more modern and may be a good place to start: + +| Name | Purpose | +|------------------|-----------| +| `lf_associate_ap.pl` | LANforge server script for associating virtual stations to an arbitrary SSID | +| `lf_attenmod.pl` | Query and update CT70X programmable attenuators | +| `lf_firemod.pl` | Query and update connections (Layer 3) | +| `lf_icemod.pl` | Query and update WAN links and impairments | +| `lf_portmod.pl` | Query and update physical and virtual ports | +| `lf_tos_test.py` | Generate traffic at different QoS and report in spreadsheet | +| `lf_sniff.py` | Create packet capture files, especially OFDMA /AX captures | + +The `lf_wifi_rest_example.pl` script shows how one might call the other scripts from +within a script. + +### Examples and Documents ### +Read more examples in the [scripting LANforge](http://www.candelatech.com/lfcli_api_cookbook.php) cookbook. + +## Python Scripts ## + +When starting to use Python, please run the update_dependencies.py script located in py-scripts to install all necessary dependencies for this library. + + +### Python Scripts py-json/LANforge ### + +Core communication files to LANforge + +| Name | Purpose | +|------|---------| +| `add_dut.py` | defined list of DUT keys, cli equivalent: https://www.candelatech.com/lfcli_ug.php#add_dut | +| `add_file_endp.py` | Add a File endpoint to the LANforge Manager. cli equivalent: add_file_endp https://www.candelatech.com/lfcli_ug.php#add_file_endp | +| `add_l4_endp.py` | Add a Layer 4-7 (HTTP, FTP, TELNET, ..) endpoint to the LANforge Manager. cli equivalent: add_l4_endp https://www.candelatech.com/lfcli_ug.php#add_l4_endp | +| `add_monitor.py` | Add a WIFI Monitor interface. These are useful for doing low-level wifi packet capturing. cli equivalent: add_monitor https://www.candelatech.com/lfcli_ug.php#add_monitor | +| `add_sta.py` | Add a WIFI Virtual Station (Virtual STA) interface. cli equivalent: add_sta https://www.candelatech.com/lfcli_ug.php#add_sta | +| `add_vap.py` | Add a WIFI Virtual Access Point (VAP) interface. cli equivalent: add_vap https://www.candelatech.com/lfcli_ug.php#add_vap | +| `set_port.py` | This command allows you to modify attributes on an Ethernet port. cli equivalent: set_port https://www.candelatech.com/lfcli_ug.php#set_port | +| `lfcli_base.py` | json communication to LANforge | +| `LFRequest.py` | Class holds default settings for json requests to LANforge, see: https://gist.github.com/aleiphoenix/4159510| +| `LFUtils.py` | Defines useful common methods | +| `set_port.py` | This command allows you to modify attributes on an Ethernet port. These options includes the IP address, netmask, gateway address, MAC, MTU, and TX Queue Length. cli equivalent: set_port https://www.candelatech.com/lfcli_ug.php#set_port | + + +### Python Scripts py-json/ ### +| Name | Purpose | +|------|---------| +| `base_profile.py` | Class: BaseProfile Use example: py-json/l3_cxprofile2.py used to define generic utility methods to be inherited by other classes | +| `create_wanlink.py` | Create and modify WAN Links Using LANforge JSON AP : http://www.candelatech.com/cookbook.php?vol=cli&book=JSON:+Managing+WANlinks+using+JSON+and+Python | +| `cv_commands.py` | This is a library file used to create a chamber view scenario. import this file as showed in create_chamberview.py to create a scenario | +| `cv_test_manager.py` | This script is working as library for chamberview tests. It holds different commands to automate test. | +| `cv_test_reports.py` | Class: lanforge_reports Pulls reports from LANforge | +| `dataplane_test_profile.py` | Library to Run Dataplane Test: Using lf_cv_base class | +| `dut_profile.py` | Class: DUTProfile (new_dut_profile) Use example: py-scripts/update_dut.py used to updates a Device Under Test (DUT) entry in the LANforge test scenario A common reason to use this would be to update MAC addresses in a DUT when you switch between different items of the same make/model of a DUT | +| `fio_endp_profile.py` | Class: FIOEndpProfile (new_fio_endp_profile) Use example: py-scripts/test_fileio.py will create stations or macvlans with matching fileio endpoints to generate and verify fileio related traffic | +| `gen_cxprofile.py` | Class: GenCXProfile (new_generic_endp_profile) Use example: test_generic.py will create stations and endpoints to generate traffic based on a command-line specified command type | +| `http_profile.py` | Class: HTTPProfile (new_http_profile) Use example: test_ipv4_l4_wifi.py will create stations and endpoints to generate and verify layer-4 upload traffic | +| `l3_cxprofile.py` | Class: L3CXProfile (new_l3_cx_profile) Use example: test_ipv4_variable_time.py will create stations and endpoints to generate and verify layer-3 traffic | +| `l3_cxprofile2.py` | Class: L3CXProfile2 (new_l3_cx_profile, ver=2) No current use example, inherits utility functions from BaseProfile, maintains functionality of L3CXProfile | +| `l4_cxprofile.py` | Class: L4CXProfile (new_l4_cx_profile) Use example: test_ipv4_l4.py will create stations and endpoints to generate and verify layer-4 traffic | +| `lf_cv_base.py` | Class: ChamberViewBase, Base Class to be used for Chamber View Tests, inherited by DataPlaneTest in dataplane_test_profile.py | +| `lfdata.py` | Class: LFDataCollection, class used for data collection utility methods | +| `mac_vlan_profile.py` | Class: MACVLANProfile (new_mvlan_profile) Use example: test_fileio.py will create stations or macvlans with matching fileio endpoints to generate and verify fileio related traffic. | +| `multicast_profile.py` | Class: MULTICASTProfile (new_multicast_profile) Use example: test_l3_longevity.py multi cast profiles are created in this test | +| `port_utils.py` | Class: PortUtils used to set the ftp or http port | +| `qvlan_profile.py` | Class: QVLANProfile (new_qvlan_profile) Use example: create_qvlan.py (802.1Q VLAN) | +| `realm.py` | Class: The Realm Class is inherited by most python tests. Realm Class inherites from LFCliBase. The Realm Class contains the configurable components for LANforge, For example L3 / L4 cross connects, stations. http://www.candelatech.com/cookbook.php?vol=cli&book=Python_Create_Test_Scripts_With_the_Realm_Class | +| `realm_test.py` | Python script meant to test functionality of realm methods | +| `show_ports.py` | Python script example of how to check a LANforge json url | +| `station_profile.py` | Class: StationProfile (new_station_profile) Use example: most scripts create and use station profiles | +| `test_base.py` | Class: TestBase, basic class for creating tests, uses basic functions for cleanup, starting/stopping, and passing of tests | +| `test_group_profile.py` | Class: TestGroupProfile (new_test_group_profile) Use example: test_fileio.py will create stations or macvlans with matching fileio endpoints to generate and verify fileio related traffic | +| `test_utility.py` | Standard Script for Webconsole Test Utility | +| `vap_profile.py` | Class: VAPProfile (new_vap_profile) profile for creating Virtual AP's Use example: create_vap.py | +| `vr_profile2.py` | Class: VRProfile (new_vap_profile, ver=2) No current use example, inherits utility functions from BaseProfile | +| `wifi_monitor_profile.py` | Class: WifiMonitor (new_wifi_monitor_profile) Use example: tip_station_powersave.py This script uses filters from realm's PacketFilter class to filter pcap output for specific packets. | +| `wlan_theoretical_sta.py` | Class: abg11_calculator Standard Script for WLAN Capaity Calculator Use example: wlan_capacitycalculator.py | +| `ws-sta-monitor.py` | Example of how to filter messages from the :8081 websocket | +| `ws_generic_monitor.py` | Class: WS_Listener web socket listener Use example: ws_generic_monitor_test.py, ws_generic_monitor to monitor events triggered by scripts, This script when running, will monitor the events triggered by test_ipv4_connection.py | + + + +### Python Scripts py-scripts ### + +Test scripts and helper scripts + +| Name | Purpose | +|------|---------| +| `cicd_TipIntegration.py` | Facebook TIP infrastructure| +| `cicd_testrail.py` | TestRail API binding for Python 3 | +| `cicd_testrailAndInfraSetup.py` | Facebook TIP infrastructure | +| `connection_test.py` | Standard Script for Connection Testing - Creates HTML and pdf report as a result (Used for web-console) | +| `create_bond.py` | This script can be used to create a bond | +| `create_bridge.py` | Script for creating a variable number of bridges | +| `create_chamberview.py` | Script for creating a chamberview scenario | +| `create_l3.py` | This script will create a variable number of layer3 stations each with their own set of cross-connects and endpoints | +| `create_l4.py` | This script will create a variable number of layer4 stations each with their own set of cross-connects and endpoints | +| `create_macvlan.py` | Script for creating a variable number of macvlans | +| `create_qvlan.py` | Script for creating a variable number of qvlans | +| `create_station.py` | Script for creating a variable number of stations | +| `create_station_from_df.py` | Script for creating a variable number of stations from a file | +| `create_vap.py` | Script for creating a variable number of VAPs | +| `create_vr.py` | Script for creating a variable number of bridges | +| `csv_convert.py` | Python script to read in a LANforge Dataplane CSV file and output a csv file that works with a customer's RvRvO visualization tool.| +| `csv_processor.py` | Python script to assist processing csv files| +| `csv_to_influx.py` | Python script to copy the data from a CSV file from the KPI file generated from a Wifi Capacity test to an Influx database| +| `download_test.py` | download_test.py will do lf_report::add_kpi(tags, 'throughput-download-bps', $my_value);| +| `event_breaker.py` | This file is intended to expose concurrency problems in the /events/ URL handler by querying events rapidly. Please use concurrently with event_flood.py. | +| `event_flood.py` | This file is intended to expose concurrency problems in the /events/ URL handler by inserting events rapidly. Please concurrently use with event_breaker.py.| +| `example_security_connection.py` | This python script creates a variable number of stations using user-input security +| `ftp_html.py` | This FTP Test is used to "Verify that N clients connected on Specified band and can simultaneously download some amount of file from FTP server and measures the time taken by client to Download/Upload the file | +| `grafana_profile.py` | Class for creating and managing a grafana dashboard | +| `html_template.py` | This script is used for DFS Test Report generation | +| `influx.py` | Class for communicating with influx | +| `influx2.py` | Class for communicating with influx | +| `layer3_test.py` | Python script to test and monitor layer 3 connections | +| `layer4_test.py` | Python script to test and monitor layer 4 connections | +| `lf_ap_auto_test.py` | This script is used to automate running AP-Auto tests | +| `lf_dataplane_test.py` | This script is used to automate running Dataplane tests | +| `lf_dfs_test.py` | Test testing dynamic frequency selection (dfs) between an AP connected to a controller and Lanforge| +| `lf_dut_sta_vap_test.py` | Load an existing scenario, start some layer 3 traffic, and test the Linux based DUT that has SSH server | +| `lf_ftp_test.py` | Python script will create stations and endpoints to generate and verify layer-4 traffic over an ftp connection | +| `lf_graph.py` | Classes for creating images from graphs using data sets | +| `lf_mesh_test.py` | This script is used to automate running Mesh tests | +| `lf_report.py` | This program is a helper class for reporting results for a lanforge python script | +| `lf_report_test.py` | Python script to test reporting | +| `lf_rvr_test.py` | This script is used to automate running Rate-vs-Range tests | +| `lf_snp_test.py` | Test scaling and performance (snp) run various configurations and measures data rates | +| `lf_tr398_test.py` | This script is used to automate running TR398 tests | +| `lf_wifi_capacity_test.py` | This is a test file which will run a wifi capacity test | +| `recordinflux.py` | recordinflux will record data from existing lanforge endpoints to record to an already existing influx database | +| `run_cv_scenario.py` | Set the LANforge to a BLANK database then it will load the specified database and start a graphical report | +| `rvr_scenario.py` | This script will set the LANforge to a BLANK database then it will load the specified database and start a graphical report | +| `scenario.py` | Python script to load a database file and control test groups | +| `sta_connect.py` | Create a station, run TCP and UDP traffic then verify traffic was received. Stations are cleaned up afterwards | +| `sta_connect2.py` | Create a station, run TCP and UDP traffic then verify traffic was received. Stations are cleaned up afterwards | +| `sta_connect_example.py` | Example of how to instantiate StaConnect and run the test | +| `sta_connect_multi_example.py` | Example of how to instantiate StaConnect and run the test | +| `station_layer3.py` | this script creates one station with given arguments | +| `stations_connected.py` | Contains examples of using realm to query stations and get specific information from them | +| `test_1k_clients_jedtest.py` | Python script to test 1k client connections | +| `test_client_admission.py` | This script will create one station at a time and generate downstream traffic | +| `test_fileio.py` | Test FileIO traffic | +| `test_generic.py` | Test generic traffic using generic cross-connect and endpoint type | +| `test_ipv4_connection.py` | Test connections to a VAP of varying security types (WEP, WPA, WPA2, WPA3, Open) | +| `test_ipv4_l4.py` | Test layer 4 traffic using layer 4 cross-connect and endpoint type | +| `test_ipv4_l4_ftp_upload.py` | Test ftp upload traffic | +| `test_ipv4_l4_ftp_urls_per_ten.py` | Test the number of urls per ten minutes in ftp traffic | +| `test_ipv4_l4_ftp_wifi.py` | Test ftp upload traffic wifi-wifi | +| `test_ipv4_l4_urls_per_ten.py` | Test urls per ten minutes in layer 4 traffic | +| `test_ipv4_l4_wifi.py` | Test layer 4 upload traffic wifi-wifi| +| `test_ipv4_ttls.py` | Test connection to ttls system | +| `test_ipv4_variable_time.py` | Test connection and traffic on VAPs of varying security types (WEP, WPA, WPA2, WPA3, Open) | +| `test_ipv6_connection.py` | Test IPV6 connection to VAPs of varying security types (WEP, WPA, WPA2, WPA3, Open) | +| `test_ipv6_variable_time.py` | Test IPV6 connection and traffic on VAPs of varying security types (WEP, WPA, WPA2, WPA3, Open) | +| `test_l3_WAN_LAN.py` | Test traffic over a bridged NAT connection | +| `test_l3_longevity.py` | Create variable stations on multiple radios, configurable rates, PDU, ToS, TCP and/or UDP traffic, upload and download, attenuation | +| `test_l3_powersave_traffic.py` | Python script to test for layer 3 powersave traffic | +| `test_l3_scenario_throughput.py` | Load an existing scenario and run the simultaneous throughput over time and generate report and P=plot the G=graph| +| `test_l3_unicast_traffic_gen.py` | Generate unicast traffic over a list of stations| +| `test_status_msg.py` | Test the status message passing functions of /status-msg | +| `test_wanlink.py` | Python script to test wanlink creation | +| `test_wpa_passphrases.py` | Python script to test challenging wpa psk passphrases | +| `testgroup.py` | Python script to test creation and control of test groups | +| `testgroup2.py` | Python script to test creation and control of test groups | +| `tip_station_powersave.py` | Generate and test for powersave packets within traffic run over multiple stations | +| `update_dependencies.py` | Python script to update dependencies for various Candelatech python scripts | +| `update_dut.py` | This script updates a Device Under Test (DUT) entry in the LANforge test scenario | +| `wlan_capacity_calculator.py` | Standard Script for WLAN Capacity Calculator | +| `ws_generic_monitor_test.py` | This example is to demonstrate ws_generic_monitor to monitor events triggered by scripts, This script when running, will monitor the events triggered by test_ipv4_connection.py | + +## Perl and Shell Scripts ## + +| Name | Purpose | +|------|---------| +| `associate_loop.sh` | Use this script to associate stations between SSIDs A and B | +| `attenuator_series_example.csv` | Example of CSV input for a series of attenuator settings | +| `attenuator_series.pl` | Reads a CSV of attenuator settings and replays them to CT70X programmble attenuator | +| `ftp-upload.pl` | Use this script to collect and upload station data to FTP site | +| `imix.pl` | packet loss survey tool | +| `lf_associate_ap.pl` | LANforge server script for associating virtual stations to an chosen SSID | +| `lf_attenmod.pl` | This program is used to modify the LANforge attenuator through the LANforge | +| `lf_auto_wifi_cap.pl` | This program is used to automatically run LANforge-GUI WiFi Capacity tests | +| `lf_cmc_macvlan.pl` | Stress test sets up traffic types of udp , tcp , continuously starts and stops the connections | +| `lf_create_bcast.pl` | creates a L3 broadcast connection | +| `lf_cycle_wanlinks.pl` | example of how to call lf_icemod.pl from a script | +| `lf_endp_script.pl` | create a hunt script on a L3 connection endpoint | +| `lf_firemod.pl` | queries and modifies L3 connections | +| `lf_generic_ping.pl` | Generate a batch of Generic lfping endpoints | +| `lf_gui_cmd.pl` | Initiate a stress test | +| `lf_icemod.pl` | queries and modified WANLink connections | +| `lf_ice.pl` | adds and configures wanlinks | +| `lf_l4_auth.pl` | example of scripting L4 http script with basic auth | +| `lf_l4_reset.sh` | reset any layer 4 connection that reaches 0 Mbps over last minute | +| `lf_log_parse.pl` | Convert the timestamp in LANforge logs (it is in unix-time, miliseconds) to readable date | +| `lf_loop_traffic.sh` | Repeatedly start and stop a L3 connection | +| `lf_macvlan_l4.pl` | Set up connection types: lf_udp, lf_tcp across 1 real port and many macvlan ports on 2 machines. Then continously starts and stops the connections. | +| `lf_mcast.bash` | Create a multicast L3 connection endpoint | +| `lf_monitor.pl` | Monitor L4 connections | +| `lf_nfs_io.pl` | Creates and runs NFS connections | +| `lf_parse_tshark_log.pl` | Basic parsing of tshark logs | +| `lf_portmod.pl` | Queries and changes LANforge physical and virtual ports | +| `lf_port_walk.pl` | Creates a series of connections, useful for basic firewall testing | +| `lf_show_events.pl` | Displays and clears LANforge event log | +| `lf_staggered_dl.sh` | his script starts a series of Layer-3 connections across a series of stations each station will wait $nap seconds, download $quantity KB and then remove its old CX. | +| `lf_sta_name.pl` | Use this script to alter a virtual station names | +| `lf_verify.pl` | Creates a basic L3 connection to verify that two ethernet ports are physically connected | +| `lf_voip.pl` | Creates series of VOIP connections between two LANforge machines | +| `lf_voip_test.pl` | Creates series of VOIP connections and runs them | +| `lf_vue_mod.sh` | Bash script that wraps common operations for Virtual User Endpoint operations done by `lf_associate_ap` | +| `lf_wifi_rest_example.pl` | Example script that queries a LF GUI for JSON data and displays a slice of it | +| `lf_zlt_binary.pl` | Configures a Zero Loss Throughput test | +| `list_phy_sta.sh` | Lists virtual stations backed by specified physical radio | +| `min_max_ave_station.pl` | This script looks for min-max-average bps for rx-rate in a station csv data file | +| `multi_routers.pl` | Routing cleanup script that can be used with virtual routers | +| `print_udev.sh` | Prints out Linux Udev rules describing how to name ports by MAC address | +| `sensorz.pl` | Displays temperature readings for CPU and ATH10K radios | +| `show-port-from-json.pl` | Example script showing how to display a slice from a JSON GUI response | +| `station-toggle.sh` | Use this script to toggle a set of stations on or off | +| `sysmon.sh` | grabs netdev stats and timestamp every second or so, saves to logfile. | +| `test_refcnt.pl` | creates MAC-VLANs and curl requests for each | +| `topmon.sh` | LANforge system monitor that can be used from cron | +| `wait_on_ports.pl` | waits on ports to have IP addresses, can up/down port to stimulate new DHCP lease | +| `wifi-roaming-times.pl` | parses `wpa_supplicant_log.wiphyX` file to determine roaming times | + +### LANForge Monitoring ### +From LANforge cli on port 4001 do a 'show_event' to see events from LANforge + +### Compatibility ### +Scripts will be kept backwards and forwards compatible with LANforge +releases as much as possible. + +### Installation ### +These scripts call each other and rely on the structure of this directory. To use these scripts in other locations, +such as your laptop, either copy the entire scripts directory or do a __git clone__ of this repository. Just copying +one script to a separate directory is going to break its requirements. + +### Requirements ### +The perl scripts require the following perl packages to be installed. Most of these +perl packages are available through your repository as `.deb` or `.rpm` packages. + +| Perl Package | RPM | Required | +| -------------------|------------------|----------------| +| Net::Telnet | perl-Net-Telnet | Yes | +| JSON | perl-JSON | Yes, for JSON parsing | +| JSON::PrettyPrint | perl-JSON-PP | No, useful for debugging | + +| Python3 Package | RPM | Required | +|-------------------------|-----------|---------------| +| Pexpect | python3-pexpect | yes | +| XlsxWriter | python3-xlsxwriter | yes, Xlsx output | + + +#### Pip v Pip3 #### +Please use pip3, we are targeting Python 3 with our scripts. If your pip/pip3 repositories have a difficult time connecting, +it's likely that you are trying to download from **pypi.python.org**. This is a deprecated location. Please update +using the **pypi.org** servers. Consider updating your ``~/.pypirc`` file: + +```` +[distutils] +index-servers = + pypi + +[pypi] +repository: https://upload.pypi.org/legacy/ +```` + + +As [described on Python.org](https://packaging.python.org/guides/migrating-to-pypi-org/). + +### License ### +Code in this repository is released under the BSD license (see license.txt). + + +### Support ### +Please contact support@candelatech.com if you have any questions. + +_Thanks, +Ben_ diff --git a/lanforge/lanforge-scripts/WlanPro.desktop b/lanforge/lanforge-scripts/WlanPro.desktop new file mode 100755 index 000000000..dc63b2726 --- /dev/null +++ b/lanforge/lanforge-scripts/WlanPro.desktop @@ -0,0 +1,15 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Version=1.0 +Encoding=UTF-8 +Name=WLANPro Test +Name[en_US.ISO8859-1]=WLANPro Test +Comment=WLANPro Test +Comment[en_US.ISO8859-1]=WLANPro Test +Path=/home/lanforge/Desktop +Exec=/home/lanforge/Desktop/wpro.sh +Icon=/usr/share/icons/Adwaita/scalable/apps/utilities-terminal-symbolic.svg +Type=Application +Terminal=true +Categories=Network;X-Candela-Technologies +GenericName[en_US]=WLANPro Test diff --git a/lanforge/lanforge-scripts/add-dhcp-hostname.pl b/lanforge/lanforge-scripts/add-dhcp-hostname.pl new file mode 100755 index 000000000..cb0053890 --- /dev/null +++ b/lanforge/lanforge-scripts/add-dhcp-hostname.pl @@ -0,0 +1,57 @@ +#!/bin/perl +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# use this script after bringing up stations with a DHCP lease +# then admin-down the stations. Go into /home/lanforge/vr_conf +# and change the settings with this script on all the files +# in a manner like this: +# +# root# cd /home/lanforge/vr_conf +# root# ~lanforge/scripts/add-dhcp-hostname.pl dhclient_sta*conf +# +# At this point you can bring up your stations. If you need to +# check a lanforge dhcpd lease file, please look at the dhcp database +# found in /home/lanforge/vr_conf/vrcx_br1_dhcp_lease.db +# entries should have a "client-hostname: " entry. +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +use strict; +use warnings; +use diagnostics; +$| = 1; + +if (@ARGV < 1) { + print "No files requested.\n"; + exit 1; +} +my @chars = ("A".."Z", "a".."z", "0".."9"); + +my $rand_name = ""; +for my $fname (@ARGV) { + print "File: $fname "; + + my @lines = `cat $fname`; + chomp @lines; + die("Unable to open $fname for writing: $!") + unless open(my $fh, ">", $fname); + + my $ifname=''; + for my $line (@lines) { + next if ($line =~ /^\s*$/); + next if ($line =~ /^. LANforge-generated/); + next if ($line =~ /^. Remove first/); + next if ($line =~ /^. automatically over-write/); + next if ($line =~ /^send host-name/); + if ($line =~ /^# sta\d+/) { + ($ifname) = $line =~ /^# (sta\d+)\s*$/; + } + print $fh "$line\n"; + } + $rand_name = ""; + $rand_name .= $chars[ rand @chars] for 1..8; + #print "* "; + #print "$rand_name\n"; + print $fh "interface \"$ifname\" {\n"; + print $fh " send host-name \"$rand_name\";\n"; + print $fh "}\n"; + close $fh; +} +print "done\n"; diff --git a/lanforge/lanforge-scripts/adjust_apache.pl b/lanforge/lanforge-scripts/adjust_apache.pl new file mode 100755 index 000000000..d071b1f41 --- /dev/null +++ b/lanforge/lanforge-scripts/adjust_apache.pl @@ -0,0 +1,516 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use diagnostics; +use Carp; +use Data::Dumper; +use File::Temp qw(tempfile tempdir); +use Getopt::Long; + +my $Q='"'; +my $q="'"; +my @idhunks = split(' ', `id`); +my @hunks = grep { /uid=/ } @idhunks; +die ("Must be root to use this") + unless( $hunks[0] eq "uid=0(root)" ); +@idhunks = undef; +@hunks = undef; +my $start_time = `date +%Y%m%d-%H%M%S`; +chomp($start_time); +my $do_help = 0; +my $do_automatic = ( -t STDIN ) ? 0 : 1; # test for terminal stdin +my $debug = $ENV{'DEBUG'}; +my $usage = "$0 : + Use this to update /etc/hosts and /etc/httpd/http.conf for + LANforge server operations. By default this script will backup + your /etc/hosts file to /etc/.hosts.\$date and write a new copy + to /tmp/t_hosts_\$random. It will show you the difference between + the files and prompt you to continue. When you approve it will + copy /tmp/t_hosts_\$random to /etc/hosts. + + -d --debug enable debug (or use \$ set DEBUG=1) + -a --auto automatic operation mode, no prompts + -h --help this message + +"; +GetOptions( + "h|help" => \$do_help, + "d|debug" => \$debug, + "a|auto|automatic" => \$do_automatic, +) || die($usage); + +if ($do_help) { + print $usage; + exit(0); +} + +sub syslogg { + my $msg = join('\n', @_); + $msg =~ s/\r*\n/ /; + `logger -t adjust_apache "$msg"` +} +sub err { + my $msg = "[error] ".join("\n", @_); + print STDERR $msg, "\n"; + syslogg($msg) if ($do_automatic); +} +sub die_err { + my $msg = "[fatal] ".join("\n", @_); + syslogg($msg) if ($do_automatic); + die($msg); +} +sub warning { + my $msg = "[warning] ".join("\n", @_); + print STDOUT $msg, "\n"; + syslogg($msg) if ($do_automatic); +} +sub info { + my $msg = "[inf] ".join("\n", @_); + print STDOUT $msg, "\n"; + syslogg($msg) if ($do_automatic); +} + +my $MgrHostname = `cat /etc/hostname`; +chomp($MgrHostname); +info("Will be setting hostname to $MgrHostname"); +sleep 3 if ($debug); + +my $config_v = "/home/lanforge/config.values"; +# grab the config.values file +die_err("Unable to find $config_v" ) + unless ( -f $config_v); + +my @configv_lines = `cat $config_v`; +die_err("Probably too little data in config.values") + unless (@configv_lines > 5); +my %configv = (); +foreach my $line (@configv_lines) { + my ($key, $val) = $line =~ /^(\S+)\s+(.*)$/; + $configv{$key} = $val; +} +die_err("Unable to parse config.values") + unless ((keys %configv) > 5); +die_err("no mgt_dev in config.values") + unless defined $configv{'mgt_dev'}; +info("LANforge config states mgt_dev $configv{'mgt_dev'}"); + +if ( ! -d "/sys/class/net/$configv{'mgt_dev'}") { + die_err( "Please run lfconfig again with your updated mgt_port value."); +} +my $ipline = `ip -o a show $configv{"mgt_dev"}`; + +my ($ip) = $ipline =~ / inet ([0-9.]+)(\/\d+)? /g; +die_err("No ip found for mgt_dev; your config.values file is out of date: $!") + unless ((defined $ip) && ($ip ne "")); + +print "ip: $ip\n" if ($debug); + +# This must be kept in sync with similar code in lf_kinstall. +my $found_localhost = 0; +my $fname = "/etc/hosts"; +my $backup = "/etc/.hosts.$start_time"; +`cp $fname $backup`; +die_err("Unable to create backup of /etc/hosts at $backup") if ( ! -f $backup ); + +my ($fh, $editfile) = tempfile( "t_hosts_XXXX", DIR=>'/tmp', SUFFIX=>'.txt'); +if (-f "$fname") { + my @lines = `cat $fname`; + #open(FILE, ">$fname") or die "Couldn't open file: $fname for writing: $!\n\n"; + my $foundit = 0; + my $i; + # chomp is way to simplistic if we need to weed out \r\n characters as well + #chomp(@lines); + for (my $i = 0; $i < @lines; $i++) { + ($lines[$i]) = $lines[$i] =~ /^([^\r\n]+)\r?\n$/; + } + # we want to consolidate the $ip $hostname entry for MgrHostname + my @newlines = (); + my %more_hostnames = (); + my $new_entry = "$ip "; + #my $blank = 0; + #my $was_blank = 0; + my $counter = 0; + + if ((exists $ENV{"DEBUG"}) && ($ENV{"DEBUG"} eq "1")) { + $debug = 1; + } + my %host_map = ( + "localhost.localdomain" => "127.0.0.1", + "localhost" => "127.0.0.1", + "localhost4.localdomain4" => "127.0.0.1", + "localhost4" => "127.0.0.1", + "localhost.localdomain" => "::1", + "localhost" => "::1", + "localhost6.loaldomain6" => "::1", + "localhost6" => "::1", + $MgrHostname => $ip, + "lanforge.localnet" => "192.168.1.101", + "lanforge.localdomain" => "192.168.1.101", + ); + my %comment_map = (); + my %address_marker_map = (); + my %address_map = ( + "127.0.0.1" => "localhost.localdomain localhost localhost4.localdomain4 localhost4", + "::1" => "localhost.localdomain localhost localhost6.loaldomain6 localhost6", + $ip => $MgrHostname, + "192.168.1.101" => "lanforge.localnet lanforge.localdomain", + ); + if ($debug) { + print Dumper(\%address_map); + print Dumper(\%host_map); + } + + my $prevname = ""; + my $previp = ""; + + for my $ln (@lines) { + next if (!(defined $ln)); + # print "\nLN[$ln]\n" if ($debug); + next if ($ln =~ /^\s*$/); + next if ($ln =~ /^\s*#/); + next if ($ln =~ /LF-HOSTAME-NEXT/); # old typo + next if ($ln =~ /LF-HOSTNAME-NEXT/); + my $comment = undef; + # print "PARSING IPv4 ln[$ln]\n" if ($debug); + if ($ln =~ /#/) { + ($comment) = $ln =~ /^[^#]+(#.*)$/; + ($ln) = $ln =~ /^([^#]+)\s*#/; + print "line with comment becomes [$ln]\n" if ($debug); + } + @hunks = split(/\s+/, $ln); + my $middleip = 0; + my $counter2 = -1; + my $linehasip = 0; + my $lfhostname = 0; + if ((defined $comment) && ($comment ne "")) { + $comment_map{$hunks[0]} = $comment; + } + for my $hunk (@hunks) { + # print "\n HUNK",$counter2,"-:$hunk:- " if ($debug); + $counter2++; + next if ($hunk =~ /^localhost/); + next if ($hunk =~ /^lanforge-srv$/); + next if ($hunk =~ /^lanforge\.local(domain|net)$/); + next if ($hunk =~ /^extra6?-\d+/); + + if ($hunk =~ /^\s*$/) { + next; + } + + if ($hunk =~ /^$ip$/) { + $linehasip++; + $lfhostname++; + } + elsif ($hunk =~ /^$MgrHostname$/) { + $lfhostname++; + $prevname = $hunk; + } + + $previp = "" if (!defined($previp)); + + if (($hunk =~ /^127\.0\.0\.1/) + || ($hunk =~ /^192\.168\.1\.101/) + || ($hunk =~ /^::1$/)) { + $previp = $hunk; + $linehasip++; + } + elsif ($hunk =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) { + $linehasip++; + # print " IP4($hunk)" if ($debug); + if ($counter2 > 0) { # we're not first item on line + $middleip++ if ($counter2 > 0); + # print "middle" if ($debug); + } + if (!(defined $address_map{$hunk})) { + $address_map{$hunk} = ""; + } + # print "+IP4" if ($debug); + + if (("" ne $prevname) && ($counter2 > 0)) { + # print " hunk($hunk)prev($prevname)" if ($debug); + $address_map{$hunk} .= " $prevname" + if ($address_map{$hunk} !~ /\s*$prevname\s*/); + # $host_map{$prevname} .= " $hunk"; + if ($host_map{$prevname} !~ /\b$hunk\b/) { + $host_map{$prevname} .= " $hunk"; + } + } + $previp = $hunk; + } + elsif (($hunk =~ /[G-Zg-z]+\.?/) || ($hunk =~ /^[^:A-Fa-f0-9]+/)) { + # print " notIP($hunk)" if ($debug); + $prevname = $hunk; + if ($middleip) { + # print " middle($previp)" if ($debug); + $address_map{$previp} .= " $hunk" + if ($address_map{$previp} !~ /\b$hunk\b/); + $prevname = $hunk; + if ($host_map{$prevname} !~ /\b$hunk\b/) { + $host_map{$prevname} .= " $previp"; + } + } + elsif ($linehasip) { + # print " prev($previp) hunk($hunk)" if ($debug); + $address_map{$previp} .= " $hunk" + if ($address_map{$previp} !~ /\s*$hunk\s*/); + if ((defined $prevname) && (exists $host_map{$prevname}) && ($host_map{$prevname} !~ /\b$hunk\b/)) { + $host_map{$hunk} .= " $previp"; + } + } + elsif ($lfhostname) { + $more_hostnames{$hunk} = 1; + if ($host_map{$prevname} !~ /\b$hunk\b/) { + $host_map{$hunk} .= " $previp"; + } + } + else { # strange word + if ("" eq $previp) { + print " hunk($hunk) has no IP***" if ($debug); + $more_hostnames{$hunk} = 1; + } + elsif ($address_map{$previp} !~ /\s*$hunk\s*/) { + $address_map{$previp} .= " $hunk"; + if ($host_map{$prevname} !~ /\b$hunk\b/) { + $host_map{$hunk} .= " $previp"; + } + } + } + } + elsif (($hunk =~ /::/) + || ($hunk =~ /[0-9A-Fa-f]+:/)) { + # print " hunk6($hunk)" if ($debug); + $linehasip++; + if (!(defined $address_map{$hunk})) { + $address_map{$hunk} = ""; + } + $previp = $hunk; + } + elsif ($address_map{$previp} !~ /\s*$hunk\s*/) { + # is hostname and not an ip + $address_map{$previp} .= " $hunk"; + if ($host_map{$prevname} !~ /\b$hunk\b/) { + $host_map{$hunk} .= " $previp"; + } + } + } # ~foreach hunk + } # ~foreach line + + if (($host_map{$MgrHostname} !~ /^\s*$/) && ($host_map{$MgrHostname} =~ /\S+\s+\S+/)) { + print("Multiple IPs for this hostname: " . $host_map{$MgrHostname} . "\n") if ($debug); + my @iphunks = split(/\s+/, $host_map{$MgrHostname}); + print "Changing $MgrHostname to $ip; hostmap: <<$host_map{$MgrHostname}>> addrmap: <<$address_map{$ip}>>\n" + if ($debug); + $host_map{$MgrHostname} = $ip; + } + for my $name (sort keys %more_hostnames) { + $address_map{$ip} .= " $name"; + print "updated address_map entry: $ip -> $address_map{$ip}\n" if ($debug); + } + + # this might be premature + unshift(@newlines, "192.168.1.101 " . $address_map{"192.168.1.101"}); + unshift(@newlines, "127.0.0.1 " . $address_map{"127.0.0.1"}); + unshift(@newlines, "::1 " . $address_map{"::1"}); + + my %used_addresses = (); + + delete($address_map{"192.168.1.101"}); + $used_addresses{"192.168.1.101"} = 1; + delete($address_map{"127.0.0.1"}); + $used_addresses{"127.0.0.1"} = 1; + delete($address_map{"::1"}); + $used_addresses{"::1"} = 1; + + if ($debug) { + print "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----\n"; + print "\nAddress map\n"; + print Dumper(\%address_map); + print "\nHost map\n"; + print Dumper(\%host_map); + print "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----\n"; + sleep 2; + } + + # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + # we want to maintain the original line ordering as faithfully as possible + # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + for my $ln (@lines) { + $ln = "" if (!(defined $ln)); + print "old[$ln]\n" if ($debug); + # if we are comments or blank lines, preserve them + next if ($ln =~ /LF-HOSTNAME-NEXT/); + next if ($ln =~ /\b$MgrHostname\b/); # skip our mgt hostname + next if ($ln =~ /^$host_map{$MgrHostname}\s+/); # line starts with present IP addr + + if (($ln =~ /^\s*$/) || ($ln =~ /^\s*#/)) { + push(@newlines, $ln); + next; + } + @hunks = split(/\s+/, $ln); + + if (exists $address_map{$hunks[0]}) { + if ((exists $address_marker_map{$hunks[0]}) + || (exists $used_addresses{$hunks[0]})) { + print "already printed $hunks[0]\n" if ($debug); + next; + } + my $comment = ""; + if (exists $comment_map{$hunks[0]}) { + $comment = " $comment_map{$hunks[0]}"; + } + push(@newlines, "$hunks[0] $address_map{$hunks[0]}$comment"); + $address_marker_map{$hunks[0]} = 1; + next; + } + if (!(exists $used_addresses{$hunks[0]})) { + warning("untracked IP <<$hunks[0]>> Used addresses:"); + print Dumper(\%address_marker_map) if ($debug); + print Dumper(\%used_addresses) if ($debug); + } + } + + push(@newlines, "###-LF-HOSTNAME-NEXT-###"); + push(@newlines, $ip . " " . $address_map{$ip}); + if ($debug) { + print "# ----- new /etc/hosts ----- ----- ----- ----- ----- ----- ----- ----- ----- -----\n"; + for my $ln (@newlines) { + print "$ln\n"; + } + print "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----\n"; + sleep 5; + } + # write to /tmp/t_hosts_$random + for my $ln (@newlines) { + print $fh "$ln\n"; + } + + close $fh; + my $wc_edit_file = `wc -l < $editfile`; + chomp($wc_edit_file); + my $wc_orig_file = `wc -l < $backup`; + + if ($wc_edit_file == 0) { + die_err("Abandoning $editfile, it was blank."); + exit(1); + } + my $there_are_diffs = `/bin/diff /etc/hosts $editfile > /dev/null && echo 0 || echo 1`; + chomp($there_are_diffs); + $there_are_diffs = int($there_are_diffs); + if (! $there_are_diffs) { + info("No difference in hosts file."); + sleep(1) if (!$do_automatic); + } + elsif (!$do_automatic) { + my $msg = "Original /etc/hosts file backed up to $backup\n" + . "The hosts file differs by " . ($wc_orig_file - $wc_edit_file) . "lines, at: $editfile\n" + . "Displaying difference...\n"; + info($msg); + sleep(2); + my $diffcmd = "diff -y /etc/hosts $editfile"; + if ( -x "/usr/bin/colordiff" ) { + $diffcmd = "colordiff -y /etc/hosts $editfile"; + } + open(my $diff_in, "-|", $diffcmd); + my ($diff_out, $diff_file) = tempfile( "diff_hosts_XXXX", DIR=>"/tmp" ); + my @diff_lines = <$diff_in>; + close($diff_in); + print $diff_out join("", @diff_lines); + close($diff_out); + system("/bin/less -Nr $diff_file"); + print "/bin/less -dNr $diff_file\n" if ($debug); + # prompt to exit + print "Press Enter to continue, [ctrl-c] to quit >"; + my $i = ; + } + if ($there_are_diffs) { + warning("Line comparison: $backup\: $wc_orig_file, $editfile\: $wc_edit_file"); + warning("Installing new hosts file from $editfile, backup at $backup"); + system("cp $editfile /etc/hosts"); + } +} # ~if found hosts file + +my $local_crt =""; +my $local_key =""; +my $hostname_crt =""; +my $hostname_key =""; +# check for hostname shaped cert files +if (-f "/etc/pki/tls/certs/localhost.crt") { + $local_crt = "/etc/pki/tls/certs/localhost.crt"; +} +if (-f "/etc/pki/tls/private/localhost.key") { + $local_key = "/etc/pki/tls/private/localhost.key"; +} + +if (-f "/etc/pki/tls/certs/$MgrHostname.crt") { + $hostname_crt = "/etc/pki/tls/certs/$MgrHostname.crt"; +} +if (-f "/etc/pki/tls/private/$MgrHostname.key") { + $hostname_key = "/etc/pki/tls/private/$MgrHostname.key"; +} + +# grab the 0000-default.conf file +my @places_to_check = ( + "/etc/apache2/apache2.conf", + "/etc/apache2/ports.conf", + "/etc/apache2/sites-available/000-default.conf", + "/etc/apache2/sites-available/0000-default.conf", + "/etc/httpd/conf/http.conf", + "/etc/httpd/conf/httpd.conf", + "/etc/httpd/conf.d/ssl.conf", + "/etc/httpd/conf.d/00-ServerName.conf", +); +foreach my $file (@places_to_check) { + if (-f $file) { + print "Checking $file...\n"; + my @lines = `cat $file`; + chomp @lines; + # we want to match Listen 80$ or Listen 443 https$ + # we want to replace with Listen lanforge-mgr:80$ or Listen lanforge-mgr:443 https$ + @hunks = grep {/^\s*(Listen|SSLCertificate)/} @lines; + if (@hunks) { + my $edited = 0; + my @newlines = (); + @hunks = (@hunks, "\n"); + print "Something to change in $file\n"; + print "These lines are interesting:\n"; + print join("\n", @hunks); + foreach my $confline (@lines) { + if ($confline =~ /^\s*Listen\s+(?:80|443) */) { + $confline =~ s/Listen /Listen ${MgrHostname}:/; + print "$confline\n"; + } + elsif ($confline =~ /^\s*Listen\s+(?:[^:]+:(80|443)) */) { + $confline =~ s/Listen [^:]+:/Listen ${MgrHostname}:/; + print "$confline\n"; + } + if ($confline =~ /^\s*SSLCertificateFile /) { + $confline = "SSLCertificateFile $hostname_crt" if ("" ne $hostname_crt); + } + if ($confline =~ /^\s*SSLCertificateKeyFile /) { + $confline = "SSLCertificateKeyFile $hostname_key" if ("" ne $hostname_key); + } + push @newlines, $confline; + $edited++ if ($confline =~ /# modified by lanforge/); + } + push(@newlines, "# modified by lanforge\n") if ($edited == 0); + + my $fh; + die($!) unless open($fh, ">", $file); + print $fh join("\n", @newlines); + close $fh; + } + else { + print "Nothing looking like [Listen 80|443] in $file\n"; + } + } +} # ~for places_to_check +if (-d "/etc/httpd/conf.d") { + die($!) unless open(FILE, ">", "/etc/httpd/conf.d/00-ServerName.conf"); + print FILE "ServerName $MgrHostname\n"; + #print FILE "Listen $MgrHostname:80\n"; + #print FILE "Listen $MgrHostname:443\n"; + close FILE; +} + +# diff --git a/lanforge/lanforge-scripts/adjust_ice.sh b/lanforge/lanforge-scripts/adjust_ice.sh new file mode 100755 index 000000000..4a7846328 --- /dev/null +++ b/lanforge/lanforge-scripts/adjust_ice.sh @@ -0,0 +1,203 @@ +#!/bin/bash +# +# adjust_ice.sh +# +# This script takes a csv data file with downlink, uplink and rtt values +# and uses the values to adjust an existing running wanlink. +# +# Each value is checked against the committed information rate (CIR) limits +# and when the limit is exceeded a shuffled value is chosen between a default +# min and max. +# +# Inputs are: +# csv filename +# name of wanlink +# run time between value changes + +cd /home/lanforge/scripts + +file=$1 +name=$2 +run_time=$3 + + +print_help() { + echo "Usage: $0 {csv filename} {wanlink name} {run time}" + echo " Use this on lanforge localhost" + echo " Run time units are seconds" + echo " $0 values.csv wanlink-01 60" + echo "" +} + +if [ $# -eq 0 ]; then + print_help + exit 1 +fi +slices=10 +cir_dn=3500000 +cir_up=2000000 +min=20000 +max=200000 +dates=() +downlink=() +uplink=() +delay=() +declare -A months +months=([Jan]=1 [Feb]=2 [Mar]=3 [Apr]=4 [May]=5 [Jun]=6 [Jul]=7 [Aug]=8 [Sep]=9 [Oct]=10 [Nov]=11 [Dec]=12) + +# expects a "d-m-y hours:minutes meridian" format +function date_to_ts() { + local year + local hourmin + local meridian + [ -z "$1" ] && echo "date_to_ts: wants \$1 date string" && exit 1 + local hunks=($1); + local datehunks=() + + IFS=- read -r -a datehunks < <(echo "${hunks[0]}") + + local month=${datehunks[1]} + local monthno=${months[$month]} + + #echo "${monthno}/${datehunks[0]}/${datehunks[2]} ${hunks[1]} ${hunks[2]}" + date --date "${monthno}/${datehunks[0]}/${datehunks[2]} ${hunks[1]} ${hunks[2]}" +"%s" +} + +function get_values() { + while read -r line + do + if [[ $line != "" ]]; then + if [[ $line == *DATE* ]]; then + continue; + fi + if [[ $line != *-* ]]; then + continue; + fi + + local datestr=`echo $line |cut -d '"' -f1 |sed 's/,/ /g'` + local timest=$(date_to_ts "$datestr") + dates+=($timest) + local dl=`echo $line |cut -d '"' -f2 |sed 's/,//g'` + if [[ $dl < $cir_dn ]]; then + let dl=$(( $cir_dn - $dl )) + downlink+=( $dl ) + else + let bas=$(shuf -i "$min-$max" -n1) + downlink+=( $bas ) + fi + + local ul=`echo $line |cut -d '"' -f6 |sed 's/,//g'` + if [[ $ul < $cir_up ]]; then + let ul=$(( $cir_up - $ul )) + uplink+=( $ul ) + else + let bas=$(shuf -i "$min-$max" -n1) + uplink+=( $bas ) + fi + + local lat=`echo $line |cut -d '"' -f9 |sed 's/,//g' |cut -d '.' -f1` + let lat=$(( $lat/2 )) + delay+=( $lat ) + + fi + done < $file +} + + +function modify_values { + [ -z "$1" ] && echo "modify_values wants row index \$1, bye" && exit 1 + local row_idx=$1 + local dl_now=0 + local ul_now=0 + local lt_now=0 + local ts_now=${dates[$row_idx]} + local ts_next=${dates[ $(( $row_idx +1 )) ]} + local delta=$(( $ts_next - $ts_now )) + local pause=$(( $delta / $slices )) + #echo "now: $ts_now, next: $ts_next, delta: $delta pause: $pause" + local downlink_now="${downlink[$row_idx]}" + local downlink_next="${downlink[ $(( $row_idx +1 )) ]}" + local downlink_delta=$(( $downlink_next - $downlink_now )) + local uplink_now="${uplink[$row_idx]}" + local uplink_next="${uplink[ $(( $row_idx +1 )) ]}" + local uplink_delta=$(( $uplink_next - $uplink_now )) + local delay_now="${delay[ $row_idx ]}" + local delay_next="${delay[ $(( $row_idx +1 )) ]}" + local delay_delta=$(( $delay_next - $delay_now )) + local dl_series=() + local ul_series=() + local delay_series=() + local j; + + #echo "Deltas: $downlink_delta $uplink_delta $delay_delta" + echo "Now: $downlink_now $uplink_now $delay_now" + for ((j=1; j < $slices; ++j)); do + + local d_j=$(( $downlink_delta / $slices )) + local u_j=$(( $uplink_delta / $slices )) + local l_j=$(( $delay_delta / $slices )) + + local d_a=$d_j + local u_a=$u_j + + if [[ $l_j != 0 ]]; then + local l_a=$l_j + else + local l_a=1 + fi + + [[ $d_j -lt 0 ]] && d_a=$(( -1 * $d_j )) + [[ $u_j -lt 0 ]] && u_a=$(( -1 * $u_j )) + [[ $l_j -lt 0 ]] && l_a=$(( -1 * $l_j )) + local d_i=$(( ($j * $d_j) + `shuf -i 1-$d_a -n1` )) + local u_i=$(( ($j * $u_j) + `shuf -i 1-$u_a -n1` )) + local l_i=$(( ($j * $l_j) + `shuf -i 1-$l_a -n1` )) + #echo "$j: $d_i $u_i $l_i" + echo "$j: $(($downlink_now + $d_i)) $(($uplink_now + $u_i)) $(($delay_now + $l_i))" + dl_series+=( $(($downlink_now + $d_i)) ) + ul_series+=( $(($uplink_now + $u_i)) ) + delay_series+=( $(($delay_now + $l_i)) ) + done + echo "Next: $downlink_next $uplink_next $delay_next" + + for ((j=0; j < 9; ++j)); do + + dl_now=${dl_series[$j]} + ul_now=${ul_series[$j]} + lt_now=${delay_series[$j]} + + echo "set wanlink $name: $dl_now $ul_now $lt_now" + echo "set wanlink $name-A: Downlink $dl_now, Delay $lt_now" + ./lf_firemod.pl --mgr localhost --quiet on --action do_cmd --cmd \ + "set_wanlink_info $name-A $dl_now $lt_now NA NA NA NA" &>/dev/null + echo "set wanlink $name-B: Uplink $ul_now, Delay $lt_now" + ./lf_firemod.pl --mgr localhost --quiet on --action do_cmd --cmd \ + "set_wanlink_info $name-B $ul_now $lt_now NA NA NA NA" &>/dev/null + + echo "B-LOOP Waiting for $pause seconds." + sleep $pause + done +} + + +get_values + +stop_at="$(( ${#downlink[@]} - 1 ))" + +for ((i=0; i < $stop_at; ++i)); do + + #echo "set wanlink $name: ${downlink[i]} ${uplink[i]} ${delay[i]}" + echo "set wanlink $name-A: Downlink ${downlink[i]}, Delay ${delay[i]}" + ./lf_firemod.pl --mgr localhost --quiet on --action do_cmd --cmd \ + "set_wanlink_info $name-A ${downlink[i]} ${delay[i]} NA NA NA NA" &>/dev/null + echo "set wanlink $name-B: Uplink ${uplink[i]}, Delay ${delay[i]}" + ./lf_firemod.pl --mgr localhost --quiet on --action do_cmd --cmd \ + "set_wanlink_info $name-B ${uplink[i]} ${delay[i]} NA NA NA NA" &>/dev/null + + echo "A-LOOP Waiting for $run_time seconds." + sleep $run_time + + [[ $i -ge $stop_at ]] && break + modify_values $i +done + diff --git a/lanforge/lanforge-scripts/antenna_stations_traffic.sh b/lanforge/lanforge-scripts/antenna_stations_traffic.sh new file mode 100755 index 000000000..ea1852d88 --- /dev/null +++ b/lanforge/lanforge-scripts/antenna_stations_traffic.sh @@ -0,0 +1,83 @@ +#!/bin/bash +. ~lanforge/lanforge.profile + +# We can set channel and number of antennas when creating one station or many stations +# Example 1 +./lf_associate_ap.pl --mgr idtest \ + --resource 7 \ + --action add \ + --radio wiphy0 \ + --antenna ALL \ + --channel 153 \ + --first_ip DHCP \ + --num_sta 5 \ + --ssid hedtest-wpa2-153 \ + --security wpa2 \ + --passphrase hedtest-wpa2-153 + + +# Example 2 +# we can also start traffic with this example +# create layer 3 connections between those stations and upstream bridge port +# action step1 make the layer 3 connections and runs them for duration +./lf_associate_ap.pl --mgr idtest \ + --resource 7 \ + --radio wiphy0 \ + --antenna ALL \ + --channel 153 \ + --first_ip DHCP \ + --num_sta 5 \ + --ssid hedtest-wpa2-153 \ + --security wpa2 \ + --passphrase hedtest-wpa2-153 \ + --duration 5min \ + --upstream br0 \ + --bps_min 1000000 \ + --cxtype tcp \ + --poll_time 5 \ + --action step1 + +# Example 3 +# if we wanted to create the connections independently, we can do it like this: +pkt_size=720 +for n in `seq 100 105`; do + station="sta$n" + endpa="tcp$n-A" + endpb="tcp$n-B" + cxname="tcp$n" + + ./lf_firemod.pl --mgr idtest --resource 7 --action create_endp --endp_type lf_tcp \ + --endp_name "$endpa" --port_name "br0" --speed 1000000 --min_pkt_sz $pkt_size + + ./lf_firemod.pl --mgr idtest --resource 7 --action create_endp --endp_type lf_tcp \ + --endp_name "$endpb" --port_name "sta$n" --speed 1000000 --min_pkt_sz $pkt_size + + ./lf_firemod.pl --mgr idtest --resource 7 --action create_cx --cx_name $cxname --cx_endps $endpa,$endpb +done + +for n in `seq 100 105`; do + cxname="tcp$n" + ./lf_firemod.pl --mgr idtest --resource 7 --action do_cmd --cli_cmd "set_cx_state all $cxname RUNNING" +done + +# poll every two seconds +for i in `seq 1 $((5 * 30))` ; do + for n in `seq 100 105`; do + ./lf_firemod.pl --mgr idtest --resource 7 --action show_endp --endp_name "tcp$n-A" --endp_vals rx_bps + ./lf_firemod.pl --mgr idtest --resource 7 --action show_endp --endp_name "tcp$n-B" --endp_vals rx_bps + done + sleep 2 +done + +for n in `seq 100 105`; do + cxname="tcp$1" + ./lf_firemod.pl --mgr idtest --resource 7 --action do_cmd --cli_cmd "set_cx_state all $cxname QUIESCE" +done + +# print out all connections: +for n in `seq 100 105`; do + ./lf_firemod.pl --mgr idtest --resource 7 --action show_cx --endp_name "tcp$n" --endp_vals rx_bps +done + + + diff --git a/lanforge/lanforge-scripts/ap_ctl.py b/lanforge/lanforge-scripts/ap_ctl.py new file mode 100755 index 000000000..c562b302c --- /dev/null +++ b/lanforge/lanforge-scripts/ap_ctl.py @@ -0,0 +1,498 @@ +#!/usr/bin/python3 +''' + +NAME: +ap_ctl.py + +PURPOSE: +Script that logs into an AP via Serial, SSH, or Telnet to read data or execute commands + +EXAMPLE: + +./ap_ctl.py --scheme "serial" "--tty "Serial port for accessing AP" --prompt "#" --dest --port --user + --passwd --action + +In a program: +ap_info= subprocess.run(["./ap_ctl.py", "--scheme", ap_dict['ap_scheme'], "--prompt", ap_dict['ap_prompt'],"--dest", ap_dict['ap_ip'], "--port", ap_dict["ap_port"], + "--user", ap_dict['ap_user'], "--passwd", ap_dict['ap_pw'],"--action", "powercfg"],stdout=subprocess.PIPE) + +NOTES: + +LANforge 192.168.100.178 +Controller at 192.168.100.112 admin/Cisco123 +Controller is 192.1.0.10 +AP is on serial port /dev/ttyUSB1 or /dev/ttyUSB2 9600 8 n 1 + +make sure pexpect is installed: +$ sudo yum install python3-pexpect + +You might need to install pexpect-serial using pip: +$ pip3 install pexpect-serial + +$ sudo pip3 install pexpect-serial + +./ap_ctl.py +''' + + +import sys +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +import logging +import time +from time import sleep +import argparse +import pexpect +import serial +from pexpect_serial import SerialSpawn + +# pip install pexpect-serial (on Ubuntu) +# sudo pip install pexpect-serial (on Ubuntu for everyone) + +default_host = "localhost" +default_ports = { + "serial": None, + "ssh": 22, + "telnet": 23 +} +NL = "\n" +CR = "\r\n" +Q = '"' +A = "'" +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' +band = "a" +logfile = "stdout" + +# regex101.com , +# this will be in the tx_power script +# ^\s+1\s+6\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+) + +def usage(): + print("$0 used connect to Cisco AP:") + print("-a|--ap: AP to act upon") + print("-d|--dest: destination host") + print("-o|--port: destination port") + print("-u|--user: AP login name") + print("-p|--pass: AP password") + print("-s|--scheme (serial|telnet|ssh): connect to controller via serial, ssh or telnet") + print("--tty Serial port for accessing AP") + print("-l|--log file: log messages here") + print("-b|--baud: serial baud rate") + print("-z|--action: action") + print("-h|--help") + +# see https://stackoverflow.com/a/13306095/11014343 +class FileAdapter(object): + def __init__(self, logger): + self.logger = logger + def write(self, data): + # NOTE: data can be a partial line, multiple lines + data = data.strip() # ignore leading/trailing whitespace + if data: # non-blank + self.logger.info(data) + def flush(self): + pass # leave it to logging to flush properly + +# Test command if lanforge connected ttyUSB0 +# sudo ./ap_ctl.py -a lanforge -d 0 -o 0 -u "lanforge" -p "lanforge" -s "serial" -t "/dev/ttyUSB0" +# sample for lanforge 192.168.100.178 +# sudo ./ap_ctl.py -a APA53.0E7B.EF9C -d 0 -o 0 -u "admin" -p "Admin123" -s "serial" -t "/dev/ttyUSB2" -z "show_log" +def main(): + + global logfile + + AP_ESCAPE = "Escape character is '^]'." + AP_USERNAME = "Username:" + AP_PASSWORD = "Password:" + AP_EN = "en" + AP_MORE = "--More--" + AP_EXIT = "exit" + LF_PROMPT = "$" + CR = "\r\n" + + + parser = argparse.ArgumentParser(description="Cisco AP Control Script") + parser.add_argument("-a", "--prompt", type=str, help="ap prompt") + parser.add_argument("-d", "--dest", type=str, help="address of the AP 172.19.27.55") + parser.add_argument("-o", "--port", type=int, help="control port on the AP, 2008") + parser.add_argument("-u", "--user", type=str, help="credential login/username, admin") + parser.add_argument("-p", "--passwd", type=str, help="credential password Wnbulab@123") + parser.add_argument("-s", "--scheme", type=str, choices=["serial", "ssh", "telnet"], help="Connect via serial, ssh or telnet") + parser.add_argument("-t", "--tty", type=str, help="tty serial device for connecting to AP") + parser.add_argument("-l", "--log", type=str, help="logfile for messages, stdout means output to console",default="stdout") + parser.add_argument("-z", "--action", type=str, help="action, current action is powercfg") + parser.add_argument("-b", "--baud", type=str, help="action, baud rate lanforge: 115200 cisco: 9600") + + args = None + try: + args = parser.parse_args() + host = args.dest + scheme = args.scheme + port = (default_ports[scheme], args.port)[args.port != None] + user = args.user + if (args.log != None): + logfile = args.log + except Exception as e: + logging.exception(e) + usage() + exit(2) + console_handler = logging.StreamHandler() + formatter = logging.Formatter(FORMAT) + logg = logging.getLogger(__name__) + logg.setLevel(logging.DEBUG) + file_handler = None + if (logfile is not None): + if (logfile != "stdout"): + file_handler = logging.FileHandler(logfile, "w") + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + logg.addHandler(file_handler) + logging.basicConfig(format=FORMAT, handlers=[file_handler]) + else: + # stdout logging + logging.basicConfig(format=FORMAT, handlers=[console_handler]) + egg = None # think "eggpect" + ser = None + try: + if (scheme == "serial"): + #eggspect = pexpect.fdpexpect.fdspan(telcon, logfile=sys.stdout.buffer) + ser = serial.Serial(args.tty, int(args.baud), timeout=5) + print("Created serial connection on %s, open: %s"%(args.tty, ser.is_open)) + egg = SerialSpawn(ser) + egg.logfile = FileAdapter(logg) + time.sleep(1) + egg.sendline(CR) + time.sleep(1) + + elif (scheme == "ssh"): + if (port is None): + port = 22 + cmd = "ssh -p%d %s@%s"%(port, user, host) + logg.info("Spawn: "+cmd+NL) + egg = pexpect.spawn(cmd) + #egg.logfile_read = sys.stdout.buffer + egg.logfile = FileAdapter(logg) + elif (scheme == "telnet"): + if (port is None): + port = 23 + cmd = "telnet {} {}".format(host, port) + logg.info("Spawn: "+cmd+NL) + egg = pexpect.spawn(cmd) + egg.logfile = FileAdapter(logg) + # Will login below as needed. + else: + usage() + exit(1) + except Exception as e: + logging.exception(e) + + AP_PROMPT = "{}>".format(args.prompt) + AP_HASH = "{}#".format(args.prompt) + time.sleep(0.1) + logged_in = False + loop_count = 0 + while (loop_count <= 8 and logged_in == False): + loop_count += 1 + i = egg.expect_exact([AP_ESCAPE,AP_PROMPT,AP_HASH,AP_USERNAME,AP_PASSWORD,AP_MORE,LF_PROMPT,pexpect.TIMEOUT],timeout=5) + if i == 0: + logg.info("Expect: {} i: {} before: {} after: {}".format(AP_ESCAPE,i,egg.before,egg.after)) + egg.sendline(CR) # Needed after Escape or should just do timeout and then a CR? + sleep(1) + if i == 1: + logg.info("Expect: {} i: {} before: {} after: {}".format(AP_PROMPT,i,egg.before,egg.after)) + egg.sendline(AP_EN) + sleep(1) + j = egg.expect_exact([AP_PASSWORD,pexpect.TIMEOUT],timeout=5) + if j == 0: + logg.info("Expect: {} i: {} j: {} before: {} after: {}".format(AP_PASSWORD,i,j,egg.before,egg.after)) + egg.sendline(args.passwd) + sleep(1) + k = egg.expect_exact([AP_HASH,pexpect.TIMEOUT],timeout=5) + if k == 0: + logg.info("Expect: {} i: {} j: {} k: {} before: {} after: {}".format(AP_PASSWORD,i,j,k,egg.before,egg.after)) + logged_in = True + if k == 1: + logg.info("Expect: {} i: {} j: {} k: {} before: {} after: {}".format("Timeout",i,j,k,egg.before,egg.after)) + if j == 1: + logg.info("Expect: {} i: {} j: {} before: {} after: {}".format("Timeout",i,j,egg.before,egg.after)) + + if i == 2: + logg.info("Expect: {} i: {} before: {} after: {}".format(AP_HASH,i,egg.before,egg.after)) + logged_in = True + sleep(1) + if i == 3: + logg.info("Expect: {} i: {} before: {} after: {}".format(AP_USERNAME,i,egg.before,egg.after)) + egg.sendline(args.user) + sleep(1) + if i == 4: + logg.info("Expect: {} i: {} before: {} after: {}".format(AP_PASSWORD,i,egg.before,egg.after)) + egg.sendline(args.passwd) + sleep(1) + if i == 5: + logg.info("Expect: {} i: {} before: {} after: {}".format(AP_MORE,i,egg.before,egg.after)) + if (scheme == "serial"): + egg.sendline("r") + else: + egg.sendcontrol('c') + sleep(1) + # for Testing serial connection using Lanforge + if i == 6: + logg.info("Expect: {} i: {} before: {} after: {}".format(LF_PROMPT,i,egg.before.decode('utf-8', 'ignore'),egg.after.decode('utf-8', 'ignore'))) + if (loop_count < 3): + egg.send("ls -lrt") + sleep(1) + if (loop_count > 4): + logged_in = True # basically a test mode using lanforge serial + if i == 7: + logg.info("Expect: {} i: {} before: {} after: {}".format("Timeout",i,egg.before,egg.after)) + egg.sendline(CR) + sleep(1) + + + if (args.action == "powercfg"): + logg.info("execute: show controllers dot11Radio 1 powercfg | g T1") + egg.sendline('show controllers dot11Radio 1 powercfg | g T1') + egg.expect([pexpect.TIMEOUT], timeout=3) # do not delete this for it allows for subprocess to see output + print(egg.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + i = egg.expect_exact([AP_MORE,pexpect.TIMEOUT],timeout=5) + if i == 0: + egg.sendcontrol('c') + if i == 1: + logg.info("send cntl c anyway") + egg.sendcontrol('c') + + elif (args.action == "clear_log"): + logg.info("execute: clear log") + egg.sendline('clear log') + sleep(0.4) + egg.sendline('show log') + egg.expect([pexpect.TIMEOUT], timeout=2) # do not delete this for it allows for subprocess to see output + print(egg.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + # allow for normal logout below + + elif (args.action == "show_log"): + logg.info("execute: show log") + egg.sendline('show log') + sleep(0.4) + egg.expect([pexpect.TIMEOUT], timeout=2) # do not delete this for it allows for subprocess to see output + print(egg.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + i = egg.expect_exact([AP_MORE,pexpect.TIMEOUT],timeout=4) + if i == 0: + egg.sendline('r') + egg.expect([pexpect.TIMEOUT], timeout=4) # do not delete this for it allows for subprocess to see output + print(egg.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + if i == 1: + print(egg.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + # allow for normal logout below + # show log | g DOT11_DRV + + # CAC_EXPIRY_EVT: CAC finished on DFS channel 52 + elif (args.action == "cac_expiry_evt"): + logg.info("execute: show log | g CAC_EXPIRY_EVT") + egg.sendline('show log | g CAC_EXPIRY_EVT') + sleep(0.4) + egg.expect([pexpect.TIMEOUT], timeout=2) # do not delete this for it allows for subprocess to see output + print(egg.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + i = egg.expect_exact([AP_MORE,pexpect.TIMEOUT],timeout=4) + if i == 0: + egg.sendline('r') + egg.expect([pexpect.TIMEOUT], timeout=4) # do not delete this for it allows for subprocess to see output + print(egg.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + if i == 1: + print(egg.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + + elif (args.action == "ds_data_5ghz"): + logg.info("execute: wl -i wl1 bs_data") + egg.sendline('wl -i wl1 bs_data') + egg.expect([pexpect.TIMEOUT], timeout=4) # do not detete this for it allow for subprocess to read + print(egg.before.decode('utf-8','ignore')) # do not delete this for it allows for subprocess to see output + + + elif (args.action == "ds_data_24ghz"): + logg.info("execute: wl -i wl0 bs_data") + egg.sendline('wl -i wl1 bs_data') + egg.expect([pexpect.TIMEOUT], timeout=4) # do not detete this for it allow for subprocess to read + print(egg.before.decode('utf-8','ignore')) # do not delete this for it allows for subprocess to see output + + + else: # no other command at this time so send the same power command + #logg.info("no action so execute: show controllers dot11Radio 1 powercfg | g T1") + logg.info("no action") + + i = egg.expect_exact([AP_PROMPT,AP_HASH,pexpect.TIMEOUT],timeout=1) + if i == 0: + logg.info("received {} we are done send exit".format(AP_PROMPT)) + egg.sendline(AP_EXIT) + if i == 1: + logg.info("received {} send exit".format(AP_HASH)) + egg.sendline(AP_EXIT) + if i == 2: + logg.info("timed out waiting for {} or {}".format(AP_PROMPT,AP_HASH)) + + # ctlr.execute(cn_cmd) + +''' NOTES for AP DFS + +############################# + +1. Do "show AP summary" on the controller to see the list of AP's connected. +2. Now, check the channel configured on the AP using the commend "show ap channel " + +3. Check for the current channel and Channel width for Slot id 1. See the output of this command. + +4. Before making any changes, please connect at least 1 client to this AP in 5GHz radio. +Keep running the ping traffic to the default gateway of the AP. + +4. Now, configure dfs channel for this AP with 20MHz as channel width. + +6. After CAC Expiry, Client should connect back - Verify the pings are passing through or not. +Note time: +"show logging" in the AP will show the CAC timer details. You can grep for "DFS CAC timer enabled time 60" and "changed to DFS channel 52, running CAC for 60 seconds. +Wait for 60 sec and check for this log "CAC_EXPIRY_EVT: CAC finished on DFS channel 52" + +[*07/07/2020 23:37:48.1460] changed to DFS channel 52, running CAC for 60 seconds. +[*07/07/2020 23:38:48.7240] CAC_EXPIRY_EVT: CAC finished on DFS channel 52 + +"make a note of the time and check the CAC timer expired in 60-61 seconds. + +7. Now, trigger the radar on Channel 52. AP should move to another channel. + Also, When the radar is triggered, capture the CSA frames and verify the CSA count is set to 10 or not. + + 8. Now, verify the black-list time of the channel for this AP. : show ap auto-rf 802.11a + In the controller, give the command "show ap auto-rf 802.11a " under Radar information you should see the "Detected Channels and Blacklist Times" . +Black list time will be 1800 seconds which is 30 minutes. + +Radar Information + DFS stats on serving radio................... 0 + DFS stats on RHL radio....................... 0 + DFS stats triggered.......................... 0 + Cumulative stats on serving radio............ 0 + Cumulative stats on RHL radio................ 0 + Cumulative stats triggered................... 0 + Detected Channels + Channel 100................................ 5 seconds ago + Blacklist Times + Channel 100................................ 1795 seconds remaining + +(Cisco Controller) >show ap channel APA453.0E7B.CF9C + + Slot Id ..................................... 0 + 802.11b/g Current Channel ..................... 11* + Allowed Channel List........................... 1,2,3,4,5,6,7,8,9,10,11 + + Slot Id ..................................... 1 + 802.11a Current Channel ....................... (36,40) 40MHz / Cap 160MHz + Allowed Channel List........................... 36,40,44,48,52,56,60,64,100, + ........................... 104,108,112,116,120,124,128, + ........................... 132,136,140,144,149,153,157, + ........................... 161,165 + + + + +########################### + +Password: [*02/09/2021 14:30:04.2290] Radio [1] Admininstrative state ENABLED change to DISABLED +[*02/09/2021 14:30:04.2300] DOT11_DRV[1]: Stop Radio1 +[*02/09/2021 14:30:04.2520] DOT11_DRV[1]: DFS CAC timer enabled time 60 +[*02/09/2021 14:30:04.2740] DOT11_DRV[1]: DFS CAC timer enabled time 60 +[*02/09/2021 14:30:04.2740] Stopped Radio 1 +[*02/09/2021 14:30:36.2810] Radio [1] Admininstrative state DISABLED change to ENABLED +[*02/09/2021 14:30:36.3160] DOT11_DRV[1]: set_channel Channel set to 52/20 <<<<<< ???? +[*02/09/2021 14:30:36.3390] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 14:30:36.4420] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 14:30:36.5440] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 14:30:36.6490] DOT11_DRV[1]: DFS CAC timer enabled time 60 <<<<<< ???? +[*02/09/2021 14:30:37.2100] wl0: wlc_iovar_ext: vap_amsdu_rx_max: BCME -23 +[*02/09/2021 14:30:37.2100] wl: Unsupported +[*02/09/2021 14:30:37.2100] ERROR: return from vap_amsdu_rx_max was -45 +[*02/09/2021 14:30:37.4100] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 14:30:37.5040] DOT11_CFG[1]: Starting radio 1 +[*02/09/2021 14:30:37.5050] DOT11_DRV[1]: Start Radio1 <<<<<<<<< +[*02/09/2021 14:30:37.5120] DOT11_DRV[1]: set_channel Channel set to 52/20 +[*02/09/2021 14:30:37.5340] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 14:30:37.6360] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 14:30:37.7370] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 14:30:37.8410] DOT11_DRV[1]: DFS CAC timer enabled time 60 <<<<<<< +[*02/09/2021 14:30:37.8650] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 14:30:37.9800] changed to DFS channel 52, running CAC for 60 seconds. <<<<< Note use this one +[*02/09/2021 14:30:38.0020] Started Radio 1 <<<<< After start radio +[*02/09/2021 14:31:07.4650] wl0: wlc_iovar_ext: olpc_cal_force: BCME -16 +[*02/09/2021 14:31:38.4210] CAC_EXPIRY_EVT: CAC finished on DFS channel 52 <<<<<< Start with this very unique CAC finished +[*02/09/2021 14:31:48.2850] chatter: client_ip_table :: ClientIPTable no client entry found, dropping packet 04:F0:21:F + +# note lf_hackrf.py begins transmitting immediately... see if that is what is to happen? + +[*02/09/2021 15:20:53.7470] wcp/dfs :: RadarDetection: radar detected <<<<< Radar detected +[*02/09/2021 15:20:53.7470] wcp/dfs :: RadarDetection: sending packet out to capwapd, slotId=1, msgLen=386, chanCnt=1 2 +[*02/09/2021 15:20:53.7720] DOT11_DRV[1]: DFS CAC timer disabled time 0 +[*02/09/2021 15:20:53.7780] Enabling Channel and channel width Switch Announcement on current channel +[*02/09/2021 15:20:53.7870] DOT11_DRV[1]: set_dfs Channel set to 36/20, CSA count 6 <<<<<<< Channel Set +[*02/09/2021 15:20:53.8530] DOT11_DRV[1]: DFS CAC timer enabled time 60 + + +Trying another station +*02/09/2021 15:25:32.6130] Radio [1] Admininstrative state ENABLED change to DISABLED +[*02/09/2021 15:25:32.6450] DOT11_DRV[1]: Stop Radio1 +[*02/09/2021 15:25:32.6590] Stopped Radio 1 +[*02/09/2021 15:25:52.1700] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:04.6640] Radio [1] Admininstrative state DISABLED change to ENABLED +[*02/09/2021 15:26:04.6850] DOT11_DRV[1]: set_channel Channel set to 36/20 +[*02/09/2021 15:26:04.7070] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:04.8090] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:04.9090] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:05.5620] wl0: wlc_iovar_ext: vap_amsdu_rx_max: BCME -23 +[*02/09/2021 15:26:05.5620] wl: Unsupported +[*02/09/2021 15:26:05.5620] ERROR: return from vap_amsdu_rx_max was -45 +[*02/09/2021 15:26:05.7600] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:05.8530] DOT11_CFG[1]: Starting radio 1 +[*02/09/2021 15:26:05.8540] DOT11_DRV[1]: Start Radio1 +[*02/09/2021 15:26:05.8610] DOT11_DRV[1]: set_channel Channel set to 36/20 +[*02/09/2021 15:26:05.8830] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:05.9890] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:06.0900] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:06.2080] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:26:06.5350] Started Radio 1 +[*02/09/2021 15:26:15.9750] chatter: client_ip_table :: ClientIPTable no client entry found, dropping packet 04:F0:21: + + + + +Username: [*02/09/2021 15:33:49.8680] Radio [1] Admininstrative state ENABLED change to DISABLED +[*02/09/2021 15:33:49.9010] DOT11_DRV[1]: Stop Radio1 +[*02/09/2021 15:33:49.9160] Stopped Radio 1 +[*02/09/2021 15:34:14.4150] DOT11_DRV[1]: set_channel Channel set to 56/20 +[*02/09/2021 15:34:14.4370] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:14.5390] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:14.6400] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:14.7450] DOT11_DRV[1]: DFS CAC timer enabled time 60 +[*02/09/2021 15:34:21.9160] Radio [1] Admininstrative state DISABLED change to ENABLED +[*02/09/2021 15:34:21.9370] DOT11_DRV[1]: set_channel Channel set to 56/20 +[*02/09/2021 15:34:21.9590] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:22.0610] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:22.1610] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:22.2650] DOT11_DRV[1]: DFS CAC timer enabled time 60 +[*02/09/2021 15:34:22.8270] wl0: wlc_iovar_ext: vap_amsdu_rx_max: BCME -23 +[*02/09/2021 15:34:22.8270] wl: Unsupported +[*02/09/2021 15:34:22.8270] ERROR: return from vap_amsdu_rx_max was -45 +[*02/09/2021 15:34:23.0280] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:23.1210] DOT11_CFG[1]: Starting radio 1 +[*02/09/2021 15:34:23.1210] DOT11_DRV[1]: Start Radio1 +[*02/09/2021 15:34:23.1280] DOT11_DRV[1]: set_channel Channel set to 56/20 +[*02/09/2021 15:34:23.1510] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:23.2520] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:23.3520] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:23.4560] DOT11_DRV[1]: DFS CAC timer enabled time 60 +[*02/09/2021 15:34:23.4800] wlc_ucode_download: wl0: Loading 129 MU ucode +[*02/09/2021 15:34:23.5960] changed to DFS channel 56, running CAC for 60 seconds. +[*02/09/2021 15:34:23.6180] Started Radio 1 + +''' +if __name__ == '__main__': + main() + +#### +#### +#### diff --git a/lanforge/lanforge-scripts/arp-flood.sh b/lanforge/lanforge-scripts/arp-flood.sh new file mode 100755 index 000000000..d0f013205 --- /dev/null +++ b/lanforge/lanforge-scripts/arp-flood.sh @@ -0,0 +1,138 @@ +#!/bin/bash +echo "----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- " +echo " This script will issue local arp flushes. " +echo " Those commands cannot be issued against a remote lanforge." +echo "----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- " +sleep 2 +mgr=localhost +port=4001 +station=wlan0 +upstream=eth1 +num_mvlans=200 +cxlist=() +ports=($station) +saved_gc_stale_time=`cat /proc/sys/net/ipv4/neigh/default/gc_stale_time` +saved_base_reachable_time_ms=`cat /proc/sys/net/ipv4/neigh/default/base_reachable_time_ms` +trap do_sigint ABRT +trap do_sigint INT +trap do_sigint KILL +trap do_sigint PIPE +trap do_sigint QUIT +trap do_sigint SEGV +trap do_sigint TERM + +function do_sigint() { + echo -en "\nDefaulting arp timings " + for ((i=0; i < num_mvlans; i++)); do + mvlan="${upstream}#${i}" + echo $saved_gc_stale_time > /proc/sys/net/ipv4/neigh/$mvlan/gc_stale_time + echo $saved_base_reachable_time_ms > /proc/sys/net/ipv4/neigh/$mvlan/base_reachable_time_ms + echo -n "." + done + echo "" + echo -en "\nStopping connections: " + fire_cmd stop_group udp-arp + #for cx in "${cxlist[@]}"; do + # echo -n ":" + # fire_cmd set_cx_state default_tm $cx STOPPED >/dev/null + #done + echo "" + fire_cmd clear_group udp-arp + echo -n "Removing connections: " + for cx in "${cxlist[@]}"; do + echo -n "x" + fire_cmd rm_cx default_tm $cx STOPPED >/dev/null + done + echo "" + echo -n "Removing endpoints: " + for cx in "${cxlist[@]}"; do + echo -n "-" + fire_cmd rm_endp ${cx}-A STOPPED >/dev/null + fire_cmd rm_endp ${cx}-B STOPPED >/dev/null + done + echo "" + set +x + exit 0 +} + +function fire_cmd() { + ./lf_firemod.pl --mgr $mgr --mgr_port $port --quiet yes \ + --action do_cmd --cmd "$*" &>/dev/null +} +function fire_newcx() { + local cxname=$1; shift + local sta=$1; shift + local eth=$1; shift + ./lf_firemod.pl --mgr $mgr --mgr_port $port --action create_cx --quiet yes \ + --cx_name $cxname --use_ports $sta,$eth --use_speeds 11500,11500 --endp_type udp \ + &>/dev/null +} + +# create new set of vlans, this will also recreate them using random mac addresses + +#num_vlans=$(( $num_mvlans - 1)) +set -e +if (( num_mvlans < 1 )); then + echo "Too few vlans" + exit 1 +fi + +echo -n "Removing old $num_mvlans macvlans: " +for ((i=0; i < num_mvlans; i++)); do + mvlan="${upstream}#${i}" + fire_cmd rm_vlan 1 1 $mvlan + echo -n "-" + sleep 0.03 +done +sleep 1 +echo " Checking for $num_mvlans old vlans:" +while (./lf_portmod.pl --mgr localhost --list_port_names | grep -q "$upstream#"); do + sleep 1 + echo -n "," +done + +echo -n "Adding $num_mvlans new macvlans: " +for ((i=0; i < num_mvlans; i++)); do + fire_cmd add_mvlan 1 1 $upstream 'xx:xx:xx:*:*:xx' $i + echo -n ":" + sleep 0.05 +done +# "84033538" +for ((i=0; i < num_mvlans; i++)); do + mvlan="${upstream}#${i}" + fire_cmd set_port 1 1 "$mvlan" NA NA NA NA 2147483648 NA NA NA NA 75513858 + echo -n "=" + sleep 0.05 + echo 1 > /proc/sys/net/ipv4/neigh/$mvlan/gc_stale_time + echo 1 > /proc/sys/net/ipv4/neigh/$mvlan/base_reachable_time +done +echo "" +fire_cmd add_group udp-arp +sleep 2 +echo -n "Creating $num_mvlans connections: " +for ((i=0; i < num_mvlans; i++)); do + mvlan="${upstream}#${i}" + fire_newcx "udp-$i" $station $mvlan + echo -n "+" + cxlist+=("udp-$i") + ports+=($mvlan) + fire_cmd add_tgcx udp-arp "udp-$i" +done + +sleep 2 +#for ((i=0; i < num_mvlans; i++)); do + #echo -n "=" + #fire_cmd set_cx_state default_tm "udp-$i" RUNNING +#done +fire_cmd start_group udp-arp +sleep 2 +echo "" +echo -n "Starting arp flushing: " +while : ; do + for p in "${ports[@]}"; do + ip neigh flush dev $p + done + echo -n "!" + sleep 0.2 +done +# diff --git a/lanforge/lanforge-scripts/associate_loop.sh b/lanforge/lanforge-scripts/associate_loop.sh new file mode 100755 index 000000000..9d4392bff --- /dev/null +++ b/lanforge/lanforge-scripts/associate_loop.sh @@ -0,0 +1,158 @@ +#!/bin/bash +##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### +## ## +## Use this script to associate stations between SSIDs A and B ## +## ## +## Install this script in /home/lanforge ## +## Usage: ./associate_loop -m localhost -r 1 -a SSIDA -b SSIDB -n 10 -i 5 ## +## -w wiphy0 -s sta1,sta2,sta3,sta4,sta5,sta6,sta7,sta8,sta9,sta10 ## +## ## +## ## +## ## +## ## +##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### +Q='"' +A="'" +#set -e +#set -x +usage="$0 -m localhost -r 1 -w wiphy0 -s sta1,sta2... -a SSIDA -b SSIDB -n -i + -m: manager ip address + -r: resourse id + -w: radio name for stations + -s: station list, comma separated (no spaces) + -a: first ssid + -b: second ssid + -n: naptime in seconds + -i: iteration to loop + +Associate one station (sta1) for 1 second, 10 iterations: + $0 -m localhost -r 1 -w wiphy0 -s sta1,wlan1 -a testap1 -b testap2 -n 1 -i 10 + +Associate ten stations (sta105..sta109) for 5 seconds, indefinitely: + stations=\`seq -f 'sta%g' -s, 105 109\` + $0 -m 192.168.101.1 -r 2 -w wiphy1 -s \$stations -a testap1 -b testab2 -n 5 -i 0 + +Hit control-c to stop. +" +modscript="" +if [ -f "lf_firemod" ]; then + modscript="./lf_firemod.pl" +elif [ -f "/home/lanforge/scripts/lf_firemod.pl" ]; then + modscript="/home/lanforge/scripts/lf_firemod.pl" +fi +cd /home/lanforge/scripts + +[ -z "$modscript" ] && { + echo "script [$modscript] not present, please use this script from /home/lanforge or /home/lanforge/scripts" + exit 1 +} + +infinite=0 +while getopts ":a:b:i:m:n:r:s:w:" opt ; do + case $opt in + a) SSIDA="$OPTARG" ;; + b) SSIDB="$OPTARG" ;; + i) iterations="$OPTARG" ;; + m) manager="$OPTARG" ;; + n) naptime="$OPTARG" ;; + r) resource="$OPTARG" ;; + s) stations="$OPTARG" ;; + w) wiphy="$OPTARG" ;; + esac +done +[ -z "$stations" ] && { + echo "-s: stations, requires {begin,...end} for stations;" + echo "$usage" + exit 1 +} + +sta_start=0 +sta_end=0; +IFS="," sta_hunks=($stations); +unset IFS +#if [ ${#sta_hunks[@]} -gt 1 ] ; then +# sta_start=${sta_hunks[0]} +# sta_end=${sta_hunks[1]} +#else +# sta_start=${sta_hunks[0]} +# sta_end=${sta_hunks[0]} +#fi + +[ -z "$naptime" ] && { + echo "-n: naptime required: seconds between changing ssids" + echo "$usage" + exit 1 +} + +[ -z "$iterations" ] && { + echo "-i: iterations to switch ssids" + echo "$usage" + exit 1 +} + +[ $iterations -lt 0 ] && { + echo "-i: positive number of iterations only, please" + exit 1; +} + +[ $iterations -eq 0 ] && { + echo "Infinite iterations selected." + infinite=1; +} + +[ -z "$SSIDB" ] && { + echo "-b: SSID B required" + echo "$usage" + exit 1 +} + +[ -z "$SSIDA" ] && { + echo "-a: SSID A required" + echo "$usage" + exit 1 +} + +[ -z "$resource" ] && { + echo "-r: resource number for radio owning the station to modify" + echo "$usage" + exit 1 +} + +[ -z "$wiphy" ] && { + echo "-w: wiphy radio owning the station" + echo "$usage" + exit 1; +} + +[ -z "$manager" ] && { + echo "-m: ip address of LANforge manager " + echo "$usage" + exit 1; +} +use_ssid=0 # 0 := a, 1 := b +while [ $infinite == 1 -o $iterations -ge 0 ] ; do + for sta in "${sta_hunks[@]}"; do + if [ $use_ssid == 0 ]; then + newssid=$SSIDA + else + newssid=$SSIDB + fi + [ -z "$wiphy" ] && { + echo "radio unconfigured, error." + exit 1 + } + clicmd="add_sta 1 $resource $wiphy $sta NA $newssid" + $modscript --quiet yes --mgr $manager --resource $resource --action do_cmd --cmd "$clicmd" + sleep 0.05 + done + + if [ $use_ssid = 1 ]; then + use_ssid=0; + else + use_ssid=1; + fi + iterations=$(($iterations - 1)) + sleep $naptime +done + +#eof diff --git a/lanforge/lanforge-scripts/attenuator_series.pl b/lanforge/lanforge-scripts/attenuator_series.pl new file mode 100755 index 000000000..b47dbbe85 --- /dev/null +++ b/lanforge/lanforge-scripts/attenuator_series.pl @@ -0,0 +1,357 @@ +#!/usr/bin/perl +## +## Reads a CSV of attenuator settings and plays them back +## Remember that 300 is deci-dB; eg 300: sets a channel to 30.0 dB +## +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{__DIE__} = sub{Carp::confess(@_)}; +use Getopt::Long; +use lib '/home/lanforge/scripts'; +use Net::Telnet; +use Time::HiRes qw(usleep); +use LANforge::Utils; +use LANforge::csv qw(); +$| = 1; + +our $usage = qq($0: replay a csv file of attenuator values + --mgr|m LANforge manager host + --file|f CSV file + --delay|d Override of %delay variable, milliseconds between applying rows + --loop|l Repeat indefinitely + --channel|c Override of channels variable, eg: 1.2.3.1,2.3.4.3 + --minimum|min|i Set minimum attenuation value (not lower than zero) + --maximum|max|x Set maximum attenuation value (not higher than 955) + --dry_run|dryrun|dry|n Do not apply attenuation, just parse file, ignore nap times + +Example that works on localhost manager: + $0 --file values.csv + +Example that overrides delay to 1600, overrides channels and runs once: + $0 --mgr 192.168.101.1 --file values.csv --delay 1600 --channel 1.1.3.1,1.1.3.2,1.1.3.3 + +Example that overrides delay to 600ms, loops forever, and overrides min and max attenuation + $0 -m 192.168.101.1 -f values.csv -d 600 -l -min 10 -max 900 + +File Format: + # < comment lines are ignored + # 60 milliseconds between rows + delay,60 + # Directives: DELAY,delay and naptime are equivalent + # Sets minimum and maximum attenuation for all channels + min,10 + max,900 + # Directives: MINIMUM,MAXIMUM,MIN,MAX,minimum,min,maximum and max are allowed + + # The next line defines column B as attenuator channel 1.1.13.1 + # and column C as attenuator channel 2.1.25.1. Remember that + # attennuator channels are values (shelf).(resource).(serialno).(channel) + # and channels are presently values {1, 2, 3, 4}. + channels,1.1.13.1,2.1.25.1 + # Directives: CHANNELS,channels are equivalent + + # Attenuation values are in deci-dBm, resolution of 5ddB: + # The next line sets 1.1.13.1 to 36.5dB, 2.1.25.1 to 30.0dB: + attenuate,365,300 + # Directives: ATTENUATE,attenuate, "", and _ are equivalent. + + # The next line leaves 1.1.13.1 alone, sets 2.1.25.1 to 31.0dB, + # _ is an abbreviation for attenuate + _,NA,+10 + # The next line leaves 1.1.13.1 alone, sets 2.1.25.1 to 30.5dB, + # Blank first column is an abbreviation for attenuate + ,NA,-5 + + # Only some basic CSV formulas are interpretable, and only operate + # on the previous values of the attenuator; the next line sets + # sets 1.1.13.1 to 36.0dB, sets 2.1.25.1 to 31.0dB + ,=B6-5,=C6+5 + + # does nothing for a period + _,_,NA,, + + # does nothing for 35ms + sleep,35 + # Directives: SLEEP,sleep, and nap are equivalent +); + + +our $csvfile = undef; +our $delay = -1; +our $delay_override = -1; +our $do_loop = 0; +our @channels = (); # in order list of channels +our %last_atten = (); # a map of last-known values +our $channel_override= undef; +our $quiet = "yes"; +our $line = 0; # line number +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; +our $dryrun = 0; +our $min_atten = 0; +our $max_atten = 995; + +GetOptions ( + 'manager|mgr|m=s' => \$::lfmgr_host, + 'mgr_port|port|p=i' => \$::lfmgr_port, + 'file|f=s' => \$::csvfile, + 'delay|d=i' => \$::delay_override, + 'loop|l' => \$::do_loop, + 'channels|c' => \$::channel_override, + 'quiet|q=s' => \$::quiet, + 'dry_run|dry|n' => \$::dryrun, + 'minimum|min|mn|i=i' => \$::min_atten, + 'maximum|max|mx|x=i' => \$::max_atten, +) || die("$::usage"); + +die("Please specify a manager address;\n$::usage") + if (!defined $::lfmgr_host || "$::lfmgr_host" eq ""); + +die("Please specify a csv file;\n$::usage") + if (!defined $::csvfile || "$::csvfile" eq ""); + +die("Unable to find csv file: $::csvfile") + unless(-f $::csvfile ); + +our $cfile=new LANforge::csv(); +$::cfile->readFile($::csvfile); + +if ($::cfile->numRows < 1) { + die( "empty file, nothing to do"); +} + +if ($::quiet eq "1" ) { + $::quiet = "yes"; +} +elsif ($::quiet eq "0" ) { + $::quiet = "no"; +} + +if (defined $::channel_override && "$::channel_override" != "") { + for my $c ( split(/,/, $::channel_override)) { + push(@::channels, $c); + $::last_atten{$c} = 0; + } +} + +die("Minimum attenuation must be between [0-954]") + if ($::min_atten > 994 || $::min_atten < 0); +die("Maximum attenuation must be between [1-995]") + if ($::max_atten > 995 || $::max_atten < 1); +die("Minimum attenuation must be less than maximum attenuation") + if ($::max_atten <= $::min_atten); + +sub lastAtten { + my $arg = shift; + die ("lastAtten: called without argument") + if (! defined $arg || "$arg" eq ""); + if ($arg =~ /^\d+$/) { + if (!defined($::channels[$arg])) { + warn "Channels: ".join(', ', @::channels); + die ("no channel recorded at position $arg"); + } + die ("no channel [$::channels[$arg]]") + if (!defined $::last_atten{$::channels[$arg]}); + + return $::last_atten{$::channels[$arg]}; + } + elsif ($arg =~ /^\d+\.\d+\.\d+\.\d+$/) { + die ("no channel [$::channels[$arg]]") + if (!defined $::last_atten{$::channels[$arg]}); + + return $::last_atten{$arg}; + } + die ("lastAtten: What is channel $arg?"); +} + +sub attenuate { + my $channel = shift; + my $value = shift; + + die("attenuate: no line number") + if (!defined $::line || "$::line" eq ""); + die("attenuate: $::line: no channel") + if (!defined $channel || "$channel" eq ""); + + return if (!defined $value || "$value" eq ""); + return if (lc($value) =~ /^(na|_)$/); + return if (lc($value) =~ /^\s*[!;\#]/); + + my ($shelf, $resource, $serno, $chan) = split(/\./, $channel); + #print "shelf:$shelf, r:$resource, ser:$serno, ch:$chan\n"; + die( "[$::line] attenuate: shelf misconfigured:[$channel][$value]") + if ($shelf != 1); + + die( "[$::line] attenuate: resource misconfigured:[$channel][$value]") + if ($resource < 1); + + die( "[$::line] attenuate: serial number misconfigured:[$channel][$value]") + if ($serno < 1); + + die( "[$::line] attenuate: channel misconfigured:[$channel][$value]") + if ($chan < 0 || $chan > 4); + + my $prev_value = $::last_atten{$channel}; + if ($value =~ /^[-+]/) { + die("[$::line] attenuate: no previous value set for $channel") + if (! defined $prev_value); + + $value = $prev_value + (0+$value); + #warn "VALUE MATH[$value] "; + } + + if ($value > $::max_atten) { + warn("[$::line] attenuate: value cannot be higher than $::max_atten") + unless($::quiet eq "yes"); + $value = $::max_atten; + } + + if ($value < $::min_atten) { + warn("[$::line] attenuate: value cannot be lower than $::min_atten") + unless($::quiet eq "yes"); + $value = $::min_atten; + } + + $::last_atten{$channel} = $value; + $::utils->doAsyncCmd("set_atten $shelf $resource $serno $chan $value") + unless (defined $::dryrun && $::dryrun); + + print "$::line: set_atten $shelf.$resource.$serno.$chan $value\n" + if ($::quiet ne "yes" || $::dryrun); +} +## +## M A I N +## + +# connect to manager +our $utils = new LANforge::Utils(); +$utils->connect($lfmgr_host, $lfmgr_port); +if ($::quiet eq "yes") { + $::utils->cli_send_silent(1); # Do show input to CLI + $::utils->cli_rcv_silent(1); # Repress output from CLI ?? +} +else { + $::utils->cli_send_silent(0); # Do show input to CLI + $::utils->cli_rcv_silent(0); # Repress output from CLI ?? +} + + +if (defined $::delay_override && $::delay_override != -1 && $::delay_override < 1000) { + warn("$0: --delay is in milliseconds, values less than 1000 (1 second) might be meaningless"); + sleep 2; +} +die ("$0: --delay of zero or less is not permitted.") + if (defined $::delay_override && $::delay_override != -1 && $::delay_override <= 0); + +$::delay = $::delay_override if (defined $::delay_override && $::delay_override > 0); + +my $loop_count = 0; +while ($loop_count == 0 || $::do_loop) { + $loop_count++; + for (my $rownum = 0; $rownum < $::cfile->numRows(); $rownum++) { + $::line = $rownum+1; + my $ra_row = $::cfile->getRow($rownum); + + next if (@{$ra_row} == 0); # empty row + + if (lc($ra_row->[0]) =~ /^(delay|naptime)$/) { + next if (defined $::delay_override && $::delay_override != -1); + + $::delay = 0 + $ra_row->[1]; + die ("$line: delay of zero or less is not permitted") + if ($::delay <= 0); + next; + } + + if (lc($ra_row->[0]) =~ /^channels$/ && (!defined $::channel_override)) { + my @tempchannels = @$ra_row; + shift @tempchannels; + %::last_atten= (); + for my $c (@tempchannels) { + push(@::channels, $c); + $::last_atten{$c} = -1; + } + next; + } + + if (lc($ra_row->[0]) =~ /^(sleep|nap)$/) { + if (!defined $ra_row->[1] || (0 + $ra_row->[1]) < 1) { + die("$line: sleep value needs to be 1ms or greater"); + } + usleep($ra_row->[1] *1000) unless ($::dryrun); + next; + } + + if (lc($ra_row->[0]) =~ /^(attenuate|_)$/ || $ra_row->[0] eq "") { + #print "\n"; + my $col = 1; + foreach my $ch (@::channels) { + my $value = "NA"; + my $data = $::cfile->getCell($col, $rownum, "na"); + #print "DATA($col,$::line)[$data] "; + + if (!defined $data || "$data" eq "" ) { + $col++; + next; + } + if (lc($data) =~ /^(na|_)$/ || $data =~ /^\s*\#.*$/) { + #warn ("skipping data[$data] at $col,$::line"); + $col++; + next; + } + if ($data =~ /^\d+$/) { + $value = 0 + $data; + } + elsif ($data =~ /^=[B-Z]\d+[+-]\d+$/i) { # we have a formula + my ($acol,$arow,$delta) = $data =~ /^=([B-Z])(\d+)([+-]\d+)$/i; + $acol = ord(uc($acol)) - 65; + my $pval = $::cfile->getCell($acol, $arow-1, 0); + if (!defined $pval) { + $pval = lastAtten($col-1);# $::last_atten{$::channels[$col]}; + warn("Failed to find valid references at cell[$col,$::line], using previous attenuation:".$pval); + } + if ( $pval !~ /^\d+$/) { + $value = lastAtten($col-1);# $::last_atten{$::channels[$col]}; + die("Failed to find valid references at cell[$col,$::line]:".$value) + if ( ! defined $value); + + #$value = $value + (0+$delta); + warn "Substituting [$value]: cell[$col,$::line] refers to cell[$acol,$arow] with non absolute value:$pval"; + } + else { + $value = $pval + (0 + $delta); + } + #print "acol[$acol] arow[$arow] delta[$delta] pval[$pval] value[$value]\n"; + } + elsif ($data =~ /^\@?[+]+\d+$/ ) { # add relative + my ($delta) = $data =~ /^\@?[+]+(\d+)$/; + my $pval = lastAtten($col-1); #$::last_atten{$::channels[$col]}; + $value = $pval + (0 + $delta); + } + elsif ( $data =~ /^\@?[-]+\d+$/ ) { # subtract relative + my ($delta) = $data =~ /^\@?[-]+(\d+)$/; + my $pval = lastAtten($col-1); #$::last_atten{$::channels[$col]}; + $value = $pval + (-1 * (0 + $delta)); + } + else { + warn "Unknown directive[$data] "; + $col++; + next; + } + attenuate($ch, "$value"); + $col++; + } + + die("Step delay not set correctly[$::delay]") + if (!defined $::delay || "$::delay" eq "" || (0+$::delay) < 1); + + usleep($::delay * 1000) unless ($::dryrun); + next + } + die("$::line: unknown directive[".$ra_row->[0]); + } +} + +## eof diff --git a/lanforge/lanforge-scripts/attenuator_series_example.csv b/lanforge/lanforge-scripts/attenuator_series_example.csv new file mode 100644 index 000000000..409c338bd --- /dev/null +++ b/lanforge/lanforge-scripts/attenuator_series_example.csv @@ -0,0 +1,15 @@ +# test of the attenuator loop +channels, 1.1.14.1, 1.1.14.2, 1.1.14.3, 1.1.3.1, 1.1.3.2, 1.1.3.3 +delay,1000 +950,850,750, 950,850,750 +sleep,1000 + +-10,-10,-10, -10,-10,-10 +-10,-10,NA, -10,-10,NA +-10,-10,NA, -10,-10,NA +-10,-10,-10, -10,-10,-10 ++10,+10,+10, +10,+10,+10 ++10,+10,NA, +10,+10,NA ++10,+10,NA, +10,+10,NA ++10,+10,+10, +10,+10,+10 +# eof diff --git a/lanforge/lanforge-scripts/auto-install-gui.py b/lanforge/lanforge-scripts/auto-install-gui.py new file mode 100755 index 000000000..ca07cba21 --- /dev/null +++ b/lanforge/lanforge-scripts/auto-install-gui.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +from html.parser import HTMLParser +import urllib.request +from html.entities import name2codepoint +import re +import datetime +import argparse +import os +import time +import glob +import sys +import subprocess + + +#===========ARGUMENT PARSING============== +parser = argparse.ArgumentParser() +parser.add_argument("--versionNumber", type=str, help="Specify version number to search for") + +args = parser.parse_args() +if args.versionNumber != None: + ver = args.versionNumber +else: + parser.print_help() + parser.exit() + +#=============HTML PARSING================ +url = "http://jed-centos7/pld/" +with urllib.request.urlopen(url) as response: + html = response.read() + +webpage = html.decode('utf-8') +#print(webpage) + +searchPattern = '(LANforgeGUI_(\d+\.\d+\.\d+)_Linux64.tar.bz2)<\/a><\/td>(\d+\-\d+\-\d+\ \d+\:\d+)' +searchResults = re.findall(searchPattern, webpage) + +webFiles = [] + +for file in searchResults: + if ver == file[1]: + webFiles.append({'filename':file[0], 'timestamp': datetime.datetime.strptime(file[2], "%Y-%m-%d %H:%M")}) +if len(webFiles) == 0: + print("Failed to find webfile with version number %s" % (ver)) + sys.exit(1) + + +#=========CHECK DIR FOR FILES============= +filePath = "/home/lanforge/Downloads/" +dir = glob.glob(filePath + "LANforgeGUI_%s*" % ver) +dirFiles = [] + +for file in dir: + if ver in file: + fileTime = datetime.datetime.strptime(time.ctime(os.stat(file).st_ctime), "%a %b %d %H:%M:%S %Y") # Fri May 8 08:31:43 2020 + dirFiles.append({'filename':file[25:], 'timestamp':fileTime}) + +if len(dirFiles) == 0: + print("Unable to find file in {filePath} with version %s" % ver) + #sys.exit(1) + +#============FIND NEWEST FILES============ +def findNewestVersion(filesArray): + newest = filesArray[0] + if len(filesArray) > 0: + for file in filesArray: + if file['timestamp'] > newest['timestamp']: + newest = file + + return newest + +newestWebFile = findNewestVersion(webFiles) +if len(dirFiles) != 0: + newestDirFile = findNewestVersion(dirFiles) +else: + newestDirFile = {'filename':'placeholder', 'timestamp': datetime.datetime.strptime("0", "%H")} + + +#=======COMPARE WEB AND DIR FILES========= +if newestWebFile['timestamp'] > newestDirFile['timestamp']: + try: + if newestDirFile['filename'] != 'placeholder': + subprocess.call(["rm", "%s%s" % (filePath, newestDirFile['filename'])]) + print("No file found") + print("Downloading newest %s from %s" % (newestWebFile['filename'], url)) + else: + print("Found newer version of GUI") + print("Downloading %s from %s" % (newestWebFile['filename'], url)) +#=====ATTEMPT DOWNLOAD AND INSTALL========= + subprocess.call(["curl", "-o", "%s%s" % (filePath, newestWebFile['filename']), "%s%s" % (url, newestWebFile['filename'])]) + time.sleep(5) + except Exception as e: + print("%s Download failed. Please try again." % e) + sys.exit(1) + try: + print("Attempting to extract files") + subprocess.call(["tar", "-xf", "%s%s" % (filePath, newestWebFile['filename']), "-C", "/home/lanforge/"]) + except Exception as e: + print("%s\nExtraction failed. Please try again" % e) + sys.exit(1) + + #time.sleep(90) + try: + if "/home/lanforge/.config/autostart/LANforge-auto.desktop" not in glob.glob("/home/lanforge/.config/autostart/*"): + print("Copying LANforge-auto.desktop to /home/lanforge/.config/autostart/") + subprocess.call(["cp", "/home/lanforge/%s/LANforge-auto.desktop" % (newestWebFile['filename'][:len(newestWebFile)-18]), "/home/lanforge/.config/autostart/"]) + except Exception as e: + print("%s\nCopy failed. Please try again" % e) + sys.exit(1) + + try: + print("Attempting to install %s at /home/lanforge" % newestWebFile['filename']) + os.system("cd /home/lanforge/%s; sudo bash lfgui_install.bash" % (newestWebFile['filename'][:len(newestWebFile)-18])) + except Exception as e: + print("%s\nInstallation failed. Please Try again." % e) + sys.exit(1) +#=========ATTEMPT TO RESTART GUI========== +# try: +# print("Killing current GUI process") +# os.system("if pgrep java; then pgrep java | xargs kill -9 ;fi") +# except Exception as e: +# print("%s\nProcess kill failed. Please try again" % e) +# sys.exit(1) + +else: + print("Current GUI version up to date") + sys.exit(0) + diff --git a/lanforge/lanforge-scripts/brent_showport.sh b/lanforge/lanforge-scripts/brent_showport.sh new file mode 100755 index 000000000..4465d7c61 --- /dev/null +++ b/lanforge/lanforge-scripts/brent_showport.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +mgr="192.168.100.86" + +./lf_portmod.pl --manager $mgr --load port-regression > /dev/null +sleep 10s + +for x in vap0 sta0 eth1#0 eth1 eth1.1 rddVR0 br0 +do + #Test MAC + port_output=`./lf_portmod.pl --quiet 1 --manager $mgr --card 2 --port_name $x --show_port MAC` + answer=${port_output:5} + # echo "MAC exists: $x $answer + if [ -z "$answer" ]; then + echo "Failed to find MAC address for $x." + exit 1 + fi + + #Test port UP + port_output=`./lf_portmod.pl --quiet 1 --manager $mgr --card 2 --port_name $x --show_port Current` + answer=${port_output:9:2} + # echo "DB UP: $x $answer" + if [ $answer != "UP" ]; then + echo "Failed, port $x is down after loading DB." + exit 1 + fi + + #Test port UP after reset + ./lf_portmod.pl --quiet 1 --manager $mgr --card 2 --port_name $x --cmd reset > /dev/null + sleep 2s + port_output=`./lf_portmod.pl --quiet 1 --manager $mgr --card 2 --port_name $x --show_port Current` + answer=${port_output:9:2} + # echo "UP after reset: $x $answer" + if [ $answer != "UP" ]; then + echo "Failed, port $x is down after resetting." + exit 1 + fi + + #Test DOWN after ifdown + ./lf_portmod.pl --quiet 1 --manager $mgr --card 2 --port_name $x --set_ifstate down + port_output=`./lf_portmod.pl --quiet 1 --manager $mgr --card 2 --port_name $x --show_port Current` + answer=${port_output:9:4} + # echo "DOWN after ifdown: $x $answer" + if [ $answer != "DOWN" ]; then + echo "Failed, port $x is still up after ifdown." + exit 1 + fi + + #Test UP after ifup + ./lf_portmod.pl --quiet 1 --manager $mgr --card 2 --port_name $x --set_ifstate up + sleep 5s + port_output=`./lf_portmod.pl --quiet 1 --manager $mgr --card 2 --port_name $x --show_port Current` + answer=${port_output:9:2} + # echo "UP after ifup: $x $answer" + if [ $answer != "UP" ]; then + echo "Failed, port $x is still down after ifup." + exit 1 + fi +done + +echo "Test passed." diff --git a/lanforge/lanforge-scripts/calc_autn.pl b/lanforge/lanforge-scripts/calc_autn.pl new file mode 100755 index 000000000..baa5daa1b --- /dev/null +++ b/lanforge/lanforge-scripts/calc_autn.pl @@ -0,0 +1,59 @@ +#!/usr/bin/perl +## +## Calculated EAP-AKA AUTN based on: +## AUTN = (SQN xor AK) || AMF || MAC = 48 + 16 + 64 = 128 bits +## +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{__DIE__} = sub{Carp::confess(@_)}; +use Getopt::Long; +$| = 1; + +our $usage = qq($0: calculate AUTN + All input is in ascii hex. + --sqn SQN + --ak AK, output of f5 + --amf AMF for test-set configuration + --mac MAC, output of f1 + +Example for test-set 19 (4.3.19) from 3GPP TS 35.208, v6.0.0 Release 6 + $0 --sqn 16f3b3f70fc2 --ak ada15aeb7bb8 --amf c3ab --mac 2a5c23d15ee351d5 +); + + +our $sqn = ""; +our $ak = ""; +our $amf = ""; +our $mac = ""; + +GetOptions ( + 'sqn=s' => \$::sqn, + 'ak=s' => \$::ak, + 'amf=s' => \$::amf, + 'mac=s' => \$::mac, +) || die("$::usage"); + +#AUTN = (SQN xor AK) || AMF || MAC = 48 + 16 + 64 = 128 bits +print "AUTN for SQN: $sqn AK: $ak AMF: $amf MAC: $mac\n"; +my $i; +my @sqnc = split("", $sqn); +my @akc = split("", $ak); +if (@sqnc != 12) { + die("sqn must have 12 ascii bytes."); +} +if (@akc != 12) { + die("akc must have 12 ascii bytes."); +} + +for ($i = 0; $i<12; $i++) { + my $v = hex($sqnc[$i]); + $v ^= hex($akc[$i]); + printf "%1x", $v; +} +print $amf; +print $mac; +print "\n"; + +## eof diff --git a/lanforge/lanforge-scripts/cert-builder.sh b/lanforge/lanforge-scripts/cert-builder.sh new file mode 100755 index 000000000..f28048b9f --- /dev/null +++ b/lanforge/lanforge-scripts/cert-builder.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# example based off https://www.endpoint.com/blog/2014/10/30/openssl-csr-with-alternative-names-one + +hostname="$1" +hostname1=$hostname.local +ipaddr= + +cat > tmp_csr_details.txt <<-EOF +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = req_ext +distinguished_name = dn + +[ dn ] +C=US +ST=Washington +L=Ferndale +O=Candela Technologies, Inc. +OU=LANforge +emailAddress=support@candelatech.com +CN = $hostname + +[ req_ext ] +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = $hostname1 +DNS.2 = $ipaddr +EOF + +# Let’s call openssl now by piping the newly created file in +openssl req -new -sha256 -nodes -out ${hostname}.csr -newkey rsa:2048\ + -keyout ${hostname}.key -config <( cat temp_csr_details.txt ) +# diff --git a/lanforge/lanforge-scripts/check_large_files.bash b/lanforge/lanforge-scripts/check_large_files.bash new file mode 100755 index 000000000..888f66956 --- /dev/null +++ b/lanforge/lanforge-scripts/check_large_files.bash @@ -0,0 +1,1051 @@ +#!/bin/bash +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# Check for large files and purge the ones requested # +# # +# The -a switch will automatically purge core files when there # +# is only 5GB of space left on filesystem. # +# # +# To install as a cron-job, add the following line to /etc/crontab: # +# 1 * * * * root /home/lanforge/scripts/check_large_files.sh -a 2>&1 | logger -t check_large_files # +# # +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# set -x +# set -e +# these are default selections +selections=() +show_menu=1 +verbose=0 +quiet=0 +starting_dir="$PWD" +cleanup_size_mb=$(( 1024 * 5 )) +# do not name this file "core_x" because it will get removed +lf_core_log="/home/lanforge/found_cores_log.txt" + +USAGE="$0 # Check for large files and purge many of the most inconsequencial + -a # automatic: quietly empty trash and remove crash files if free space is < ${cleanup_size_mb}MB + -b # remove extra kernels and modules + -c # remove all core files + -d # remove old LANforge downloads + -h # help + -k # remove ath10k crash files + -l # remove old files from /var/log, truncate /var/log/messages + -m # remove orphaned fileio items in /mnt/lf + -q # quiet + -r # compress /home/lanforge report data and pcap files + -s # empty the trash + -t # remove /var/tmp files + -v # verbose + -z # compressed files in /home/lanforge +" + +eyedee=`id -u` +if (( eyedee != 0 )); then + echo "$0: Please become root to use this script, bye" + exit 1 +fi + +debug() { + if [[ x$verbose = x ]] || (( $verbose < 1 )); then return; fi + echo ": $1" +} + +note() { + if (( $quiet > 0 )); then return; fi + echo "# $1" +} + +function contains() { + if [[ x$1 = x ]] || [[ x$2 = x ]]; then + echo "contains wants ARRAY and ITEM arguments: if contains name joe; then... }$" + exit 1 + fi + # these two lines below are important to not modify + local tmp="${1}[@]" + local array=( ${!tmp} ) + + # if [[ x$verbose = x1 ]]; then + # printf "contains array %s\n" "${array[@]}" + # fi + if (( ${#array[@]} < 1 )); then + return 1 + fi + local item + for item in "${array[@]}"; do + # debug "contains testing $2 == $item" + [[ "$2" = "$item" ]] && return 0 + done + return 1 +} + +function remove() { + if [[ x$1 = x ]] || [[ x$2 = x ]]; then + echo "remove wants ARRAY and ITEM arguments: if contains name joe; then... }$" + exit 1 + fi + # these two lines below are important to not modify + local tmp="${1}[@]" + local array=( ${!tmp} ) + + # if [[ x$verbose = x1 ]]; then + # printf "contains array %s\n" "${array[@]}" + # fi + if (( ${#array[@]} < 1 )); then + return 1 + fi + local item + for i in "${!array[@]}"; do + if [[ ${array[$i]} = "$2" ]]; then + unset 'array[i]' + debug "removed $2 from $1" + return 0 + fi + done + return 1 +} + +function disk_space_below() { + if [[ x$1 = x ]] || [[ x$2 = x ]]; then + echo "disk_free: needs to know what filesystem, size in bytes to alarm on" + return + fi + local amount_left_mb=`df -BM --output=iavail | tail -1` + if (( $amount_left_mb < $cleanup_size_mb )) ; then + debug "amount left $amount_left_mb lt $cleanup_size_mb" + return 0 + fi + debug "amount left $amount_left_mb ge $cleanup_size_mb" + return 1 +} + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# ----- ----- M A I N ----- ----- # +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # + +#opts="" +opts="abcdhklmqrtv" +while getopts $opts opt; do + case "$opt" in + a) + if contains "selections" "v"; then + verbose=1 + else + verbose=0 + fi + quiet=1 + selections+=($opt) + selections+=(s) # dump trash + show_menu=0 + ;; + b) + selections+=($opt) + ;; + c) + selections+=($opt) + ;; + d) + selections+=($opt) + ;; + h) + echo "$USAGE" + exit 0 + ;; + k) + selections+=($opt) + ;; + l) + selections+=($opt) + ;; + m) + selections+=($opt) + ;; + r) + selections+=($opt) + ;; + s) + selections+=($opt) + ;; + q) + quiet=1 + verbose=0 + ;; + t) + selections+=($opt) + ;; + v) + quiet=0 + verbose=1 + ;; + z) + selections+=($opt) + ;; + *) + echo "unknown option: $opt" + echo "$USAGE" + exit 1 + ;; + esac +done + +#if (( ${#selections} < 1 )); then +# echo "$USAGE" +# exit 0 +#fi + +HR=" ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----" +function hr() { + echo "$HR" +} + +declare -A totals=( + [b]=0 + [c]=0 + [d]=0 + [k]=0 + [l]=0 + [m]=0 + [r]=0 + [s]=0 + [t]=0 + [z]=0 +) +declare -A desc=( + [b]="kernel files" + [c]="core files" + [d]="lf downloads" + [k]="lf/ath10 files" + [l]="/var/log" + [m]="/mnt/lf files" + [n]="dnf cache" + [r]="report data" + [s]="trash can" + [t]="/var/tmp" + [z]="compressed files" +) +declare -A surveyors_map=( + [b]="survey_kernel_files" + [c]="survey_core_files" + [d]="survey_lf_downloads" + [k]="survey_ath10_files" + [l]="survey_var_log" + [m]="survey_mnt_lf_files" + [n]="survey_dnf_cache" + [r]="survey_report_data" + [s]="survey_trash_can" + [t]="survey_var_tmp" + [z]="survey_compressed_files" +) + +declare -A cleaners_map=( + [b]="clean_old_kernels" + [c]="clean_core_files" + [d]="clean_lf_downloads" + [k]="clean_ath10_files" + [l]="clean_var_log" + [m]="clean_mnt_lf_files" + [n]="clean_dnf_cache" + [r]="compress_report_data" + [s]="empty_trash_can" + [t]="clean_var_tmp" + [z]="clean_compressed_files" +) + +kernel_to_relnum() { + #set -euxv + local hunks=() + # 1>&2 echo "KERNEL RELNUM:[$1]" + local my1="${1/*[^0-9]-/}" # Dang, this is not intuitive to a PCRE user + #1>&2 echo "KERNEL [$1] REGEX:[$my1]" + my1="${my1//\+/}" + if [[ $my1 =~ ^[^0-9] ]]; then + 1>&2 echo "BAD SERIES: [$1]" + exit 1 + fi + IFS="." read -ra hunks <<< "$my1" + IFS= + local tmpstr + local max_width=8 + local last_len=0 + local expandos=() + for i in 0 1 2; do + if (( $i < 2 )); then + #1>&2 echo "HUNK $i: [${hunks[$i]}]" + expandos+=( $(( 100 + ${hunks[$i]} )) ) + else + tmpstr="00000000${hunks[i]}" + last_len=$(( ${#tmpstr} - $max_width )) + expandos+=( ${tmpstr:$last_len:${#tmpstr}} ) + #1>&2 echo "TRIMMED ${tmpstr:$last_len:${#tmpstr}}" + fi + done + + set +x + #1>&2 echo "EXPANDO: ${expandos[0]}${expandos[1]}${expandos[2]}" + echo "k${expandos[0]}${expandos[1]}${expandos[2]}" +} + +empty_trash_can() { + #set -vux + if [ -x /usr/bin/trash-empty ]; then + for can in "${trash_cans[@]}"; do + if [[ $can = /home* ]]; then + # that =() nonsense is turning the '/' into spaces and letting default IFS let () treat it as array items + local hunks=(${can//\// }) + local uzer="${hunks[1]}" + su -l $uzer -c "unset DISPLAY; /usr/bin/trash-empty" + else + # we should be root + /usr/bin/trash-empty + fi + done + else + note "trash-cli not installed, destroying trash files" + for can in "${trash_cans[@]}"; do + find "${can}" -type f -exec rm -vf {} \; + done + fi + totals[s]=0 + set +vux +} + +clean_compressed_files() { + note "Listing compressed files..." + local f + if (( ${#compressed_files[@]} > 0 )); then + for f in "${compressed_files[@]}"; do + echo "$f" + done | paste - - - | less + else + echo "No compressed files." + fi + totals[z]=0 +} +clean_old_kernels() { + note "Cleaning old CT kernels..." + local f + if (( ${#removable_packages[@]} > 0 )); then + for f in "${removable_packages[@]}"; do + echo "$f\*" + done | xargs /usr/bin/rpm --nodeps -hve + fi + if (( ${#removable_kernels[@]} > 0 )); then + for f in "${removable_kernels[@]}"; do + echo "$f" + done | xargs rm -f + fi + + if (( ${#removable_libmod_dirs[@]} > 0 )); then + printf " removable_libmod_dirs[/lib/modules/%s]\n" "${removable_libmod_dirs[@]}" + for f in "${removable_libmod_dirs[@]}"; do + echo "/lib/modules/$f" + done | xargs rm -rf + fi +} + +clean_core_files() { + note "Cleaning core files..." + if (( ${#core_files[@]} < 1 )); then + debug "No core files ?" + return 0 + fi + + local counter=0 + if [ ! -f "$lf_core_log" ]; then + touch "$lf_core_log" + fi + date +"%Y-%m-%d-%H:%M.%S" >> $lf_core_log + for f in "${core_files[@]}"; do + file "$f" >> "$lf_core_log" + done + note "Recorded ${#core_files[@]} core files to $lf_core_log: " + tail -n $(( 1 + ${#core_files[@]} )) $lf_core_log + local do_delete=0 + if contains "selections" "a"; then + disk_space_below / $cleanup_size_mb && do_delete=$(( $do_delete + 1 )) + disk_space_below /home $cleanup_size_mb && do_delete=$(( $do_delete + 1 )) + (( $do_delete > 0)) && note "disk space below $cleanup_size_mfb, removing core files" + elif contains "selections" "c"; then + do_delete=1 + note "core file cleaning selected" + fi + if (( $do_delete > 0 )); then + for f in "${core_files[@]}"; do + echo -n "-" + rm -f "$f" && remove "core_files" "$f" + counter=$(( counter + 1 )) + if (( ($counter % 100) == 0 )); then + sleep 0.2 + fi + done + else + note "disk space above $cleanup_size_mb, not removing core files" + fi + #set +vux + echo "" + totals[c]=0 + survey_core_files +} + +clean_lf_downloads() { + if (( ${#lf_downloads[@]} < 1 )); then + note "No /home/lanforge/downloads files to remove" + return 0 + fi + note "Clean LF downloads..." + if (( $verbose > 0 )); then + echo "Would Delete: " + printf "[%s] " "${lf_downloads[@]}" | sort + fi + cd /home/lanforge/Downloads + for f in "${lf_downloads[@]}"; do + [[ "$f" = "/" ]] && echo "Whuuut? this is not good, bye." && exit 1 + # echo "Next:[$f]" + sleep 0.02 + rm -f "$f" + done + totals[d]=0 + cd "$starting_dir" +} + +clean_ath10_files() { + note "clean_ath10_files WIP" + local f + while read f; do + echo "removing $f" + rm -f "$f" + done < <( find /home/lanforge -type f -iname "ath10*" ||:) +} + +clean_var_log() { + note "Vacuuming journal..." + journalctl --vacuum-size 1M + if (( ${#var_log_files[@]} < 1 )); then + note "No notable files in /var/log to remove" + return + fi + local vee="" + if (( $verbose > 0 )); then + printf "%s\n" "${var_log_files[@]}" + vee="-v" + fi + cd /var/log + while read file; do + if [[ $file = /var/log/messages ]]; then + echo "" > /var/log/messages + else + rm -f $vee "$file" + fi + done <<< "${var_log_files[@]}" + cd "$starting_dir" +} + +clean_dnf_cache() { + local yum="dnf" + which --skip-alias dnf &> /dev/null + (( $? < 0 )) && yum="yum" + debug "Purging $yum cache" + $yum clean all + totals[n]=0 +} + +clean_mnt_lf_files() { + note "cleaning mnt lf files..." + if (( $verbose > 0 )); then + printf "%s\n" "${mnt_lf_files[@]}" + fi + rm -f "${mnt_lf_files[@]}" + totals[m]=0 +} + +compress_report_data() { + note "compress report data..." + cd /home/lanforge + # local csvfiles=( $( find /home/lanforge -iname "*.csv" -print0 )) + local vile_list=(`find html-reports/ lf_reports/ report-data/ tmp/ -type f \ + -a \( -name '*.csv' -o -name '*.pdf' -o -name '*.pdf' -o -name '*.pcap' -o -name '*.pcapng' \)`) + counter=1 + for f in "${vile_list[@]}"; do + (( $verbose > 0 )) && echo " compressing $f" || echo -n " ${counter}/${#vile_list[@]}" + nice xz -T0 -5 "$f" + (( counter+=1 )) + done + totals[r]=0 + cd - + echo "" +} + +clean_var_tmp() { + note "clean var tmp" + if (( $verbose > 0 )); then + printf " %s\n" "${var_tmp_files[@]}" + sleep 1 + fi + for f in "${var_tmp_files[@]}"; do + rm -f "$f" + sleep 0.2 + done +} + +kernel_files=() # temp +lib_module_dirs=() # temp +declare -A kernel_sort_names +declare -A pkg_sort_names +declare -A libmod_sort_names +removable_kernels=() # these are for CT kernels +removable_libmod_dirs=() # these are for CT kernels +removable_packages=() # these are for Fedora kernels +removable_pkg_series=() +survey_kernel_files() { + unset removable_kernels + unset removable_libmod_dirs + unset removable_packages + unset lib_module_dirs + unset kernel_sort_names + unset kernel_sort_serial + unset pkg_sort_names + unset libmod_sort_names + declare -A kernel_sort_serial=() + declare -A kernel_sort_names=() + declare -A pkg_sort_names=() + declare -A libmod_sort_names=() + local ser + local file + debug "Surveying Kernel files" + mapfile -t kernel_files < <(find /boot -maxdepth 1 -type f -a \( \ + -iname "System*" -o -iname "init*img" -o -iname "vm*" -o -iname "ct*" \) \ + 2>/dev/null | grep -v rescue | sort) + mapfile -t lib_module_dirs < <(find /lib/modules -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort) + local booted=`uname -r` + + note "** You are running kernel $booted **" + + local file + local fiile + for file in "${kernel_files[@]}"; do + debug "kernel_file [$file]" + [[ $file =~ /boot/initramfs* ]] && continue + [[ $file =~ *.fc*.x86_64 ]] && continue + [[ $file = *initrd-plymouth.img ]] && continue + fiile=$( basename $file ) + fiile=${fiile%.img} + + if [[ $fiile =~ $booted ]]; then + debug " ignoring booted CT kernel $file" + # sleep 2 + continue + else + ser=$( kernel_to_relnum ${fiile#*ct} ) + kernel_sort_serial[$ser]=1 + # debug "file[$file] ser[$ser]" + kernel_sort_names["$file"]="$ser" + removable_kernels+=($file) + fi + done + # sleep 2 + local booted_ser=$( kernel_to_relnum $booted ) + if (( ${#kernel_sort_names[@]} > 0 )); then + declare -A ser_files + for file in "${!kernel_sort_names[@]}"; do + ser="${kernel_sort_names[$file]}" + done + debug "Removable CT kernels:" + while read ser; do + (( $verbose > 0 )) && printf " kernel file [%s]\n" "${kernel_sort_names[$ser]}" + removable_kernels+=(${kernel_sort_names["$ser"]}) + done < <(echo "${!kernel_sort_names[@]}" | sort | head -n -1) + fi + + debug "Module directories elegible for removal: " + for file in "${lib_module_dirs[@]}"; do + file=${file#/lib/modules/} + # debug "/lib/modules/ ... $file" + if [[ $file =~ $booted ]]; then + debug " Ignoring booted module directory $file" + continue + elif [[ $file = *.fc??.x86_64 ]]; then + debug " Ignoring Fedora module directory $file" + continue + else + ser=$( kernel_to_relnum $file ) + # debug " eligible [$ser] -> $file" + libmod_sort_names[$ser]="$file" + fi + done + + if (( ${#libmod_sort_names[@]} > 0 )); then + # debug "Removable libmod dirs: " + while read ser; do + file="${libmod_sort_names[$ser]}" + # debug " $ser -> $file" + if [[ $file =~ $booted ]]; then + debug " Ignoring booted $booted module directory $file" + continue + fi + removable_libmod_dirs+=( "$file" ) + # echo " [$ser][${libmod_sort_names[$ser]}] -> $file" + done < <( printf "%s\n" "${!libmod_sort_names[@]}" | sort | uniq) + # we don't need to sort these ^^^ because they were picked out near line 419 + fi + #if (( $verbose > 0 )); then + # printf " removable_libmod_dirs: %s\n" "${removable_libmod_dirs[@]}" + #fi + # set +veux + + local boot_image_sz=$(du -hc "${kernel_files[@]}" | awk '/total/{print $1}') + local lib_dir_sz=$(du -hc "${lib_module_dirs[@]}" | awk '/total/{print $1}') + totals[b]="kernels: $boot_image_sz, modules: $lib_dir_sz" + + local pkg + local k_pkgs=() + removable_pkg_series=() + + # need to avoid most recent fedora kernel + if [ ! -x /usr/bin/rpm ]; then + note "Does not appear to be an rpm system." + return 0 + fi + local ur=$( uname -r ) + local kern_pkgs=( $( rpm -qa 'kernel*' | sort ) ) + local ser + local zpkg + declare -A pkg_to_ser + for pkg in "${kern_pkgs[@]}"; do + if [[ $pkg = kernel-tools-* ]] \ + || [[ $pkg = kernel-headers-* ]] \ + || [[ $pkg = kernel-devel-* ]] ; then + continue + fi + if [[ $pkg =~ $booted ]]; then + debug " ignoring current kernel [$pkg]" + continue + fi + k_pkgs+=( $pkg ) + done + + for pkg in "${k_pkgs[@]}"; do + zpkg="$pkg" + zpkg=${pkg##kernel-modules-extra-} + zpkg=${pkg##kernel-modules-} + zpkg=${pkg##kernel-core-} + zpkg=${pkg%.fc??.x86_64} + + if [[ $zpkg =~ $booted ]]; then + continue + fi + kernel_series=$( kernel_to_relnum ${zpkg##kernel-} ) + + pkg_to_ser[$pkg]="$kernel_series" + pkg_sort_names[$kernel_series]=1 + done + + while read ser; do + # debug " can remove series [$ser] " + removable_pkg_series+=($ser) + done < <( printf "%s\n" "${!pkg_sort_names[@]}" | sort | head -n -1) + + for pkg in "${k_pkgs[@]}"; do + pkg=${pkg%.fc??.x86_64} + ser=$( kernel_to_relnum $pkg ) + if (( ${#removable_pkg_series[@]} > 0 )); then + for zpkg in "${removable_pkg_series[@]}"; do + if (( $ser == $zpkg )); then + removable_packages+=($pkg) + fi + done + fi + done + + set +x + if (( $quiet < 1 )); then + if [[ ${removable_packages+x} = x ]] && (( ${#removable_packages[@]} > 0 )); then + echo "Removable packages " + printf " %s\n" "${removable_packages[@]}" + fi + if [[ ${removable_kernels+x} = x ]] && (( ${#removable_kernels[@]} > 0 )); then + echo "Removable kernel files " + printf " %s\n" "${removable_kernels[@]}" + fi + if [[ ${removable_libmod_dirs+x} = x ]] && (( ${#removable_libmod_dirs[@]} > 0 )); then + echo "Removable /lib/module directories " + printf " %s\n" "${removable_libmod_dirs[@]}" + fi + fi +} # ~survey_kernel_files + +# check the trash can +trash_cans=() +survey_trash_can() { + debug "Checking trash can" + local lstf=".local/share/Trash/files" + local some_items=() + mapfile -t trash_cans < <(ls -d /root/$lstf /home/lanforge/$lstf 2>/dev/null) 2>/dev/null + if [[ $verbose = 1 ]] && (( ${#trash_cans} > 0 )); then + printf " %s\n" "${trash_cans[@]}" + fi + if (( ${#trash_cans[@]} > 0 )); then + totals[s]=$( du -hc "${trash_cans[@]}" | awk '/total/{print $1}' ) + [[ x${totals[s]} = x ]] && totals[s]=0 ||: + for can in "${trash_cans[@]}"; do + mapfile -t -O "${#some_items[@]}" some_items < <(find "$can" -type f | head 2>/dev/null ) + done + fi + if (( ${#some_items[@]} > 0 )) && (( $quiet < 1 )) && (( $verbose > 0 )); then + hr + note "Some trash can items:" + note "${some_items[@]}" + hr + # sleep 1 + fi + if [ ! -x /usr/bin/trash-empty ] && (( $quiet < 1 )); then + note "Package trash-cli not installed. Please install it using command:" + note "sudo dnf install -y trash-cli" + sleep 2 + fi +} + +# Find core files +core_files=() +survey_core_files() { + debug "Surveying core files" + cd / + + mapfile -t core_files < <(ls /core* /home/lanforge/core* 2>/dev/null) 2>/dev/null + if [[ $verbose = 1 ]] && (( ${#core_files[@]} > 0 )); then + printf " %s\n" "${core_files[@]}" | head + fi + if (( ${#core_files[@]} > 0 )); then + totals[c]=$(du -hc "${core_files[@]}" | awk '/total/{print $1}') + [[ x${totals[c]} = x ]] && totals[c]=0 ||: + else + totals[c]=0 + fi + + cd "$starting_dir" +} + +# downloads +lf_downloads=() +survey_lf_downloads() { + debug "Surveying /home/lanforge downloads" + [ ! -d "/home/lanforge/Downloads" ] && note "No downloads folder " && return 0 + cd /home/lanforge/Downloads + mapfile -t lf_downloads < <(ls *gz *z2 *-Installer.exe *firmware* kinst_* *Docs* 2>/dev/null) + if [[ ${lf_downloads+x} = x ]]; then + totals[d]=$(du -hc "${lf_downloads[@]}" | awk '/total/{print $1}') + [[ x${totals[d]} = x ]] && totals[d]=0 + else + totals[d]=0 + fi + cd "$starting_dir" +} + +# Find ath10k crash residue +ath10_files=() +survey_ath10_files() { + debug "Surveying ath10 crash files" + mapfile -t ath10_files < <(ls /home/lanforge/ath10* 2>/dev/null) + if [[ ${ath10_files+x} = x ]]; then + totals[k]=$(du -hc "${ath10_files[@]}" 2>/dev/null | awk '/total/{print $1}') + [[ x${totals[k]} = x ]] && totals[k]=0 ||: + else + totals[k]=0 + return + fi + +} + +# stuff in var log +var_log_files=() +survey_var_log() { + debug "Surveying var log" + mapfile -t var_log_files < <(find /var/log -type f -size +35M \ + -not \( -path '*/journal/*' -o -path '*/sa/*' -o -path '*/lastlog' \) 2>/dev/null ||:) + if [[ ${var_log_files+x} = x ]]; then + totals[l]=$(du -hc "${var_log_files[@]}" 2>/dev/null | awk '/total/{print $1}' ) + [[ x${totals[l]} = x ]] && totals[l]=0 ||: + else + totals[l]=0 + return + fi +} + +# stuff in var tmp +var_tmp_files=() +survey_var_tmp() { + debug "Surveying var tmp" + mapfile -t var_tmp_files < <(find /var/tmp -type f 2>/dev/null || :) + if [[ ${var_tmp_files+x} = x ]]; then + totals[t]=$(du -sh "${var_tmp_files[@]}" 2>/dev/null | awk '/total/{print $1}' ) + [[ x${totals[t]} = x ]] && totals[t]=0 ||: + else + totals[t]=0 + fi +} + +# Find size of /mnt/lf that is not mounted +mnt_lf_files=() +survey_mnt_lf_files() { + [ ! -d /mnt/lf ] && return 0 + debug "Surveying mnt lf" + + mapfile -t mnt_lf_files < <(find /mnt/lf -xdev -type f 2>/dev/null ||:) + if [[ ${mnt_lf_files+x} = x ]]; then + if (( ${#mnt_lf_files[@]} < 1 )); then + totals[m]=0 + return + fi + totals[m]=$( du -xhc "${mnt_lf_files[@]}" 2>/dev/null | awk '/total/{print $1}' ) + [[ x${totals[m]} = x ]] && totals[m]=0 ||: + else + totals[m]=0 + return + fi + # set +vx +} + +survey_dnf_cache() { + local yum="dnf" + which --skip-alias dnf &> /dev/null + (( $? < 0 )) && yum="yum" + debug "Surveying $yum cache" + totals[n]=$(du -hc '/var/cache/{dnf,yum}' 2>/dev/null | awk '/total/{print $1}') +} + +compressed_files=() +survey_compressed_files() { + debug "Surveying compressed /home/lanforge " + cd /home/lanforge + mapfile -t compressed_files < <( find Documents/ html-reports/ lf_reports/ report-data/ tmp/ -type f \ + -a \( -name "*.gz" -o -name "*.xz" -o -name "*.bz2" -o -name "*.7z" -o -name "*.zip" \) 2>/dev/null ||:) + if (( ${#compressed_files[@]} < 1 )); then + debug "no compressed files found" + totals[z]=0 + return + fi + + totals[z]=$( du -xhc "${compressed_files[@]}" 2>/dev/null | awk '/total/{print $1}' ) + # set +veux + [[ x${totals[z]} = x ]] && totals[z]=0 ||: + cd - +} + +## Find size of /lib/modules +# cd /lib/modules +# mapfile -t usage_libmod < <(du -sh *) + +# Find how many kernels are installed +# cd /boot +# mapfile -t boot_kernels < <(ls init*) +# boot_usage=`du -sh .` + +# report_files=() +survey_report_data() { + debug "Surveying for lanforge report data" + cd /home/lanforge + + local fsiz=0 + local fnum=0 + local csv_list=(`find report-data/ html-reports/ lf_reports/ -type f -a -name '*.csv' 2>/dev/null ||:`) + local pdf_list=(`find report-data/ html-reports/ lf_reports/ Documents/ -type f -a -name '*.pdf' 2>/dev/null ||:`) + local pcap_list=(`find tmp/ report-data/ local/tmp/ lf_reports/ Documents/ -type f -a \( -name '*.pcap' -o -name '*.pcapng' \) 2>/dev/null ||:`) + fnum=$(( ${#csv_list[@]} + ${#pdf_list[@]} + ${#pcap_list[@]} )) + + if (( $fnum > 0 )); then + fsiz=$(du -hc "${csv_list[@]}" "${pdf_list[@]}" "${pcap_list[@]}" | awk '/total/{print $1}') + fi + totals[r]="$fnum files ($fsiz): ${#csv_list[@]} csv, ${#pdf_list[@]} pdf, ${#pcap_list[@]} pcap" + [[ x${totals[r]} = x ]] && totals[r]=0 + # report_files=("CSV files: $fnum tt $fsiz") + cd "$starting_dir" +} + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# gather usage areas +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +survey_areas() { + local area + note "Surveying..." + for area in "${!surveyors_map[@]}"; do + if (( $quiet < 1 )) && (( $verbose < 1 )); then + echo -n "#" + fi + + if [[ $area = b ]]; then + if [[ ${kernel_sort_names+x} != x ]] || (( ${#kernel_sort_names[@] < 1 } )); then + debug "surveying kernel area" + # sleep 5 + ${surveyors_map[$area]} + else + debug "kernel area already surveyed" + fi + + else + debug "surveying $area" + # sleep 2 + ${surveyors_map[$area]} + fi + done + if (( $quiet < 1 )); then + echo "" + fi +} + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# report sizes here # +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +disk_usage_report() { + for k in "${!totals[@]}"; do + echo -e "\t${desc[$k]}:\t${totals[$k]}" + done +} +survey_areas +disk_usage_report + +if (( ${#core_files[@]} > 0 )); then + hr + note "${#core_files[@]} Core Files detected:" + declare -A core_groups + # set -e + # note that the long pipe at the bottom of the loop is the best way to get + # the system to operate with thousands of core files + while read group7; do + (( $verbose > 0 )) && echo -n '+' + group7="${group7%, *}" + group7="${group7//\'/}" + [[ ${core_groups[$group7]+_} != _ ]] && core_groups[$group7]=0 + core_groups[$group7]=$(( ${core_groups[$group7]} + 1 )) + done < <(echo "${core_files[@]}" | xargs file | awk -F": " '/execfn:/{print $7}') + echo "" + echo "These types of core files were found:" + for group in "${!core_groups[@]}"; do + echo "${core_groups[$group]} files of $group" + done | sort -n + hr + (( ${#core_files[@]} > 0 )) && selections+=("c") +fi + +#echo "Usage of /mnt: $usage_mnt" +#echo "Usage of /lib/modules: $usage_libmod" +#echo "Boot usage: $boot_usage" + +#if (( ${#boot_kernels[@]} > 1 )); then +# echo "Boot ramdisks:" +# hr +# printf ' %s\n' "${boot_kernels[@]}" +# hr +#fi + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# delete extra things now # +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # + +if contains "selections" "a" ; then + # note "Automatic deletion will include: " + # printf "%s\n" "${selections[@]}" + debug "Doing automatic cleanup" + for z in "${selections[@]}"; do + debug "Will perform ${desc[$z]}" + ${cleaners_map[$z]} + done + survey_areas + disk_usage_report + exit 0 +fi + +if (( ${#selections[@]} > 0 )) ; then + debug "Doing selected cleanup: " + # printf " %s\n" "${selections[@]}" + # sleep 1 + for z in "${selections[@]}"; do + debug "Performing ${desc[$z]}" + ${cleaners_map[$z]} + # selections=("${selections[@]/$z}") + remove selections "$z" + done + survey_areas + disk_usage_report +else + debug "No selections present" +fi + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# ask for things to remove if we are interactive # +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +choice="" +refresh=0 +while [[ $choice != q ]]; do + echo "" + hr + df -h --type ext4 + echo "" + hr + echo "What would you like to delete or compress? " + echo " b) old kernels : ${totals[b]}" + echo " c) core crash files : ${totals[c]}" + echo " d) old LANforge downloads : ${totals[d]}" + echo " k) ath10k crash files : ${totals[k]}" + echo " l) old /var/log files : ${totals[l]}" + echo " m) orphaned /mnt/lf files : ${totals[m]}" + echo " n) purge dnf/yum cache : ${totals[n]}" + echo " r) report data : ${totals[r]}" + echo " s) trash cans : ${totals[s]}" + echo " t) clean /var/tmp : ${totals[t]}" + echo " z) list compressed files : ${totals[z]}" + echo " q) quit" + read -p "> " choice + refresh=0 + case "$choice" in + b ) + clean_old_kernels + refresh=1 + ;; + c ) + clean_core_files + refresh=1 + ;; + d ) + clean_lf_downloads + refresh=1 + ;; + k ) + clean_ath10_files + refresh=1 + ;; + l ) + clean_var_log + refresh=1 + ;; + m ) + clean_mnt_lf_files + refresh=1 + ;; + r ) + compress_report_data + refresh=1 + ;; + s ) + empty_trash_can + refresh=1 + ;; + t ) + clean_var_tmp + refresh=1 + ;; + z ) + clean_compressed_files + refresh=1 + ;; + q ) + echo "" + exit + ;; + * ) + echo "not an option [$choice]" + ;; + esac + if (( $refresh > 0 )) ; then + survey_areas + disk_usage_report + fi +done +echo bye diff --git a/lanforge/lanforge-scripts/connectTest.py b/lanforge/lanforge-scripts/connectTest.py new file mode 100755 index 000000000..d504d8ef6 --- /dev/null +++ b/lanforge/lanforge-scripts/connectTest.py @@ -0,0 +1,509 @@ +#!/usr/bin/env python3 +import sys +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) +if 'py-json' not in sys.path: + sys.path.append('py-json') +import traceback + +from LANforge import LFUtils +from LANforge.LFUtils import * +from LANforge.lfcli_base import LFCliBase +from generic_cx import GenericCx + +mgrURL = "http://localhost:8080/" +staName = "sta0" +staNameUri = "port/1/1/" + staName +suppress_related = True + +class ConnectTest(LFCliBase): + def __init__(self, lfhost, lfport): + super().__init__(lfhost, lfport, True) + super().check_connect() + + # compare pre-test values to post-test values + @staticmethod + def CompareVals(_name, preVal, postVal): + print("Comparing %s" % _name) + if postVal > preVal: + print(" Test Passed") + else: + print(" Test Failed: %s did not increase after 5 seconds" % _name) + + def run(self): + print("See home/lanforge/Documents/connectTestLogs/connectTestLatest for specific values on latest test") + + eth1IP = super().json_get("/port/1/1/eth1") + if eth1IP['interface']['ip'] == "0.0.0.0": + print("Warning: Eth1 lacks ip address") + exit(1) + + # Create stations and turn dhcp on + print("Creating station and turning on dhcp") + + response = super().json_get("/" + staNameUri) + if response is not None: + if response["interface"] is not None: + print("removing old station") + removePort(1, staName, mgrURL) + waitUntilPortsDisappear(mgrURL, [staName]) + time.sleep(1) + + url = "cli-json/add_sta" + data = { + "shelf": 1, + "resource": 1, + "radio": "wiphy0", + "sta_name": staName, + "ssid": "jedway-wpa2-x2048-4-4", + "key": "jedway-wpa2-x2048-4-4", + "mode": 0, + "mac": "xx:xx:xx:xx:*:xx", + "flags": (0x400 + 0x20000 + 0x1000000000) # create admin down + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + wait_until_ports_appear(mgrURL, [staName], True) + time.sleep(8) + reqURL = "cli-json/set_port" + data = { + "shelf": 1, + "resource": 1, + "port": staName, + "current_flags": (0x1 + 0x80000000), + "interest": (0x2 + 0x4000 + 0x800000) # current, dhcp, down, + } + super().json_post(reqURL, data, suppress_related_commands_=suppress_related) + time.sleep(0.5) + super().json_post("cli-json/set_port", portUpRequest(1, staName)) + + reqURL = "cli-json/nc_show_ports" + data = {"shelf": 1, + "resource": 1, + "port": staName, + "probe_flags": 1} + super().json_post(reqURL, data, suppress_related_commands_=suppress_related) + time.sleep(0.5) + waitUntilPortsAdminUp(1, mgrURL, [staName]) + + duration = 0 + maxTime = 300 + ip = "0.0.0.0" + while (ip == "0.0.0.0") and (duration < maxTime): + station_info = super().json_get("/" + staNameUri + "?fields=port,ip") + LFUtils.debug_printer.pprint(station_info) + if (station_info is not None) and ("interface" in station_info) and ("ip" in station_info["interface"]): + ip = station_info["interface"]["ip"] + if ip == "0.0.0.0": + duration += 4 + time.sleep(4) + else: + break + + if duration >= maxTime: + print(staName+" failed to get an ip. Ending test") + print("Cleaning up...") + removePort(1, staName, mgrURL) + sys.exit(1) + + print("Creating endpoints and cross connects") + + #==============| ENDPOINT CREATION |================= + # create l4 endpoint + url = "/cli-json/add_l4_endp" + data = { + "alias": "l4Test", + "shelf": 1, + "resource": 1, + "port": staName, + "type": "l4_generic", + "timeout": 1000, + "url_rate": 600, + "url": "dl http://localhost/ /dev/null", + "proxy_port" : "NA" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + data = { + "endpoint": "all" + } + super().json_post("/cli-json/nc_show_endpoints", data, suppress_related_commands_=suppress_related) + time.sleep(5) + + # create fileio endpoint + url = "/cli-json/add_file_endp" + data = { + "alias": "fioTest", + "shelf": 1, + "resource": 1, + "port": staName, + "type": "fe_nfs", + "directory": "/mnt/fe-test" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + time.sleep(1) + data = { + "endpoint": "all" + } + super().json_post("/cli-json/nc_show_endpoints", data) + time.sleep(1) + + # create generic endpoints + genl = GenericCx(lfclient_host=self.lfclient_host, lfclient_port=self.lfclient_port) + genl.createGenEndp("genTest1", 1, 1, staName, "gen_generic") + genl.createGenEndp("genTest2", 1, 1, staName, "gen_generic") + genl.setFlags("genTest1", "ClearPortOnStart", 1) + genl.setFlags("genTest2", "ClearPortOnStart", 1) + genl.setFlags("genTest2", "Unmanaged", 1) + genl.setCmd("genTest1", "lfping -i 0.1 -I %s 10.40.0.1" % staName) + time.sleep(.05) + data = { + "endpoint": "all" + } + super().json_post("/cli-json/nc_show_endpoints", data, suppress_related_commands_=suppress_related) + + # create redirects for wanlink + url = "/cli-json/add_rdd" + data = { + "shelf": 1, + "resource": 1, + "port": "rdd0", + "peer_ifname": "rdd1" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + + url = "/cli-json/add_rdd" + data = { + "shelf": 1, + "resource": 1, + "port": "rdd1", + "peer_ifname": "rdd0" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + time.sleep(.05) + + # reset redirect ports + url = "/cli-json/reset_port" + data = { + "shelf": 1, + "resource": 1, + "port": "rdd0" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + + url = "/cli-json/reset_port" + data = { + "shelf": 1, + "resource": 1, + "port": "rdd1" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + time.sleep(.05) + + # create wanlink endpoints + url = "/cli-json/add_wl_endp" + data = { + "alias": "wlan0", + "shelf": 1, + "resource": 1, + "port": "rdd0", + "latency": 20, + "max_rate": 1544000 + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + + url = "/cli-json/add_wl_endp" + data = { + "alias": "wlan1", + "shelf": 1, + "resource": 1, + "port": "rdd1", + "latency": 30, + "max_rate": 1544000 + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + time.sleep(.05) + data = { + "endpoint": "all" + } + super().json_post("/cli-json/nc_show_endpoints", data, suppress_related_commands_=suppress_related) + + time.sleep(10) + + #==============| CX CREATION |=================== + # create cx for tcp and udp + cmd = ("./lf_firemod.pl --action create_cx --cx_name testTCP --use_ports %s,eth1 --use_speeds 360000," + "150000 --endp_type tcp > ~/Documents/connectTestLogs/connectTestLatest.log" % staName) + execWrap(cmd) + cmd = ("./lf_firemod.pl --action create_cx --cx_name testUDP --use_ports %s,eth1 --use_speeds 360000," + "150000 --endp_type udp >> ~/Documents/connectTestLogs/connectTestLatest.log" % staName) + execWrap(cmd) + time.sleep(.05) + + # create cx for l4_endp + url = "/cli-json/add_cx" + data = { + "alias": "CX_l4Test", + "test_mgr": "default_tm", + "tx_endp": "l4Test", + "rx_endp": "NA" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + time.sleep(.05) + + # create fileio cx + url = "/cli-json/add_cx" + data = { + "alias": "CX_fioTest", + "test_mgr": "default_tm", + "tx_endp": "fioTest", + "rx_endp": "NA" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + time.sleep(.05) + + # create generic cx + url = "/cli-json/add_cx" + data = { + "alias": "CX_genTest1", + "test_mgr": "default_tm", + "tx_endp": "genTest1", + "rx_endp": "genTest2" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + time.sleep(.05) + + # create wanlink cx + url = "/cli-json/add_cx" + data = { + "alias": "CX_wlan0", + "test_mgr": "default_tm", + "tx_endp": "wlan0", + "rx_endp": "wlan1" + } + super().json_post(url, data, suppress_related_commands_=suppress_related) + time.sleep(.5) + data = { + "endpoint": "all" + } + super().json_post("/cli-json/nc_show_endpoints", data, suppress_related_commands_=suppress_related) + + cxNames = ["testTCP", "testUDP", "CX_l4Test", "CX_fioTest", "CX_genTest1", "CX_wlan0"] + + # get data before running traffic + try: + get_info = {} + sleep(5) + get_info['testTCPA'] = super().json_get("/endp/testTCP-A?fields=tx+bytes,rx+bytes") + get_info['testTCPB'] = super().json_get("/endp/testTCP-B?fields=tx+bytes,rx+bytes") + get_info['testUDPA'] = super().json_get("/endp/testUDP-A?fields=tx+bytes,rx+bytes") + get_info['testUDPB'] = super().json_get("/endp/testUDP-B?fields=tx+bytes,rx+bytes") + get_info['l4Test'] = super().json_get("/layer4/l4Test?fields=bytes-rd") + get_info['genTest1'] = super().json_get("/generic/genTest1?fields=last+results") + get_info['wlan0'] = super().json_get("/wl_ep/wlan0") + get_info['wlan1'] = super().json_get("/wl_ep/wlan1") + + for name in get_info: + #print("==================\n"+name+"\n====================") + if 'endpoint' not in get_info[name]: + print(get_info[name]) + raise ValueError ("%s missing endpoint value" % name) + + testTCPATX = get_info['testTCPA']['endpoint']['tx bytes'] + testTCPARX = get_info['testTCPA']['endpoint']['rx bytes'] + testTCPBTX = get_info['testTCPB']['endpoint']['tx bytes'] + testTCPBRX = get_info['testTCPB']['endpoint']['rx bytes'] + + testUDPATX = get_info['testUDPA']['endpoint']['tx bytes'] + testUDPARX = get_info['testUDPA']['endpoint']['rx bytes'] + testUDPBTX = get_info['testUDPB']['endpoint']['tx bytes'] + testUDPBRX = get_info['testUDPB']['endpoint']['rx bytes'] + + l4TestBR = get_info['l4Test']['endpoint']['bytes-rd'] + genTest1LR = get_info['genTest1']['endpoint']['last results'] + + wlan0TXB = get_info['wlan0']['endpoint']['tx bytes'] + wlan0RXP = get_info['wlan0']['endpoint']['rx pkts'] + wlan1TXB = get_info['wlan1']['endpoint']['tx bytes'] + wlan1RXP = get_info['wlan1']['endpoint']['rx pkts'] + except Exception as e: + print("Something went wrong") + print(e) + print("Cleaning up...") + time.sleep(15) + LFUtils.removePort(1, staName, mgrURL) + endpNames = ["testTCP-A", "testTCP-B", + "testUDP-A", "testUDP-B", + "l4Test", "fioTest", + "genTest1", "genTest2", + "wlan0", "wlan1"] + removeCX(mgrURL, cxNames) + removeEndps(mgrURL, endpNames) + traceback.print_stack() + sys.exit(1) + + # start cx traffic + print("\nStarting CX Traffic") + for name in range(len(cxNames)): + cmd = ( + "./lf_firemod.pl --mgr localhost --quiet yes --action do_cmd --cmd \"set_cx_state default_tm %s RUNNING\" >> /tmp/connectTest.log" % (cxNames[name])) + execWrap(cmd) + + # print("Sleeping for 5 seconds") + time.sleep(5) + + # show tx and rx bytes for ports + + os.system("echo eth1 >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_portmod.pl --quiet 1 --manager localhost --port_name eth1 --show_port \"Txb,Rxb\" >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo %s >> ~/Documents/connectTestLogs/connectTestLatest.log" % staName) + cmd = ( + "./lf_portmod.pl --quiet 1 --manager localhost --port_name %s --show_port \"Txb,Rxb\" >> ~/Documents/connectTestLogs/connectTestLatest.log" % staName) + execWrap(cmd) + + # show tx and rx for endpoints PERL + os.system("echo TestTCP-A >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name testTCP-A --endp_vals \"Tx Bytes,Rx Bytes\" >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo TestTCP-B >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name testTCP-B --endp_vals \"Tx Bytes,Rx Bytes\" >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo TestUDP-A >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name testUDP-A --endp_vals \"Tx Bytes,Rx Bytes\" >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo TestUDP-B >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name testUDP-B --endp_vals \"Tx Bytes,Rx Bytes\" >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo l4Test >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name l4Test --endp_vals Bytes-Read-Total >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo fioTest >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name fioTest --endp_vals \"Bytes Written,Bytes Read\" >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo genTest1 >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name genTest1 >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo wlan0 >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name wlan0 --endp_vals \"Rx Pkts,Tx Bytes,Cur-Backlog,Dump File,Tx3s\" >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + os.system("echo wlan1 >> ~/Documents/connectTestLogs/connectTestLatest.log") + cmd = ( + "./lf_firemod.pl --action show_endp --endp_name wlan1 --endp_vals \"Rx Pkts,Tx Bytes,Cur-Backlog,Dump File,Tx3s\" >> ~/Documents/connectTestLogs/connectTestLatest.log") + execWrap(cmd) + + # stop cx traffic + print("Stopping CX Traffic") + for name in range(len(cxNames)): + cmd = ( + "./lf_firemod.pl --mgr localhost --quiet yes --action do_cmd --cmd \"set_cx_state default_tm %s STOPPED\" >> /tmp/connectTest.log" % (cxNames[name])) + execWrap(cmd) + # print("Sleeping for 15 seconds") + time.sleep(15) + + # get data for endpoints JSON + print("Collecting Data") + try: + + ptestTCPA = super().json_get("endp/testTCP-A?fields=tx+bytes,rx+bytes") + ptestTCPATX = ptestTCPA['endpoint']['tx bytes'] + ptestTCPARX = ptestTCPA['endpoint']['rx bytes'] + + ptestTCPB = super().json_get("/endp/testTCP-B?fields=tx+bytes,rx+bytes") + ptestTCPBTX = ptestTCPB['endpoint']['tx bytes'] + ptestTCPBRX = ptestTCPB['endpoint']['rx bytes'] + + ptestUDPA = super().json_get("/endp/testUDP-A?fields=tx+bytes,rx+bytes") + ptestUDPATX = ptestUDPA['endpoint']['tx bytes'] + ptestUDPARX = ptestUDPA['endpoint']['rx bytes'] + + ptestUDPB = super().json_get("/endp/testUDP-B?fields=tx+bytes,rx+bytes") + ptestUDPBTX = ptestUDPB['endpoint']['tx bytes'] + ptestUDPBRX = ptestUDPB['endpoint']['rx bytes'] + + pl4Test = super().json_get("/layer4/l4Test?fields=bytes-rd") + pl4TestBR = pl4Test['endpoint']['bytes-rd'] + + pgenTest1 = super().json_get("/generic/genTest1?fields=last+results") + pgenTest1LR = pgenTest1['endpoint']['last results'] + + pwlan0 = super().json_get("/wl_ep/wlan0") + pwlan0TXB = pwlan0['endpoint']['tx bytes'] + pwlan0RXP = pwlan0['endpoint']['rx pkts'] + pwlan1 = super().json_get("/wl_ep/wlan1") + pwlan1TXB = pwlan1['endpoint']['tx bytes'] + pwlan1RXP = pwlan1['endpoint']['rx pkts'] + except Exception as e: + print("Something went wrong") + print(e) + print("Cleaning up...") + time.sleep(15) + reqURL = "/cli-json/rm_vlan" + data = { + "shelf": 1, + "resource": 1, + "port": staName + } + super().json_post(reqURL, data, suppress_related_commands_=suppress_related) + + endpNames = ["testTCP-A", "testTCP-B", + "testUDP-A", "testUDP-B", + "l4Test", "fioTest", + "genTest1", "genTest2", + "wlan0", "wlan1"] + removeCX(mgrURL, cxNames) + removeEndps(mgrURL, endpNames) + sys.exit(1) + + # print("Sleeping for 5 seconds") + time.sleep(5) + + print("\n") + self.CompareVals("testTCP-A TX", testTCPATX, ptestTCPATX) + self.CompareVals("testTCP-A RX", testTCPARX, ptestTCPARX) + self.CompareVals("testTCP-B TX", testTCPBTX, ptestTCPBTX) + self.CompareVals("testTCP-B RX", testTCPBRX, ptestTCPBRX) + self.CompareVals("testUDP-A TX", testUDPATX, ptestUDPATX) + self.CompareVals("testUDP-A RX", testUDPARX, ptestUDPARX) + self.CompareVals("testUDP-B TX", testUDPBTX, ptestUDPBTX) + self.CompareVals("testUDP-B RX", testUDPBRX, ptestUDPBRX) + self.CompareVals("l4Test Bytes Read", l4TestBR, pl4TestBR) + self.CompareVals("genTest1 Last Results", genTest1LR, pgenTest1LR) + self.CompareVals("wlan0 TX Bytes", wlan0TXB, pwlan0TXB) + self.CompareVals("wlan0 RX Pkts", wlan0RXP, pwlan0RXP) + self.CompareVals("wlan1 TX Bytes", wlan1TXB, pwlan1TXB) + self.CompareVals("wlan1 RX Pkts", wlan1RXP, pwlan1RXP) + print("\n") + + # remove all endpoints and cxs + print("Cleaning up...") + LFUtils.removePort(1, staName, mgrURL) + + endpNames = ["testTCP-A", "testTCP-B", + "testUDP-A", "testUDP-B", + "l4Test", "fioTest", + "genTest1", "genTest2", + "wlan0", "wlan1"] + removeCX(mgrURL, cxNames) + removeEndps(mgrURL, endpNames) + + +# ~class + +def main(): + lfclient_host = "localhost" + lfclient_port = 8080 + test = ConnectTest(lfclient_host, lfclient_port) + test.run() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/countdown.bash b/lanforge/lanforge-scripts/countdown.bash new file mode 100755 index 000000000..5998c94f4 --- /dev/null +++ b/lanforge/lanforge-scripts/countdown.bash @@ -0,0 +1,19 @@ +#!/bin/bash +ticker_time="$1" +orig_time="$1" +#echo "" +while [[ $ticker_time -gt 0 ]]; do + sleep 1 + ticker_time=$(( $ticker_time - 1 )) + countd=$orig_time + printf "\r" + for n in `seq 0 $orig_time`; do + if [[ $n -lt $ticker_time ]]; then + echo -n '#' + else + echo -n '-' + fi + done + echo -n " $ticker_time/$orig_time" +done +echo "" \ No newline at end of file diff --git a/lanforge/lanforge-scripts/cpu_stats.py b/lanforge/lanforge-scripts/cpu_stats.py new file mode 100644 index 000000000..02d4989d9 --- /dev/null +++ b/lanforge/lanforge-scripts/cpu_stats.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +''' Author: Nikita Yadav + + This script will calculate the cpu and memory utilization by the system during runtime the output of which is a graph on html page also the second tab of html gives the system logs + +steps to run script: +1- On mate terminal type – python3 (file_name) -t (your duration in minutes for how much time you want to run the script). +2- Example – python3 cpu_stat.py -t 1 +3- Wait for the time provided to calculate statistics. +4- After the script ends check for log.html file. +5- Log.html file file show results in tab format selected. + +''' + +import argparse +import os +import matplotlib.pyplot as plt +import datetime +import base64 +import logging +import threading +import time + +fh = '' +fh_1 = '' + +def cpu_percent(cmd): + global fh + fh = os.popen(cmd) + +def memory_percent(cmd): + global fh_1 + fh_1 = os.popen(cmd) + +def htmlimage(data_4, data): + html = open("log.html", 'w') + img = data_4 + " " + data + html.write(img) + +def main(): + global duration + parser = argparse.ArgumentParser() + parser.add_argument("-t", "--duration", type=int, help="Enter the Time for which you want to run test (in minutes)") + try: + args = parser.parse_args() + if (args.duration is not None): + duration = args.duration + except Exception as e: + logging.exception(e) + exit(2) + endTime = datetime.datetime.now() + datetime.timedelta(seconds=duration) + now = datetime.datetime.now() + starttime = datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, now.second) + delta = datetime.timedelta(seconds=10) + cpu_stats_data = {"system": [], "kernel": []} + memory_stats_data = {"Total": [], "Used": []} + iterations = duration * 60 + + cmd_1 = "top -bn1 -d 1 -n " + str(iterations) + " | grep '%Cpu(s)' " + cmd_2 = "top -bn1 -d 1 -n " + str(iterations) + " | grep 'MiB Mem'" + + t1 = threading.Thread(target=cpu_percent, args=(cmd_1,)) + t2 = threading.Thread(target=memory_percent, args=(cmd_2,)) + + t1.start() + t2.start() + t1.join() + t2.join() + time.sleep(10) + + output = fh.read() + fh.readline() + output_1 = fh_1.read() + fh_1.readline() + data = output.split('\n') + data_1 = output_1.split('\n') + for i in data: + # print(i.split(',')) + if len(i) > 3: + cpu_stats_data["system"].append(float(i.split(',')[0].split()[1])) + cpu_stats_data["kernel"].append(float(i.split(',')[1].split()[0])) + # print(cpu_stats_data) + + for i in data_1: + if len(i) > 3: + memory_stats_data["Total"].append(float(i.split(',')[0].split()[3])) + memory_stats_data["Used"].append(float(i.split(',')[2].split()[0])) + # print(memory_stats_data) + sample_times = [starttime + i * delta for i in range(len(cpu_stats_data["system"]))] + label_locations = [d for d in sample_times if d.minute % 1 == 0] + labels = [d.strftime('%Y-%m-%d %H:%M:%S') for d in label_locations] + # print(labels) + #print(sample_times) + # thread creation + + # graphs + #plot1 + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + ax.plot(sample_times, cpu_stats_data['system'], '-', lw=1, color='r', label="system cpu%") + ax.plot(sample_times, cpu_stats_data['kernel'], '-', lw=1, color='b', label="kernel cpu%") + ax.set_ylabel('CPU (%)', color='r') + ax.set_xlabel('time (s)') + plt.tight_layout() + ax.set_ylim(0., max(cpu_stats_data['system']) + 20) + plt.xticks(rotation='vertical') + fig.legend(["System CPU Utilization", "Kernel CPU Utilization"], loc='upper center') + ax.grid() + fig.savefig("cpu.png") + + #plot2 + fig_1 = plt.figure() + ax_1 = fig_1.add_subplot(1, 1, 1) + ax_1.plot(sample_times, memory_stats_data["Total"], '-', lw=1, color='r', label="Total MEMORY AVAILABLE") + ax_1.plot(sample_times, memory_stats_data["Used"], '-', lw=1, color='b', label="Total MEMORY USED") + ax_1.set_ylabel('Total available', color='r') + ax_1.set_xlabel('time (s)') + plt.tight_layout() + ax_1.set_ylim(0., max(memory_stats_data["Total"]) + 2000) + plt.xticks(rotation='vertical') + fig_1.legend(["TOTAL MEMORY AVAILABLE", "TOTAL MEMORY USED"], loc='upper center') + ax_1.grid() + fig_1.savefig("MEMORY.png") + + cmd_1 = "timeout 2s journalctl -p err --since '24 hour ago' > syslog.txt" + fh_2 = os.system(cmd_1) + fi_open = open("syslog.txt", "r+") + out = fi_open.read() + data_3 = out.split(" ") + data_uri = base64.b64encode(open('cpu.png', 'rb').read()).decode('utf-8') + data_uri_1 = base64.b64encode(open('MEMORY.png', 'rb').read()).decode('utf-8') + data_a = ''.format( + data_uri) + " " + ''.format(data_uri_1) + + data_4 = "

" + data_a + "

" + logs = " " + for i in data_3: + logs = logs + i + data = "
" + logs + "
" + htmlimage(data_4, data) + +if __name__ == '__main__': + main() + + + + + + + + diff --git a/lanforge/lanforge-scripts/create-mounts.sh b/lanforge/lanforge-scripts/create-mounts.sh new file mode 100755 index 000000000..dbf3d7536 --- /dev/null +++ b/lanforge/lanforge-scripts/create-mounts.sh @@ -0,0 +1,101 @@ +#!/bin/bash +#set -x + +CIFS_USERNAME="lanforge" +CIFS_PASSWORD="lanforge" +NFS_SRV="192.168.100.3" +NFS_PATH="/mnt/d2" +CIFS_SRV="192.168.100.3" +CIF_PATH="/mnt/d2" +LOCAL_MOUNT_PATH="/mnt" +NFS_OPTS="" + +if [ $# -lt 4 ]; then + echo "Usage: `basename $0` NFS|CIFS " + exit 1 +fi + + +IF=$2 +MV_START=$3 +MV_STOP=$4 +if [ ! -z "$5" ] +then + NFS_SRV=$5 + CIFS_SRV=$5 +fi + +if [ ! -z "$6" ] +then + NFS_PATH=$6 + CIFS_PATH=$6 +fi + +if [ ! -z "$7" ] +then + LOCAL_MOUNT_PATH=$7 +fi + +if [ $1 = "CIFS" ]; then + LOCAL_PATH="$LOCAL_MOUNT_PATH/cifs_${IF}#" + CIFS_OPTS="username=$CIFS_USERNAME,password=$CIFS_PASSWORD,$CIFS_OPTS" +else + LOCAL_PATH="$LOCAL_MOUNT_PATH/nfs_${IF}#" +fi + +LIP=clientaddr +if uname -a | grep 2.6.20 +then + LIP=local_ip +fi + +for ((m=MV_START; m <= MV_STOP ; m++)) +do + if [ `ifconfig $IF#$m > /dev/null 2>&1; echo $?` -eq "1" ]; then + echo "*** MISSING INTERFACE: $IF#$m" + echo + elif [ `ifconfig $IF#$m | grep "inet addr" > /dev/null; echo $?` -eq "1" ]; then + echo "*** MISSING IP ADDRESS ON INTERFACE: $IF#$m" + else + if [ ! -d "$LOCAL_PATH$m" ]; then + echo "mkdir -p $LOCAL_PATH$m" + mkdir -p $LOCAL_PATH$m + fi + IPADDR=`ifconfig $IF#$m | grep "inet addr" | awk -F":" '{ print $2}' |\ + awk '{ print $1}'` + # Ping seems to fail sometimes..probably file-server is under too much load or something + # so try the ping up to 5 times. + for ((q=0;q<5;q+=1)) + do + if [ `ping -c 1 -w 1 -I $IPADDR $NFS_SRV > /dev/null; echo $?` -eq "0" ]; then + q=10; # done + if [ $1 = "CIFS" ]; then + echo "mount -t cifs -o local_ip=$IPADDR,$CIFS_OPTS //$CIFS_SRV$CIFS_PATH $LOCAL_PATH$m" + if [ `mount -t cifs -o local_ip=$IPADDR,$CIFS_OPTS //$CIFS_SRV$CIFS_PATH $LOCAL_PATH$m >\ + /dev/null; echo $?` -ne "0" ]; then + echo + fi + else + echo "mount -t nfs -o $LIP=$IPADDR,$NFS_OPTS $NFS_SRV:$NFS_PATH $LOCAL_PATH$m" + if [ `mount -t nfs -o $LIP=$IPADDR,$NFS_OPTS $NFS_SRV:$NFS_PATH $LOCAL_PATH$m >\ + /dev/null; echo $?` -ne "0" ]; then + echo + fi + fi + else + echo "*** UNABLE TO PING: $NFS_SRV FROM: $IF#$m, $IPADDR" + fi + done + fi +done +echo "********************************************" +if [ $1 = "CIFS" ]; then + echo "Total number of mounts according to 'mount': `mount | grep "$CIFS_SRV$CIFS_PATH" |\ + grep "$LOCAL_PATH" | grep "type cifs" |\ + wc | awk '{ print $1 }'`" +else + echo "Total number of NFS mounts according to 'mount': `mount | grep "$NFS_SRV:$NFS_PATH" |\ + grep "$LOCAL_PATH" | grep -i "type $1" | grep "$LIP=" | grep "addr=$NFS_SRV" |\ + wc | awk '{ print $1 }'`" + echo +fi diff --git a/lanforge/lanforge-scripts/create_file_assortment.bash b/lanforge/lanforge-scripts/create_file_assortment.bash new file mode 100755 index 000000000..545e72caa --- /dev/null +++ b/lanforge/lanforge-scripts/create_file_assortment.bash @@ -0,0 +1,25 @@ +#!/bin/bash + +[ -z "$1" ] && echo "Please tell me where to place the files." && exit 1 +[ ! -d "$1" ] && echo "I cannot see that directory." && exit 1 +[ ! -w "$1" ] && echo "I cannot write to that directory." && exit 1 + +sizes=( 4K 48K 128K 256K 2048K ) +name_prefix="data_slug" +index="$1/slug_list.html" +cat > $index < +Files of random data + + +

Files of random data

+
    +EOF +for s in "${sizes[@]}"; do + fname="${name_prefix}_${s}.bin" + echo "
  • $fname
  • " >> $index + dd if=/dev/urandom of="$1/$fname" iflag=fullblock oflag=direct bs=${s} count=1 +done +echo "
" >> $index +ls -lSs $1/$name_prefix* +# diff --git a/lanforge/lanforge-scripts/create_group_matching.sh b/lanforge/lanforge-scripts/create_group_matching.sh new file mode 100755 index 000000000..47022898b --- /dev/null +++ b/lanforge/lanforge-scripts/create_group_matching.sh @@ -0,0 +1,67 @@ +#!/bin/bash +usage="$0 " +if [[ x$1 = x ]]; then + echo "Please provide lanforge manager hostname or ip" + echo $usage + exit 1 +fi +if [[ x$2 = x ]]; then + echo "Please provide a group name" + echo $usage + exit 1 +fi +if [[ x$3 = x ]]; then + echo "Please provide an endpoint name prefix" + echo $usage + exit 1 +fi + +# query for endpoints maching prefix +if [ -f /tmp/endp.$$ ]; then + rm -f /tmp/endp.$$ +fi + +./lf_firemod.pl --mgr $1 --quiet yes --action list_endp > /tmp/endp.$$ +lines=`wc -l < /tmp/endp.$$` +if [ $lines -lt 1 ]; then + echo "Unable to see any endpoints" + exit 1 +fi + +declare -A names +while read line; do + hunks=($line) + endp="${hunks[1]}" + endp=${endp/]/} + endp=${endp/[/} + if [[ $endp = D_* ]]; then + continue + fi + + # if name lacks a -A/B ending, it is prolly generic and the cx begins with CX_ + if [[ $endp = *-A ]] || [[ $endp = *-B ]]; then + cxname=${endp%-A} + cxname=${cxname%-B} + else + cxname="CX_${endp}" + fi + if [[ $cxname = $3* ]] || [[ $cxname = CX_$3* ]]; then + names[$cxname]=1 + fi +done < /tmp/endp.$$ + +if [ ${#names[@]} -lt 1 ]; then + echo "No connections start with $3" + exit 1 +fi + +echo "Creating group $2" +./lf_firemod.pl --mgr $1 --quiet 1 --cmd "add_group '$2'" + + +for n in ${!names[@]}; do + echo "adding connection $n to '$2'" + ./lf_firemod.pl --mgr $1 --quiet 1 --cmd "add_tgcx '$2' '$n'" +done + + diff --git a/lanforge/lanforge-scripts/db_sorter.sh b/lanforge/lanforge-scripts/db_sorter.sh new file mode 100755 index 000000000..fcc69d965 --- /dev/null +++ b/lanforge/lanforge-scripts/db_sorter.sh @@ -0,0 +1,28 @@ +#!/bin/bash +#This script modifies DB directories so they appear alphabetically in LANforge. + +# Script instructions +# First become root: su - +# Copy the script to /home/lanforge/scripts/ +# cp db_sorter.sh /home/lanforge/scripts/ +# Make the script executable: +# chmod +x /home/lanforge/scripts/db_sorter.sh +# Run script: +# /home/lanforge/scripts/db_sorter.sh + +# If your databases are not in /home/lanforge/DB/ change the below line to reflect your DB directory's location. +db_dir="/home/lanforge/DB/" + +# grab alphabetical list then use awk to just get dir name +# to sort reverse alphabetical order change ls -lr below to ls -l +dir_list=`ls -lr $db_dir | awk ' ''{print $9}' | grep -v "day_*"` + +# goes through list, creates/removes file to trigger dir modified date +while read -r line; do + if [[ $line != "" ]] && [[ -d ${db_dir}/${line} ]] && [[ $line != "day_*" ]]; then + touch "${db_dir}/${line}/a" + rm "${db_dir}/${line}/a" + fi + #needs sleep otherwise file mod date does not order correctly + sleep .01s +done <<< "$dir_list" diff --git a/lanforge/lanforge-scripts/dhcp-lease-list.pl b/lanforge/lanforge-scripts/dhcp-lease-list.pl new file mode 100755 index 000000000..382c4444d --- /dev/null +++ b/lanforge/lanforge-scripts/dhcp-lease-list.pl @@ -0,0 +1,221 @@ +#!/usr/bin/perl +# +# Shows current leases. +# +# THIS SCRIPT IS PUBLIC DOMAIN, NO RIGHTS RESERVED! +# +# I've removed the email addresses of Christian and vom to avoid +# putting them on spam lists. If either of you would like to have +# your email in here please send mail to the DHCP bugs list at ISC. +# +# 2008-07-13, Christian Hammers +# +# 2009-06-?? - added loading progress counter, pulls hostname, adjusted formatting +# vom +# +# 2013-04-22 - added option to choose lease file, made manufacture information +# optional, sar +# +# 2016-01-19 - updated to better trim the manu string and output the hostnames, sar +# +# 2016-01-18 - Mainly cosmetics. Eliminated spurious output in "parsable" mode. +# Provided for the various conventional lease file locations. (cbp) + +use strict; +use warnings; +use POSIX qw(strftime); + +my @LEASES = ('/var/db/dhcpd.leases', '/var/lib/dhcp/dhcpd.leases', '/var/lib/dhcp3/dhcpd.leases'); +my @all_leases; +my @leases; + +my @OUIS = ('/usr/share/hwdata/oui.txt', '/usr/share/misc/oui.txt', '/usr/local/etc/oui.txt'); +my $OUI_URL = 'http://standards.ieee.org/regauth/oui/oui.txt'; +my $oui; + +my %data; + +my $opt_format = 'human'; +my $opt_keep = 'active'; + +our $total_leases = 0; + +## Return manufactorer name for specified MAC address (aa:bb:cc:dd:ee:ff). +sub get_manufactorer_for_mac($) { + my $manu = "-NA-"; + + if (defined $oui) { + $manu = join('-', ($_[0] =~ /^(..):(..):(..):/)); + $manu = `grep -i '$manu' $oui | cut -f3`; + $manu =~ s/^\s+|\s+$//g; + } + + return $manu; +} + +## Read oui.txt or print warning. +sub check_oui_file() { + + for my $oui_cand (@OUIS) { + if ( -r $oui_cand) { + $oui = $oui_cand; + last; + } + } + + if (not defined $oui) { + print(STDERR "To get manufacturer names please download $OUI_URL "); + print(STDERR "to /usr/local/etc/oui.txt\n"); + } +} + +## Read current leases file into array. +sub read_dhcpd_leases() { + + my $db; + for my $db_cand (@LEASES) { + if ( -r $db_cand) { + $db = $db_cand; + last; + } + } + die("Cannot find leases db") unless defined $db; + open(F, $db) or die("Cannot open $db: $!"); + print("Reading leases from $db\n") if $opt_format eq 'human'; + my $content = join('', ); + close(F); + @all_leases = split(/lease/, $content); + + foreach my $lease (@all_leases) { + if ($lease =~ /^\s+([\.\d]+)\s+{.*starts \d+ ([\/\d\ \:]+);.*ends \d+ ([\/\d\ \:]+);.*ethernet ([a-f0-9:]+);/s) { + ++$total_leases; + } + } +} + +## Add manufactor name and sort out obsolet assignements. +sub process_leases() { + my $gm_now = strftime("%Y/%m/%d %H:%M:%S", gmtime()); + my %tmp_leases; # for sorting and filtering + + my $counter = $opt_format eq 'human' ? 1 : 0; + + # parse entries + foreach my $lease (@all_leases) { + # skip invalid lines + next if not ($lease =~ /^\s+([\.\d]+)\s+{.*starts \d+ ([\/\d\ \:]+);.*ends \d+ ([\/\d\ \:]+);.*ethernet ([a-f0-9:]+);(.*client-hostname \"(\S+)\";)*/s); + # skip outdated lines + next if ($opt_keep eq 'active' and $3 lt $gm_now); + + # I like 'human' output, but this part is not helpful when parsing that human output by another script. --Ben + #if ($counter) { + # my $percent = (($counter / $total_leases)*100); + # printf "Processing: %2d%% complete\r", $percent; + # ++$counter; + #} + + my $hostname = "-NA-"; + if ($6) { + $hostname = $6; + } + + my $mac = $4; + my $date_end = $3; + my %entry = ( + 'ip' => $1, + 'date_begin' => $2, + 'date_end' => $date_end, + 'mac' => $mac, + 'hostname' => $hostname, +# Too slow. --Ben 'manu' => get_manufactorer_for_mac($mac), + 'manu' => "", + ); + + $entry{'date_begin'} =~ s#\/#-#g; # long live ISO 8601 + $entry{'date_end'} =~ s#\/#-#g; + + if ($opt_keep eq 'all') { + push(@leases, \%entry); + } elsif (not defined $tmp_leases{$mac} or $tmp_leases{$mac}{'date_end'} gt $date_end) { + $tmp_leases{$mac} = \%entry; + } + } + + # In case we used the hash to filtered + if (%tmp_leases) { + foreach (sort keys %tmp_leases) { + my $h = $tmp_leases{$_}; + push(@leases, $h); + } + } + + # print "\n"; + +} + +# Output all valid leases. +sub output_leases() { + if ($opt_format eq 'human') { + printf "%-19s%-16s%-15s%-20s%-20s\n","MAC","IP","hostname","valid until","manufacturer"; + print("===============================================================================================\n"); + } + foreach (@leases) { + if ($opt_format eq 'human') { + printf("%-19s%-16s%-14.14s %-20s%-20s\n", + $_->{'mac'}, # MAC + $_->{'ip'}, # IP address + $_->{'hostname'}, # hostname + $_->{'date_end'}, # Date + $_->{'manu'}); # manufactor name + } else { + printf("MAC %s IP %s HOSTNAME %s BEGIN %s END %s MANUFACTURER %s\n", + $_->{'mac'}, + $_->{'ip'}, + $_->{'hostname'}, + $_->{'date_begin'}, + $_->{'date_end'}, + $_->{'manu'}); + } + } +} + +# Commandline Processing. +sub cli_processing() { + while (my $arg = shift(@ARGV)) { + if ($arg eq '--help') { + print( + "Prints active DHCP leases.\n\n". + "Usage: $0 [options]\n". + " --help shows this help\n". + " --parsable machine readable output with full dates\n". + " --last prints the last (even if end 40.0) { + $maxtemp = $temp if ($temp > $maxtemp); + #print "temp($temp) maxtemp($maxtemp)\n"; + } + $temp = 0; + } + $found_a10k = 0; + } +} + +my $duty = 0; +if ($maxtemp < 40) { + $duty = 0; +} +elsif ($maxtemp < 50) { + $duty = 50; +} +elsif ($maxtemp < 56) { + $duty = 55; +} +elsif ($maxtemp < 60) { + $duty = 60; +} +elsif ($maxtemp < 70) { + $duty = 70; +} +elsif ($maxtemp < 80) { + $duty = 80; +} +elsif ($maxtemp >= 80) { + $duty = 100; +} + +#print "[$maxtemp]C -> duty[$duty]\n"; +system("/usr/bin/logger -t fanctl_lf0312 'temp:$maxtemp C, duty:$duty'"); +exec("$fan_util $duty"); + +# diff --git a/lanforge/lanforge-scripts/four_million.pl b/lanforge/lanforge-scripts/four_million.pl new file mode 100755 index 000000000..72bcccd46 --- /dev/null +++ b/lanforge/lanforge-scripts/four_million.pl @@ -0,0 +1,92 @@ +#!/usr/bin/perl +use strict; +use warnings; +use diagnostics; +use Carp; +#$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +#$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +use POSIX qw(ceil floor); +use Scalar::Util; #::looks_like_number; +use Getopt::Long; +use Socket; +use Cwd qw(getcwd); +my $cwd = getcwd(); + + +package main; + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use List::Util qw(first); +use LANforge::Endpoint; +use LANforge::Utils; +use Net::Telnet (); + +my $lfmgr_host = "jedway3"; +my $lfmgr_port = 4001; + +our $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +our $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +our $quiet = 1; +if ($utils->isQuiet()) { + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + $utils->cli_send_silent(0); + } + else { + $utils->cli_send_silent(1); # Do not show input to telnet + } + $utils->cli_rcv_silent(1); # Repress output from telnet +} +else { + $utils->cli_send_silent(0); # Show input to telnet + $utils->cli_rcv_silent(0); # Show output from telnet +} +$utils->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); + + +my $num_connects = 4000; +my $num_vlans = 1000; +my @connections = (); + +my $index = 0; +my $portnum = 0; + +my $n = 0; +for (my $c = 1; $c <= $num_connects; $c++) { + $n = (10 * $num_connects) + $c; + push(@::connections, "con".substr("$n", 1)); +} +my @cmds = (); +foreach my $con_name (@::connections) { + @cmds = ( + "add_endp ${con_name}-A 1 1 rd0a#$portnum lf_tcp -1 NO 2400 2400 NO 300 300 increasing", + "set_endp_report_timer ${con_name}-A 15000", + "set_endp_details ${con_name}-A 8912 8912", + "add_endp ${con_name}-B 1 1 rd1b lf_tcp -1 NO 2400 2400 NO 300 300 increasing", + "set_endp_report_timer ${con_name}-B 15000", + "set_endp_details ${con_name}-B 8912 8912", + "add_cx ${con_name} default_tm ${con_name}-A ${con_name}-B", + "set_cx_report_timer default_tm ${con_name} 15000 cxonly", + ); + foreach my $cmd (@cmds) { + $utils->doCmd($cmd); + print "."; + #sleep 1; + } + print "0"; +} +# diff --git a/lanforge/lanforge-scripts/four_million.sh b/lanforge/lanforge-scripts/four_million.sh new file mode 100755 index 000000000..274d494a3 --- /dev/null +++ b/lanforge/lanforge-scripts/four_million.sh @@ -0,0 +1,31 @@ +#!/bin/bash +export PATH=".:$PATH" +FM="./lf_firemod.pl" +mgr=idtest +rsrc=3 +max=1000 +numvlan=199 +connections=() +for c in `seq 1 $max`; do + n=$(( (10 * $max) + $c )) + connections[$c]="con${n:1:${#max}}" +done + +index=0 +portnum=0 +for c in "${connections[@]}"; do + echo -n . + $FM --mgr $mgr --resource $rsrc --action create_endp --endp_name ${c}-A --speed 25000 --endp_type lf_tcp --port_name "eth1#$portnum" + + $FM --mgr $mgr --resource $rsrc --action create_endp --endp_name ${c}-B --speed 25000 --endp_type lf_tcp --port_name "eth2" + + $FM --mgr $mgr --resource $rsrc --action create_cx --cx_name ${c} --cx_endps ${c}-A,${c}-B --report_timer 8000 + echo -n o + + index=$((index + 1)) + portnum=$((index % $numvlan)) +done +echo " done" + + +# diff --git a/lanforge/lanforge-scripts/ftp-upload.pl b/lanforge/lanforge-scripts/ftp-upload.pl new file mode 100755 index 000000000..15e6397de --- /dev/null +++ b/lanforge/lanforge-scripts/ftp-upload.pl @@ -0,0 +1,105 @@ +#!/usr/bin/perl -w + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## +## Use this script to collect and upload station data +## to an FTP host. +## +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +use strict; +use warnings; +use Carp; +use Getopt::Long; +use Socket; +use Cwd; +use Net::FTP; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$| = 1; + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +our $def_user = 'anonymous'; +our $def_pass = 'anonymous'; +our $def_srcdir = Cwd::getcwd(); +our $def_destdir = '/WIN7_LanForge_Data/'; +our $def_ftphost = "192.168.1.222"; +our @file_list = (); +our $verbose = 0; +our $debug = 0; +our $username = $def_user; +our $password = $def_pass; +our $ftp_host = $def_ftphost; +our $srcdir = $def_srcdir; +our $destdir = $def_destdir; +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +our $usage = "\n$0: + --user username [$def_user] + --passwd password [$def_pass] + --srcdir sourcedir [$def_srcdir] + --host host [$def_ftphost] + --destdir destdir [$def_destdir] + --verbose=1 [$verbose] + --debug=1 [$debug] + -- file1 file2 ... fileN # use -- to start a list of files or globs on cmdline +"; + +GetOptions ( + 'user|u=s' => \$::username, + 'passwd|p=s' => \$::password, + 'host|h=s' => \$::ftp_host, + 'srcdir|s=s' => \$::srcdir, + 'destdir|t=s' => \$::destdir, + 'verbose|v=n' => \$::verbose, + 'debug|d=n' => \$::debug +) || die($usage); + +die "Cannot open $srcdir " if ( ! -e $srcdir ); + +if (@ARGV > 0) { + # we were passed -- file1 file2 ... fileN on commandline + print "Checking files listed on command line...\n" if ($verbose); + for my $filename (@ARGV) { + if ($filename =~ /(\*|\?|\{\n)/) { + my @expanded = glob("$srcdir/$filename"); + for my $filename2 (@expanded) { + if ( -e $filename2 ) { + push(@file_list, $filename2); + } + else { + print STDERR "File $filename2 not found\n"; + } + } + } + else { + if ( -e "$srcdir/$filename" ) { + push(@file_list, "$srcdir/$filename"); + } + else { + print STDERR "File $srcdir/$filename not found\n"; + } + } + } +} +else { + # we were just given a directory + print "Looking for 'sta*.csv' files in $srcdir...\n" if ($verbose); + @file_list = glob("$srcdir/sta*.csv"); +} + +die "No CSV files present in $srcdir" if (@file_list < 1); +my $ftp_server = Net::FTP->new($ftp_host, + Debug=>$debug, + Timeout=>15, + Port=>21, + Passive=>0) + or die "Can't open $ftp_host\n"; + +$ftp_server->login($username, $password) or die "Can't log $username in\n"; +$ftp_server->cwd($destdir) or die "Unable to cd to $destdir\n"; + +for my $filename (@file_list) { + print "uploading $filename\n" if ($verbose); + $ftp_server->put($filename) or die "Unable to upload $filename\n"; +} +## +## eof +## diff --git a/lanforge/lanforge-scripts/genia-stations.sh b/lanforge/lanforge-scripts/genia-stations.sh new file mode 100755 index 000000000..e19b9b8dd --- /dev/null +++ b/lanforge/lanforge-scripts/genia-stations.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Ideally we want to get close to 600 stations +# but we'll want to see how big a batch we can create + +#wiphy0 stations sta0000-0199 +#wiphy1 stations sta0200-0399 +#wiphy2 stations sta0400-0462 +#wiphy3 stations sta0463-0526 + +M=ct524-genia.jbr.candelatech.com +SSID=(jedway-wpa2-x2048-5-1 jedway-wpa2-x2048-5-1) +#declare -A batches=( +# [wiphy0]=000,200 +# [wiphy1]=200,200 +# [wiphy2]=400,63 +# [wiphy3]=463,63 +#) +declare -A batches=( + [wiphy0]=000,4 + [wiphy1]=004,4 + [wiphy2]=008,4 + [wiphy3]=012,4 +) + +function create_batch() { + local radio=$1 + local start=$2 + local num=$3 + [[ x$radio = x ]] && echo "create_batch wants (radio, first, number_sta)" && exit 1 + [[ x$start = x ]] && echo "create_batch wants (radio, first, number_sta)" && exit 2 + [[ x$num = x ]] && echo "create_batch wants (radio, first, number_sta)" && exit 3 + + #echo "radio[$radio] start[$start] num[$num]" + set -x + ./lf_associate_ap.pl --mgr $M --radio $radio --action add \ + --first_sta "sta${start}" --num_sta ${num} \ + --first_ip DHCP --ssid "${SSID[0]}" \ + --security wpa2 --passphrase "${SSID[1]}" \ + --admin_down_on_add || exit 1 + set +x +} + +sorted=$(for radio in "${!batches[@]}"; do + echo $radio +done | sort) + +set -e +for radio in $sorted; do + value="${batches[$radio]}" + hunks=( ${value//,/ } ) + create_batch $radio ${hunks[0]} ${hunks[1]} + sleep 1 +done + diff --git a/lanforge/lanforge-scripts/gnuradio-dependencies.bash b/lanforge/lanforge-scripts/gnuradio-dependencies.bash new file mode 100755 index 000000000..d378dbcc0 --- /dev/null +++ b/lanforge/lanforge-scripts/gnuradio-dependencies.bash @@ -0,0 +1,96 @@ +#!/bin/bash +files=( + gr-osmosdr + hackrf + PyQt4 + PyQwt + SDL + SoapySDR + airspyone_host + boost-program-options + boost-serialization + codec2 + comedilib + dbusmenu-qt + fftw-libs-single + flex + freeglut + gnuradio + gr-fcdproplus + gr-iqbal + gsl + hidapi + jack-audio-connection-kit + kde-filesystem + libffado + libgfortran + libmng + libosmo-dsp + libquadmath + libsodium + libxml++ + log4cpp + log4cpp-devel + openblas + openblas-serial + openblas-threads + openpgm + phonon + portaudio + python-rpm-macros + python2-cheetah + python2-devel + python2-nose + python2-numpy + python2-numpy-f2py + python2-pyopengl + python2-pyqt4-sip + python2-rpm-macros + python2-scipy + python2-sip + python2-tkinter + python2-wxpython + python3-rpm-generators + qt + qt-common + qt-x11 + qwt + qwt5-qt4 + rtl-sdr + tix + tk + uhd + wxGTK3-gl + wxGTK3-media + zeromq + phonon-backend-gstreamer + sni-qt +) + +#G=/var/tmp/deps_list.txt +#echo "" > $G +urls_file="urls_file.txt" +echo "" > $urls_file + +while read L; do + [[ x$L = x ]] && continue + o=${L:0:1} + o=${o,,} + + # where would be a logical place to see if the package has already been installed , use rpm -qa "$L" + echo "https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/${o}/${L}.rpm" +done < deps_list.uniq.txt > $urls_file +exit + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +function f() { + for f in "${files[@]}"; do + dnf repoquery --deplist --queryformat '%{name}.%{%arch}' "$f" \ + | grep 'provider:' \ + | sort | uniq \ + | grep -v '\.i686' \ + >> $G + echo -n "." + done +} + diff --git a/lanforge/lanforge-scripts/gnuradio-offline-install.bash b/lanforge/lanforge-scripts/gnuradio-offline-install.bash new file mode 100755 index 000000000..edb033c35 --- /dev/null +++ b/lanforge/lanforge-scripts/gnuradio-offline-install.bash @@ -0,0 +1,62 @@ +#!/bin/bash + +[ ! -f urls_file.txt ] && echo "Where is urls_list.txt?" && exit 1 +DLD=/home/lanforge/Downloads +installed_list=() +already_downloaded_list=() +not_found_list=() +candidate_list=() + +while read L; do + # echo "$L" + bzname=`basename $L` + short=${bzname%.fc30*} + if [[ x$short = x ]]; then + echo "bad substitution on $L" + continue + fi + echo -n "Looking for $short" + + rez=`rpm -qa ${short}*` + # echo "result $?" + if [[ x$rez = x ]]; then + echo -n "$bzname is not installed" + if compgen -G "${DLD}/${bzname}"; then + echo " already downloaded" + already_downloaded_list+=($bzname); + else + wget -q -O "${DLD}/${bzname}" "$L" + if (( $? != 0 )); then + letterurl="${L%/*}/" + needle="${short:0:13}" + echo -n " need to look for ${letterurl}${needle} ..." + some_match=`curl -sq "${letterurl}" | grep -- "$needle"` + if (( $? != 0 )) || [[ x$some_match = x ]]; then + echo "Unable to find $short" + not_found_list+=("$L") + else + echo "possible candidate" + candidate_list+=("$some_match") + fi + fi + fi + fi +done < urls_file.txt +echo "" +echo "Installed list: " +printf "%s, " "${installed_list[@]}" + +echo "" +echo "Already downloaded list: " +printf " %s\\n" "${already_downloaded_list[@]}" + +echo "" +echo "Not found list: " +printf " %s\\n" "${not_found_list[@]}" + +echo "" +echo "Candidate list: " +printf " %s\\n" "${candidate_list[@]}" + + +echo "done." \ No newline at end of file diff --git a/lanforge/lanforge-scripts/gnuradio_urls_file.txt b/lanforge/lanforge-scripts/gnuradio_urls_file.txt new file mode 100644 index 000000000..3d12b858b --- /dev/null +++ b/lanforge/lanforge-scripts/gnuradio_urls_file.txt @@ -0,0 +1,150 @@ +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/a/airspyone_host-1.0.9-6.20180615gitbfb66708.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/a/alsa-lib-1.2.1.2-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/a/alternatives-1.11-4.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/b/bash-5.0.11-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/b/boost-date-time-1.69.0-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/b/boost-filesystem-1.69.0-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/b/boost-program-options-1.69.0-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/b/boost-regex-1.69.0-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/b/boost-serialization-1.69.0-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/b/boost-thread-1.69.0-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/c/ca-certificates-2020.2.40-1.1.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/c/cairo-1.16.0-6.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/c/codec2-0.8.1-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/c/comedilib-0.8.1-23.fc29.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/d/dbus-libs-1:1.12.16-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/d/dbusmenu-qt-0.9.3-0.21.20160218.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/f/fftw-libs-single-3.3.8-4.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/f/filesystem-3.10-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/f/flex-2.6.4-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/f/fontconfig-2.13.1-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/f/freeglut-3.0.0-10.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/f/freetype-2.9.1-7.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/glib2-2.60.7-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/glibc-2.29-29.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/glibc-common-2.29-29.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/glibmm24-2.60.1-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gnuradio-3.7.13.4-5.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gnuradio-3.7.13.5-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gr-fcdproplus-3.7.2-0.9.rc1.20180618gite5ff8396.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gr-iqbal-0.37.2-34.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gsl-2.4-8.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gsm-1.0.18-4.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gstreamer1-1.16.0-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gstreamer1-plugins-base-1.16.0-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gstreamer1-plugins-good-1.16.0-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/g/gtk3-3.24.11-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/h/hackrf-2018.01.1-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/h/hidapi-0.8.0-0.11.d17db57.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/j/jack-audio-connection-kit-1.9.12-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/k/kde-filesystem-4-61.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/k/krb5-libs-1.17-15.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/lcms2-2.9-5.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libconfig-1.7.2-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libffado-2.4.1-6.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libgcc-9.3.1-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libgfortran-9.3.1-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libglvnd-glx-1:1.1.0-4.gitf92208b.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libgomp-9.3.1-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libICE-1.0.9-15.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libiec61883-1.2.0-21.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libjpeg-turbo-2.0.2-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libmng-2.0.3-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libosmo-dsp-0.3-10.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libpng-2:1.6.36-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libquadmath-9.0.1-0.10.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libquadmath-9.3.1-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libraw1394-2.1.2-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libsamplerate-0.1.9-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libsigc++20-2.10.2-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libSM-1.2.3-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libsndfile-1.0.28-10.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libsodium-1.0.18-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libstdc++-9.3.1-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libtiff-4.0.10-8.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libunwind-1.3.1-5.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libusbx-1.0.22-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libX11-1.6.7-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXcursor-1.1.15-5.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXext-1.3.3-11.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXfixes-5.0.3-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXft-2.3.2-12.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXi-1.7.10-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXinerama-1.1.4-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libxml2-2.9.10-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libxml++-2.40.1-8.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXrandr-1.5.1-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXrender-0.9.10-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/libXxf86vm-1.1.4-11.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/log4cpp-1.1.1-10.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/l/log4cpp-devel-1.1.1-10.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/m/m4-1.4.18-10.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/n/ncurses-libs-6.1-10.20180923.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/o/openblas-0.3.5-5.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/o/openblas-0.3.9-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/o/openblas-serial-0.3.9-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/o/openblas-threads-0.3.9-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/o/openpgm-5.2.122-16.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/o/openssl-libs-1:1.1.1g-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/o/opus-1.3.1-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/o/orc-0.4.29-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/pam-1.3.1-17.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/phonon-4.10.2-3.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/pkgconf-pkg-config-1.6.1-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/portaudio-19-29.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/pulseaudio-libs-12.2-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/pulseaudio-libs-glib2-12.2-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/pygtk2-2.24.0-25.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/PyQt4-4.12.3-6.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/PyQwt-5.2.0-42.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-2.7.16-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-2.7.18-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-cheetah-3.1.0-7.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-dbus-1.2.8-5.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-devel-2.7.18-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-libs-2.7.18-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-lxml-4.2.5-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-nose-1.3.7-22.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-numpy-1:1.16.2-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-numpy-1:1.16.4-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-numpy-f2py-1:1.16.4-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-pyopengl-3.1.1a1-15.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-pyqt4-sip-4.19.19-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-rpm-macros-3-47.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-scipy-1.2.0-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-setuptools-40.8.0-2.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-sip-4.19.19-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-tkinter-2.7.18-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python2-wxpython-3.0.2.0-25.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python3-3.7.7-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python3-rpm-generators-8-1.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python3-setuptools-40.8.0-2.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python-rpm-macros-3-47.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/p/python-srpm-macros-3-47.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/q/qt-1:4.8.7-47.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/q/qt-1:4.8.7-49.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/q/qt-common-1:4.8.7-47.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/q/qt-common-1:4.8.7-49.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/q/qt-settings-30.3-1.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/q/qt-x11-1:4.8.7-49.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/q/qwt5-qt4-5.2.2-36.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/q/qwt-6.1.3-10.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/r/redhat-rpm-config-132-1.fc30.noarch.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/r/rpm-4.14.2.1-5.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/r/rtl-sdr-0.6.0-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/s/SDL-1.2.15-41.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/s/shadow-utils-2:4.6-9.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/s/SoapySDR-0.6.1-3.20180806gite694813.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/s/sqlite-libs-3.26.0-7.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/s/systemd-libs-241-14.git18dd3fb.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/s/systemd-udev-241-14.git18dd3fb.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/t/tcl-1:8.6.8-2.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/t/tix-1:8.4.3-26.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/t/tk-1:8.6.8-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/u/uhd-3.12.0.0-4.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/w/wxBase3-3.0.4-8.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/w/wxGTK3-3.0.4-8.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/w/wxGTK3-gl-3.0.4-8.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/w/wxGTK3-media-3.0.4-8.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/z/zeromq-4.3.2-1.fc30.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/30/Everything/x86_64/Packages/z/zlib-1.2.11-19.fc30.x86_64.rpm diff --git a/lanforge/lanforge-scripts/gui/README.txt b/lanforge/lanforge-scripts/gui/README.txt new file mode 100644 index 000000000..42c935117 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/README.txt @@ -0,0 +1,65 @@ + +Notes on writing GUI automated tests (such as AP-Auto, TR-398, Dataplane, Capacity test, etc) + +AP-Auto: + +In the GUI, configure the AP-Auto test as wished, and save the test config on the +Advanced Configuration page. In this example, I use the name: ap-auto-32-64-dual + +In LANforge CLI, you can dump the saved configuration: + +default@btbits>> show_text_blob Plugin-Settings AP-Auto-ap-auto-32-64-dual +TEXT-BLOB:Plugin-Settings.AP-Auto-ap-auto-32-64-dual::[BLANK] +show_events: 1 +show_log: 1 +port_sorting: 0 +notes0: Chamber to Chamber test. +bg: 0xE0ECF8 +test_rig: TR-398 test bed +.... + +Save this text to a file for later use: AP-Auto-ap-auto-32-64-dual.txt + +# Save AP-Auto configuration text using the ../lf_testmod.pl script: +../lf_testmod.pl --mgr 192.168.100.156 --action show --test_name AP-Auto-ap-auto-32-64-dual > test_configs/mytest.txt + +# Save WiFi-Capacity configuration (saved as 'fb-192' using the ../lf_testmod.pl script: +../lf_testmod.pl --mgr 192.168.100.156 --action show --test_name Wifi-Capacity-fb-192 > test_configs/mytest.txt + +# Save Chamber View scenario: +../lf_testmod.pl --mgr 192.168.100.156 --action show --test_name simpleThput --test_type Network-Connectivity > test_configs/myscenario.txt + + + +# To load a test file into the LANforge server configuration: +../lf_testmod.pl --mgr 192.168.100.156 --action set --test_name AP-Auto-ben --file test_configs/mytest.txt + + + +# Load a scenario into the LANforge server configuration +../lf_testmod.pl --mgr 192.168.100.156 --action set --test_type Network-Connectivity --test_name 64sta --file test_configs/myscenario.txt + + +### +Once test cases have been loaded into the server, you can tell the GUI to run tests for you, potentially modifying the +test configuration through the GUI. + +# Tell the GUI to read the latest test config from the server. +../lf_gui_cmd.pl --manager localhost --port 3990 --cmd "cli show_text_blob" + +# Tell the Chamber-View GUI widget to load and build the specified scenario. +../lf_gui_cmd.pl --manager localhost --port 3990 --load 64sta + +# Now, tell the GUI to run a test with the new config. +# Note that the --tconfig option does not have the "AP-Auto-" prepended to it, that is automatically +# done by the GUI in order to make sure each test has its own namespace. +../lf_gui_cmd.pl --manager localhost --port 3990 --ttype "AP-Auto" --tname ap-auto-ben --tconfig ben --rpt_dest /tmp/lf_reports/ + + +Check /tmp/lf_reports for the report files. + + +The cicd and testbeds directories contain some code that came from another CICD implementation +Candela worked on. It is a good starting point, but will need modifications before it fully +works for your test bed. + diff --git a/lanforge/lanforge-scripts/gui/basic_regression.bash b/lanforge/lanforge-scripts/gui/basic_regression.bash new file mode 100755 index 000000000..33a9799da --- /dev/null +++ b/lanforge/lanforge-scripts/gui/basic_regression.bash @@ -0,0 +1,408 @@ +#!/bin/bash + +# Run some automated GUI tests, save the results +# Example of how to run this and override LFMANAGER default settings. Other values can +# be over-ridden as well. +# +# LFMANAGER=192.168.100.156 ./basic_regression.bash +# +# Run subset of tests +# LFMANAGER=192.168.100.156 DEFAULT_ENABLE=0 DO_SHORT_AP_STABILITY_RESET=1 ./basic_regression.bash +# +# + +# Disable stdout buffering in python so that we get serial console log +# output promptly. + +#set -x + +PYTHONUNBUFFERED=1 +export PYTHONUNBUFFERED + +AP_SERIAL=${AP_SERIAL:-NONE} +LF_SERIAL=${LF_SERIAL:-NONE} +APGW=${APGW:-172.16.0.1} +LFPASSWD=${LFPASSWD:-lanforge} # Root password on LANforge machine +AP_AUTO_CFG_FILE=${AP_AUTO_CFG_FILE:-test_configs/AP-Auto-ap-auto-32-64-dual.txt} +WCT_CFG_FILE=${WCT_CFG_FILE:-test_configs/WCT-64sta.txt} +DPT_CFG_FILE=${DPT_CFG_FILE:-test_configs/dpt-pkt-sz.txt} +SCENARIO_CFG_FILE=${SCENARIO_CFG_FILE:-test_configs/64_sta_scenario.txt} +WCT_DURATION=${WCT_DURATION:-60s} + +# LANforge target machine +LFMANAGER=${LFMANAGER:-localhost} + +# LANforge GUI machine (may often be same as target) +GMANAGER=${GMANAGER:-localhost} +GMPORT=${GMPORT:-3990} +MY_TMPDIR=${MY_TMPDIR:-/tmp} + +# Test configuration (10 minutes by default, in interest of time) +STABILITY_DURATION=${STABILITY_DURATION:-600} +TEST_RIG_ID=${TEST_RIG_ID:-Unspecified} + +# DUT configuration +DUT_FLAGS=${DUT_FLAGS:-NA} +DUT_FLAGS_MASK=${DUT_FLAGS_MASK:-NA} +DUT_SW_VER=${DUT_SW_VER:-NA} +DUT_HW_VER=${DUT_HW_VER:-NA} +DUT_MODEL=${DUT_MODEL:-NA} +DUT_SERIAL=${DUT_SERIAL:-NA} +DUT_SSID1=${DUT_SSID1:-NA} +DUT_SSID2=${DUT_SSID2:-NA} +DUT_SSID3=${DUT_SSID3:-NA} +DUT_PASSWD1=${DUT_PASSWD1:-NA} +DUT_PASSWD2=${DUT_PASSWD2:-NA} +DUT_PASSWD3=${DUT_PASSWD3:-NA} +DUT_BSSID1=${DUT_BSSID1:-NA} +DUT_BSSID2=${DUT_BSSID2:-NA} +DUT_BSSID3=${DUT_BSSID3:-NA} + + +# Tests to run +DEFAULT_ENABLE=${DEFAULT_ENABLE:-1} +DO_DPT_PKT_SZ=${DO_DPT_PKT_SZ:-$DEFAULT_ENABLE} +DO_WCT_DL=${DO_WCT_DL:-$DEFAULT_ENABLE} +DO_WCT_UL=${DO_WCT_UL:-$DEFAULT_ENABLE} +DO_WCT_BI=${DO_WCT_BI:-$DEFAULT_ENABLE} +DO_SHORT_AP_BASIC_CX=${DO_SHORT_AP_BASIC_CX:-$DEFAULT_ENABLE} +DO_SHORT_AP_TPUT=${DO_SHORT_AP_TPUT:-$DEFAULT_ENABLE} +DO_SHORT_AP_STABILITY_RESET=${DO_SHORT_AP_STABILITY_RESET:-$DEFAULT_ENABLE} +DO_SHORT_AP_STABILITY_RADIO_RESET=${DO_SHORT_AP_STABILITY_RADIO_RESET:-$DEFAULT_ENABLE} +DO_SHORT_AP_STABILITY_NO_RESET=${DO_SHORT_AP_STABILITY_NO_RESET:-$DEFAULT_ENABLE} + +DATESTR=$(date +%F-%T) +RSLTS_DIR=${RSLTS_DIR:-basic_regression_results_$DATESTR} + + +# Probably no config below here +AP_AUTO_CFG=ben +WCT_CFG=ben +DPT_CFG=ben +# Change testbed_poll.pl if scenario changes below. +SCENARIO=tip-auto +RPT_TMPDIR=${MY_TMPDIR}/lf_reports +LF_SER_DEV=$(basename $LF_SERIAL) +DUT_SER_DEV=$(basename $AP_SERIAL) +LF_SER_LOG=$MY_TMPDIR/lanforge_console_log_$LF_SER_DEV.txt +DUT_SER_LOG=$MY_TMPDIR/dut_console_log_$DUT_SER_DEV.txt +REGLOG=$MY_TMPDIR/basic_regression_log_$$.txt + +# Query DUT from the scenario +DUT=`grep DUT: $SCENARIO_CFG_FILE |head -1|grep -o "DUT: .*"|cut -f2 -d ' '` + +echo "Found DUT: $DUT from scenario $SCENARIO_CFG_FILE" + +function pre_test { + # Remove existing data connections. + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --cmd "cli rm_cx all all" + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --cmd "cli rm_endp YES_ALL" + + # Clear port counters. + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --cmd "cli clear_port_counters ALL ALL ALL" + + # Clean logs, this bounces radios and such too as side effect + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --cmd "cli admin clean_logs" + + if [ "_${LF_SERIAL}" != "_NONE" ] + then + # Kill any existing processes on this serial port + pkill -f ".*openwrt_ctl.*$LF_SERIAL.*" + ../openwrt_ctl.py --action lurk $OWRTCTL_ARGS --tty $LF_SERIAL --scheme serial --user root --passwd $LFPASSWD --prompt "\[root@" > $LF_SER_LOG 2>&1 & + fi + + if [ "_${AP_SERIAL}" != "_NONE" ] + then + # Kill any existing processes on this serial port + pkill -f ".*openwrt_ctl.*$AP_SERIAL.*" + ../openwrt_ctl.py --action logread $OWRTCTL_ARGS --tty $AP_SERIAL --scheme serial > $DUT_SER_LOG 2>&1 & + fi +} + +function reboot_dut { + ../openwrt_ctl.py --action reboot $OWRTCTL_ARGS --tty $AP_SERIAL --scheme serial + # TODO: Support hard-power cycle with power-ctl switch as well? +} + +function post_test { + DEST=$1 + mkdir -p $DEST/logs + + if [ "_${LF_SERIAL}" != "_NONE" ] + then + # Kill any existing processes on this serial port + pkill -f ".*openwrt_ctl.*$LF_SERIAL.*" + mv $LF_SER_LOG $DEST/logs/lanforge_console_log.txt + fi + + if [ "_${AP_SERIAL}" != "_NONE" ] + then + # Kill any existing processes on this serial port + pkill -f ".*openwrt_ctl.*$AP_SERIAL.*" + mv $DUT_SER_LOG $DEST/logs/dut_console_log.txt + + # Look for firmware crash files + PIDD=$$ + ../openwrt_ctl.py $OWRTCTL_ARGS --tty $AP_SERIAL --scheme serial --action cmd --value "tar -cvzf /tmp/bugcheck.tgz /tmp/bugcheck" + ../openwrt_ctl.py $OWRTCTL_ARGS --tty $AP_SERIAL --scheme serial --action upload --value "/tmp/bugcheck.tgz" --value2 "lanforge@$APGW:bugcheck-$PIDD.tgz" + + # Grab the file from LANforge + scp lanforge@$LFMANAGER:bugcheck-$PIDD.tgz $DEST/logs/bugcheck.tgz + + # Clean log file + ssh lanforge@$LFMANAGER "rm bugcheck-$PIDD.tgz" + + # detect a few fatal flaws and reqest AP restart if found. + grep "Hardware became unavailable" $DEST/logs/dut_console_log.txt && reboot_dut + fi + + mv $REGLOG $DEST/logs/test_automation_log.txt + if [ -f /home/lanforge/lanforge_log_0.txt ] + then + # Must be running on LF itself + cp /home/lanforge/lanforge_log_*.txt* $DEST/logs/ + cp /home/lanforge/wifi/*log* $DEST/logs/ + else + # Try via scp. Root perms needed to read wifi logs, thus root@ + scp root@$LFMANAGER:/home/lanforge/lanforge_log_*.txt* $DEST/logs/ + scp root@$LFMANAGER:/home/lanforge/wifi/*log* $DEST/logs/ + fi +} + + +set -x + +mkdir -p $RSLTS_DIR + +# Load scenario file +../lf_testmod.pl --mgr $LFMANAGER --action set --test_type Network-Connectivity --test_name $SCENARIO --file $SCENARIO_CFG_FILE + +# Load AP-Auto config file +../lf_testmod.pl --mgr $LFMANAGER --action set --test_name AP-Auto-$AP_AUTO_CFG --file $AP_AUTO_CFG_FILE + +# Load Wifi Capacity config file +../lf_testmod.pl --mgr $LFMANAGER --action set --test_name Wifi-Capacity-$WCT_CFG --file $WCT_CFG_FILE + +# Load Dataplane config file +../lf_testmod.pl --mgr $LFMANAGER --action set --test_name dataplane-test-latest-$DPT_CFG --file $DPT_CFG_FILE + +# Set DUT info if configured. +if [ "_$DUT" != "_" ] +then + ../lf_portmod.pl --manager $LFMANAGER \ + --cli_cmd "add_dut $DUT $DUT_FLAGS NA '$DUT_SW_VER' '$DUT_HW_VER' '$DUT_MODEL' '$DUT_SERIAL' NA NA NA '$DUT_SSID1' '$DUT_PASSWD1' '$DUT_SSID2' '$DUT_PASSWD2' '$DUT_SSID3' '$DUT_PASSWD3' NA NA $DUT_FLAGS_MASK NA NA NA $DUT_BSSID1 $DUT_BSSID2 $DUT_BSSID3" +fi + +# Make sure GUI is synced up with the server +../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --cmd "cli show_text_blob" +../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --cmd "cli show_dut" + +# Pause to let GUI finish getting data from the server +sleep 20 + +# Tell GUI to load and build the scenario +../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --scenario $SCENARIO + +# Clean out temp report directory on local system and remote +if [ -d $RPT_TMPDIR ] +then + rm -fr $RPT_TMPDIR/* +fi +ssh lanforge\@$GMANAGER "test -d $RPT_TMPDIR && rm -fr $RPT_TMPDIR"; + +# Do dataplane pkt size test +echo "Checking if we should run Dataplane packet size test." +if [ "_$DO_DPT_PKT_SZ" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "Dataplane" --tname dpt-ben --tconfig $DPT_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "KPI_TEST_ID" --modifier_val "Dataplane Pkt-Size" \ + --modifier_key "Show Low-Level Graphs" --modifier_val true \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/dataplane_pkt_sz + post_test $RSLTS_DIR/dataplane_pkt_sz +fi + +# Do capacity test +echo "Checking if we should run WCT Download test." +if [ "_$DO_WCT_DL" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "WiFi Capacity" --tname wct-ben --tconfig $WCT_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "KPI_TEST_ID" --modifier_val "Capacity-Download" \ + --modifier_key "RATE_DL" --modifier_val "1Gbps" \ + --modifier_key "RATE_UL" --modifier_val "0" \ + --modifier_key "VERBOSITY" --modifier_val "9" \ + --modifier_key "Duration:" --modifier_val "$WCT_DURATION" \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/wifi_capacity_dl + post_test $RSLTS_DIR/wifi_capacity_dl +fi + +echo "Checking if we should run WCT Upload test." +if [ "_$DO_WCT_UL" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "WiFi Capacity" --tname wct-ben --tconfig $WCT_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "KPI_TEST_ID" --modifier_val "Capacity-Upload" \ + --modifier_key "RATE_UL" --modifier_val "1Gbps" \ + --modifier_key "RATE_DL" --modifier_val "0" \ + --modifier_key "VERBOSITY" --modifier_val "9" \ + --modifier_key "Duration:" --modifier_val "$WCT_DURATION" \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/wifi_capacity_ul + post_test $RSLTS_DIR/wifi_capacity_ul +fi + +echo "Checking if we should run WCT Bi-Direction test." +if [ "_$DO_WCT_BI" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "WiFi Capacity" --tname wct-ben --tconfig $WCT_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "KPI_TEST_ID" --modifier_val "Capacity-TCP-UL+DL" \ + --modifier_key "RATE_UL" --modifier_val "1Gbps" \ + --modifier_key "RATE_DL" --modifier_val "1Gbps" \ + --modifier_key "Protocol:" --modifier_val "TCP-IPv4" \ + --modifier_key "VERBOSITY" --modifier_val "9" \ + --modifier_key "Duration:" --modifier_val "$WCT_DURATION" \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/wifi_capacity_bi + post_test $RSLTS_DIR/wifi_capacity_bi +fi + + +# Run basic-cx test +echo "Checking if we should run Short-AP Basic CX test." +if [ "_$DO_SHORT_AP_BASIC_CX" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "AP-Auto" --tname ap-auto-ben --tconfig $AP_AUTO_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "Band-Steering" --modifier_val false \ + --modifier_key "Multi-Station Throughput vs Pkt Size" --modifier_val false \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/ap_auto_basic_cx + post_test $RSLTS_DIR/ap_auto_basic_cx +fi + +# Run Throughput, Dual-Band, Capacity test in a row, the Capacity will use results from earlier +# tests. +echo "Checking if we should run Short-AP Throughput test." +if [ "_$DO_SHORT_AP_TPUT" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "AP-Auto" --tname ap-auto-ben --tconfig $AP_AUTO_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "Basic Client Connectivity" --modifier_val false \ + --modifier_key "Throughput vs Pkt Size" --modifier_val true \ + --modifier_key "Dual Band Performance" --modifier_val true \ + --modifier_key "Band-Steering" --modifier_val false \ + --modifier_key "Multi-Station Throughput vs Pkt Size" --modifier_val false \ + --modifier_key "Capacity" --modifier_val true \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/ap_auto_capacity + post_test $RSLTS_DIR/ap_auto_capacity +fi + +# Run Stability test (single port resets, voip, tcp, udp) +echo "Checking if we should run Short-AP Stability Reset test." +if [ "_$DO_SHORT_AP_STABILITY_RESET" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "AP-Auto" --tname ap-auto-ben --tconfig $AP_AUTO_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "KPI_TEST_ID" --modifier_val "AP-Auto Port-Reset" \ + --modifier_key "Basic Client Connectivity" --modifier_val false \ + --modifier_key "Stability" --modifier_val true \ + --modifier_key "Band-Steering" --modifier_val false \ + --modifier_key "Multi-Station Throughput vs Pkt Size" --modifier_val false \ + --modifier_key "Stability Duration:" --modifier_val $STABILITY_DURATION \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/ap_auto_stability_reset_ports + post_test $RSLTS_DIR/ap_auto_stability_reset_ports +fi + +# Run Stability test (radio resets, voip, tcp, udp) +echo "Checking if we should run Short-AP Stability Radio Reset test." +if [ "_$DO_SHORT_AP_STABILITY_RADIO_RESET" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "AP-Auto" --tname ap-auto-ben --tconfig $AP_AUTO_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "KPI_TEST_ID" --modifier_val "AP-Auto Radio-Reset" \ + --modifier_key "Basic Client Connectivity" --modifier_val false \ + --modifier_key "Stability" --modifier_val true \ + --modifier_key "Band-Steering" --modifier_val false \ + --modifier_key "Stability Duration:" --modifier_val $STABILITY_DURATION \ + --modifier_key "Multi-Station Throughput vs Pkt Size" --modifier_val false \ + --modifier_key "Reset Radios" --modifier_val true \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/ap_auto_stability_reset_radios + post_test $RSLTS_DIR/ap_auto_stability_reset_radios +fi + +# Run Stability test (no resets, no voip, tcp, udp) +echo "Checking if we should run Short-AP Stability No-Reset test." +if [ "_$DO_SHORT_AP_STABILITY_NO_RESET" == "_1" ] +then + pre_test + ../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --ttype "AP-Auto" --tname ap-auto-ben --tconfig $AP_AUTO_CFG \ + --modifier_key "Test Rig ID:" --modifier_val "$TEST_RIG_ID" \ + --modifier_key "DUT_NAME" --modifier_val "$DUT" \ + --modifier_key "KPI_TEST_ID" --modifier_val "AP-Auto No-Reset" \ + --modifier_key "Basic Client Connectivity" --modifier_val false \ + --modifier_key "Band-Steering" --modifier_val false \ + --modifier_key "Stability" --modifier_val true \ + --modifier_key "Stability Duration:" --modifier_val $STABILITY_DURATION \ + --modifier_key "Multi-Station Throughput vs Pkt Size" --modifier_val false \ + --modifier_key "VOIP Call Count:" --modifier_val 0 \ + --modifier_key "Concurrent Ports To Reset:" --modifier_val 0 \ + --rpt_dest $RPT_TMPDIR > $REGLOG 2>&1 + mv $RPT_TMPDIR/* $RSLTS_DIR/ap_auto_stability_no_reset + post_test $RSLTS_DIR/ap_auto_stability_no_reset +fi + +if [ "_${LFLOG_PID}" != "_" ] +then + kill $LFLOG_PID + mv $LF_SER_LOG $RSLTS_DIR/lanforge_console_log.txt +fi +if [ "_${DUTLOG_PID}" != "_" ] +then + kill $DUTLOG_PID + mv $$DUT_SER_LOG $RSLTS_DIR/dut_console_log.txt +fi + +RPT_ARGS= +if [ "_${NOTES_HTML}" == "_" ] +then + NOTES_HTML=NA +fi + +if [ "_${GITLOG}" == "_" ] +then + GITLOG=NA +fi + +if [ "_${DUTGITLOG}" == "_" ] +then + DUTGITLOG=NA +fi + +echo "./lf_gui_report_summary.pl --title \"$TEST_RIG_ID: $DUT_SW_VER\" --dir $RSLTS_DIR --dutgitlog $DUTGITLOG --gitlog $GITLOG --notes $NOTES_HTML" +./lf_gui_report_summary.pl --title "$TEST_RIG_ID: $DUT_SW_VER" --dir $RSLTS_DIR --dutgitlog $DUTGITLOG --gitlog $GITLOG --notes $NOTES_HTML < index_template.html > $RSLTS_DIR/index.html + +echo "Done with automated regression test." +echo "Results-Dir: $RSLTS_DIR" diff --git a/lanforge/lanforge-scripts/gui/cicd/README.txt b/lanforge/lanforge-scripts/gui/cicd/README.txt new file mode 100644 index 000000000..bb1cfc1fa --- /dev/null +++ b/lanforge/lanforge-scripts/gui/cicd/README.txt @@ -0,0 +1,104 @@ +Potential polling method for CICD integration. + +* Polling should decrease network security head-aches such as setting + up VPN access for all test beds. + +*** + +Implementation: + +* Web server accessible to all CICD test beds runs a 'test orchestrator' logic, henceforth TO + * This TO will periodically query jfrog for latest openwrt builds (see jfrog.pl) + * If new build is found, a work-item file containing pertinent info, including the HW platform + will be created, example: + +CICD_TYPE=fast +CICD_RPT_NAME=ea8300 +CICD_RPT_DIR=greearb@192.168.100.195:/var/www/html/myco/testbeds//ferndale-basic-01/reports +CICD_HW=ea8300 +CICD_FILEDATE= +CICD_GITHASH= +CICD_URL=https://myco.jfrog.io/artifactory/wlan-ap-firmware/ +CICD_FILE_NAME=ea8300 +CICD_URL_DATE=24-Apr-2020 16:32 + + * TO has manually configured list of test-beds, with some info about each test + bed, including the DUT HW platform and testing capabilities. + * It picks a test bed that matches the new build HW. + * That test bed will have a URL directory for it and it alone. + * The TO writes a new test configuration file into this directory. + The test configuration file will have the info above, and also have other + info including the tests to run and where to upload results when complete. + * TO looks for any completed results, and removes the work-item if result is found. + * TO will re-calculate historical charts and publish those if new results are found for a testbed. + It could generate email and/or poke the results into testrails or similar at this point. + * TO should run periodically every 1 minute or so to check on progress. + + +* Test bed polling: + * The test-bed (hence forth TB) will poll its directory on the TO web server to look for new jobs. + * When new job is found, the TB will download the test config file, and use scp to upload a file + to the TO to indicate it is working on the test. + * When test is complete, TB will upload results to TO server. TO now knows test bed is available + for more jobs. + * TB should pause for 2 minutes after uploading results to make sure TO notices the new results and + removes the old work item so that TB does not re-test the same work item. + + + +************* Installation / Usage *************** + +The jfrog.pl runs on the web server. This is the Test Orchestrator. +Create a directory structure looking similar to this: + +[greearb@ben-dt4 html]$ find myco -name "*" -print +myco +myco/testbeds +myco/testbeds/ferndale-basic-01 +myco/testbeds/ferndale-basic-01/pending_work +myco/testbeds/ferndale-basic-01/reports + +Copy the TESTBED_INFO from testbeds directory to the myco/testbeds directory: + +[greearb@ben-dt4 testbeds]$ pwd +/var/www/html/myco/testbeds +cp -ar /home/greearb/git/lanforge-scripts/gui/cicd/ferndale-basic-01/ ./ + + +Run the jfrog.pl script from the myco/testbeds directory: + +/home/greearb/git/lanforge-scripts/gui/cicd/cicd/jfrog.pl --passwd secret --tb_url_base greearb@192.168.100.195:/var/www/html/myco/testbeds/ + +A work-item file will be created as needed, in my case, it is here: + +[greearb@ben-dt4 testbeds]$ cat ferndale-basic-01/pending_work/fast/CICD_TEST-ea8300 +CICD_TEST=fast +CICD_RPT_DIR=greearb@192.168.100.195:/var/www/html/myco/testbeds//ferndale-basic-01/reports/fast +CICD_RPT_NAME=ea8300 +CICD_HW=ea8300 +CICD_FILEDATE= +CICD_GITHASH= +CICD_URL=https://myco.jfrog.io/artifactory/wlan-ap-firmware/ +CICD_FILE_NAME=ea8300 +CICD_URL_DATE=24-Apr-2020 16:32 + + + +************ Installation / Usage on Test Controller ************** + +# This runs on the test controller or Jump-Box. + +# Set up OS +sudo needs to work w/out password. + +sudo chmod a+rwx /dev/ttyUSB* +sudo pip3 install pexpect-serial + +Run testbed_poll.pl from the cicd testbed directory: + +The 192.168.100.195 system is the jfrog / Orchestrator machine. The jfrog +password is so that it can download the OpenWrt binary file from jfrog. + +cd ~/git/lanforge-scripts/gui/cicd/ferndale-basic-01 + +../testbed_poll.pl --jfrog_passwd secret --url http://192.168.100.195/myco/testbeds/testbed-ferndale-01/pending_work/ diff --git a/lanforge/lanforge-scripts/gui/cicd/ferndale-basic-01/TESTBED_INFO.txt b/lanforge/lanforge-scripts/gui/cicd/ferndale-basic-01/TESTBED_INFO.txt new file mode 100644 index 000000000..0db49f434 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/cicd/ferndale-basic-01/TESTBED_INFO.txt @@ -0,0 +1,7 @@ +TESTBED_HW=ea8300 + +# Controller's view of the test bed, from wlan-testing/cicd/[testbed] directory +TESTBED_DIR=../../testbeds/ferndale-basic-01 + +TESTBED_CASEID_FAST=C1308 +TESTBED_CASEID_BASIC=C1309 diff --git a/lanforge/lanforge-scripts/gui/cicd/jfrog.pl b/lanforge/lanforge-scripts/gui/cicd/jfrog.pl new file mode 100755 index 000000000..d136e3cb2 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/cicd/jfrog.pl @@ -0,0 +1,379 @@ +#!/usr/bin/perl + +# Query jfrog URL and get list of builds. +# This will be run on the test-bed orchestrator +# Run this in directory that contains the testbed_$hw/ directories +# Assumes cicd.class is found in ~/git/lanforge-scripts/gui/ + +use strict; +use warnings; +use Getopt::Long; + +my $user = "cicd_user"; +my $passwd = ""; +my $url = "https://myco.jfrog.io/artifactory/wlan-ap-firmware"; +my @platforms = ("ea8300", "ecw5410"); # Add more here as we have test beds that support them. +my $files_processed = "jfrog_files_processed.txt"; +my $tb_url_base = "cicd_user\@myco.cicd.cloud.com/testbeds"; # Used by SSH: scp -R results_dir cicd_user@myco.cicd.cloud.com/testbeds/ +my $help = 0; +my $cicd_prefix = "CICD_TEST"; +my $kpi_dir = "/home/greearb/git/lanforge-scripts/gui/"; +my @ttypes = ("fast", "basic"); +my $duplicate_work = 1; + +my $ul_host = "www"; +my $ul_dir = "candela_html/examples/cicd/"; # used by scp +my $ul_dest = "$ul_host:$ul_dir"; # used by scp +my $other_ul_dest = ""; # used by scp +my $result_url_base = "http://localhost/myco/cicd"; + +my $usage = qq($0 + [--user { jfrog user (default: cicd_user) } + [--passwd { jfrog password } + [--result_url_base { http://foo.com/myco/cicd } + [--url { jfrog URL, default is OpenWrt URL: https://myco.jfrog.io/artifactory/wlan-ap-firmware/ } + [--files_processed { text file containing file names we have already processed } + [--tb_url_base { Where to report the test results? } + +Example: + +# Use MY-CO jfrog repo +$0 --user cicd_user --passwd secret --url https://myco.jfrog.io/artifactory/wlan-ap-firmware/ \\ + --files_processed jfrog_files_processed.txt \\ + --tb_url_base cicd_user\@myco.cicd.cloud.com/testbeds + +# Download images from candelatech.com web site (for developer testing and such) +$0 --tb_url_base greearb@192.168.100.195:/var/www/html/myco/testbeds/ \\ + --url http://www.candelatech.com/downloads/myco/test_images + +); + +GetOptions +( + 'user=s' => \$user, + 'passwd=s' => \$passwd, + 'url=s' => \$url, + 'files_processed=s' => \$files_processed, + 'tb_url_base=s' => \$tb_url_base, + 'result_url_base=s' => \$result_url_base, + 'help|?' => \$help, +) || (print($usage) && exit(1)); + +if ($help) { + print($usage) && exit(0); +} + +#if ($passwd eq "") { +# print("ERROR: You must specify jfrog password.\n"); +# exit(1); +#} + +my $i; + +my $pwd = `pwd`; +chomp($pwd); + +my $listing; +my @lines; +my $j; + +# Check for any completed reports. +for ($j = 0; $j<@ttypes; $j++) { + my $ttype = $ttypes[$j]; + $listing = `ls */reports/$ttype/NEW_RESULTS-*`; + @lines = split(/\n/, $listing); + for ($i = 0; $i<@lines; $i++) { + my $ln = $lines[$i]; + chomp($ln); + if ($ln =~ /(.*)\/NEW_RESULTS/) { + my $process = $1; # For example: ben-home/reports/fast + my $completed = `cat $ln`; # Contents of the results file + chomp($completed); + if ($ln =~ /(.*)\/reports\/$ttype\/NEW_RESULTS/) { + my $tbed = $1; + my $cmd; + my $caseid = ""; + + print "Processing new results, line: $ln process: $process completed: $completed testbed: $tbed\n"; + + # Figure out the new directory from the work-item. + my $wi = `cat ./$tbed/pending_work/$completed`; + + `mv ./$tbed/pending_work/$completed /tmp/`; + + if ($wi =~ /CICD_CASE_ID=(\S+)/) { + $caseid = "--caseid $1"; + } + + if ($wi =~ /CICD_RPT_NAME=(.*)/) { + my $widir = $1; + + # Ensure we have a place to copy the new report + $cmd = "ssh $ul_host \"mkdir -p $ul_dir/$tbed/$ttype\""; + print "Ensure directory exists: $cmd\n"; + `$cmd`; + + # Upload the report directory + $cmd = "scp -C -r $process/$widir $ul_dest/$tbed/$ttype/"; + print "Uploading: $cmd\n"; + `$cmd`; + + $caseid .= " --results_url $result_url_base/$tbed/$ttype/$widir"; + } + else { + print "WARNING: No CICD_RPT_NAME line found in work-item contents:\n$wi\n"; + } + + $cmd = "cd $kpi_dir && java kpi $caseid --dir \"$pwd/$process\" && cd -"; + print ("Running kpi: $cmd\n"); + `$cmd`; + `rm $ln`; + $cmd = "scp -C $process/*.png $process/*.html $process/*.csv $process/*.ico $process/*.css $ul_dest/$tbed/$ttype/"; + print "Uploading: $cmd"; + `$cmd`; + + # This might need similar partial-upload logic as that above, if it is ever actually + # enabled. + if ($other_ul_dest ne "") { + $cmd = "scp -C -r $process $other_ul_dest/$tbed/"; + print "Uploading to secondary location: $cmd"; + `$cmd`; + } + } + } + } +} + +#Read in already_processed builds +my @processed = (); +$listing = `cat $files_processed`; +@lines = split(/\n/, $listing); +for ($i = 0; $i<@lines; $i++) { + my $ln = $lines[$i]; + chomp($ln); + print("Reported already processed: $ln\n"); + push(@processed, $ln); +} + +my $z; +for ($z = 0; $z<@platforms; $z++) { + my $pf = $platforms[$z]; + my $cmd = "curl -u $user:$passwd $url/$pf/"; + print ("Calling command: $cmd\n"); + $listing = `$cmd`; + @lines = split(/\n/, $listing); + for ($i = 0; $i<@lines; $i++) { + my $ln = $lines[$i]; + chomp($ln); + + #print("ln -:$ln:-\n"); + + if (($ln =~ /href=\"(.*)\">(.*)<\/a>\s+(.*)\s+\S+\s+\S+/) + || ($ln =~ /class=\"indexcolname\">(.*)<\/a>.*class=\"indexcollastmod\">(\S+)\s+.*/)) { + my $fname = $1; + my $name = $2; + my $date = $3; + + # Skip header + if ($ln =~ /Last modified/) { + next; + } + + # Skip parent-dir + if ($ln =~ /Parent Directory/) { + next; + } + + #print("line matched -:$ln:-\n"); + #print("fname: $fname name: $name date: $date\n"); + + if ( grep( /^$fname\s+/, @processed ) ) { + # Skip this one, already processed. + next; + } + + my $hw = ""; + my $fdate = ""; + my $githash = ""; + + if ($fname =~ /^(\S+)-(\d\d\d\d-\d\d-\d\d)-(\S+).tar.gz/) { + $hw = $1; + $fdate = $2; + $githash = $3; + } else { + print "ERROR: Un-handled filename syntax: $fname, assuming file-name is hardware name.\n"; + $hw = $fname; + } + + # Find the least used testbed for this hardware. + my $dirs = `ls`; + my @dira = split(/\n/, $dirs); + my $best_tb = ""; + my $best_backlog = 0; + my $di; + for ($di = 0; $di<@dira; $di++) { + my $dname = $dira[$di]; + chomp($dname); + if (! -d $dname) { + next; + } + if (! -f "$dname/TESTBED_INFO.txt") { + next; + } + my $tb_info = `cat $dname/TESTBED_INFO.txt`; + my $tb_hw_type = ""; + if ($tb_info =~ /TESTBED_HW=(.*)/g) { + $tb_hw_type = $1; + } + if (!hw_matches($tb_hw_type, $hw)) { + print "Skipping test bed $dname, jfrog hardware type: -:$hw:- testbed hardware type: -:$tb_hw_type:-\n"; + next; + } + print "Checking testbed $dname backlog..\n"; + my $bklog = `ls $dname/pending_work/$cicd_prefix-*`; + my $bklog_count = split(/\n/, $bklog); + if ($best_tb eq "") { + $best_tb = $dname; + $best_backlog = $bklog_count; + } else { + if ($best_backlog > $bklog_count) { + $best_tb = $dname; + $best_backlog = $bklog_count; + } + } + } + + if ($best_tb eq "") { + print "ERROR: No test bed found for hardware type: $hw\n"; + last; + } + + my $fname_nogz = $fname; + if ($fname =~ /(.*)\.tar\.gz/) { + $fname_nogz = $1; + } + + my @tbs = ($best_tb); + + # For more test coverage, send work to rest of the available test beds as well. + if ($duplicate_work) { + for ($di = 0; $di<@dira; $di++) { + my $dname = $dira[$di]; + chomp($dname); + if (! -d $dname) { + next; + } + if ($dname eq $best_tb) { + next; # processed this one above + } + if (! -f "$dname/TESTBED_INFO.txt") { + next; + } + + my $tb_info = `cat $dname/TESTBED_INFO.txt`; + my $tb_hw_type = ""; + if ($tb_info =~ /TESTBED_HW=(.*)/g) { + $tb_hw_type = $1; + } + + if (!hw_matches($tb_hw_type, $hw)) { + print "Skipping test bed $dname, jfrog hardware type: -:$hw:- testbed hardware type: -:$tb_hw_type:-\n"; + next; + } + + push(@tbs, "$dname"); + } + } + + my $q; + for ($q = 0; $q < @tbs; $q++) { + $best_tb = $tbs[$q]; + my $caseid_fast = ""; + my $caseid_basic = ""; + + my $tb_info = `cat $best_tb/TESTBED_INFO.txt`; + if ($tb_info =~ /TESTBED_CASEID_FAST=(.*)/g) { + $caseid_fast = $1; + } + if ($tb_info =~ /TESTBED_CASEID_BASIC=(.*)/g) { + $caseid_basic = $1; + } + + my $ttype = "fast"; + my $work_fname = "$best_tb/pending_work/$cicd_prefix-$fname_nogz-$ttype"; + my $work_fname_a = $work_fname; + + system("mkdir -p $best_tb/pending_work"); + system("mkdir -p $best_tb/reports/$ttype"); + + open(FILE, ">", "$work_fname"); + + print FILE "CICD_TYPE=$ttype\n"; + print FILE "CICD_RPT_NAME=$fname_nogz\n"; + print FILE "CICD_RPT_DIR=$tb_url_base/$best_tb/reports/$ttype\n"; + + print FILE "CICD_HW=$hw\nCICD_FILEDATE=$fdate\nCICD_GITHASH=$githash\n"; + print FILE "CICD_URL=$url/$pf\nCICD_FILE_NAME=$fname\nCICD_URL_DATE=$date\n"; + if ($caseid_fast ne "") { + print FILE "CICD_CASE_ID=$caseid_fast\n"; + } + + close(FILE); + + print("Next: File Name: $fname Display Name: $name Date: $date TType: $ttype\n"); + print("Work item placed at: $work_fname\n"); + + + $ttype = "basic"; + $work_fname = "$best_tb/pending_work/$cicd_prefix-$fname_nogz-$ttype"; + + system("mkdir -p $best_tb/reports/$ttype"); + + open(FILE, ">", "$work_fname"); + + print FILE "CICD_TYPE=$ttype\n"; + print FILE "CICD_RPT_NAME=$fname_nogz\n"; + print FILE "CICD_RPT_DIR=$tb_url_base/$best_tb/reports/$ttype\n"; + + print FILE "CICD_HW=$hw\nCICD_FILEDATE=$fdate\nCICD_GITHASH=$githash\n"; + print FILE "CICD_URL=$url/$pf\nCICD_FILE_NAME=$fname\nCICD_URL_DATE=$date\n"; + if ($caseid_basic ne "") { + print FILE "CICD_CASE_ID=$caseid_basic\n"; + } + + close(FILE); + + print("Next: File Name: $fname Display Name: $name Date: $date TType: $ttype\n"); + print("Work item placed at: $work_fname\n"); + #print("To download: curl --location -o /tmp/$fname -u $user:$passwd $url/$pf/$fname\n"); + } # for all testbeds + + # Note this one is processed + `echo -n "$fname " >> $files_processed`; + `date >> $files_processed`; + } + + #print "$ln\n"; + }# for all lines in a directory listing +}# for all URLs to process + +exit 0; + + +sub hw_matches { + my $a = shift; + my $b = shift; + + # Normalize equivalent HW types. + if ($a eq "mr8300") { + $a = "ea8300"; + } + if ($b eq "mr8300") { + $b = "ea8300"; + } + + if ($a eq $b) { + return 1; + } + return 0; +} diff --git a/lanforge/lanforge-scripts/gui/cicd/testbed_poll.pl b/lanforge/lanforge-scripts/gui/cicd/testbed_poll.pl new file mode 100755 index 000000000..d2c76103b --- /dev/null +++ b/lanforge/lanforge-scripts/gui/cicd/testbed_poll.pl @@ -0,0 +1,449 @@ +#!/usr/bin/perl + +# Query test-bed orchestator URL to see if there are new tests for us to run. +# This is expected to be run on the test-bed controller (not orchestrator) +# One of these processes will run for each test bed controlled by the controller. + +use strict; +use warnings; +use Getopt::Long; + +my $user = ""; +my $passwd = ""; +my $jfrog_user = "cicd_user"; +my $jfrog_passwd = ""; +my $url = ""; +my $next_info = "__next_test.txt"; +my $help = 0; +my $owt_log = ""; +my $log = ""; + +my $usage = qq($0 + [--jfrog_user { jfrog user (default: cicd_user) } + [--jfrog_passwd { jfrog password } + [--user { for accessing URL } + [--passwd { for accessing URL } + [--url { test-orchestrator URL for this test bed } + [--next_info { output text file containing info about the next test to process } + [--log {location} For instance: --log stdout, for openwrt_ctl expect script. + +Example: +$0 --user to_user --passwd secret --jfrog_user myco-read --jfrog_passwd myco-read \\ + --url https://myco.cicd.mycloud.com/testbed-ferndale-01/ + +# Use specific scenario file. +SCENARIO_CFG_FILE=/home/lanforge/git/wlan-testing/testbeds/ferndale-basic-01/scenario_small.txt \\ + ../testbed_poll.pl --jfrog_passwd myco-read --jfrog_user myco-read \\ + --url http://192.168.100.195/myco/testbeds/ferndale-basic-01/pending_work/ + +); + +GetOptions +( + 'jfrog_user=s' => \$jfrog_user, + 'jfrog_passwd=s' => \$jfrog_passwd, + 'user=s' => \$user, + 'passwd=s' => \$passwd, + 'url=s' => \$url, + 'next_info=s' => \$next_info, + 'log=s' => \$log, + 'help|?' => \$help, +) || (print($usage) && exit(1)); + +if ($help) { + print($usage) && exit(0); +} + +if ($jfrog_passwd eq "") { + print("ERROR: You must specify jfrog password.\n"); + exit(1); +} + +if ($user ne "" && $passwd eq "") { + print("ERROR: You must specify a password if specifying a user.\n"); + exit(1); +} + +if ($log ne "") { + $owt_log = "--log $log"; +} + +my $i; + +my $cuser = "-u $user:$passwd"; +if ($user eq "") { + $cuser = ""; +} + +my $cmd = "curl $cuser $url"; + +print_note("Checking Test-Orchestrator for new work-items"); +my $listing = do_system($cmd); +my @lines = split(/\n/, $listing); + +# First, if any have 'fast' in them, they get precedence. +for ($i = 0; $i<@lines; $i++) { + my $ln = $lines[$i]; + chomp($ln); + my $fast = 0; + if ($ln =~ /href=\"(CICD_TEST-.*-fast)\">(.*)<\/a>\s+(.*)\s+\S+\s+\S+/) { + $fast = 1; + } + elsif ($ln =~ /href=\"(CICD_TEST-.*-fast)\">(.*)<\/a>/) { + $fast = 1; + } + if ($fast) { + @lines[0] = $ln; + last; + } +} + +for ($i = 0; $i<@lines; $i++) { + my $ln = $lines[$i]; + chomp($ln); + + my $fname = ""; + my $name = ""; + my $date = ""; + + if ($ln =~ /href=\"(CICD_TEST-.*)\">(.*)<\/a>\s+(.*)\s+\S+\s+\S+/) { + $fname = $1; + $name = $2; + $date = $3; + } + elsif ($ln =~ /href=\"(CICD_TEST-.*)\">(.*)<\/a>/) { + $fname = $1; + } + + if ($fname ne "") { + # Grab that test file + $cmd = "curl --location $cuser -o $next_info $url/$fname"; + do_system($cmd); + + # Read in that file + my $jurl = ""; + my $jfile = ""; + my $report_to = ""; + my $report_name = ""; + my $swver = ""; + my $fdate = ""; + my $ttype = ""; + my $listing = do_system("cat $next_info"); + my @lines = split(/\n/, $listing); + for ($i = 0; $i<@lines; $i++) { + my $ln = $lines[$i]; + chomp($ln); + if ($ln =~ /^CICD_URL=(.*)/) { + $jurl = $1; + } + elsif ($ln =~ /^CICD_TYPE=(.*)/) { + $ttype = $1; + } + elsif ($ln =~ /^CICD_FILE_NAME=(.*)/) { + $jfile = $1; + } + elsif ($ln =~ /^CICD_RPT_DIR=(.*)/) { + $report_to = $1; + } + elsif ($ln =~ /^CICD_RPT_NAME=(.*)/) { + $report_name = $1; + } + elsif ($ln =~ /^CICD_GITHASH=(.*)/) { + $swver = $1; + } + elsif ($ln =~ /^CICD_FILEDATE=(.*)/) { + $fdate = $1; + } + } + + if ($swver eq "") { + $swver = $fdate; + } + + if ($swver eq "") { + $swver = "$jfile"; + } + + if ($jurl eq "") { + print("ERROR: No CICD_URL found, cannot download file.\n"); + exit(1); + } + if ($jfile eq "") { + print("ERROR: No CICD_FILE_NAME found, cannot download file.\n"); + exit(1); + } + + # Refresh wlan-ap repo if it exists. + if ( -d "../../../wlan-ap") { + do_system("cd ../../../wlan-ap && git pull && cd -"); + } + + print_note("Download latest AP Build from jfrog repository."); + my $cmd = "curl --location -o $jfile -u $jfrog_user:$jfrog_passwd $jurl/$jfile"; + do_system($cmd); + + do_system("rm -f openwrt-*.bin"); + do_system("rm -f *sysupgrade.bin"); # just in case openwrt prefix changes. + do_system("tar xf $jfile"); + + print_note("Copy AP build to LANforge so LANforge can serve the file to AP"); + # Next steps here are to put the OpenWrt file on the LANforge system + my $tb_info = do_system("cat TESTBED_INFO.txt"); + my $tb_dir = ""; + if ($tb_info =~ /TESTBED_DIR=(.*)/) { + $tb_dir = $1; + } + + my $env = do_system(". $tb_dir/test_bed_cfg.bash && env"); + my $lfmgr = ""; + my $serial = ""; + my $cloud_sdk = ""; + + if ($env =~ /LFMANAGER=(.*)/) { + $lfmgr = $1; + } + else { + print("ERRROR: Could not find LFMANAGER in environment, configuration error!\n"); + print("env: $env\n"); + exit(1); + } + + if ($env =~ /USE_CLOUD_SDK=(\S+)/) { + $cloud_sdk = $1; + print("NOTE: Using cloud controller: $cloud_sdk\n"); + } + else { + print("NOTE: NOT Using cloud controller\n"); + } + #print("env: $env"); + #exit(0); + + if ($env =~ /AP_SERIAL=(.*)/) { + $serial = $1; + } + else { + print("ERRROR: Could not find AP_SERIAL in environment, configuration error!\n"); + exit(1); + } + + my $gmport = "3990"; + my $gmanager = $lfmgr; + my $scenario = "myco-auto"; # matches basic_regression.bash + + if ($env =~ /GMANAGER=(.*)/) { + $gmanager = $1; + } + if ($env =~ /GMPORT=(.*)/) { + $gmport = $1; + } + + print_note("Restart LANforge GUI to be sure it is in known state."); + # Restart the GUI on the LANforge system + do_system("ssh lanforge\@$lfmgr pkill -f \"miglayout.*8080\""); + + # and then get it onto the DUT, reboot DUT, re-configure as needed, + print_note("Request AP DUT to install the test image."); + do_system("scp *sysupgrade.bin lanforge\@$lfmgr:myco-$jfile"); + + + # TODO: Kill anything using the serial port + do_system("sudo lsof -t $serial | sudo xargs --no-run-if-empty kill -9"); + + print_note("Find AP DUT default gateway."); + # and then kick off automated regression test. + # Default gateway on the AP should be one of the ports on the LANforge system, so we can use + # that to scp the file to the DUT, via serial-console connection this controller has to the DUT. + my $ap_route = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action cmd --value \"ip route show\""); + my $ap_gw = ""; + if ($ap_route =~ /default via (\S+)/) { + $ap_gw = $1; + } + if ($ap_gw eq "") { + print("ERROR: Could not find default gateway for AP, route info:\n$ap_route\n"); + if ($ap_route =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(33); + } + # Re-apply scenario so the LANforge gateway/NAT is enabled for sure. + my $out = do_system("../../lanforge/lanforge-scripts/lf_gui_cmd.pl --manager $gmanager --port $gmport --scenario $scenario"); + # TODO: Use power-controller to reboot the AP and retry. + if ($out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(34); + } + + $out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action reboot"); + print ("Reboot DUT to try to recover networking:\n$out\n"); + if ($out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(35); + } + sleep(15); + + $ap_route = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action cmd --value \"ip route show\""); + if ($ap_route =~ /default via (\S+)/g) { + $ap_gw = $1; + } + if ($ap_route =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(36); + } + if ($ap_gw eq "") { + exit(1); + } + } + + print_note("Request AP DUT to install the test image and reboot."); + # TODO: Change this to curl download?? + my $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action sysupgrade --value \"lanforge\@$ap_gw:myco-$jfile\""); + print ("Sys-upgrade results:\n$ap_out\n"); + if ($ap_out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(37); + } + # TODO: Verify this (and reboot below) worked. DUT can get wedged and in that case it will need + # a power-cycle to continue. + + # System should be rebooted at this point. + sleep(10); # Give it some more time + + if ($cloud_sdk eq "") { + print_note("Initialize AP, disable OpenVsync since this is stand-alone testbed."); + # Disable openvsync, it will re-write /etc/config/wireless + # This code should not be used when we get cloud-sdk wired up. + $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action cmd --value \"service opensync stop\""); + print ("Stop openvsync:\n$ap_out\n"); + if ($ap_out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(38); + } + $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action cmd --value \"service opensync disable\""); + print ("Disable openvsync:\n$ap_out\n"); + if ($ap_out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(39); + } + } + else { + print_note("Initialize AP, enable OpenVsync since this testbed is using Cloud-Controler: $cloud_sdk."); + $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action cmd --value \"service opensync enable\""); + print ("Enable openvsync:\n$ap_out\n"); + if ($ap_out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(40); + } + } + + # Re-apply overlay + print_note("Apply default AP configuration for this test bed."); + if ($cloud_sdk eq "") { + $ap_out = do_system("cd $tb_dir/OpenWrt-overlay && tar -cvzf ../overlay_tmp.tar.gz * && scp ../overlay_tmp.tar.gz lanforge\@$lfmgr:myco-overlay.tar.gz"); + } + else { + # Create /etc/hosts file that points us towards correct cloud-sdk machine + my $etc_hosts = "$tb_dir/OpenWrt-overlay/etc/hosts"; + open(FILE, ">", "$etc_hosts"); + print FILE "# Auto-Created by CICD process +127.0.0.1 localhost + +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +$cloud_sdk opensync-mqtt-broker +$cloud_sdk opensync-wifi-controller +$cloud_sdk opensync.zone1.art2wave.com +"; + + # Leave 'wireless' out of the overlay since opensync will be designed to work with default config. + $ap_out = do_system("cd $tb_dir/OpenWrt-overlay && tar -cvzf ../overlay_tmp.tar.gz --exclude etc/config/wireless * && scp ../overlay_tmp.tar.gz lanforge\@$lfmgr:myco-overlay.tar.gz"); + unlink($etc_hosts); + } + + print ("Create overlay zip:\n$ap_out\n"); + + for (my $q = 0; $q<10; $q++) { + $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action download --value \"lanforge\@$ap_gw:myco-overlay.tar.gz\" --value2 \"overlay.tgz\""); + print ("Download overlay to DUT:\n$ap_out\n"); + if ($ap_out =~ /ERROR: Could not connect to LANforge/g) { + # Try to restart the network + $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action cmd --value \"/etc/init.d/network restart\""); + print ("Request restart of DUT networking:\n$ap_out\n"); + if ($q == 9) { + # We have failed to apply overlay at this point, bail out. + print("ERROR: Could not apply overlay to DUT, exiting test attempt.\n"); + exit(1); + } + print("Will retry overlay download in 10 seconds, try $q / 10\n"); + sleep(10); + } + else { + last; + } + } + $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action cmd --value \"cd / && tar -xzf /tmp/overlay.tgz\""); + print ("Un-zip overlay on DUT:\n$ap_out\n"); + if ($ap_out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(41); + } + + print_note("Reboot AP so that new configuration is applied."); + $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action reboot"); + print ("Rebooted DUT so overlay takes effect:\n$ap_out\n"); + if ($ap_out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(42); + } + + if ($ttype eq "fast") { + print_note("Start 'Fast' LANforge regression test."); + $ap_out = do_system("cd $tb_dir && DUT_SW_VER=$swver ./run_basic_fast.bash"); + } + else { + print_note("Start 'Fast' LANforge regression test."); + $ap_out = do_system("cd $tb_dir && DUT_SW_VER=$swver ./run_basic.bash"); + } + print("Regression $ttype test script output:\n$ap_out\n"); + + print_note("Upload results."); + + #When complete, upload the results to the requested location. + if ($ap_out =~ /Results-Dir: (.*)/) { + my $rslts_dir = $1; + if ($rslts_dir =~ /(.*)\'/) { + $rslts_dir = $1; + } + print ("Found results at: $rslts_dir\n"); + do_system("rm -fr /tmp/$report_name"); + do_system("mv $rslts_dir /tmp/$report_name"); + do_system("scp -C -r /tmp/$report_name $report_to/"); + do_system("echo $fname > /tmp/NEW_RESULTS-$fname"); + do_system("scp /tmp/NEW_RESULTS-$fname $report_to/"); + + # This will indirectly stop logread if it is running. + $ap_out = do_system("../../lanforge/lanforge-scripts/openwrt_ctl.py $owt_log --scheme serial --tty $serial --action cmd --value \"uptime\""); + if ($ap_out =~ /pexpect.exceptions.TIMEOUT/) { + print("FATAL-ERROR: DUT is in bad state, bail out.\n"); + exit(43); + } + } + + exit(0); + } + + #print "$ln\n"; +} + +exit 0; + +sub do_system { + my $cmd = shift; + print ">>> $cmd\n"; + return `$cmd 2>&1`; +} + +sub print_note { + my $n = shift; + my $hdr = "###############################################################"; + print "\n\n\n$hdr\n### $n\n$hdr\n\n"; +} diff --git a/lanforge/lanforge-scripts/gui/default.plot b/lanforge/lanforge-scripts/gui/default.plot new file mode 100644 index 000000000..0efc06d6f --- /dev/null +++ b/lanforge/lanforge-scripts/gui/default.plot @@ -0,0 +1,11 @@ +set term pngcairo size 1280,768 +set output "plot.png" +set datafile separator "\t" +set ylabel "Data" +set xlabel "Test#" +#set xdata time +set grid +#set key outside +set key off +plot filename using 1:2 with lines + diff --git a/lanforge/lanforge-scripts/gui/default_group.plot b/lanforge/lanforge-scripts/gui/default_group.plot new file mode 100644 index 000000000..f43c9a0aa --- /dev/null +++ b/lanforge/lanforge-scripts/gui/default_group.plot @@ -0,0 +1,11 @@ +set term pngcairo size 1280,768 +set output "plot.png" +set datafile separator "\t" +#set ylabel "Data" +set xlabel "Test#" +#set xdata time +set grid +set key outside +#set key off +#plot filename using 1:2 with lines + diff --git a/lanforge/lanforge-scripts/gui/index_template.html b/lanforge/lanforge-scripts/gui/index_template.html new file mode 100644 index 000000000..2253b8c57 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/index_template.html @@ -0,0 +1,58 @@ + + + + + + ___TITLE___ + + + + + +
+

___TITLE___


+


___DATE___

+ +
+ +
+ +

Individual test results and logs.

+ + + ___TR_TESTS___ +
+

+ + + + + ___TR_DUT___ +
Test RigDUT HardwareDUT SoftwareDUT ModelDUT Serial
+ +

+ + + + + ___TR_KPI___ +
Test-IDShort DescriptionPass/FailSubtest-PassedSubtest-FailedNumeric ResultTest Details
+

+ +___TESTBED_NOTES___ + +

+

+ + diff --git a/lanforge/lanforge-scripts/gui/kpi.java b/lanforge/lanforge-scripts/gui/kpi.java new file mode 100644 index 000000000..ad2d38dd1 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/kpi.java @@ -0,0 +1,1386 @@ +// +// LANforge-GUI Source Code +// Copyright (C) 1999-2020 Candela Technologies Inc +// http://www.candelatech.com +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Library General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Contact: Candela Technologies if you have any +// questions. +// + +import java.text.*; +import java.util.concurrent.*; +import java.io.*; +import java.net.URL; +import java.util.*; +import java.nio.file.*; + +/** Process a set of test results generated by the CI/CD process for a test type in a single testbed. + * There are currently no automated comparisons done across different testbeds. + * Generate historical graphs and links to each test run. + * Each test results directory would be packaged up by the lf_gui_report_summary.pl script, + * called by the basic_regression.bash automated regression test script. + * Example: java kpi --dir /var/www/html/tip/testbeds/ferndale-basic-01/reports/basic + */ +public class kpi { + String lc_osname; + String home_dir; + + static final String out_sep = "\t"; + static final String in_sep = "\t"; + + public static int PRIORITY_IDX = 6; + public static int TEST_ID_IDX = 7; + public static int SHORT_DESC_IDX = 8; + public static int PASS_FAIL_IDX = 9; + public static int NUMERIC_SCORE_IDX = 10; + public static int NOTES_IDX = 11; + public static int UNITS_IDX = 12; + public static int GRAPH_GROUP_IDX = 13; + public static int SUBTEST_PASS_IDX = 14; + public static int SUBTEST_FAIL_IDX = 15; + + public static String TESTBED_TEMPLATE = "testbed_template.html"; + public static String AP_AUTO_BASIC_CX = "ap_auto_basic_cx"; + + public static String HTML_COLOR_PASS = "#bee7aa"; + public static String HTML_COLOR_FAIL = "#f9c5c0"; + public static String HTML_COLOR_WARNING = "#f8f6ad"; + + public static String PF = "PASS-FAIL"; + + public kpi() { + priv_init(); + } + + static String yellowTd(String txt) { + return "" + txt + ""; + } + + static String redTd(String txt) { + return "" + txt + ""; + } + + static String greenTd(String txt) { + return "" + txt + ""; + } + + public boolean is_mac() { + return lc_osname.startsWith("mac os x"); + } + public boolean is_win() { + return lc_osname.startsWith("windows"); + } + public boolean is_linux() { + return lc_osname.startsWith("linux"); + } + + private void priv_init() { + lc_osname = System.getProperty("os.name").toLowerCase(); + home_dir = System.getProperty("user.home"); + } + + public static void main(String[] args) { + kpi k = new kpi(); + try { + k.work(args); + } + catch (Exception ee) { + ee.printStackTrace(); + } + } + + public static String toStringNum(double i, int precision) { + NumberFormat nf = NumberFormat.getInstance(); + if (0.0 == i) { + // Don't pad precision on '0', just adds needless clutter. + return nf.format(i); + } + else { + nf = (NumberFormat)(nf.clone()); + nf.setMaximumFractionDigits(precision); + nf.setMinimumFractionDigits(precision); + return nf.format(i); + } + } + + public void work(String[] args) { + String dir = null; + String results_url = ""; + String caseid = ""; + String slack_fname = ""; + String testbed_name = "LANforge CICD"; + + for (int i = 0; i test_names = new Hashtable(); + Vector test_namesv = new Vector(); + Vector runs = new Vector(); + + test_names.put(PF, PF); + test_namesv.add(PF); + + try { + DirectoryStream stream = Files.newDirectoryStream(Paths.get(dir)); + for (Path file: stream) { + File f = file.toFile(); // this is the test run dir + // Inside of it is individual tests. + if (!f.isDirectory()) { + continue; + } + //System.out.println("Checking sub-directory/file (run): " + f.getAbsolutePath()); + DirectoryStream stream2 = Files.newDirectoryStream(file); + Run run = null; + + for (Path file2: stream2) { + File f2 = file2.toFile(); // this is the test case dir in the test run + //System.out.println("Checking test-case directory/file: " + f2.getAbsolutePath()); + // Directory full of test results? + if (f2.isDirectory()) { + DirectoryStream stream3 = Files.newDirectoryStream(file2); + Test test = new Test(f2.getName()); + if (run == null) { + run = new Run(f.getName()); + runs.add(run); + } + System.out.println("Run: " + run + " adding test: " + test); + run.addTest(test); + + // TODO: + // Add log processing. In particular, look in console logs for things like: + // "ath10k_pci 0000:01:00.0: wmi command 36967 timeout, restarting hardware" + // "WARNING: CPU: 0 PID: 8907 at backports-4. + + for (Path file3: stream3) { + File f3 = file3.toFile(); + String fname = f3.getName(); + if (fname.equals("kpi.csv")) { + try { + BufferedReader br = new BufferedReader(new FileReader(f3)); + if (test_names.get(f2.getName()) == null) { + test_names.put(f2.getName(), f2.getName()); + test_namesv.add(f2.getName()); + } + String line; + while ((line = br.readLine()) != null) { + test.addLine(line); + } + } + catch (FileNotFoundException enf) { + // ignore + } + catch (Exception e) { + e.printStackTrace(); + } + }// if kpi.csv + else if (fname.startsWith("kpi-") && fname.endsWith(".png")) { + test.addKpiImage(fname); + } + else if (fname.equals("logs")) { + File logs_csv = new File(f3.getAbsolutePath() + File.separator + "logs.csv"); + if (logs_csv.exists()) { + try { + BufferedReader br = new BufferedReader(new FileReader(logs_csv)); + String line; + while ((line = br.readLine()) != null) { + test.addLogCsv(line); + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + }// for all files in the test dir + } + } + } + } catch (IOException | DirectoryIteratorException x) { + // IOException can never be thrown by the iteration. + // In this snippet, it can only be thrown by newDirectoryStream. + System.err.println(x); + } + + // Sort runs so that earliest is first. + class SortbyDate implements Comparator { + // Used for sorting in ascending order of + // roll number + public int compare(Run a, Run b) { + long c = a.getDateMs() - b.getDateMs(); + if (c < 0) + return -1; + if (c > 0) + return 1; + return 0; + } + } + + runs.sort(new SortbyDate()); + + // Create meta-test results for each run. + for (int i = 0; i test_id_links = new Hashtable(); + + // We have read everything into memory. + // For each test, generate data over time. + Hashtable hist_data = new Hashtable(); + Vector hist_datav = new Vector(); + for (String tname: test_namesv) { + System.out.println("Checking testname: " + tname + " runs.size: " + runs.size()); + // For each test, find all runs that have this test and consolidate data + for (int i = 0; i groupsh = new Hashtable(); + Vector groupsn = new Vector(); + System.out.println("Checking history-data: " + hk); + for (HistRow csv : hist.csv) { + String ck = csv.getName(); + String title = csv.getTitle(); + String units = csv.getUnits(); + String g = csv.getGraphGroup(); + + System.out.println("csv name: " + ck); + + if (!(g.equals("NA") || units.equals(""))) { + Vector gv = groupsh.get(g); + if (gv == null) { + gv = new Vector(); + groupsh.put(g, gv); + groupsn.add(g); + } + gv.add(csv); + } + + if (units.equals("NA") || units.equals("")) { + units = "Data"; + } + try { + String cf = dir + File.separator + hk + "::" + ck + ".csv"; + System.out.println("Creating gnuplot csv: " + cf); + FileWriter f = new FileWriter(cf); + f.write(csv.csv.toString()); + f.close(); + csv.setFname(cf); + + ShellExec exec = new ShellExec(true, true); + int rv = exec.execute("gnuplot", null, true, + "-e", "set ylabel \"" + units + "\"", + "-e", "filename='" + cf + "'", + "-e", "set title '" + title + "'", + "default.plot"); + if (rv != 0) { + System.out.println("gnuplot for filename: " + cf + " rv: " + rv); + System.out.println(exec.getOutput()); + System.out.println(exec.getError()); + } + + File png = new File("plot.png"); + if (!png.exists()) { + System.out.println("ERROR: File: " + png + " does not exist."); + } + String npng = hk + "::" + ck + ".png"; + String nplot_name = dir + File.separator + npng; + System.out.println("Rename plot: " + png + " to: " + nplot_name); + File nf = new File(nplot_name); + Path nfp = Files.move(png.toPath(), nf.toPath(), StandardCopyOption.REPLACE_EXISTING);//boolean renamed = png.renameTo(nf); + if (!nf.exists()) { + System.out.println("ERROR: New file: " + nf + " does not exist."); + } + + exec = new ShellExec(true, true); + rv = exec.execute("gnuplot", null, true, "-e", "filename='" + cf + "'", + "mini.plot"); + if (rv != 0) { + System.out.println("mini gnuplot for filename: " + cf + " rv: " + rv); + System.out.println(exec.getOutput()); + System.out.println(exec.getError()); + } + + png = new File("plot.png"); + String npngt = hk + "::" + ck + "-thumb.png"; + Files.move(png.toPath(), new File(dir + File.separator + npngt).toPath(), StandardCopyOption.REPLACE_EXISTING); + + String hk_str = hk; + Run last = test_id_links.get(hk); + if (!hk.equals(PF)) { + if (last != null) { + hk_str = "" + hk + ""; + } + } + + StringBuffer change = new StringBuffer(); + Double cur = csv.scores.elementAt(csv.scores.size() - 1); + change.append("Last Value: " + toStringNum(cur, 2)); + if (csv.scores.size() > 1) { + Double prev = csv.scores.elementAt(csv.scores.size() - 2); + change.append("
Delta for last run: " + toStringNum((cur - prev), 2)); + if (cur != 0) { + change.append("
Percentage change: " + toStringNum(100.0 * ((cur - prev) / cur), 2) + "%"); + } + } + + String row_str = ("" + hk_str + "" + title + "" + + change + "\n"); + if (csv.getPriority() >= 100) { + scores.append(row_str); + } + else { + plots.append(row_str); + } + } + catch (Exception ee) { + ee.printStackTrace(); + } + } + + // Graph groups + for (String g : groupsn) { + Vector datasets = groupsh.get(g); + if (datasets.size() < 2) { + continue; // group of 1 doesn't count + } + + HistRow csv0 = datasets.elementAt(0); + String title = g; + String units = csv0.getUnits(); + + // Don't group scores + if (units.equalsIgnoreCase("Score")) { + continue; + } + + if (units.equals("NA") || units.equals("")) { + units = "Data"; + } + + System.out.println("title: " + title + " units: " + units); + try { + StringBuffer plot = new StringBuffer("plot "); + StringBuffer mplot = new StringBuffer("plot "); + boolean first = true; + for (HistRow csv: datasets) { + if (!first) { + plot.append(", "); + mplot.append(", "); + } + plot.append("\'" + csv.getFname() + "\' using 1:2 with lines title \'" + csv.getName().replace("_", " ") + "\'"); + mplot.append("\'" + csv.getFname() + "\' using 1:2 with lines notitle"); + first = false; + } + + BufferedReader br = new BufferedReader(new FileReader("default_group.plot")); + FileWriter f = new FileWriter("tmp.plot"); + String line; + while ((line = br.readLine()) != null) { + f.write(line); + f.write(System.lineSeparator()); + } + f.write(plot.toString()); + f.write(System.lineSeparator()); + f.close(); + br.close(); + + //System.out.println("group plot: " + plot); + ShellExec exec = new ShellExec(true, true); + int rv = exec.execute("gnuplot", null, true, + "-e", "set ylabel '" + units + "'", + "-e", "set title '" + title + "'", + "tmp.plot"); + if (rv != 0) { + System.out.println("gnuplot for group: " + title + " rv: " + rv); + System.out.println(exec.getOutput()); + System.out.println(exec.getError()); + } + + File png = new File("plot.png"); + String npng = hk + "::" + g + ".png"; + Files.move(png.toPath(), new File(dir + File.separator + npng).toPath(), StandardCopyOption.REPLACE_EXISTING); + + br = new BufferedReader(new FileReader("mini_group.plot")); + f = new FileWriter("tmp.plot"); + while ((line = br.readLine()) != null) { + f.write(line); + f.write(System.lineSeparator()); + } + f.write(mplot.toString()); + f.write(System.lineSeparator()); + f.close(); + br.close(); + + exec = new ShellExec(true, true); + rv = exec.execute("gnuplot", null, true, + "tmp.plot"); + if (rv != 0) { + System.out.println("mini gnuplot for group: " + title + " rv: " + rv); + System.out.println(exec.getOutput()); + System.out.println(exec.getError()); + } + + png = new File("plot.png"); + String npngt = hk + "::" + g + "-thumb.png"; + Files.move(png.toPath(), new File(dir + File.separator + npngt).toPath(), StandardCopyOption.REPLACE_EXISTING); + + String hk_str = hk; + Run last = test_id_links.get(hk); + if (!hk.equals(PF)) { + if (last != null) { + hk_str = "" + hk + ""; + } + } + + groups.append("" + hk_str + "" + title + "\n"); + + } + catch (Exception ee) { + ee.printStackTrace(); + } + } + + } + + String test_bed = "Test Bed"; + String last_run = ""; + String last_run_kpi_images = ""; + StringBuffer pngs = new StringBuffer(); + + boolean cp = true; + for (int i = 0; i 0) { + logs_str.append(redTd(logv + "")); + } + else { + logs_str.append(greenTd(logv + "")); + } + + logv = run.getLogWarnings(); + if (logv > 0) { + logs_str.append(redTd(logv + "")); + } + else { + logs_str.append(greenTd(logv + "")); + } + + logv = run.getLogCrashes(); + if (logv > 0) { + logs_str.append(redTd(logv + "")); + } + else { + logs_str.append(greenTd(logv + "")); + } + + logv = run.getLogRestarting(); + if (logv > 0) { + logs_str.append(redTd(logv + "")); + } + else { + logs_str.append(greenTd(logv + "")); + } + + String row_text = ("" + i + "" + run.getName() + "" + run.getDate() + + "" + run.getDutHwVer() + "" + run.getDutSwVer() + + "" + run.getDutModelNum() + "" + greenTd(run.getPass() + "") + redTd(run.getFail() + "") + logs_str + "\n"); + + if (i == (runs.size() - 1)) { + // Last run + int png_row_count = 0; + boolean needs_tr = true; + last_run = row_text; + for (Test t : run.testsv) { + for (String png : t.kpi_images) { + if (png.indexOf("-print") >= 0) { + continue; // skip the print variants of the image. + } + if (needs_tr) { + pngs.append(""); + needs_tr = false; + } + String img_title = "Test: " + t.getName(); + String fname = run.getName() + "/" + t.getName() + "/" + png; + pngs.append("\n"); + png_row_count++; + if (png_row_count == 2) { + png_row_count = 0; + pngs.append("\n"); + needs_tr = true; + } + } + } + + if ((!needs_tr) && pngs.length() > 0) { + pngs.append("\n"); + } + + String testrails_msg = ("MSG Run: " + run.getName() + " Date: " + run.getDate() + " DUT-HW: " + run.getDutHwVer() + "DUT-SW: " + run.getDutSwVer() + + " Passed: " + run.getPass() + " Fail: " + run.getFail() + + " Log-Bugs: " + run.getLogBugs() + " Log-Warnings: " + run.getLogWarnings() + + " Log-Crashes: " + run.getLogCrashes() + " Log-Restarting: " + run.getLogRestarting()); + + if (!caseid.equals("")) { + try { + // Create file for propagating results into external tool + String ofile = dir + File.separator + "testrails.txt"; + BufferedWriter bw = new BufferedWriter(new FileWriter(ofile)); + + bw.write("CASE_ID " + caseid); + bw.write(System.lineSeparator()); + bw.write("RUN_ID " + i); + bw.write(System.lineSeparator()); + bw.write(testrails_msg); + bw.write(System.lineSeparator()); + bw.write("MSG URL: " + results_url + "/" + run.getName()); + bw.write(System.lineSeparator()); + + bw.close(); + + System.out.println("See testrails results: " + ofile); + } + catch (Exception eee) { + eee.printStackTrace(); + } + } + + if (!slack_fname.equals("")) { + String slack_content_type = "Content-type: application/json"; + String slack_msg = "{\"text\":\"<" + results_url + "|" + testbed_name + ">" + + " Results: <" + results_url + "/" + run.getName() + "|" + run.getName() + ">" + + " Errors: " + (run.getLogCrashes() + run.getLogRestarting() + run.getLogBugs()) + " Warnings: " + run.getLogWarnings() + + "\"}"; + try { + BufferedReader br = new BufferedReader(new FileReader(slack_fname)); + String slack = br.readLine(); + String cmd = "curl -X POST -H '" + slack_content_type + "' --data '" + slack_msg + "' " + slack; + System.out.println(cmd); + + ShellExec exec = new ShellExec(true, true); + int rv = exec.execute("curl", null, true, + "-X", "POST", + "-H", slack_content_type, + "--data", slack_msg, slack); + if (rv != 0) { + System.out.println("curl slack post of msg: " + slack_msg + " to: " + slack + " failed.\n"); + System.out.println(exec.getOutput()); + System.out.println(exec.getError()); + } + } + catch (Exception eeee) { + System.out.println("slack_msg: " + slack_msg + " slack_fname: " + slack_fname); + eeee.printStackTrace(); + } + } + } + + runs_rows.append(row_text); + + if (cp) { + try { + String fname; + copy("CandelaLogo2-90dpi-200x90-trans.png", dir + File.separator + run.getName(), dir); + copy("candela_swirl_small-72h.png", dir + File.separator + run.getName(), dir); + copy("canvil.ico", dir + File.separator + run.getName(), dir); + copy("custom.css", dir + File.separator + run.getName(), dir); + copy("report.css", dir + File.separator + run.getName(), dir); + + cp = false; + } + catch (Exception ee) { + ee.printStackTrace(); + } + } + } + + try { + // Read in the testbed_template.html and update it with our info + BufferedReader br = new BufferedReader(new FileReader(new File(kpi.TESTBED_TEMPLATE))); + String ofile = dir + File.separator + "index.html"; + BufferedWriter bw = new BufferedWriter(new FileWriter(ofile)); + String line; + while ((line = br.readLine()) != null) { + line = line.replace("___TITLE___", test_bed + " Report History"); + line = line.replace("___SCORE_RUNS___", scores.toString()); + line = line.replace("___GROUP_GRAPHS___", groups.toString()); + line = line.replace("___DATA_GRAPHS___", plots.toString()); + line = line.replace("___TEST_RUNS___", runs_rows.toString()); + line = line.replace("___LATEST_RUN___", last_run); + line = line.replace("___LATEST_RUN_PNGS___", pngs.toString()); + bw.write(line); + bw.write(System.lineSeparator()); + } + + br.close(); + bw.close(); + + System.out.println("See " + ofile); + } + catch (Exception eee) { + eee.printStackTrace(); + } + } // ~work() + + public void copy(String fname, String from, String to) throws Exception { + Path fromp = Paths.get(from + File.separator + fname); + Path top = Paths.get(to + File.separator + fname); + Files.copy(fromp, top, StandardCopyOption.REPLACE_EXISTING); + } +} + + +/** This holds a datapoint for a particular test result for each of the Runs. + * This is used to generate historical graphs and comparisons. + * If a test generates two KPI results, there will be two HistRows for that + * test case. + */ +class HistRow { + String fname; + String name; + String title = ""; + String units = ""; + String graph_group = ""; + int prio = 0; + StringBuffer csv = new StringBuffer(); + Vector scores = new Vector(); + + public HistRow(Row r) { + name = r.getShortDescKey(); + title = r.getShortDesc(); + units = r.getUnits(); + graph_group = r.getGraphGroup(); + prio = r.getPriority(); + } + + int getPriority() { + return prio; + } + + String getFname() { + return fname; + } + + void setFname(String s) { + fname = s; + } + + String getName() { + return name; + } + + String getTitle() { + return title; + } + + String getUnits() { + return units; + } + + String getGraphGroup() { + return graph_group; + } +} + +/** This holds all HistRow objects for each test name for each run. + * It is used for generating historical graphs and comparisons. + */ +class History { + String name; + Vector csv = new Vector(); + Hashtable csvh = new Hashtable(); // lookup by name + + public History(String n) { + name = n; + } + + public String getName() { + return name; + } + + HistRow findRow(String n) { + //System.out.println("findCsv, n: " + n); + return csvh.get(n); + } + + void addRow(HistRow r) { + csv.add(r); + csvh.put(r.getName(), r); + } +} + +/** A Row represents a single KPI csv data point. The csv is split into tokens + * for easier manipulation by other code. + */ +class Row { + Vector rdata = new Vector(); + String short_desc_key = null; + + String getNotes() { + return rdata.elementAt(kpi.NOTES_IDX); + } + + String getSubtestPassed() { + if (rdata.size() > kpi.SUBTEST_PASS_IDX) + return rdata.elementAt(kpi.SUBTEST_PASS_IDX); + return ""; + } + + String getSubtestFailed() { + if (rdata.size() > kpi.SUBTEST_FAIL_IDX) + return rdata.elementAt(kpi.SUBTEST_FAIL_IDX); + return ""; + } + + String getUnits() { + try { + return rdata.elementAt(kpi.UNITS_IDX); + } + catch (Exception e) { + return ""; + } + } + + String getGraphGroup() { + try { + return rdata.elementAt(kpi.GRAPH_GROUP_IDX); + } + catch (Exception e) { + return ""; + } + } + + int getPriority() { + try { + return Long.valueOf(rdata.elementAt(kpi.PRIORITY_IDX)).intValue(); + } + catch (Exception e) { + return 0; + } + } + + String getPassFail() { + return rdata.elementAt(kpi.PASS_FAIL_IDX); + } + + String getScore() { + return rdata.elementAt(kpi.NUMERIC_SCORE_IDX); + } + + String getShortDesc() { + return rdata.elementAt(kpi.SHORT_DESC_IDX); + } + + String getTestId() { + return rdata.elementAt(kpi.TEST_ID_IDX); + } + + String getShortDescKey() { + return short_desc_key; + } + + void setShortDescKey(String s) { + short_desc_key = s; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("Row " + getShortDescKey() + " "); + for (int i = 0; i titles = null; + Vector data = new Vector(); + Hashtable descs = new Hashtable(); + Vector kpi_images = new Vector(); + Vector log_csv = new Vector(); + int pass = 0; + int fail = 0; + + int log_bugs = 0; + int log_warnings = 0; + int log_crashes = 0; + int log_restarting = 0; + + long date_ms = 0; + public String date = "NA"; + public String test_rig = "NA"; + public String dut_hw_version = "NA"; + public String dut_sw_version = "NA"; + public String dut_model_num = "NA"; + public String dut_serial_num = "NA"; + + public Test(String n) { + name = n; + } + + public int getLogBugs() { + return log_bugs; + } + + public int getLogWarnings() { + return log_warnings; + } + + public int getLogCrashes() { + return log_crashes; + } + + public int getLogRestarting() { + return log_restarting; + } + + public String toString() { + return "Name: " + name; + } + + public int getPass() { + return pass; + } + + public int getFail() { + return fail; + } + + void addKpiImage(String s) { + kpi_images.add(s); + } + + long getDateMs() { + return date_ms; + } + + String getTestRig() { + return test_rig; + } + + String getDutHwVer() { + return dut_hw_version; + } + + String getDutSwVer() { + return dut_sw_version; + } + + String getDutSerialNum() { + return dut_serial_num; + } + + String getDutModelNum() { + //System.out.println("Test: " + getName() + " model-num: " + dut_model_num); + return dut_model_num; + } + + String getDate() { + return date; + } + + String getName() { + return name; + } + + void addLogCsv(String l) { + log_csv.add(l); + try { + StringTokenizer st = new StringTokenizer(l, "\t"); + String tok = st.nextToken(); + if (tok.equals("FILE")) { + // title, ignore rest of this title + return; + } + log_bugs += Long.valueOf(st.nextToken()); + log_warnings += Long.valueOf(st.nextToken()); + log_crashes += Long.valueOf(st.nextToken()); + log_restarting += Long.valueOf(st.nextToken()); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + void addLine(String l) { + if (titles == null) { + titles = new Vector(); + StringTokenizer st = new StringTokenizer(l, kpi.in_sep, true); + boolean last_was_sep = false; + while (st.hasMoreTokens()) { + String tok = st.nextToken(); + if (tok.equals(kpi.in_sep)) { + if (last_was_sep) { + titles.add(new String()); + } + last_was_sep = true; + } + else { + titles.add(tok); + last_was_sep = false; + } + } + } + else { + Row row = new Row(); + data.add(row); + StringTokenizer st = new StringTokenizer(l, kpi.in_sep, true); + int idx = 0; + System.out.println("new line: " + l); + boolean last_was_sep = false; + while (st.hasMoreTokens()) { + String rtok = st.nextToken(); + if (rtok.equals(kpi.in_sep)) { + if (last_was_sep) { + row.rdata.add(new String()); + idx++; + } + last_was_sep = true; + } + else { + row.rdata.add(rtok); + idx++; + last_was_sep = false; + } + + if ((data.size() >= 1) && (!last_was_sep) && dut_sw_version.equals("NA")) { // first row is being added + if (titles.elementAt(idx - 1).equalsIgnoreCase("test-rig")) { + test_rig = rtok; + } + else if (titles.elementAt(idx - 1).equalsIgnoreCase("dut-hw-version")) { + dut_hw_version = rtok; + } + else if (titles.elementAt(idx - 1).equalsIgnoreCase("dut-sw-version")) { + dut_sw_version = rtok; + } + else if (titles.elementAt(idx - 1).equalsIgnoreCase("dut-model-num")) { + dut_model_num = rtok; + } + else if (titles.elementAt(idx - 1).equalsIgnoreCase("Date")) { + //System.out.println("idx: " + idx + " rtok: " + rtok); + date_ms = Long.valueOf(rtok).longValue(); + date = new Date(date_ms).toString(); + } + } + //System.out.println("idx: " + idx); + } + //System.out.println("done tok reading loop"); + + String pf = row.getPassFail().toLowerCase(); + if (pf.indexOf("pass") >= 0) { + pass++; + } + else if (pf.indexOf("fail") >= 0) { + fail++; + } + + String spass = row.getSubtestPassed(); + try { + if (!spass.equals("")) { + pass += Long.valueOf(spass).longValue(); + } + } + catch (Exception ee) { + ee.printStackTrace(); + } + + String sfail = row.getSubtestFailed(); + try { + if (!sfail.equals("")) { + fail += Long.valueOf(sfail).longValue(); + } + } + catch (Exception ee) { + ee.printStackTrace(); + } + + row.setShortDescKey(row.getShortDesc().replace(" ", "_")); + //System.out.println("Row: " + row); + descs.put(row.getShortDesc(), row.getShortDesc()); + } + }//addline +}//Test + + +/** A Run is a collection of tests. This class encompasses the entire regression test + * for a particular flavor of test (basic, fast, etc). + * The CI/CD process will create one or more Runs per build artifact per testbed. + */ +class Run { + String name; + Hashtable tests = new Hashtable(); + Vector testsv = new Vector(); + + public Run(String n) { + name = n; + } + + public String toString() { + return "Name: " + name; + } + + int getPass() { + int pass = 0; + for (Test t: testsv) { + pass += t.getPass(); + } + return pass; + } + + int getFail() { + int fail = 0; + for (Test t: testsv) { + fail += t.getFail(); + } + return fail; + } + + int getLogBugs() { + int fail = 0; + for (Test t: testsv) { + fail += t.getLogBugs(); + } + return fail; + } + + int getLogWarnings() { + int fail = 0; + for (Test t: testsv) { + fail += t.getLogWarnings(); + } + return fail; + } + + int getLogCrashes() { + int fail = 0; + for (Test t: testsv) { + fail += t.getLogCrashes(); + } + return fail; + } + + int getLogRestarting() { + int fail = 0; + for (Test t: testsv) { + fail += t.getLogRestarting(); + } + return fail; + } + + Test getFirstTest() { + return testsv.elementAt(0); + } + + String getDate() { + Test t = getFirstTest(); + if (t != null) + return t.getDate(); + return ""; + } + + long getDateMs() { + Test t = getFirstTest(); + if (t != null) + return t.getDateMs(); + return 0; + } + + String getTestRig() { + Test t = getFirstTest(); + if (t != null) + return t.getTestRig(); + return ""; + } + + String getDutHwVer() { + Test t = getFirstTest(); + if (t != null) + return t.getDutHwVer(); + return ""; + } + + String getDutSwVer() { + Test t = getFirstTest(); + if (t != null) + return t.getDutSwVer(); + return ""; + } + + String getDutSerNum() { + Test t = getFirstTest(); + if (t != null) + return t.getDutSerialNum(); + return ""; + } + + String getDutModelNum() { + Test t = getFirstTest(); + if (t != null) + return t.getDutModelNum(); + return ""; + } + + String getName() { + return name; + } + + void addTest(Test t) { + tests.put(t.getName(), t); + testsv.add(t); + } + + Test findTest(String n) { + return tests.get(n); + } +}//Run + + +// From: https://stackoverflow.com/questions/882772/capturing-stdout-when-calling-runtime-exec + +/** + * Execute external process and optionally read output buffer. + */ +class ShellExec { + private int exitCode; + private boolean readOutput, readError; + private StreamGobbler errorGobbler, outputGobbler; + + public ShellExec() { + this(false, false); + } + + public ShellExec(boolean readOutput, boolean readError) { + this.readOutput = readOutput; + this.readError = readError; + } + + /** + * Execute a command. + * @param command command ("c:/some/folder/script.bat" or "some/folder/script.sh") + * @param workdir working directory or NULL to use command folder + * @param wait wait for process to end + * @param args 0..n command line arguments + * @return process exit code + */ + public int execute(String command, String workdir, boolean wait, String...args) throws IOException { + String[] cmdArr; + StringBuffer dbg = new StringBuffer(); + dbg.append("Command: " + command); + if (args != null && args.length > 0) { + cmdArr = new String[1+args.length]; + cmdArr[0] = command; + System.arraycopy(args, 0, cmdArr, 1, args.length); + for (int i = 0; i \$::dir, + 'notes|n=s' => \$::notes, + 'gitlog|g=s' => \$::gitlog, + 'dutgitlog=s' => \$::dutgitlog, + 'title|t=s' => \$::title, + 'help|h' => \$show_help, +) || die("$::usage"); + +if ($show_help) { + print $usage; + exit 0; +} + +my $testbed_notes = ""; +if (-f "$notes") { + $testbed_notes .= "Test Bed Notes.
\n"; + $testbed_notes .= `cat $notes`; +} + +if (-f "$dutgitlog") { + $testbed_notes .= "

\n"; + $testbed_notes .= `cat $dutgitlog`; + $testbed_notes .= "

\n"; +} + +if (-f "$gitlog") { + $testbed_notes .= "

\n"; + $testbed_notes .= `cat $gitlog`; + $testbed_notes .= "

\n"; +} + +$testbed_notes .= "

Top lanforge-scripts commits.

\n";
+$testbed_notes .= `git log -n 8 --oneline`;
+$testbed_notes .= "
\n"; + + +chdir($dir); + +my @files = `ls`; +chomp(@files); + +my $line; + +# Find some html helpers and copy them to current dir. +foreach $line (@files) { + if ( -d $line) { + if ( -f "$line/canvil.ico") { + `cp $line/canvil.ico ./`; + `cp $line/*.css ./`; + `cp $line/candela_swirl* ./`; + `cp $line/CandelaLogo* ./`; + last; + } + } +} + +my $dut_tr = ""; +my $kpi_tr = ""; +my $tests_tr = ""; + +# TODO: Add git commit history for other repositories perhaps? + +foreach my $line (@files) { + if ( -d $line) { + #print "Checking report: $line\n"; + if ( -d "$line/logs") { + processLogs("$line/logs"); + my $log_links = ""; + my $li = 0; + my $iline; + my @ifiles = `ls $line/logs/*-idx.html`; + chomp(@ifiles); + foreach $iline (@ifiles) { + $log_links .= " [$li]"; + } + if ($log_links ne "") { + $log_links = "Errors: $log_links"; + } + $tests_tr .= "$lineLogs $log_links\n"; + } + else { + $tests_tr .= "$line\n"; + } + + if ( -f "$line/kpi.csv") { + my @kpi = `cat $line/kpi.csv`; + chomp(@kpi); + my $i = 0; + foreach my $k (@kpi) { + $i++; + if ($i == 1) { + next; # skip header + } + + my @cols = split(/\t/, $k); + if ($dut_tr eq "") { + $dut_tr = "$cols[1]$cols[2]$cols[3]$cols[4]$cols[5]\n"; + } + + my $nval = $cols[10]; + if ( $nval =~ /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\d+)?\z/i ) { + $nval = sprintf("%.2f", $nval); + } + + my $s_passed = "0"; + my $s_failed = "0"; + if (@cols >= 16) { + $s_passed = $cols[14]; + $s_failed = $cols[15]; + } + + $kpi_tr .= "$cols[7]$cols[8]$cols[9]$s_passed$s_failed$nval$cols[11]\n"; + } + } + } +} + +my $date = `date`; + +while (<>) { + my $ln = $_; + chomp($ln); + + $ln =~ s/___TITLE___/$title/g; + $ln =~ s/___DATE___/$date/g; + $ln =~ s/___TR_DUT___/$dut_tr/g; + $ln =~ s/___TR_KPI___/$kpi_tr/g; + $ln =~ s/___TR_TESTS___/$tests_tr/g; + $ln =~ s/___TESTBED_NOTES___/$testbed_notes/g; + print "$ln\n"; +} + +exit(0); + +sub processLogs { + my $ldir = shift; + + my @files = `ls $ldir`; + chomp(@files); + + open(CSV, ">$ldir/logs.csv"); + print CSV "FILE\tBUGS\tWARNINGS\tCRASHED\tRESTARTING\n"; + + foreach $line (@files) { + if ($line =~ /console.*\.txt$/) { + + my $bugs = 0; + my $warnings = 0; + my $crashed = 0; + my $restarting = 0; + + my $tag = 0; + my $logf = $ldir . "/" . $line; + my $logh = $logf . ".html"; + my $loghb = $line . ".html"; + my $loghi = $logf . "-idx.html"; + + #print("Processing log file: $logf\n"); + + open(IFILE, "<", $logf ) or die( "could not read $logf"); + open(IDX, ">$loghi"); + open(LOGH, ">$logh"); + + print IDX getHtmlHdr("Log file index: $line"); + print LOGH getHtmlHdr("Log file: $line"); + + print LOGH "
\n";
+
+         print IDX "Full Logs in HTML format
\n"; + print IDX "Full Logs in Text format

\n"; + print IDX "Interesting log sections.
\n"; + print IDX "

    \n"; + + while () { + my $ln = $_; + chomp($ln); + + #print("ln: $ln\n"); + my $enc_ln = encode_entities($ln); + # Get rid of some funk + $enc_ln =~ s/\&\#0\;//g; + + if (($ln =~ /WARNING:/) || + ($ln =~ /BUG:/) || + ($ln =~ /Hardware became unavailable during restart/) || + ($ln =~ /restarting hardware/) || + ($ln =~ /oom-killer/) || + ($ln =~ /crashed/)) { + if ($ln =~ /WARNING:/) { + $warnings++; + } + elsif ($ln =~ /BUG:/) { + $bugs++; + } + elsif ($ln =~ /restarting hardware/) { + $restarting++; + } + elsif (($ln =~ /crashed/) || # software/firmware crashed + ($ln =~ /oom-killer/) || # System OOM, processes were force-killed by kernel + ($ln =~ /became unavailable/)) { # hardware crashed + $crashed++; + } + print IDX "
  1. $enc_ln
  2. \n"; + print LOGH "\n"; + print LOGH "
    $enc_ln
    \n"; + $tag++; + } + else { + print LOGH "$enc_ln\n"; + } + } + + #print ("Done with file\n"); + + print LOGH "
\n"; + print IDX "\n"; + + print LOGH getHtmlFooter(); + print IDX getHtmlFooter(); + + close(IDX); + close(LOGH); + close(IFILE); + + print CSV "$line\t$bugs\t$warnings\t$crashed\t$restarting\n"; + + if ($bugs + $warnings +$crashed + $restarting == 0) { + # Remove index since it has no useful data + unlink($loghi); + } + } + } + #print("Done processing logs.\n"); +} + +sub getHtmlHdr { + my $title = shift; + + return "\n" . + "\n" . + " \n" . + " \n" . + " \n" . + " $title \n" . + " \n" . + " \n" . + " \n" . + " \n" . + " \n" . + "
\n" . + "

$title


\n" . + "\n" . + "
\n"; +} + +sub getHtmlFooter { + return "
\n" . + "
Generated by Candela Technologies LANforge network testing tool.
\n" . + " www.candelatech.com\n" . + "
\n" . + "

\n" . + " \n" . + "\n"; +} + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- diff --git a/lanforge/lanforge-scripts/gui/mini.plot b/lanforge/lanforge-scripts/gui/mini.plot new file mode 100644 index 000000000..549fa5e45 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/mini.plot @@ -0,0 +1,17 @@ +set term pngcairo size 150,100 +set output "plot.png" +set datafile separator "\t" +#set ylabel "Data" +#set xlabel "Test#" +set ylabel +set xlabel +#set xdata time +#set grid +#set key outside +set key off +unset xtics +unset ytics +unset border +set title +plot filename using 1:2 with lines + diff --git a/lanforge/lanforge-scripts/gui/mini_group.plot b/lanforge/lanforge-scripts/gui/mini_group.plot new file mode 100644 index 000000000..a89d43e61 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/mini_group.plot @@ -0,0 +1,17 @@ +set term pngcairo size 150,100 +set output "plot.png" +set datafile separator "\t" +#set ylabel "Data" +#set xlabel "Test#" +set ylabel +set xlabel +#set xdata time +#set grid +#set key outside +set key off +unset xtics +unset ytics +unset border +set title +#plot filename using 1:2 with lines + diff --git a/lanforge/lanforge-scripts/gui/test_configs/64_sta_scenario.txt b/lanforge/lanforge-scripts/gui/test_configs/64_sta_scenario.txt new file mode 100644 index 000000000..abacd0c9c --- /dev/null +++ b/lanforge/lanforge-scripts/gui/test_configs/64_sta_scenario.txt @@ -0,0 +1,12 @@ +profile_link 1.1 STA-AC 64 'DUT: TR398-DUT Radio-1' NA wiphy0,AUTO -1 +profile_link 1.1 upstream 1 'DUT: TR398-DUT LAN' NA eth1,AUTO -1 +chamber TR-398 495 239 NA 10.0 +chamber MobileStations 305 240 NA 10.0 +dut Lexus 0 0 +dut SurfacePro 110 152 +dut iphone 0 0 +dut Comcast 565 309 +dut NetgearAP 987 177 +resource 1.2 0 0 + + diff --git a/lanforge/lanforge-scripts/gui/test_configs/AP-Auto-ap-auto-32-64-dual.txt b/lanforge/lanforge-scripts/gui/test_configs/AP-Auto-ap-auto-32-64-dual.txt new file mode 100644 index 000000000..5de0fa89d --- /dev/null +++ b/lanforge/lanforge-scripts/gui/test_configs/AP-Auto-ap-auto-32-64-dual.txt @@ -0,0 +1,171 @@ +show_events: 1 +show_log: 1 +port_sorting: 0 +notes0: Chamber to Chamber test. +bg: 0xE0ECF8 +test_rig: TR-398 test bed +show_scan: 1 +auto_helper: 1 +skip_2: 1 +skip_5: 1 +dut5-0: TR398-DUT NETGEAR68-5G +dut2-0: TR398-DUT NETGEAR68 +dut5-1: NA +dut2-1: NA +dut5-2: NA +dut2-2: NA +spatial_streams: AUTO +bandw_options: AUTO +modes: Auto +upstream_port: 1.1.1 eth1 +operator: Ben Greear @ Candela Technologies +mconn: 1 +tos: 0 +vid_buf: 500000 +vid_speed: 700000 +reset_stall_thresh_udp_dl: 100000 +reset_stall_thresh_udp_ul: 100000 +reset_stall_thresh_tcp_dl: 100000 +reset_stall_thresh_tcp_ul: 100000 +reset_stall_thresh_l4: 100000 +reset_stall_thresh_voip: 0 +stab_udp_dl_min: 500000 +stab_udp_dl_max: 1544000 +stab_udp_ul_min: 500000 +stab_udp_ul_max: 1544000 +stab_tcp_dl_min: 500000 +stab_tcp_dl_max: 1544000 +stab_tcp_ul_min: 500000 +stab_tcp_ul_max: 1544000 +dl_speed: 85% +ul_speed: 85% +max_stations_2: 32 +max_stations_5: 64 +max_stations_dual: 64 +lt_sta: 2 +voip_calls: 10 +lt_dur: 120 +reset_dur: 300 +lt_gi: 30 +dur20: 20 +hunt_retries: 1 +cap_dl: 1 +cap_ul: 1 +cap_use_pkt_sizes: 0 +stability_reset_radios: 0 +pkt_loss_thresh: 10000 +frame_sizes: 200, 512, 1024, MTU +capacities: 1, 10, 32, 64, 128 +pf_text0: # modes: /a, /an-20 4x4, /an-40 4x4, /b, /bg, /bgn-20, /anAC-20,40,80 +pf_text1: # stations: 1, 10, 20, 50, ... +pf_text2: # Non-specified and fields set to "*" means match all. +pf_text3: # For a/b/g modes, Auto-BW == 20, Auto-NSS == 1 +pf_text4: # For /n modes, Auto-BW == 40, Auto-NSS == 4 +pf_text5: # For /ac and /ax modes, Auto-BW == 80, Auto-NSS == 4 +pf_text6: +pf_text7: # /a mode +pf_text8: 5 * 64 2Mbps mode=802.11a sta=1 +pf_text9: 5 * 1370 25Mbps mode=802.11a sta=1 +pf_text10: 5 * MTU 26Mbps mode=802.11a sta=1 +pf_text11: +pf_text12: 5 * 64 50Mbps mode=802.11an sta=1 bw=20 nss=4 +pf_text13: 5 * 1370 245Mbps mode=802.11an sta=1 bw=20 nss=4 +pf_text14: 5 * MTU 245Mbps mode=802.11an sta=1 bw=20 nss=4 +pf_text15: +pf_text16: 5 * 64 50Mbps mode=802.11an sta=1 bw=40 nss=4 +pf_text17: 5 * 1370 455Mbps mode=802.11an sta=1 bw=40 nss=4 +pf_text18: 5 * MTU 456Mbps mode=802.11an sta=1 bw=40 nss=4 +pf_text19: +pf_text20: # For any amount of /b stations +pf_text21: 2.4 * 64 1Mbps mode=802.11b sta=* +pf_text22: 2.4 * 1370 7Mbps mode=802.11b sta=* +pf_text23: 2.4 * MTU 7Mbps mode=802.11b sta=* +pf_text24: +pf_text25: # For any amount of /bg stations +pf_text26: 2.4 * 64 2Mbps mode=802.11bg sta=* +pf_text27: 2.4 * 1370 21Mbps mode=802.11bg sta=* +pf_text28: 2.4 * MTU 22Mbps mode=802.11bg sta=* +pf_text29: +pf_text30: # For /bgn 20Mhz stations. +pf_text31: 2.4 * 64 50Mbps mode=802.11bgn sta=1 bw=20 nss=4 +pf_text32: 2.4 * 1370 240Mbps mode=802.11bgn sta=1 bw=20 nss=4 +pf_text33: 2.4 * MTU 241Mbps mode=802.11bgn sta=1 bw=20 nss=4 +pf_text34: +pf_text35: 2.4 * 64 50Mbps mode=802.11bgn sta=10 bw=20 nss=4 +pf_text36: 2.4 * 1370 240Mbps mode=802.11bgn sta=10 bw=20 nss=4 +pf_text37: 2.4 * MTU 241Mbps mode=802.11bgn sta=10 bw=20 nss=4 +pf_text38: +pf_text39: 2.4 * 64 50Mbps mode=802.11bgn sta=50 bw=20 nss=4 +pf_text40: 2.4 * 1370 240Mbps mode=802.11bgn sta=50 bw=20 nss=4 +pf_text41: 2.4 * MTU 245Mbps mode=802.11bgn sta=50 bw=20 nss=4 +pf_text42: +pf_text43: 2.4 * 64 50Mbps mode=802.11bgn sta=100 bw=20 nss=4 +pf_text44: 2.4 * 1370 235Mbps mode=802.11bgn sta=100 bw=20 nss=4 +pf_text45: 2.4 * MTU 245Mbps mode=802.11bgn sta=100 bw=20 nss=4 +pf_text46: +pf_text47: 2.4 * 64 50Mbps mode=802.11bgn sta=200 bw=20 nss=4 +pf_text48: 2.4 * 1370 230Mbps mode=802.11bgn sta=200 bw=20 nss=4 +pf_text49: 2.4 * MTU 235Mbps mode=802.11bgn sta=200 bw=20 nss=4 +pf_text50: +pf_text51: # 40Mhz /n on 2.4, same values for all number of stations currently. +pf_text52: 2.4 * 64 50Mbps mode=802.11bgn sta=* bw=40 nss=4 +pf_text53: 2.4 * 1370 280Mbps mode=802.11bgn sta=* bw=40 nss=4 +pf_text54: 2.4 * MTU 281Mbps mode=802.11bgn sta=* bw=40 nss=4 +pf_text55: +pf_text56: # For /an-AC 20Mhz stations. +pf_text57: 5 * 64 50Mbps mode=802.11an-AC sta=1 bw=20 nss=4 +pf_text58: 5 * 1370 300Mbps mode=802.11an-AC sta=1 bw=20 nss=4 +pf_text59: 5 * MTU 305Mbps mode=802.11an-AC sta=1 bw=20 nss=4 +pf_text60: +pf_text61: # For /an-AC 40Mhz stations +pf_text62: 5 * 64 50Mbps mode=802.11an-AC sta=1 bw=40 nss=4 +pf_text63: 5 * 1370 615Mbps mode=802.11an-AC sta=1 bw=40 nss=4 +pf_text64: 5 * MTU 630Mbps mode=802.11an-AC sta=1 bw=40 nss=4 +pf_text65: +pf_text66: # For /an-AC 80Mhz stations. +pf_text67: 5 DL 64 50Mbps mode=802.11an-AC sta=1 bw=80 nss=4 +pf_text68: 5 DL 1370 1300Mbps mode=802.11an-AC sta=1 bw=80 nss=4 +pf_text69: 5 DL MTU 1300Mbps mode=802.11an-AC sta=1 bw=80 nss=4 +pf_text70: 5 UL 64 50Mbps mode=802.11an-AC sta=1 bw=80 nss=4 +pf_text71: 5 UL 1370 1100Mbps mode=802.11an-AC sta=1 bw=80 nss=4 +pf_text72: 5 UL MTU 1100Mbps mode=802.11an-AC sta=1 bw=80 nss=4 +pf_text73: +pf_text74: 5 DL 64 50Mbps mode=802.11an-AC sta=10 bw=80 nss=4 +pf_text75: 5 DL 1370 1300Mbps mode=802.11an-AC sta=10 bw=80 nss=4 +pf_text76: 5 DL MTU 1300Mbps mode=802.11an-AC sta=10 bw=80 nss=4 +pf_text77: 5 UL 64 50Mbps mode=802.11an-AC sta=10 bw=80 nss=4 +pf_text78: 5 UL 1370 1200Mbps mode=802.11an-AC sta=10 bw=80 nss=4 +pf_text79: 5 UL MTU 1200Mbps mode=802.11an-AC sta=10 bw=80 nss=4 +pf_text80: +pf_text81: 5 * 64 50Mbps mode=802.11an-AC sta=50 bw=80 nss=4 +pf_text82: 5 * 1370 1200Mbps mode=802.11an-AC sta=50 bw=80 nss=4 +pf_text83: 5 * MTU 1200Mbps mode=802.11an-AC sta=50 bw=80 nss=4 +pf_text84: +pf_text85: 5 * 64 50Mbps mode=802.11an-AC sta=100 bw=80 nss=4 +pf_text86: 5 * 1370 1100Mbps mode=802.11an-AC sta=100 bw=80 nss=4 +pf_text87: 5 * MTU 1100Mbps mode=802.11an-AC sta=100 bw=80 nss=4 +pf_text88: +pf_text89: # Auto (full capabilities) entries, for tput test, DUT is wave-1 3x3/2x2 +pf_text90: 2.4 * 64 50Mbps mode=Auto sta=* bw=Auto nss=Auto +pf_text91: 2.4 * 1370 280Mbps mode=Auto sta=* bw=Auto nss=Auto +pf_text92: 2.4 * MTU 282Mbps mode=Auto sta=* bw=Auto nss=Auto +pf_text93: +pf_text94: 5 * 64 50Mbps mode=Auto sta=* bw=Auto nss=Auto +pf_text95: 5 * 1370 650Mbps mode=Auto sta=* bw=Auto nss=Auto +pf_text96: 5 * MTU 650Mbps mode=Auto sta=* bw=Auto nss=Auto +radio2-0: 1.1.8 wiphy1 +radio2-1: 1.1.9 wiphy3 +radio2-2: 1.1.10 wiphy5 +radio5-0: 1.1.3 wiphy0 +radio5-1: 1.1.5 wiphy2 +radio5-2: 1.1.7 wiphy4 +basic_cx: 1 +tput: 0 +dual_band_tput: 0 +capacity: 0 +longterm: 0 +mix_stability: 0 +loop_iter: 1 +reset_batch_size: 1 +reset_duration_min: 20000 +reset_duration_max: 30000 diff --git a/lanforge/lanforge-scripts/gui/test_configs/WCT-64sta.txt b/lanforge/lanforge-scripts/gui/test_configs/WCT-64sta.txt new file mode 100644 index 000000000..55cd37aea --- /dev/null +++ b/lanforge/lanforge-scripts/gui/test_configs/WCT-64sta.txt @@ -0,0 +1,126 @@ +[BLANK] +sel_port-0: 1.1.eth1 +sel_port-1: 1.1.sta00000 +sel_port-2: 1.1.sta00001 +sel_port-3: 1.1.sta00002 +sel_port-4: 1.1.sta00003 +sel_port-5: 1.1.sta00004 +sel_port-6: 1.1.sta00005 +sel_port-7: 1.1.sta00006 +sel_port-8: 1.1.sta00007 +sel_port-9: 1.1.sta00008 +sel_port-10: 1.1.sta00009 +sel_port-11: 1.1.sta00010 +sel_port-12: 1.1.sta00011 +sel_port-13: 1.1.sta00012 +sel_port-14: 1.1.sta00013 +sel_port-15: 1.1.sta00014 +sel_port-16: 1.1.sta00015 +sel_port-17: 1.1.sta00016 +sel_port-18: 1.1.sta00017 +sel_port-19: 1.1.sta00018 +sel_port-20: 1.1.sta00019 +sel_port-21: 1.1.sta00020 +sel_port-22: 1.1.sta00021 +sel_port-23: 1.1.sta00022 +sel_port-24: 1.1.sta00023 +sel_port-25: 1.1.sta00024 +sel_port-26: 1.1.sta00025 +sel_port-27: 1.1.sta00026 +sel_port-28: 1.1.sta00027 +sel_port-29: 1.1.sta00028 +sel_port-30: 1.1.sta00029 +sel_port-31: 1.1.sta00030 +sel_port-32: 1.1.sta00031 +sel_port-33: 1.1.sta00032 +sel_port-34: 1.1.sta00033 +sel_port-35: 1.1.sta00034 +sel_port-36: 1.1.sta00035 +sel_port-37: 1.1.sta00036 +sel_port-38: 1.1.sta00037 +sel_port-39: 1.1.sta00038 +sel_port-40: 1.1.sta00039 +sel_port-41: 1.1.sta00040 +sel_port-42: 1.1.sta00041 +sel_port-43: 1.1.sta00042 +sel_port-44: 1.1.sta00043 +sel_port-45: 1.1.sta00044 +sel_port-46: 1.1.sta00045 +sel_port-47: 1.1.sta00046 +sel_port-48: 1.1.sta00047 +sel_port-49: 1.1.sta00048 +sel_port-50: 1.1.sta00049 +sel_port-51: 1.1.sta00050 +sel_port-52: 1.1.sta00051 +sel_port-53: 1.1.sta00052 +sel_port-54: 1.1.sta00053 +sel_port-55: 1.1.sta00054 +sel_port-56: 1.1.sta00055 +sel_port-57: 1.1.sta00056 +sel_port-58: 1.1.sta00057 +sel_port-59: 1.1.sta00058 +sel_port-60: 1.1.sta00059 +sel_port-61: 1.1.sta00060 +sel_port-62: 1.1.sta00061 +sel_port-63: 1.1.sta00062 +sel_port-64: 1.1.sta00063 +show_events: 1 +show_log: 0 +port_sorting: 0 +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 1 +skip_2: 0 +skip_5: 0 +batch_size: 1,2,5,10,20,40,64 +loop_iter: 1 +duration: 20000 +test_groups: 0 +test_groups_subset: 0 +protocol: UDP-IPv4 +dl_rate_sel: Total Download Rate: +dl_rate: 1000000000 +ul_rate_sel: Total Upload Rate: +ul_rate: 0 +prcnt_tcp: 100000 +l4_endp: +pdu_sz: -1 +mss_sel: 1 +sock_buffer: 0 +ip_tos: 0 +multi_conn: -1 +min_speed: -1 +ps_interval: 60-second Running Average +fairness: 0 +naptime: 0 +before_clear: 5000 +rpt_timer: 1000 +try_lower: 0 +rnd_rate: 1 +leave_ports_up: 0 +down_quiesce: 0 +udp_nat: 1 +record_other_ssids: 0 +clear_reset_counters: 1 +do_pf: 0 +pf_min_period_dl: 1544000 +pf_min_period_ul: 0 +pf_max_reconnects: 0 +use_mix_pdu: 0 +pdu_prcnt_pps: 1 +pdu_prcnt_bps: 0 +pdu_mix_ln-0: +show_scan: 1 +show_golden_3p: 0 +save_csv: 0 +show_realtime: 1 +show_pie: 1 +show_per_loop_totals: 1 +show_cx_time: 1 +show_dhcp: 1 +show_anqp: 1 +show_4way: 1 +show_latency: 1 + + diff --git a/lanforge/lanforge-scripts/gui/test_configs/dpt-pkt-sz.txt b/lanforge/lanforge-scripts/gui/test_configs/dpt-pkt-sz.txt new file mode 100644 index 000000000..1f5d7814c --- /dev/null +++ b/lanforge/lanforge-scripts/gui/test_configs/dpt-pkt-sz.txt @@ -0,0 +1,55 @@ +[BLANK] +sel_port-0: 1.1.sta0000 +show_events: 1 +show_log: 0 +port_sorting: 0 +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 0 +skip_2: 0 +skip_5: 0 +selected_dut: TR398-DUT +duration: 10000 +traffic_port: 1.1.10 sta00000 +upstream_port: 1.1.1 eth1 +path_loss: 10 +speed: 75% +speed2: 56Kbps +min_rssi_bound: -150 +max_rssi_bound: 0 +channels: AUTO +modes: Auto +pkts: 60;142;256;512;1024;MTU;4000 +spatial_streams: AUTO +security_options: AUTO +bandw_options: AUTO +traffic_types: UDP +directions: DUT Transmit +txo_preamble: OFDM +txo_mcs: 0 CCK, OFDM, HT, VHT +txo_retries: No Retry +txo_sgi: OFF +txo_txpower: 15 +attenuator: 0 +attenuator2: 0 +attenuator_mod: 255 +attenuator_mod2: 255 +attenuations: 0 300 +attenuations2: 0 300 +chamber: 0 +tt_deg: 0..+45..359 +cust_pkt_sz: +show_3s: 0 +show_ll_graphs: 0 +show_gp_graphs: 1 +show_1m: 1 +pause_iter: 0 +show_realtime: 1 +operator: +mconn: 1 +mpkt: 1000 +tos: 0 +loop_iterations: 1 + + diff --git a/lanforge/lanforge-scripts/gui/testbed_template.html b/lanforge/lanforge-scripts/gui/testbed_template.html new file mode 100644 index 000000000..e9784dcd7 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbed_template.html @@ -0,0 +1,70 @@ + + + + + + ___TITLE___ + + + + + +
+

___TITLE___


+ +
+ +
+

Latest test run details.

+ + +___LATEST_RUN___ +
Test IDTest RunDateDUT HardwareDUT SofwareDUT ModelPassFailLog BugsWarningsCrashesOther
+ +___LATEST_RUN_PNGS___ +
+

+ +

Scores and other top-priority test results.

+ + + ___SCORE_RUNS___ +
Test IDDescriptionPlot ThumbnailLast Run Details
+

+ +

Historical graphs for each Data Set Group.

+ + + ___GROUP_GRAPHS___ +
Test-IdDescriptionPlot Thumbnail
+

+ +

Individual Test Run Details for all data sets.

+ + + ___TEST_RUNS___ +
Test IDTest RunDateDUT HardwareDUT SofwareDUT ModelPassFailLog BugsWarningsCrashesOther
+

+ +

Historical graphs for each Data Set.

+ + + ___DATA_GRAPHS___ +
Test IDDescriptionPlot ThumbnailLast Run Details
+

+ +

+
Generated by Candela Technologies LANforge network testing tool.
+ www.candelatech.com +
+

+ + diff --git a/lanforge/lanforge-scripts/gui/testbeds/README_OPENWRT.txt b/lanforge/lanforge-scripts/gui/testbeds/README_OPENWRT.txt new file mode 100644 index 000000000..98443bbe6 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/README_OPENWRT.txt @@ -0,0 +1,23 @@ +This automation assumes that the AP is configured as pass-through AP, not +router: + +# Until cloud-sdk is integrated, stop opensync so we can do local config +service opensync stop +service opensync disable + +# Configure /etc/config/network to bridge eth ports and wifi devices. + +# Disable DHCP +/etc/init.d/dnsmasq disable +/etc/init.d/dnsmasq stop + +# Disable DHCP v6 +/etc/init.d/odhcpd disable +/etc/init.d/odhcpd stop + +# Disable firewall ??? +/etc/init.d/firewall disable +/etc/init.d/firewall stop + +/etc/init.d/network reload + diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/NOTES.txt b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/NOTES.txt new file mode 100644 index 000000000..2f48c4a48 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/NOTES.txt @@ -0,0 +1,10 @@ + +DUT is an MR8300, running OpenWrt. +This uses ath10k-ct firmware, and by default, the ath10k driver only supports 32 stations per +radio. To improve this, I tweaked the setup using the fwcfg files. +The radios also only work on certain frequencies, so one has to configure them +carefully. + +See the OpenWrt-overlay directory for files that should be copied onto the DUT +to work with this test. Once OpenSync cloud stuff is complete, the overlay may +not be needed. diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/OpenWrt-overlay/etc/config/wireless b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/OpenWrt-overlay/etc/config/wireless new file mode 100644 index 000000000..49398647e --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/OpenWrt-overlay/etc/config/wireless @@ -0,0 +1,57 @@ +config wifi-device 'radio0' + option type 'mac80211' + option hwmode '11a' + option path 'soc/40000000.pci/pci0000:00/0000:00:00.0/0000:01:00.0' + option htmode 'VHT80' + option disabled '0' + option channel '149' + +config wifi-iface 'default_radio0' + option device 'radio0' + option network 'lan' + option mode 'ap' + option disabled '0' + option ssid 'Default-SSID-5gu' + option hidden '0' + option key '12345678' + option encryption 'psk-mixed' + option isolate '1' + +config wifi-device 'radio1' + option type 'mac80211' + option hwmode '11g' + option path 'platform/soc/a000000.wifi' + option htmode 'HT20' + option disabled '0' + option channel '6' + +config wifi-iface 'default_radio1' + option device 'radio1' + option network 'lan' + option mode 'ap' + option disabled '0' + option ssid 'Default-SSID-2g' + option hidden '0' + option key '12345678' + option encryption 'psk-mixed' + option isolate '1' + +config wifi-device 'radio2' + option type 'mac80211' + option hwmode '11a' + option path 'platform/soc/a800000.wifi' + option htmode 'VHT80' + option disabled '0' + option channel '36' + +config wifi-iface 'default_radio2' + option device 'radio2' + option network 'lan' + option mode 'ap' + option ssid 'Default-SSID-5gl' + option key '12345678' + option encryption 'psk-mixed' + option isolate '1' + option hidden '0' + option disabled '0' + diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/ap-auto.txt b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/ap-auto.txt new file mode 100644 index 000000000..2aabe2cd9 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/ap-auto.txt @@ -0,0 +1,107 @@ +[BLANK] +sel_port-0: 1.1.sta00500 +show_events: 1 +show_log: 0 +port_sorting: 0 +kpi_id: AP Auto +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 1 +skip_2: 1 +skip_5: 1 +dut5-0: ea8300 OpenWrt-5lo +dut2-0: ea8300 OpenWrt-2 +dut5-1: NA +dut2-1: NA +dut5-2: ea8300 OpenWrt-2 +dut2-2: NA +spatial_streams: AUTO +bandw_options: AUTO +modes: Auto +upstream_port: 1.1.2 eth2 +operator: +mconn: 1 +tos: 0 +vid_buf: 1000000 +vid_speed: 700000 +reset_stall_thresh_udp_dl: 9600 +reset_stall_thresh_udp_ul: 9600 +reset_stall_thresh_tcp_dl: 9600 +reset_stall_thresh_tcp_ul: 9600 +reset_stall_thresh_l4: 100000 +reset_stall_thresh_voip: 20000 +stab_udp_dl_min: 56000 +stab_udp_dl_max: 0 +stab_udp_ul_min: 56000 +stab_udp_ul_max: 0 +stab_tcp_dl_min: 500000 +stab_tcp_dl_max: 0 +stab_tcp_ul_min: 500000 +stab_tcp_ul_max: 0 +dl_speed: 85% +ul_speed: 85% +max_stations_2: 128 +max_stations_5: 132 +max_stations_dual: 260 +lt_sta: 2 +voip_calls: 0 +lt_dur: 3600 +reset_dur: 600 +lt_gi: 30 +dur20: 20 +hunt_retries: 1 +cap_dl: 1 +cap_ul: 0 +cap_use_pkt_sizes: 0 +stability_reset_radios: 0 +pkt_loss_thresh: 10000 +frame_sizes: 200, 512, 1024, MTU +capacities: 1, 2, 5, 10, 20, 40, 64, 128, 256, 512, 1024, MAX +radio2-0: 1.1.4 wiphy0 +radio2-1: 1.1.6 wiphy2 +radio5-0: 1.1.5 wiphy1 +radio5-1: 1.1.7 wiphy3 +radio5-2: 1.1.8 wiphy4 +radio5-3: 1.1.9 wiphy5 +radio5-4: 1.1.10 wiphy6 +radio5-5: 1.1.11 wiphy7 +basic_cx: 1 +tput: 0 +dual_band_tput: 0 +capacity: 0 +longterm: 0 +mix_stability: 0 +loop_iter: 1 +reset_batch_size: 1 +reset_duration_min: 10000 +reset_duration_max: 60000 + +# Configure pass/fail metrics for this testbed. +pf_text0: 2.4 DL 200 70Mbps +pf_text1: 2.4 DL 512 110Mbps +pf_text2: 2.4 DL 1024 115Mbps +pf_text3: 2.4 DL MTU 120Mbps +pf_text4: +pf_text5: 2.4 UL 200 88Mbps +pf_text6: 2.4 UL 512 106Mbps +pf_text7: 2.4 UL 1024 115Mbps +pf_text8: 2.4 UL MTU 120Mbps +pf_text9: +pf_text10: 5 DL 200 72Mbps +pf_text11: 5 DL 512 185Mbps +pf_text12: 5 DL 1024 370Mbps +pf_text13: 5 DL MTU 525Mbps +pf_text14: +pf_text15: 5 UL 200 90Mbps +pf_text16: 5 UL 512 230Mbps +pf_text17: 5 UL 1024 450Mbps +pf_text18: 5 UL MTU 630Mbps + +# Tune connect-time thresholds. +cx_prcnt: 950000 +cx_open_thresh: 35 +cx_psk_thresh: 75 +cx_1x_thresh: 130 + + diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/dpt-pkt-sz.txt b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/dpt-pkt-sz.txt new file mode 100644 index 000000000..b51685d6a --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/dpt-pkt-sz.txt @@ -0,0 +1,55 @@ +[BLANK] +show_events: 1 +show_log: 0 +port_sorting: 0 +kpi_id: Dataplane +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 0 +skip_2: 0 +skip_5: 0 +selected_dut: ea8300 +duration: 15000 +traffic_port: 1.1.136 sta01001 +upstream_port: 1.1.2 eth2 +path_loss: 10 +speed: 85% +speed2: 0Kbps +min_rssi_bound: -150 +max_rssi_bound: 0 +channels: AUTO +modes: Auto +pkts: 60;142;256;512;1024;MTU;4000 +spatial_streams: AUTO +security_options: AUTO +bandw_options: AUTO +traffic_types: UDP +directions: DUT Transmit;DUT Receive +txo_preamble: OFDM +txo_mcs: 0 CCK, OFDM, HT, VHT +txo_retries: No Retry +txo_sgi: OFF +txo_txpower: 15 +attenuator: 0 +attenuator2: 0 +attenuator_mod: 255 +attenuator_mod2: 255 +attenuations: 0..+50..950 +attenuations2: 0..+50..950 +chamber: 0 +tt_deg: 0..+45..359 +cust_pkt_sz: +show_3s: 0 +show_ll_graphs: 1 +show_gp_graphs: 1 +show_1m: 1 +pause_iter: 0 +show_realtime: 1 +operator: +mconn: 1 +mpkt: 1000 +tos: 0 +loop_iterations: 1 + + diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/run_basic.bash b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/run_basic.bash new file mode 100755 index 000000000..9f3800c22 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/run_basic.bash @@ -0,0 +1,71 @@ +#!/bin/bash + +# Example usage of this script +# DUT_SW_VER=my-build-id ./run_basic.bash +# +# Other DUT variables in test_bed_cfg.bash may also be over-ridden, +# including those below. See LANforge 'add_dut' CLI command for +# details on what these variables are for. + +# DUT_FLAGS DUT_FLAGS_MASK DUT_SW_VER DUT_HW_VER DUT_MODEL +# DUT_SERIAL DUT_SSID1 DUT_SSID2 DUT_SSID3 +# DUT_PASSWD1 DUT_PASSWD2 DUT_PASSWD3 +# DUT_BSSID1 DUT_BSSID2 DUT_BSSID3 + +# Source config file +. test_bed_cfg.bash + +echo "Top wlan-testing git commits.
" > ./tmp_gitlog.html
+git log -n 8 --oneline >> ./tmp_gitlog.html
+echo "
" >> ./tmp_gitlog.html + +NOTES_HTML=`pwd`/testbed_notes.html +GITLOG=`pwd`/tmp_gitlog.html + +if [ -d "../../../wlan-ap" ] +then + DUTGITLOG=/tmp/${DUT_SW_VER}_dut_gitlog.html + echo "Top wlan-ap git commits.
" > $DUTGITLOG
+    (cd ../../../wlan-ap && git log -n 8 --oneline $DUT_SW_VER >> $DUTGITLOG && cd -)
+    echo "
" >> $DUTGITLOG + export DUTGITLOG +fi +export NOTES_HTML GITLOG + +# TODO: Copy config file to cloud controller and restart it +# and/or do other config to make it work. + +# Change to scripts dir +cd ../../ + +# Where to place results. basic_regression.bash will use this variable. +RSLTS_DIR=/tmp/ferndale-01-basic-regression +export RSLTS_DIR + +# Clean any existing data from the results dir +rm -fr $RSLTS_DIR + +# Run one test +# DEFAULT_ENABLE=0 DO_SHORT_AP_STABILITY_RESET=1 ./basic_regression.bash + +# Clean up old DHCP leases +../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --cmd "cli clear_port_counters ALL ALL ALL dhcp_leases" + +# Run all tests +./basic_regression.bash + +cd - + +if [ ! -d $RSLTS_DIR ] +then + echo "Test did not run as expected, $RSLTS_DIR not found." + mkdir -p $RSLTS_DIR +fi + +if [ -f ${MY_TMPDIR}/basic_regression_log.txt ] +then + echo "Found ${MY_TMPDIR}/basic_regression_log.txt, moving into $RSLTS_DIR" + mv ${MY_TMPDIR}/basic_regression_log.txt $RSLTS_DIR/ +fi + +echo "See results in $RSLTS_DIR" diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/run_basic_fast.bash b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/run_basic_fast.bash new file mode 100755 index 000000000..b8d2d5d4e --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/run_basic_fast.bash @@ -0,0 +1,81 @@ +#!/bin/bash + +# Example usage of this script +# DUT_SW_VER=my-build-id ./run_basic.bash +# +# Other DUT variables in test_bed_cfg.bash may also be over-ridden, +# including those below. See LANforge 'add_dut' CLI command for +# details on what these variables are for. + +# DUT_FLAGS DUT_FLAGS_MASK DUT_SW_VER DUT_HW_VER DUT_MODEL +# DUT_SERIAL DUT_SSID1 DUT_SSID2 DUT_SSID3 +# DUT_PASSWD1 DUT_PASSWD2 DUT_PASSWD3 +# DUT_BSSID1 DUT_BSSID2 DUT_BSSID3 + +DO_SHORT_AP_BASIC_CX=${DO_SHORT_AP_BASIC_CX:-1} +DO_WCT_BI=${DO_WCT_BI:-1} + +export DO_SHORT_AP_BASI_CX DO_WCT_BI + +# Source config file +. test_bed_cfg.bash + +echo "Top wlan-testing git commits.
" > ./tmp_gitlog.html
+git log -n 8 --oneline >> ./tmp_gitlog.html
+echo "
" >> ./tmp_gitlog.html + +NOTES_HTML=`pwd`/testbed_notes.html +GITLOG=`pwd`/tmp_gitlog.html + +if [ -d "../../../wlan-ap" ] +then + DUTGITLOG=/tmp/${DUT_SW_VER}_dut_gitlog.html + echo "Top wlan-ap git commits.
" > $DUTGITLOG
+    (cd ../../../wlan-ap && git log -n 8 --oneline $DUT_SW_VER >> $DUTGITLOG && cd -)
+    echo "
" >> $DUTGITLOG + export DUTGITLOG +fi +export NOTES_HTML GITLOG + +# TODO: Copy config file to cloud controller and restart it +# and/or do other config to make it work. + +# Change to scripts dir +cd ../../ + +# Where to place results. basic_regression.bash will use this variable. +RSLTS_DIR=/tmp/ferndale-01-basic-regression-fast +export RSLTS_DIR + +# Clean any existing data from the results dir +rm -fr $RSLTS_DIR + +# Clean up old DHCP leases +../lf_gui_cmd.pl --manager $GMANAGER --port $GMPORT --cmd "cli clear_port_counters ALL ALL ALL dhcp_leases" + +# Run a subset of available tests +# See 'Tests to run' comment in basic_regression.bash for available options. + +#DEFAULT_ENABLE=0 WCT_DURATION=20s DO_SHORT_AP_BASIC_CX=1 DO_WCT_BI=1 ./basic_regression.bash + +DEFAULT_ENABLE=0 WCT_DURATION=20s ./basic_regression.bash + + +# Run all tests +#./basic_regression.bash + +cd - + +if [ ! -d $RSLTS_DIR ] +then + echo "Test did not run as expected, $RSLTS_DIR not found." + mkdir -p $RSLTS_DIR +fi + +if [ -f ${MY_TMPDIR}/basic_regression_log.txt ] +then + echo "Found ${MY_TMPDIR}/basic_regression_log.txt, moving into $RSLTS_DIR" + mv ${MY_TMPDIR}/basic_regression_log.txt $RSLTS_DIR/ +fi + +echo "See results in $RSLTS_DIR" diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/scenario.txt b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/scenario.txt new file mode 100644 index 000000000..78da790a3 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/scenario.txt @@ -0,0 +1,15 @@ +profile_link 1.1 STA-AC 64 'DUT: ea8300 Radio-1' NA wiphy0,AUTO -1 +profile_link 1.1 STA-AC 64 'DUT: ea8300 Radio-1' NA wiphy2,AUTO -1 +profile_link 1.1 STA-AC 64 'DUT: ea8300 Radio-2' NA wiphy1,AUTO -1 +profile_link 1.1 STA-AC 64 'DUT: ea8300 Radio-3' NA wiphy3,AUTO -1 +profile_link 1.1 upstream-dhcp 1 NA NA eth2,AUTO -1 +profile_link 1.1 uplink-nat 1 'DUT: upstream LAN 92.168.100.1/24' NA eth3,eth2 -1 +profile_link 1.1 STA-AC 1 'DUT: ea8300 Radio-2' NA wiphy4,AUTO -1 +profile_link 1.1 STA-AC 1 'DUT: ea8300 Radio-3' NA wiphy5,AUTO -1 +profile_link 1.1 STA-AC 1 'DUT: ea8300 Radio-2' NA wiphy6,AUTO -1 +profile_link 1.1 STA-AC 1 'DUT: ea8300 Radio-3' NA wiphy7,AUTO -1 +dut ea8300 393 148 +dut upstream 306 62 +resource 1.1 132 218 + + diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/scenario_small.txt b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/scenario_small.txt new file mode 100644 index 000000000..29e92c5ac --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/scenario_small.txt @@ -0,0 +1,15 @@ +profile_link 1.1 STA-AC 8 'DUT: ea8300 Radio-1' NA wiphy0,AUTO -1 +profile_link 1.1 STA-AC 8 'DUT: ea8300 Radio-1' NA wiphy2,AUTO -1 +profile_link 1.1 STA-AC 8 'DUT: ea8300 Radio-2' NA wiphy1,AUTO -1 +profile_link 1.1 STA-AC 8 'DUT: ea8300 Radio-3' NA wiphy3,AUTO -1 +profile_link 1.1 upstream-dhcp 1 NA NA eth2,AUTO -1 +profile_link 1.1 uplink-nat 1 'DUT: upstream LAN 92.168.100.1/24' NA eth3,eth2 -1 +#profile_link 1.1 STA-AC 1 'DUT: ea8300 Radio-2' NA wiphy4,AUTO -1 +#profile_link 1.1 STA-AC 1 'DUT: ea8300 Radio-3' NA wiphy5,AUTO -1 +#profile_link 1.1 STA-AC 1 'DUT: ea8300 Radio-2' NA wiphy6,AUTO -1 +#profile_link 1.1 STA-AC 1 'DUT: ea8300 Radio-3' NA wiphy7,AUTO -1 +dut ea8300 393 148 +dut upstream 306 62 +resource 1.1 132 218 + + diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/test_bed_cfg.bash b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/test_bed_cfg.bash new file mode 100644 index 000000000..76144e45d --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/test_bed_cfg.bash @@ -0,0 +1,61 @@ +# Example test-bed configuration + +# Scripts should source this file to set the default environment variables +# and then override the variables specific to their test case (and it can be done +# in opposite order for same results +# +# After the env variables are set, +# call the 'lanforge/lanforge-scripts/gui/basic_regression.bash' +# from the directory in which it resides. + +PWD=`pwd` +AP_SERIAL=${AP_SERIAL:-/dev/ttyUSB1} +LF_SERIAL=${LF_SERIAL:-/dev/ttyUSB0} +LFPASSWD=${LFPASSWD:-lanforge} # Root password on LANforge machine +AP_AUTO_CFG_FILE=${AP_AUTO_CFG_FILE:-$PWD/ap-auto.txt} +WCT_CFG_FILE=${WCT_CFG_FILE:-$PWD/wct.txt} +DPT_CFG_FILE=${DPT_CFG_FILE:-$PWD/dpt-pkt-sz.txt} +SCENARIO_CFG_FILE=${SCENARIO_CFG_FILE:-$PWD/scenario.txt} + +# Default to enable cloud-sdk for this testbed, cloud-sdk is at IP addr below +USE_CLOUD_SDK=${USE_CLOUD_SDK:-192.168.100.164} + +# LANforge target machine +LFMANAGER=${LFMANAGER:-192.168.100.209} + +# LANforge GUI machine (may often be same as target) +GMANAGER=${GMANAGER:-192.168.100.209} +GMPORT=${GMPORT:-3990} +MY_TMPDIR=${MY_TMPDIR:-/tmp} + +# Test configuration (10 minutes by default, in interest of time) +STABILITY_DURATION=${STABILITY_DURATION:-600} +TEST_RIG_ID=${TEST_RIG_ID:-Ferndale-01-Basic} + +# DUT configuration +DUT_FLAGS=${DUT_FLAGS:-0x22} # AP, WPA-PSK +#DUT_FLAGS=${DUT_FLAGS:-0x2} # AP, Open +DUT_FLAGS_MASK=${DUT_FLAGS_MASK:-0xFFFF} +DUT_SW_VER=${DUT_SW_VER:-OpenWrt-Stock} +DUT_HW_VER=Linksys-EA8300 +DUT_MODEL=Linksys-EA8300 +DUT_SERIAL=${DUT_SERIAL:-NA} +DUT_SSID1=${DUT_SSID1:-Default-SSID-2g} +DUT_SSID2=${DUT_SSID2:-Default-SSID-5gl} +DUT_SSID3=${DUT_SSID3:-Default-SSID-5gu} +DUT_PASSWD1=${DUT_PASSWD1:-12345678} +DUT_PASSWD2=${DUT_PASSWD2:-12345678} +DUT_PASSWD3=${DUT_PASSWD3:-12345678} +DUT_BSSID1=24:f5:a2:08:21:6c +DUT_BSSID2=24:f5:a2:08:21:6d +DUT_BSSID3=24:f5:a2:08:21:6e + +export LF_SERIAL AP_SERIAL LFPASSWD +export AP_AUTO_CFG_FILE WCT_CFG_FILE DPT_CFG_FILE SCENARIO_CFG_FILE +export LFMANAGER GMANAGER GMPORT MY_TMPDIR +export STABILITY_DURATION TEST_RIG_ID +export DUT_FLAGS DUT_FLAGS_MASK DUT_SW_VER DUT_HW_VER DUT_MODEL +export DUT_SERIAL DUT_SSID1 DUT_SSID2 DUT_SSID3 +export DUT_PASSWD1 DUT_PASSWD2 DUT_PASSWD3 +export DUT_BSSID1 DUT_BSSID2 DUT_BSSID3 +export USE_CLOUD_SDK diff --git a/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/wct.txt b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/wct.txt new file mode 100644 index 000000000..3c7910719 --- /dev/null +++ b/lanforge/lanforge-scripts/gui/testbeds/ferndale-basic-01/wct.txt @@ -0,0 +1,323 @@ +[BLANK] +sel_port-0: 1.1.eth2 +sel_port-1: 1.1.sta00000 +sel_port-2: 1.1.sta01000 +sel_port-3: 1.1.sta00500 +sel_port-4: 1.1.sta01500 +sel_port-5: 1.1.sta03000 +sel_port-6: 1.1.sta03500 +sel_port-7: 1.1.sta04000 +sel_port-8: 1.1.sta04500 +sel_port-9: 1.1.sta00001 +sel_port-10: 1.1.sta01001 +sel_port-11: 1.1.sta00501 +sel_port-12: 1.1.sta01501 +sel_port-13: 1.1.sta00002 +sel_port-14: 1.1.sta01002 +sel_port-15: 1.1.sta00502 +sel_port-16: 1.1.sta01502 +sel_port-17: 1.1.sta00003 +sel_port-18: 1.1.sta01003 +sel_port-19: 1.1.sta00503 +sel_port-20: 1.1.sta01503 +sel_port-21: 1.1.sta00004 +sel_port-22: 1.1.sta01004 +sel_port-23: 1.1.sta00504 +sel_port-24: 1.1.sta01504 +sel_port-25: 1.1.sta00005 +sel_port-26: 1.1.sta01005 +sel_port-27: 1.1.sta00505 +sel_port-28: 1.1.sta01505 +sel_port-29: 1.1.sta00006 +sel_port-30: 1.1.sta01006 +sel_port-31: 1.1.sta00506 +sel_port-32: 1.1.sta01506 +sel_port-33: 1.1.sta00007 +sel_port-34: 1.1.sta01007 +sel_port-35: 1.1.sta00507 +sel_port-36: 1.1.sta01507 +sel_port-37: 1.1.sta00008 +sel_port-38: 1.1.sta01008 +sel_port-39: 1.1.sta00508 +sel_port-40: 1.1.sta01508 +sel_port-41: 1.1.sta00009 +sel_port-42: 1.1.sta01009 +sel_port-43: 1.1.sta00509 +sel_port-44: 1.1.sta01509 +sel_port-45: 1.1.sta00010 +sel_port-46: 1.1.sta01010 +sel_port-47: 1.1.sta00510 +sel_port-48: 1.1.sta01510 +sel_port-49: 1.1.sta00011 +sel_port-50: 1.1.sta01011 +sel_port-51: 1.1.sta00511 +sel_port-52: 1.1.sta01511 +sel_port-53: 1.1.sta00012 +sel_port-54: 1.1.sta01012 +sel_port-55: 1.1.sta00512 +sel_port-56: 1.1.sta01512 +sel_port-57: 1.1.sta00013 +sel_port-58: 1.1.sta01013 +sel_port-59: 1.1.sta00513 +sel_port-60: 1.1.sta01513 +sel_port-61: 1.1.sta00014 +sel_port-62: 1.1.sta01014 +sel_port-63: 1.1.sta00514 +sel_port-64: 1.1.sta01514 +sel_port-65: 1.1.sta00015 +sel_port-66: 1.1.sta01015 +sel_port-67: 1.1.sta00515 +sel_port-68: 1.1.sta01515 +sel_port-69: 1.1.sta00016 +sel_port-70: 1.1.sta01016 +sel_port-71: 1.1.sta00516 +sel_port-72: 1.1.sta01516 +sel_port-73: 1.1.sta00017 +sel_port-74: 1.1.sta01017 +sel_port-75: 1.1.sta00517 +sel_port-76: 1.1.sta01517 +sel_port-77: 1.1.sta00018 +sel_port-78: 1.1.sta01018 +sel_port-79: 1.1.sta00518 +sel_port-80: 1.1.sta01518 +sel_port-81: 1.1.sta00019 +sel_port-82: 1.1.sta01019 +sel_port-83: 1.1.sta00519 +sel_port-84: 1.1.sta01519 +sel_port-85: 1.1.sta00020 +sel_port-86: 1.1.sta01020 +sel_port-87: 1.1.sta00520 +sel_port-88: 1.1.sta01520 +sel_port-89: 1.1.sta00021 +sel_port-90: 1.1.sta01021 +sel_port-91: 1.1.sta00521 +sel_port-92: 1.1.sta01521 +sel_port-93: 1.1.sta00022 +sel_port-94: 1.1.sta01022 +sel_port-95: 1.1.sta00522 +sel_port-96: 1.1.sta01522 +sel_port-97: 1.1.sta00023 +sel_port-98: 1.1.sta01023 +sel_port-99: 1.1.sta00523 +sel_port-100: 1.1.sta01523 +sel_port-101: 1.1.sta00024 +sel_port-102: 1.1.sta01024 +sel_port-103: 1.1.sta00524 +sel_port-104: 1.1.sta01524 +sel_port-105: 1.1.sta00025 +sel_port-106: 1.1.sta01025 +sel_port-107: 1.1.sta00525 +sel_port-108: 1.1.sta01525 +sel_port-109: 1.1.sta00026 +sel_port-110: 1.1.sta01026 +sel_port-111: 1.1.sta00526 +sel_port-112: 1.1.sta01526 +sel_port-113: 1.1.sta00027 +sel_port-114: 1.1.sta01027 +sel_port-115: 1.1.sta00527 +sel_port-116: 1.1.sta01527 +sel_port-117: 1.1.sta00028 +sel_port-118: 1.1.sta01028 +sel_port-119: 1.1.sta00528 +sel_port-120: 1.1.sta01528 +sel_port-121: 1.1.sta00029 +sel_port-122: 1.1.sta01029 +sel_port-123: 1.1.sta00529 +sel_port-124: 1.1.sta01529 +sel_port-125: 1.1.sta00030 +sel_port-126: 1.1.sta01030 +sel_port-127: 1.1.sta00530 +sel_port-128: 1.1.sta01530 +sel_port-129: 1.1.sta00031 +sel_port-130: 1.1.sta01031 +sel_port-131: 1.1.sta00531 +sel_port-132: 1.1.sta01531 +sel_port-133: 1.1.sta00032 +sel_port-134: 1.1.sta01032 +sel_port-135: 1.1.sta00532 +sel_port-136: 1.1.sta01532 +sel_port-137: 1.1.sta00033 +sel_port-138: 1.1.sta01033 +sel_port-139: 1.1.sta00533 +sel_port-140: 1.1.sta01533 +sel_port-141: 1.1.sta00034 +sel_port-142: 1.1.sta01034 +sel_port-143: 1.1.sta00534 +sel_port-144: 1.1.sta01534 +sel_port-145: 1.1.sta00035 +sel_port-146: 1.1.sta01035 +sel_port-147: 1.1.sta00535 +sel_port-148: 1.1.sta01535 +sel_port-149: 1.1.sta00036 +sel_port-150: 1.1.sta01036 +sel_port-151: 1.1.sta00536 +sel_port-152: 1.1.sta01536 +sel_port-153: 1.1.sta00037 +sel_port-154: 1.1.sta01037 +sel_port-155: 1.1.sta00537 +sel_port-156: 1.1.sta01537 +sel_port-157: 1.1.sta00038 +sel_port-158: 1.1.sta01038 +sel_port-159: 1.1.sta00538 +sel_port-160: 1.1.sta01538 +sel_port-161: 1.1.sta00039 +sel_port-162: 1.1.sta01039 +sel_port-163: 1.1.sta00539 +sel_port-164: 1.1.sta01539 +sel_port-165: 1.1.sta00040 +sel_port-166: 1.1.sta01040 +sel_port-167: 1.1.sta00540 +sel_port-168: 1.1.sta01540 +sel_port-169: 1.1.sta00041 +sel_port-170: 1.1.sta01041 +sel_port-171: 1.1.sta00541 +sel_port-172: 1.1.sta01541 +sel_port-173: 1.1.sta00042 +sel_port-174: 1.1.sta01042 +sel_port-175: 1.1.sta00542 +sel_port-176: 1.1.sta01542 +sel_port-177: 1.1.sta00043 +sel_port-178: 1.1.sta01043 +sel_port-179: 1.1.sta00543 +sel_port-180: 1.1.sta01543 +sel_port-181: 1.1.sta00044 +sel_port-182: 1.1.sta01044 +sel_port-183: 1.1.sta00544 +sel_port-184: 1.1.sta01544 +sel_port-185: 1.1.sta00045 +sel_port-186: 1.1.sta01045 +sel_port-187: 1.1.sta00545 +sel_port-188: 1.1.sta01545 +sel_port-189: 1.1.sta00046 +sel_port-190: 1.1.sta01046 +sel_port-191: 1.1.sta00546 +sel_port-192: 1.1.sta01546 +sel_port-193: 1.1.sta00047 +sel_port-194: 1.1.sta01047 +sel_port-195: 1.1.sta00547 +sel_port-196: 1.1.sta01547 +sel_port-197: 1.1.sta00048 +sel_port-198: 1.1.sta01048 +sel_port-199: 1.1.sta00548 +sel_port-200: 1.1.sta01548 +sel_port-201: 1.1.sta00049 +sel_port-202: 1.1.sta01049 +sel_port-203: 1.1.sta00549 +sel_port-204: 1.1.sta01549 +sel_port-205: 1.1.sta00050 +sel_port-206: 1.1.sta01050 +sel_port-207: 1.1.sta00550 +sel_port-208: 1.1.sta01550 +sel_port-209: 1.1.sta00051 +sel_port-210: 1.1.sta01051 +sel_port-211: 1.1.sta00551 +sel_port-212: 1.1.sta01551 +sel_port-213: 1.1.sta00052 +sel_port-214: 1.1.sta01052 +sel_port-215: 1.1.sta00552 +sel_port-216: 1.1.sta01552 +sel_port-217: 1.1.sta00053 +sel_port-218: 1.1.sta01053 +sel_port-219: 1.1.sta00553 +sel_port-220: 1.1.sta01553 +sel_port-221: 1.1.sta00054 +sel_port-222: 1.1.sta01054 +sel_port-223: 1.1.sta00554 +sel_port-224: 1.1.sta01554 +sel_port-225: 1.1.sta00055 +sel_port-226: 1.1.sta01055 +sel_port-227: 1.1.sta00555 +sel_port-228: 1.1.sta01555 +sel_port-229: 1.1.sta00056 +sel_port-230: 1.1.sta01056 +sel_port-231: 1.1.sta00556 +sel_port-232: 1.1.sta01556 +sel_port-233: 1.1.sta00057 +sel_port-234: 1.1.sta01057 +sel_port-235: 1.1.sta00557 +sel_port-236: 1.1.sta01557 +sel_port-237: 1.1.sta00058 +sel_port-238: 1.1.sta01058 +sel_port-239: 1.1.sta00558 +sel_port-240: 1.1.sta01558 +sel_port-241: 1.1.sta00059 +sel_port-242: 1.1.sta01059 +sel_port-243: 1.1.sta00559 +sel_port-244: 1.1.sta01559 +sel_port-245: 1.1.sta00060 +sel_port-246: 1.1.sta01060 +sel_port-247: 1.1.sta00560 +sel_port-248: 1.1.sta01560 +sel_port-249: 1.1.sta00061 +sel_port-250: 1.1.sta01061 +sel_port-251: 1.1.sta00561 +sel_port-252: 1.1.sta01561 +sel_port-253: 1.1.sta00062 +sel_port-254: 1.1.sta01062 +sel_port-255: 1.1.sta00562 +sel_port-256: 1.1.sta01562 +sel_port-257: 1.1.sta00063 +sel_port-258: 1.1.sta01063 +sel_port-259: 1.1.sta00563 +sel_port-260: 1.1.sta01563 +show_events: 1 +show_log: 0 +port_sorting: 2 +kpi_id: WiFi Capacity +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 1 +skip_2: 0 +skip_5: 0 +batch_size: 1,5,10,20,40,80 +loop_iter: 1 +duration: 30000 +test_groups: 0 +test_groups_subset: 0 +protocol: TCP-IPv4 +dl_rate_sel: Total Download Rate: +dl_rate: 1000000000 +ul_rate_sel: Total Upload Rate: +ul_rate: 1000000000 +prcnt_tcp: 100000 +l4_endp: +pdu_sz: -1 +mss_sel: 1 +sock_buffer: 0 +ip_tos: 0 +multi_conn: -1 +min_speed: -1 +ps_interval: 60-second Running Average +fairness: 0 +naptime: 0 +before_clear: 5000 +rpt_timer: 1000 +try_lower: 0 +rnd_rate: 1 +leave_ports_up: 0 +down_quiesce: 0 +udp_nat: 1 +record_other_ssids: 0 +clear_reset_counters: 0 +do_pf: 0 +pf_min_period_dl: 128000 +pf_min_period_ul: 0 +pf_max_reconnects: 0 +use_mix_pdu: 0 +pdu_prcnt_pps: 1 +pdu_prcnt_bps: 0 +pdu_mix_ln-0: +show_scan: 1 +show_golden_3p: 0 +save_csv: 0 +show_realtime: 1 +show_pie: 1 +show_per_loop_totals: 1 +show_cx_time: 1 +show_dhcp: 1 +show_anqp: 1 +show_4way: 1 +show_latency: 1 + + diff --git a/lanforge/lanforge-scripts/har-to-portal.pl b/lanforge/lanforge-scripts/har-to-portal.pl new file mode 100755 index 000000000..1d508dcb7 --- /dev/null +++ b/lanforge/lanforge-scripts/har-to-portal.pl @@ -0,0 +1,213 @@ +#!/usr/bin/perl +use strict; +use warnings; +use diagnostics; +use Carp; +#use Time::HiRes; +# wow, this would have been cool but ... nope +use Archive::Har(); +use Try::Tiny; +use Getopt::Long; +use utf8; +require JSON; +require JSON::XS; +#use JSON::XS; +use Data::Dumper; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +#use constant NA => "NA"; +use constant NL => "\n"; +use constant Q => q("); +use constant a => q('); +use constant qQ => qq('"); +use constant CS => q(: ); +use constant c => q(,); +use constant dQH => q( ." -H '); +use constant CA => q(=>$::curl_args); +use constant MP => q( 'method'=>'POST',); +use constant PD => q( 'post_data'=>); +#use constant nbsp => " "; +$| = 1; + +package main; + +my $usage = qq($0 --har {file.jar} # HAR file saved from browser + --out {bot.pm} # portal bot module to create + --help|-h # this + ); +our $quiet = 1; +our $help = 0; +our $outfile; +our $harfile; + +GetOptions ( + 'quiet|q:s' => \$quiet, + 'help|h' => \$help, + 'har=s' => \$::harfile, + 'out|o=s' => \$::outfile, +) || (print($usage) && exit(1)); + +if ($help) { + print($usage); + exit(0); +} + +if (!(defined $::harfile) || !(defined $::outfile)) { + print $usage; + exit(1); +} + +die("unable to open $::harfile: $!") unless open(my $fh, "<", $::harfile); +read $fh, my $har_txt, -s $fh; # should yank that into LANforge::Utils +close $fh; +our $Decoder = JSON->new->utf8; +#print "** $har_txt".NL; + + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- +## Creating an Archive::HAR is not very efficient +## ----- ----- ----- ----- ----- ----- ----- ----- ----- +#$::harfile = Archive::Har->new(); +#$::harfile->string($har_txt); +#print Dumper($::harfile); +#foreach my $log_entry ($::harfile->entries()) { +# print "Log Entry: ".$log_entry->pageref() .NL if ($log_entry->pageref()); +# print "DT: ".$log_entry->started_date_time() .NL if ($log_entry->started_date_time()); +# #print "Request: ".Dumper($log_entry->request()) .NL; +# print "Request Url:".$log_entry->request()->{url} .NL; +# my $headers = $log_entry->request()->{headers}; +# foreach my $header (@$headers) { +# print "Header: ".$header->{name} .NL; +# } +# #print "Response: ".Dumper($log_entry->response()) .NL; +# #print "Server: ".$log_entry->server_ip_address() .NL; +#} + + + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- +## Creating a plain JSON object is more efficient, +## and more compatible with FF +## ----- ----- ----- ----- ----- ----- ----- ----- ----- +my $json = $::Decoder->decode($har_txt); +$::Decoder->canonical(1); +$::Decoder->allow_blessed(1); +my %ordered_entries = (); +print "I see ".(length($json->{log}->{entries}))." entries\n"; + +foreach my $entry (@{$json->{log}->{entries}}) { + my $request_start = $entry->{startedDateTime}; + $ordered_entries{$request_start} = \$entry; +} +print "------------------------------------------------------------------------------------\n"; +die("unable to open $::outfile: $!") unless open($fh, ">", $::outfile); +print $fh q[ +##----------------------------------------------------------------------------# +## # +## Activate this module with the --bot/-b switch # +## from the portal-bot.pl command line; EG: # +## portal-bot.pl -b h1.pm ... # +## # +## It is absolutely necessary that no code in bot:: modules endangers the # +## operation of the portal-bot script. Do not call die() or exit(). # +## Communicate your displeasure using logg(), dbgdie() or signal_exit(). # +##----------------------------------------------------------------------------# +package bot; + +if (defined $ENV{'PBOT_NOFORK'} && $ENV{'PBOT_NOFORK'} eq "1") { + use strict; + use warnings; + use diagnostics; + use Carp; + $SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; + use Data::Dumper; +} + +use URI::Escape; +use botlib qw(dbg isBlank logg signal_exit dbgdie newEvent request); +use Exporter; +our @EXPORT_OK = qw(find_redirect_url submit_login submit_logout interpret_login_response); +#use HTML::TreeBuilder::XPath; +use Encode; + +@main::delays=(0.0, 0.0, 1.1); +$::curl_args .=" --max-redirs 0"; +our $extra_args = " --max-redirs 0" + ." --keepalive-time 6800" + ." --max-time 6800" + ." -H 'Connection: keep-alive'" + ." -H 'User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0'" + ; + +sub find_redirect_url { +} +]; # end module top + +my $found_redirect = 0; +my $found_login_post = 0; + +for my $request_start ( sort keys %ordered_entries ) { + print "Start: $request_start\n"; + my $entry = $ordered_entries{$request_start}; + my $request = $$entry->{request}; + my $response = $$entry->{response}; + + my $req_headers = $request->{headers}; + my $res_headers = $response->{headers}; + + my $req_cookies = $request->{cookies} if (defined $request->{cookies}) || []; + my $res_cookies = $response->{cookies} if (defined $response->{cookies}) || []; + + my $url = $request->{url}; + my $method = $request->{method}; + print $fh "\n=pod\n"; + print $fh "------------------------------------------------------------------------------------\n"; + print $fh "$method: $url\n"; + print $fh "------------------------------------------------------------------------------------\n"; + print $fh "=cut\n"; + print $fh "request({'curl_args'".CA; + for my $header_e (@$req_headers) { + print $fh NL.dQH. $header_e->{name} .CS. $header_e->{value} .qQ; + } + # seems like HTTP/2 POSTS to google lack any postData? + if (($method eq "POST") && ($request->{httpVersion} =~ m|^HTTP/1|)) { + $found_login_post++; + print $fh c.NL.MP.NL; + print $fh PD.a. $request->{'postData'}->{'text'} .a; + } + print $fh c.NL.q( 'url'=>).Q. $url .Q.c.NL; + print $fh q( 'print'=>1).NL; + print $fh q[}, \@response);].NL; + + for my $req_cookie(@$req_cookies) { + print $fh " request_cookie "; + print $fh "{'".$req_cookie->{name}."'} = '".$req_cookie->{value}."';\n"; + } + print $fh "=pod".NL; + if ($response->{status} == 301 || $response->{status} == 302) { + $found_redirect++; + print $fh "Expect redirect: ".$response->{status}.NL; + } + for my $header_e (@$res_headers) { + print $fh " response_header: ".$header_e->{name} .": ".$header_e->{value} .NL; + } + print $fh "=cut".NL; + for my $res_cookie(@$res_cookies) { + print $fh " response_cookie"; + print $fh "{'".$res_cookie->{name}."'} = '".$res_cookie->{value}."';\n"; + } + print $fh NL; +} # ~for each request sorted by time + +# + # create find_redirect_url() + + # create submit_login() + + # create interpret_login_response() + + # create submit_logout() +close $fh; +### +### +### diff --git a/lanforge/lanforge-scripts/hires_cxreport.pl b/lanforge/lanforge-scripts/hires_cxreport.pl new file mode 100755 index 000000000..bb7e20dab --- /dev/null +++ b/lanforge/lanforge-scripts/hires_cxreport.pl @@ -0,0 +1,130 @@ +#!/usr/bin/perl +package main; +if (defined $ENV{DEBUG}) { + use strict; + use warnings; + use diagnostics; + use Carp; + use Data::Dumper; +} +use Time::HiRes qw(usleep ualarm gettimeofday stat lstat utime); +#use Time::Format qw/%time/; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; +our $quiet = 1; + +my $report_filename = "/tmp/hires_report.txt"; +my $duration_sec = 60; +my $cx = "rdtest"; + + +our $lfmgr_host = 'localhost'; +our $lfmgr_port = 4001; +$| = 1; +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 60); + +$t->open(Host => $::lfmgr_host, + Port => $::lfmgr_port, + Timeout => 10); + +$t->max_buffer_length(16 * 1024 * 1000); # 16 MB buffer +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +our $utils = new LANforge::Utils(); +$::utils->telnet($t); # Set our telnet object. +if ($::utils->isQuiet()) { + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + $::utils->cli_send_silent(0); + } + else { + $::utils->cli_send_silent(1); # Do not show input to telnet + } + $::utils->cli_rcv_silent(1); # Repress output from telnet +} +else { + $::utils->cli_send_silent(0); # Show input to telnet + $::utils->cli_rcv_silent(0); # Show output from telnet +} +#$::utils->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); + +$SIG{'INT'} = sub { + $::utils->doCmd("set_cx_state all $cx STOPPED"); + exit 0; +}; + +# start rdtest +my %times = (); +$times{gettimeofday().'_before_set_cx_state'} = gettimeofday() ." before_start_cx 0 0"; +$::utils->doCmd("set_cx_report_timer all $cx 250"); +$::utils->doCmd("set_cx_state all $cx RUNNING"); +$times{gettimeofday().'_after_set_cx_state'} = gettimeofday() ." after_start_cx 0 0"; + +my $timelimit = $duration_sec + time(); + +my $show_cx_str = ''; +my $lastline = ''; +my $lasttime = 0; +my @hunks = (); +my $delta = 0; +my $tod = gettimeofday(); +my $last_a = 0; +my $last_b = 0; +my $step_a = 0; +my $step_b = 0; +while ($tod < $timelimit) { +# the command below does not indicate last reported timestamp, skip it +# $show_cx_str = $::utils->doAsyncCmd("show_cxe all $cx"); +# $times{gettimeofday()."_show_cxe"} = $show_cx_str; + $tod = gettimeofday(); + $lastline=`tail -1 /home/lanforge/lf_reports/${cx}-A*`; + @hunks = split(',', $lastline); + $hunks[0] = $hunks[0]/1000 if ($hunks[0] > 0); + $last_a = $hunks[0] if ($last_a == 0); + if ($hunks[0] gt $last_a){ + print "\nnew report A entry!\n"; + $step_a = $hunks[0] - $last_a; + $last_a = $hunks[0]; + $delta = $tod - $hunks[0]; + $times{"${tod}_tail_csv-A"} = "$hunks[0] $hunks[1] $step_a $delta"; + } + $lastline=`tail -1 /home/lanforge/lf_reports/${cx}-B*`; + @hunks = split(',', $lastline); + $hunks[0] = $hunks[0]/1000 if ($hunks[0] > 0); + $last_b = $hunks[0] if ($last_b == 0); + if ($hunks[0] gt $last_b) { + print "\nnew report B entry!\n"; + $step_b = $hunks[0] - $last_b; + $last_b = $hunks[0]; + $delta = $tod - $hunks[0]; + $times{"${tod}_tail_csv-B"} = "$hunks[0] $hunks[1] $step_b $delta"; + } + usleep(125); + if (time() gt $lasttime) { + print "\r".($timelimit - time())." sec remaining "; + $lasttime = time(); + } +} #~while +$::utils->doCmd("set_cx_state all $cx STOPPED"); +print "...collected.\n"; +die unless open(my $fh, ">", $report_filename); +#print $fh "TimeKeyInput csv_record_tstampsecs endpoint sec_since_last_report seconds_lag_since_last_report\n"; +print $fh "clock csv_tstamp_secs endpoint sec_btwn_reports tstamp_lag_sec\n"; +foreach $key (sort {$a cmp $b} (keys %times)) { + my ($clock) = $key =~ m/^([^_]+)/; + @hunks = split(' ', $times{$key}); + print$fh sprintf "%14.3f %15.3f %18s %20.3f %15.3f\n", 0.0+$clock, $hunks[0], $hunks[1], $hunks[2], $hunks[3]; +} +close $fh; +print "View the report at $report_filename\n"; + + +#eof diff --git a/lanforge/lanforge-scripts/hostap_timestamp.pl b/lanforge/lanforge-scripts/hostap_timestamp.pl new file mode 100755 index 000000000..ea6c817c3 --- /dev/null +++ b/lanforge/lanforge-scripts/hostap_timestamp.pl @@ -0,0 +1,29 @@ +#!/usr/bin/perl + +# Convert LANforge logs and hostapd log timestamps to human readable timestamp. + +use POSIX qw( strftime ); + +while (<>) { + my $ln = $_; + if ($ln =~ /^(\d+)\.(\d+): (.*)/) { + my $time_sec = $1; + my $usec = $2; + my $rest = $3; + my $usec_pad = sprintf("%06d", $usec); + my $dt = strftime("%Y-%m-%d %H:%M:%S", localtime($time_sec)); + print "$dt.$usec_pad $rest\n"; + } + elsif ($ln =~ /^(\d+): (.*)/) { + my $tot_msec = $1; + my $rest = $2; + my $sec = int($tot_msec / 1000); + my $msec = $tot_msec % 1000; + my $msec_pad = sprintf("%03d", $msec); + my $dt = strftime("%Y-%m-%d %H:%M:%S", localtime($sec)); + print "$dt.$msec_pad $rest\n"; + } + else { + print $ln; + } +} diff --git a/lanforge/lanforge-scripts/imix.pl b/lanforge/lanforge-scripts/imix.pl new file mode 100755 index 000000000..bc1b164bd --- /dev/null +++ b/lanforge/lanforge-scripts/imix.pl @@ -0,0 +1,460 @@ +#!/usr/bin/perl + +# IMIX Throughput Test +# +# Uses a binary search algorithm to determine the maximum throughput at which +# a specified percent packet loss occurs and a maximum latency is allowed +# for a given theoretical throughput rate at different packet sizes suggested +# by IMIX literature. +# +# USAGE: perl imix.pl lf_host port-1 port-2 theoretical_rate max_latency +# max_drop_percentage binary_search_attempts endpoint_duration test_loops +# +# Example: perl imix.pl 192.168.100.192 1 2 10000000 200 10 9 10 1 + +# Un-buffer output +$| = 1; + +use strict; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; +use LANforge::Endpoint; + +my $script_name = "imix.pl"; + +my $lfmgr_host = undef; +my $lfmgr_port = 4001; + +my $test_mgr = "imix_tm"; + +my $shelf = 1; + +# This sets up connections between 2 LANforge machines +my $lf1 = 1; # Minor Resource EID. +my $lf2 = 1; # Set to "" or same as $lf1 if we have no second machine. For second machine set + # to second Resource minor EID to create mac-vlans on it. + +# Port pairs. These are the ports that should be talking to each other. +# i.e. the third column in lf1_ports talks to the third column in lf2_ports. +# EIDs or aliases can be used. +# Port pairs must match on each shelf - will enhance to allow any pair on each shelf. +#my @lf1_ports = (1); #, 2, 3); +#my @lf2_ports = (2); #, 2, 3); +my @lf1_ports = ("eth2"); #, "eth0"); +my @lf2_ports = ("eth3"); #, "eth1"); + +my @lf1_port_ips = ("172.1.1.100"); +my @lf2_port_ips = ("172.1.1.101"); + +my @lf1_port_gws = ("172.1.1.1"); +my @lf2_port_gws = ("172.1.1.1"); + +# IMIX Type Definition for UDP +# Packet sizes are in bytes of UDP payload +my @cx_types = ("lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp"); +my @min_pkt_szs = ( 22, 86, 214, 470, 982, 1238, 1458, 1472); +my @max_pkt_szs = ( 22, 86, 214, 470, 982, 1238, 1458, 1472); +my @tput_rates = ( 1000000, 4000000, 12000000, 45000000,155000000,155000000,155000000,155000000); + +my $tput = 1544000; # Network/Device Under Test Maximum Theoretical Throughput in bps. + +my $max_latency = 1; # Maximum Latency in miliseconds, allowed before adjusting rate down. +my $drop_percent = 0.0001; # Maximum Drop-Percentage allowed before adjusting rate down. + +my $binary_search_attempts = 9; # Number of attempts to find throughput for a given pkt size and $drop_percent. +my $endp_wait_for_update = 10; # Seconds allowed for endpoints to update. +my $endp_duration = 30; # Seconds endpoints are allowed to run which can affect results. +my $loop_max = 1; # Number of times the entire test will be run + + +my @endp_drops = (); +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## +# Parse cmd-line args +my $i; +for ($i = 0; $i<@ARGV; $i++) { + my $var = $ARGV[$i]; + if ($var =~ m/(\S+)=(.*)/) { + my $arg = $1; + my $val = $2; + handleCmdLineArg($arg, $val); + } + else { + handleCmdLineArg($var); + } +} + +if ($lfmgr_host == undef) { + print "\nYou must define a LANforge Manager!!!\n\n" + . "For example:\n" + . "./$script_name mgr=locahost\n" + . "OR\n" + . "./$script_name mgr=192.168.1.101\n\n"; + printHelp(); + exit (1); +} + + +my $min_rate = $tput; +my $max_rate = $min_rate; + +my $report_timer = 1000; # Report timer for endpoints. + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + +my $timeout = 60; + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => $timeout); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + @endpoint_names = (); + @cx_names = (); + + initToDefaults(); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + print "Loop $loop: Done adding CXs.\n"; + print "Pause $endp_wait_for_update seconds for endpoints to update.\n"; + sleep($endp_wait_for_update); + + # Start Cross-Connects + for (my $q=0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + + my @next_adj = (int($max_rate / 2), int($max_rate / 2)); + my @current_rate = ($max_rate, $max_rate); + my @last_current_rate = (0,0); + my @new_rate = (0,0); + my $flag = 0; + my $best_rate = 0; + my $adj_count = 0; + my $p1 = $q+$q; + my $p2 = $p1+1; + + + for ($adj_count=0; $adj_count < $binary_search_attempts; $adj_count++) { + + doCmd("clear_endp_counters"); + doCmd("clear_cx_counters"); + print "Adjustment Period: $adj_count\n"; + print "sleep $endp_duration seconds\n"; + sleep($endp_duration); + + for (my $p=$p1; $p<=$p2; $p++) { + my $endp1 = new LANforge::Endpoint(); + $utils->updateEndpoint($endp1, $endpoint_names[$p]); + my $en1 = $endp1->rx_drop_seq(); + my $en2 = $endp1->port_id(); + my $en3 = $endp1->real_rx_rate(); + my $lat = $endp1->avg_latency(); + + my $i = $p-$p1; + if ( $en1 > $drop_percent || $lat > $max_latency ) { + print "RATE DOWN: Percent Dropped is $en1 : Port is $en2 : Real RX Rate is: $en3 : Latency: $lat\n"; + $new_rate[$i] = $current_rate[$i] - $next_adj[$i]; + } + elsif ( $current_rate[$i] < $max_rate ) { + print "RATE UP: Percent Dropped is $en1 : Port is $en2 : Real RX Rate is: $en3 : Latency: $lat\n"; + $last_current_rate[$i] = $current_rate[$i]; + $new_rate[$i] = $current_rate[$i] + $next_adj[$i]; + } + else { + # packet size is too small for this LF system to generate at this rate + # TO DO: make an imix script that uses armageddon instead of user-space UDP + $best_rate = $en3; + $flag = 1; + $adj_count = $binary_search_attempts; + last; + } + + $next_adj[$i] = int($next_adj[$i] / 2); + $current_rate[$i] = $new_rate[$i]; + + } #for $endpoint_names + + # set both endpoints to zero rate to quiesce + my $cmd = "add_endp " . $endpoint_names[$p1] . " $shelf $lf1 " . " NA lf_udp " . + " -1 NO 0 0 NA NA NA NA "; + doCmd($cmd); + $cmd = "add_endp " . $endpoint_names[$p2] . " $shelf $lf1 " . " NA lf_udp " . + " -1 NO 0 0 NA NA NA NA "; + doCmd($cmd); + sleep(5); + + # set both endpoints to new rate + $cmd = "add_endp " . $endpoint_names[$p1] . " $shelf $lf1 " . " NA lf_udp " . + " -1 NO " . $new_rate[0] . " " . $new_rate[0] . " NA NA NA NA "; + doCmd($cmd); + $cmd = "add_endp " . $endpoint_names[$p2] . " $shelf $lf1 " . " NA lf_udp " . + " -1 NO " . $new_rate[1] . " " . $new_rate[1] . " NA NA NA NA "; + doCmd($cmd); + } #for $adj_count + + doCmd("set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"); + doCmd("clear_cx_counters"); + doCmd("clear_port_counters"); + + if ( $flag != 1 ) { + print "\n\n*********************************************************\n"; + print "Theoretical Throughput: $max_rate bps.\n"; + print "IMIX Packet Size: $min_pkt_szs[$q] byte payload.\n"; + print "Loss and Latency Allowance: $drop_percent % drops and $max_latency ms latency.\n"; + print "Measured Throughput on Endpoint 1: $last_current_rate[0] bps.\n"; + print "Measured Throughput on Endpoint 2: $last_current_rate[1] bps.\n\n"; + sleep(10); + } + else { + print "\n\nMax Rate of $max_rate bps is too high for $min_pkt_szs[$q] byte packet size.\n"; + print "At $min_pkt_szs[$q] byte packet size, the best user-space rate is: $best_rate bps.\n\n"; + } + } #for cross-connects +} #for $loop_max + +initPortsToDefault(); + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); + +}#initToDefaults + +# Wait until the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for card: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + doCmd($ports[$i]->getDeleteCmd()); + } #fi isMacVlan + } + } +} + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = "set_port $shelf $lf1 $tmp " . $lf1_port_ips[$i] . " 255.255.255.0 " . + $lf1_port_gws[$i] . " NA NA NA"; + doCmd($cmd); + $cmd = "set_port $shelf $lf2 $tmp2 " . $lf2_port_ips[$i] . " 255.255.255.0 " . + $lf2_port_gws[$i] . " NA NA NA"; + doCmd($cmd); + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + for ($i = 0; $i<@cx_types; $i++) { + my $j = 0; + for ($j = 0; $j<@lf1_ports; $j++) { + my $burst = "NO"; + my $szrnd = "NO"; + my $pattern = "increasing"; + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $lf1_ports[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . + " " . $max_pkt_szs[$i] . " $pattern "; + doCmd($cmd); + + $cmd = "add_endp $ep2 $shelf $lf2 " . $lf2_ports[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . + " " . $max_pkt_szs[$i] . " $pattern "; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + + }#for all ports + }#for all endpoint types +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor(Match => '/ \>\>RSLT:(.*)/', + Timeout => $timeout); + + print "**************\n @rslt ................\n\n"; + #sleep(1); +} + +sub printHelp { + print "\n" + . "USAGE: mgr=[ip-of-mgr] lf1=X lf2=Y\n" + . " lf1_ports=[\"1 2 3\"|\"eth2 eth3\"] lf2_ports=[\"4 5 6\"|\"eth4 eth5\"]\n" + . " rate=1544000 (bps) max_delay=1 (ms) max_drop=0.0001 (%) search_tries=9\n" + . " ep_wait=10 (s) ep_run=30 (s) imix_loops=1\n" + . "\n"; + +} + +sub handleCmdLineArg { + my $arg = $_[0]; + my $val = $_[1]; + + if ($arg eq "mgr") { + $lfmgr_host = $val; + } + elsif ($arg eq "lf1") { + $lf1 = $val; + } + elsif ($arg eq "lf2") { + $lf2 = $val; + } + elsif ($arg eq "lf1_ports") { + @lf1_ports = split(/ /, $val); + } + elsif ($arg eq "lf2_ports") { + @lf2_ports = split(/ /, $val); + } + elsif ($arg eq "rate") { + $tput = $val; + } + elsif ($arg eq "max_delay") { + $max_latency = $val; + } + elsif ($arg eq "max_drop") { + $drop_percent = $val; + } + elsif ($arg eq "search_tries") { + $binary_search_attempts = $val; + } + elsif ($arg eq "ep_wait") { + $endp_wait_for_update = $val; + } + elsif ($arg eq "ep_run") { + $endp_duration = $val; + } + elsif ($arg eq "imix_loops") { + $loop_max = $val; + } + else { + printHelp(); + exit(1); + } +} # handleCmdLineArg diff --git a/lanforge/lanforge-scripts/influxgrafanaghost_fedora_install.sh b/lanforge/lanforge-scripts/influxgrafanaghost_fedora_install.sh new file mode 100755 index 000000000..62a9def35 --- /dev/null +++ b/lanforge/lanforge-scripts/influxgrafanaghost_fedora_install.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# This bash script installs Influx, Grafana, and Ghost on Fedora or CentOS. +# Run this script as a normal user with sudo access. +# You need to provide your username at the beginning of the script. +# There are a few fields you will need to enter when this installs Ghost, and you will be prompted by the script. +# Many scripts in this library are built around Influx, Grafana, and Ghost. Influx is a time series database, +# Grafana has dashboards which display the data stored in Influx, +# and Ghost is a blogging platform which creates an easy way for a user to view automated reports which are built using LANforge scripts +# Once a user uses this script, the user can use those features with the credentials for the system this script sets up. + +# After running this script, Grafana is at port 3000, Influx is at port 8086, and Ghost is at port 2368 +# The user will need to login to those through a web browser to create login credentials, and find API tokens. +# These API tokens are needed to run many scripts in LANforge scripts with these three programs. + +echo Type in your username here +read -r USER + +#Influx installation +wget https://dl.influxdata.com/influxdb/releases/influxdb2-2.0.4.x86_64.rpm +sudo yum localinstall influxdb2-2.0.4.x86_64.rpm +sudo service influxdb start +sudo service influxdb enable + +#Grafana installation +wget https://dl.grafana.com/oss/release/grafana-7.5.3-1.x86_64.rpm +sudo yum localinstall grafana-7.5.3-1.x86_64.rpm -y +sudo systemctl start grafana-server +sudo systemctl enable grafana-server + +#Ghost installation +sudo adduser ghost +sudo usermod -aG sudo ghost +sudo ufw allow 'Nginx Full' +curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash +sudo npm install ghost-cli@latest -g +# Ensure that NPM is up to date +npm cache verify +sudo npm install -g n +sudo n stable +curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash +npm install ghost-cli@latest -g +sudo mkdir -p /var/www/ghostsite +sudo chown ${USER}:${USER} /var/www/ghostsite +sudo chmod 775 /var/www/ghostsite +cd /var/www/ghostsite +ghost install local \ No newline at end of file diff --git a/lanforge/lanforge-scripts/influxgrafanaghost_ubuntu_install.sh b/lanforge/lanforge-scripts/influxgrafanaghost_ubuntu_install.sh new file mode 100755 index 000000000..5220db806 --- /dev/null +++ b/lanforge/lanforge-scripts/influxgrafanaghost_ubuntu_install.sh @@ -0,0 +1,52 @@ +#!/bin/bash +#This script installs Influx, Grafana, and Ghost on Ubuntu. +#Run this script as a normal user with sudo access. +#You need to provide your username at the beginning of the script. +#There are a few fields you will need to enter when it is installing Ghost, and you will be prompted by the script. +#Lanforge scripts is built around Influx, Grafana, and Ghost. Influx is a time series database, +#Grafana has dashboards which display the data stored in Influx, +#and Ghost is a blogging platform which creates an easy way for a user to view automated reports which are built using LANforge scripts +#Once a user uses this script, the user can use those features with the credentials for the system this script sets up. + +#After running this script, Grafana is accessible through port 3000, Influx is at port 8086, and Ghost is accessible at 2368 +#The user will need to login to those through a web browser to create login credentials, and find API tokens. +#These API tokens are needed to run many scripts in LANforge scripts with the functionality these three programs provide. + +#Update necessary parts of system +echo Type in your username here +read -r USER + +sudo apt-get update && sudo apt-get upgrade -y +sudo apt-get install nginx mysql-server nodejs npm -y + +#Influx installation +wget https://dl.influxdata.com/influxdb/releases/influxdb2-2.0.7-amd64.deb +sudo dpkg -i influxdb2-2.0.7-amd64.deb +sudo systemctl unmask influxdb +sudo systemctl start influxdb +sudo systemctl enable influxdb + +#Grafana installation +sudo apt-get install -y adduser libfontconfig1 +wget https://dl.grafana.com/oss/release/grafana_8.0.5_amd64.deb +sudo dpkg -i grafana_8.0.5_amd64.deb +sudo systemctl start grafana-server +sudo systemctl enable grafana-server + +#Ghost installation +sudo adduser ghost +sudo usermod -aG sudo ghost +sudo ufw allow 'Nginx Full' +curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash +sudo npm install ghost-cli@latest -g +# Ensure that NPM is up to date +npm cache verify +sudo npm install -g n +sudo n stable +curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash +npm install ghost-cli@latest -g +sudo mkdir -p /var/www/ghostsite +sudo chown ${USER}:${USER} /var/www/ghostsite +sudo chmod 775 /var/www/ghostsite +cd /var/www/ghostsite +ghost install local \ No newline at end of file diff --git a/lanforge/lanforge-scripts/jbr-test.sh b/lanforge/lanforge-scripts/jbr-test.sh new file mode 100755 index 000000000..118d9a2cc --- /dev/null +++ b/lanforge/lanforge-scripts/jbr-test.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -x +./lf_associate_ap.pl -m ct524-genia.jbr --resource 1 --radio wiphy2 --action del_all_phy --port_del wiphy0 +./lf_associate_ap.pl -m ct524-genia.jbr --resource 1 --radio wiphy2 --action del_all_phy --port_del wiphy1 +./lf_associate_ap.pl -m ct524-genia.jbr --resource 1 --radio wiphy2 --action del_all_phy --port_del wiphy2 +./lf_associate_ap.pl -m ct524-genia.jbr --resource 1 --radio wiphy2 --action del_all_phy --port_del wiphy3 +sleep 5 +while read line; do + echo "Orphan: $line" + hunks=($line) + + ./lf_portmod.pl -m ct524-genia.jbr --cmd delete --port_name "${hunks[1]}" +done < <(./lf_portmod.pl -m ct524-genia.jbr --resource 1 --list_port_names | grep sta) + +sleep 5 +echo "" > /tmp/clilog +now=`date +%Y-%m-%d-%H%M%S` +sudo tcpdump -eSni br0 -B 2048 -w "/tmp/dump-${now}" host ct524-genia.jbr.candelatech.com and port 4001 & +sleep 5 +./lf_associate_ap.pl -m ct524-genia.jbr --resource 1 --radio wiphy2 --action add --security wpa2 \ + --ssid jedway-wpa2-x64-3-1 --passphrase jedway-wpa2-x64-3-1 --wifi_mode anAC --first_sta sta2000 \ + --first_ip DHCP --num_stations 30 --log_cli /tmp/clilog +sleep 1 +sudo killall tcpdump +set +x +echo done diff --git a/lanforge/lanforge-scripts/jlanpro_test.pl b/lanforge/lanforge-scripts/jlanpro_test.pl new file mode 100755 index 000000000..7641ad50a --- /dev/null +++ b/lanforge/lanforge-scripts/jlanpro_test.pl @@ -0,0 +1,1043 @@ +#!/usr/bin/perl -w +# +# Data should be 500 bytes +# Test rig is one upstream wired system, which will be the manager as well as resource +# One ct523b as resource2, and a stand-by ct523b as resource3 +# WPA2 PSK encryption + +# 3x3 client testing: +# 20 clients uploading for 30 sec +# 20 clients downloading 30 sec +# 40 clients upload + download 1 minute +# Quiesce and wait 30 seconds + +# 2x2: Same +# 1x2: Same + +# Mixed mode: 10 3x3, 15 2x2, 15 1x1 (Same data pattern) + +# WiFi Capacit test notes: +# Mix of TCP and UDP, using MTU sized frames +# All 64 stations are on one 4x4 radio + +# Mixed With interference: Same as mixed mode +# Assume other test EQ is doing interference? + +# Each ct523b has 4 radios. We will spread stations among them. +# This script can work on systems with fewer radios as well, see comments +# below about naming the 3a,3b,4a,4b radios with duplicate names. + +use strict; +use warnings; +use diagnostics; +use Getopt::Long; +use Socket; +use POSIX; +our $pld_size = 500; +our $ssid = "wlanpro"; +our $psk = "wlanpro_passwd"; +# Default radio setup for 523b with 2ac, 2ac2. +# For something like a 522 with 2 radios, set 3a, 3b to wiphy0, and +# 4a 4b to wiphy1. +our $radio_3a = "wiphy0"; +our $radio_3b = "wiphy1"; +our $radio_4a = "wiphy2"; +our $radio_4b = "wiphy3"; +our $sta_max = 40; # For upload/download tests +our $wct_sta_max = 64; # For wifi-capacity-test on single radio (4a) +our $gui_host = "127.0.0.1"; # auto-wifi-cap script will not work properly if not run on same machine as GUI +our $gui_port = 7777; +our $resource = 2; +our $speed_dl_tot = 1000000000; +our $speed_ul_tot = 1000000000; +our $speed_ul_bi_tot = 200000000; # 200Mbps upload speed when in bi-directional mode +our $testcase = -1; +our $manager = "localhost"; +our $log_name = ""; + +our $endp_type = "lf_udp"; +our $security = "wpa2"; +our $upstream_resource = 1; +our $upstream_port = "eth1"; +our $multicon = "1"; +our $rest_time = 20; +our $quiet = "yes"; +our $report_timer = 1000; # 1 second report timer +our $rpt_timer_wct = 3000; # 3-second rpt timer for wifi-capacity test +our $settle_timer_wct = 10000; # 10-sec wait for connections to get running before clearing and starting the test proper +our $wct_duration_sec = 20; # Duration for each iteration +our $one_way_test_time = 30; +our $bi_test_time = 30; +our $interferer_cx = "inteferer_cx"; +our $ip = "DHCP"; +our $netmask = "255.255.0.0"; +our $ipn = 0; + +my $usage = "$0 + [--pld_size { bytes } ] + [--ssid {ssid}] + [--passphrase {password}] + [--3a {wiphy-radio-3x3-a}] + [--3b {wiphy-radio-3x3-b}] + [--4a {wiphy-radio-4x4-a}] + [--4b {wiphy-radio-4x4-b}] + [--resource {resource-number}] + [--upstream_resource {resource-number}] + [--upstream_port {port}] + [--speed_ul_tot {speed-bps (default: $speed_ul_tot)}] + [--speed_dl_tot {speed-bps (default: $speed_dl_tot)}] + [--speed_ul_bi_tot {speed-bps for upload in bi-directional test (default: $speed_ul_bi_tot)}] + [--security {open | wpa2}] + [--manager {manager-machine IP or hostname}] + [--testcase {test-case: -1: all except cleanup, 0: setup, 1: 3x3 ul/dl, + 2: 2x2 ul/dl 3: 1x1 ul/dl, 4: mix ul/dl, 5: mix ul/dl + interference, + 6: wifi-capacity-test, 100: cleanup}] + [--log_name {log-file-name}] + [--rest_time {seconds to sleep between rest runs, dfault is $rest_time}] + [--gui_host {LANforge gui_host (127.0.0.1): Must be same as where this script runs.}] + [--gui_port {LANforge gui_port (7777): Start your GUI with -cli-port 7777}] + [--interferer_cx { name of existing LANforge interferer-cx that we should start for the interference test }] + [--ip { DHCP | starting IP address. Default is to use DHCP. }] + [--netmask { Ignored if using DHCP, otherwise something like 255.255.255.0. Default is $netmask. }] + [--sta_max {max num stations}] + [--wct_sta_max {max num stations for capacity test}] + [--multicon {0: off|1: default|-1: auto}] + +NOTE: The total speed will be multiplied by 1.0 for 3x3 and mixed tests, 0.75 for 2x2 testing, + and 0.5 for 1x1 testing. This should still attempt near theoretical throughput without + over-driving the DUT too badly. + +For the interference test, it is expected that the user create a CX of the proper name in +LANforge, associated to a wifi station, etc, and this script will simply start and stop it. +That will simplify this script and will give more flexibility on how the interferer is +configured. By default, the intereferer CX name will be 'interferer_cx'. + +Example command: + +# Run test case 5, assumes test case 0 (setup) has already been run. +./wlanpro_test.pl --ssid mu-mimo-5G --passphrase hello123 --resource 2 --upstream_resource 1 \ + --upstream_port eth4 --manager 192.168.100.182 --gui_port 7777 --interferer_cx inter_r3_w0 --testcase 5 + +# Run all test cases with Fixed IP addresses (instead of DHCP) +./wlanpro_test.pl --ssid mu-mimo-5G --passphrase hello123 --resource 2 --upstream_resource 1 --upstream_port eth4 --manager 192.168.100.182 --gui_host 192.168.100.149 --gui_port 7777 --interferer_cx inter_r3_w0 --testcase -1 --ip 5.5.5.1 --netmask 255.255.255.0 + + +Interesting bugs: + +While testing with a netgear r7800, I noticed that the RX encoding rate received from the AP +can be NSS 2 even when the station is configured for NSS 1. I double-checked that the association +request is using NSS 1 info, so this appears to be a bug in the Netgear. But, since the LANforge +radio can actually decode NSS 2 frames, then the packets are actually received and the Netgear gets +better performance than is warranted in this configuration. This appears to be a bug in the Netgear. + +"; + +my $usage_notes = " +Errors reported by the LANforge-GUI that you should be able to ignore: + +* ERROR: Cannot change MAC address with the 'add-vwifi' command. + Reason: Existing MAC would be fine anyway. + +"; + +my $script_start = time(); + +GetOptions ( + 'pld_size=i' => \$pld_size, + 'ssid=s' => \$ssid, + 'passphrase=s' => \$psk, + '3a=s' => \$radio_3a, + '3b=s' => \$radio_3b, + '4a=s' => \$radio_4a, + '4b=s' => \$radio_4b, + 'resource=i' => \$resource, + 'upstream_resource=i' => \$upstream_resource, + 'upstream_port=s' => \$upstream_port, + 'rest_time=i' => \$rest_time, + 'speed_ul_tot=s' => \$speed_ul_tot, + 'speed_ul_bi_tot=s' => \$speed_ul_bi_tot, + 'speed_dl_tot=s' => \$speed_dl_tot, + 'security=s' => \$security, + 'manager=s' => \$manager, + 'mgr=s' => \$manager, + 'testcase=i' => \$testcase, + 'log_name=s' => \$log_name, + 'gui_host=s' => \$gui_host, + 'gui_port=i' => \$gui_port, + 'interferer_cx=s' => \$interferer_cx, + 'ip=s' => \$ip, + 'netmask=s' => \$netmask, + 'sta_max=i' => \$sta_max, + 'wct_sta_max=i' => \$wct_sta_max, + 'multicon=s' => \$multicon, + ) || (print($usage) && exit(1)); + +if ($log_name eq "") { + $log_name = "wlanpro_log_" . $ssid . "_" . time() . ".txt"; +} +my @radios = ($radio_3a, $radio_3b, $radio_4a, $radio_4b); +my $radio_count = @radios; +my $i; +my $cmd; +my $log_prefix = "LANforge wlanpro-test\nConfiguration:\n" . + " SSID: $ssid passphrase: $psk security: $security resource: $resource\n" . + " speed_dl_request: $speed_dl_tot speed_ul_request: $speed_ul_tot payload-size: $pld_size traffic-type: $endp_type\n" . + " speed_ul_bi_request: $speed_ul_bi_tot interferer: $interferer_cx\n" . + " Test started at: " . `date` . "\n\n"; +my $brief_log = "$log_prefix"; +my $summary_text = "$log_prefix"; +my $mini_summary_text = "$log_prefix"; + +if ($ip ne "DHCP") { + $ipn = ip2ipn($ip); +} + +# Initial setup for test cases, create $sta_max stations +my @cxs = (); +my @stations = (); +my @stations4a = (); +my $sta_on_4a = 0; + +$SIG{'INT'} = sub { + print "Caught ctrl-C, exiting!\n"; + exit 1; +}; + +open(LOGF, ">$log_name") or die("Could not open log file: $log_name $!\n"); + +logp($log_prefix); + +# Stop any running tests. +stop_all_cx(); + +# Delete any wifi-capacity generated connections at this time, it will clean things +# up, and it will make parsing reporting data faster. +sub remove_cxs { + my @cx_dump = `./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"show_cx\"`; + for (my $i = 0; $i<@cx_dump; $i++) { + my $line = $cx_dump[$i]; + chomp($line); + #print "looking for $upstream_resource, $upstream_port **** $line ****\n"; + # also match udp--1.eth4-02.sta102-B + if ($line =~ /CX:\s+((tcp|udp)\-\-$upstream_resource\.[^-]+-\S+)\b/) { + my $cxn = $1; + $cmd = "./lf_firemod.pl --mgr $manager --action delete_cxe --cx_name $cxn"; + do_cmd($cmd); + } + } +} + +remove_cxs(); +# Stop the interferer, just in case it is already running for some reason +$cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"set_cx_state default_tm $interferer_cx STOPPED\""; +do_cmd($cmd); + +# Set upstream port to DHCP or fixed IP as requested. +$cmd = "./lf_portmod.pl --quiet $quiet --manager $manager --card $upstream_resource --port_name $upstream_port --ip $ip --netmask $netmask"; +do_cmd($cmd); + +# Set radios to 3x3 mode. +if ($testcase == -1 || $testcase == 0) { + for ($i = 0; $i<$radio_count; $i++) { + my $radio = $radios[$i]; + my $set_cmd = "set_wifi_radio 1 $resource $radio NA NA NA NA NA NA NA NA NA 7"; + $cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"$set_cmd\""; + do_cmd($cmd); + } +} + +# Find wlanX for 4a radio. +if ($radio_4a =~ /\S+(\d+)/) { + my $sta_name = "wlan$1"; + @stations4a = (@stations4a, $sta_name); + my $radio = $radio_4a; + $sta_on_4a++; + if ($testcase == -1 || $testcase == 0 || $testcase == 6) { + my $_ip = incr_ip(); + $cmd = "./lf_vue_mod.sh --mgr $manager --create_sta --resource $resource --name $sta_name --radio $radio --security $security --ssid $ssid --passphrase $psk --ip $_ip --netmask $netmask"; + do_cmd($cmd); + + # Set to maximum mode. The stations might have been + # previously set to a different mode on an earlier run of this script. + $cmd = "./lf_portmod.pl --quiet $quiet --manager $manager --card $resource --port_name $sta_name --wifi_mode 8 --set_speed DEFAULT --set_ifstate up"; + do_cmd($cmd); + } +} + +for ($i = 0; $i < $sta_max; $i++) { + my $sta_idx = $i + 100; + my $radio_idx = $i % $radio_count; + my $radio = $radios[$radio_idx]; + my $sta_name = "sta$sta_idx"; + + if ($radio eq $radio_4a) { + $sta_on_4a++; + @stations4a = (@stations4a, $sta_name); + } + + @stations = (@stations, $sta_name); + + if ($testcase == -1 || $testcase == 0) { + my $_ip = incr_ip(); + $cmd = "./lf_vue_mod.sh --mgr $manager --create_sta --resource $resource --name $sta_name --radio $radio --security $security --ssid $ssid --passphrase $psk --ip $_ip --netmask $netmask"; + do_cmd($cmd); + + # Set to maximum mode. The stations might have been + # previously set to a different mode on an earlier run of this script. + $cmd = "./lf_portmod.pl --quiet $quiet --manager $manager --card $resource --port_name $sta_name --wifi_mode 8 --set_speed DEFAULT --set_ifstate up"; + do_cmd($cmd); + } + # Create data connection + my $cxn = "l3-${sta_name}"; + my $endpa = "$cxn-A"; + my $endpb = "$cxn-B"; + my $pkt_sz ="--min_pkt_sz $pld_size --max_pkt_sz $pld_size"; + my $gen_args = "--mgr $manager --multicon $multicon $pkt_sz --endp_type $endp_type --action create_endp --report_timer $report_timer"; + + if ($testcase == -1 || $testcase == 0) { + $cmd = "./lf_firemod.pl --resource $resource $gen_args --endp_name $endpa --speed 0 --port_name $sta_name"; + do_cmd($cmd); + + $cmd = "./lf_firemod.pl --resource $upstream_resource $gen_args --endp_name $endpb --speed 0 --port_name $upstream_port"; + do_cmd($cmd); + + $cmd = "./lf_firemod.pl --mgr $manager --action create_cx --cx_name $cxn --cx_endps $endpa,$endpb --report_timer $report_timer"; + do_cmd($cmd); + } + + @cxs = (@cxs, $cxn); +} + +# Create rest of the 4a-stations for Wifi Capacity Test +while ($sta_on_4a < $wct_sta_max) { + my $sta_idx = $sta_on_4a + 200; + my $radio = $radio_4a; + my $sta_name = "sta$sta_idx"; + + @stations4a = (@stations4a, $sta_name); + $sta_on_4a++; + + if ($testcase == -1 || $testcase == 0 || $testcase == 6) { + my $_ip = incr_ip(); + $cmd = "./lf_vue_mod.sh --mgr $manager --create_sta --resource $resource --name $sta_name --radio $radio --security $security --ssid $ssid --passphrase $psk --ip $_ip --netmask $netmask"; + do_cmd($cmd); + + # Set to maximum mode. The stations might have been + # previously set to a different mode on an earlier run of this script. + # Set them to admin-down, the wifi-capacity-test will bring them up as needed. + $cmd = "./lf_portmod.pl --quiet $quiet --manager $manager --card $resource --port_name $sta_name --wifi_mode 8 --set_speed DEFAULT --set_ifstate down"; + do_cmd($cmd); + } +} + +stop_all_cx(); + + +if ($testcase == -1 || $testcase == 1) { + wait_for_stations(); + do_test_series("3x3 station upload/download test", 1.0); +} + +if ($testcase == -1 || $testcase == 2) { + # Test case 2, set stations to 2x2 and re-test + my $start = time(); + for ($i = 0; $i<$radio_count; $i++) { + my $radio = $radios[$i]; + my $set_cmd = "set_wifi_radio 1 $resource $radio NA NA NA NA NA NA NA NA NA 4"; + $cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"$set_cmd\""; + do_cmd($cmd); + } + + wait_for_stations(); + check_more_rest($testcase, $start); + do_test_series("2x2 station upload/download test", 0.75); +} + +if ($testcase == -1 || $testcase == 3) { + # Test case 3, set stations to 1x1 and re-test + my $start = time(); + for ($i = 0; $i<$radio_count; $i++) { + my $radio = $radios[$i]; + my $set_cmd = "set_wifi_radio 1 $resource $radio NA NA NA NA NA NA NA NA NA 1"; + $cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"$set_cmd\""; + do_cmd($cmd); + } + + wait_for_stations(); + check_more_rest($testcase, $start); + do_test_series("1x1 station upload/download test", 0.50); +} + + +# Mixed mode test: 10 3x3, 15 2x2, 15 1x1 (Same data pattern) +if ($testcase == -1 || $testcase == 4 || $testcase == 5) { + # Set radio back to full antenna capacity + my $start = time(); + for ($i = 0; $i<$radio_count; $i++) { + my $radio = $radios[$i]; + my $set_cmd = "set_wifi_radio 1 $resource $radio NA NA NA NA NA NA NA NA NA 0"; + $cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"$set_cmd\""; + do_cmd($cmd); + } + + # 1/4 stations + for ($i = 0; $i $now) { + my $st = ($start + $rest_time) - $now; + print "Sleeping $st seconds for rest time..."; + sleep($st); + } + } +} + +# Wait until all stations are associated and have IP addresses. +sub wait_for_stations { + # Wait until stations are associated, return count + my $j; + for ($j = 0; $j<60; $j++) { + my $all_up = 1; + for ($i = 0; $i<@stations; $i++) { + my $sta_name = $stations[$i]; + $cmd = "./lf_portmod.pl --quiet $quiet --mgr $manager --resource $resource --show_port AP,IP --port_name $sta_name"; + logp("$cmd\n"); + my @output = `$cmd`; + if ($output[0] =~ "AP: Not-Associated") { + logp("Station $sta_name is not associated, waiting...\n"); + sleep(1); + $all_up = 0; + last; + } + if ($output[1] =~ "IP: 0.0.0.0") { + logp("Station $sta_name does not have an IP address, waiting...\n"); + sleep(1); + $all_up = 0; + last; + } + } + + if ($all_up) { + last; + } + } +} + +sub do_one_test { + my $speed_ul = shift; + my $speed_dl = shift; + my $cx_cnt = shift; + my $sleep_sec = shift; + my $series_desc = shift; + + # Download for X seconds + for ($i = 0; $i<$cx_cnt; $i++) { + my $cxn = $cxs[$i]; + my $endpa = "$cxn-A"; + my $endpb = "$cxn-B"; + + $cmd = "./lf_firemod.pl --mgr $manager --action set_endp --endp_name $endpa --speed $speed_ul"; + do_cmd($cmd); + + $cmd = "./lf_firemod.pl --mgr $manager --action set_endp --endp_name $endpb --speed $speed_dl"; + do_cmd($cmd); + + $cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"set_cx_state default_tm $cxn RUNNING\""; + do_cmd($cmd); + } + + $cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"clear_port_counters\""; + do_cmd($cmd); + + my $msg = "Waiting $sleep_sec seconds for test to run, $cx_cnt connections, requested per-connection speed, UL: $speed_ul DL: $speed_dl.\n" . + " Test-case: $series_desc\n\n"; + logp($msg); + $mini_summary_text .= "$cx_cnt connections, requested per-connection speed, UL: $speed_ul DL: $speed_dl\n"; + + sleep($sleep_sec); + + logp("Gathering stats for this test run...\n"); + + # Gather layer-3 stats data + my $sp; + + my $tmpfe = "wptest_endp_stats_tmp.txt"; + `./lf_portmod.pl --manager $manager --cli_cmd "nc_show_endp" > $tmpfe`; + + my $tmpf = "wptest_stats_tmp.txt"; + `./lf_portmod.pl --manager $manager --cli_cmd "nc_show_port 1 $resource" > $tmpf`; + if ($resource != $upstream_resource) { + `./lf_portmod.pl --manager $manager --cli_cmd "nc_show_port 1 $upstream_resource" >> $tmpf`; + } + + # Stop the connections while we process stats. + stop_all_cx(); + + logp("Processing stats for this test run...\n"); + + # Process stats + $sp = `cat $tmpfe`; + logf("$sp\n"); + + # Open-code the report gathering for efficiency. + + # For each endpoint, see if it is one we care about, and gather reporting. Put it + # in a hash so we can search for it efficiently and generate ordered output. + my %endp_rx_rate = (); + my %endp_tx_rate = (); + my @lines = split("\n", $sp); + # Append dummy line to make it easier to terminate the parse logic. + @lines = (@lines, "Endpoint [________] (NOT_RUNNING)\n"); + my $endp_text = ""; + my $ep = ""; + + for ($i = 0; $i<@lines;$i++) { + my $line = $lines[$i]; + chomp($line); + if (($line =~ /Endpoint\s+\[(.*)\]/) || + ($line =~ /WanLink\s+\[(.*)\]/) || + ($line =~ /ArmEndp\s+\[(.*)\]/) || + # TODO: Layer-4 ? + ($line =~ /VoipEndp\s+\[(.*)\]/)) { + my $m1 = $1; + + #print "Found starting line: $line name: $m1 endp_name: $endp_name\n"; + + if ($endp_text ne "") { + # endp_text holds output for endpoint $ep. Gather stats and store them in our hashes + if ($endp_text =~ /.*RealTxRate:\s+(\S+)bps.*RealRxRate:\s+(\S+)bps/s) { + $endp_tx_rate{$ep} = $1; + $endp_rx_rate{$ep} = $2; + #print "Found $ep tx-rate: $1 rx-rate: $2\n"; + } + } + + $endp_text = "$line\n"; + $ep = $m1; + } + else { + if ($endp_text ne "") { + $endp_text .= "$line\n"; + } + } + } + + my $tot_dl_bps = 0; + my $tot_ul_bps = 0; + # Our endp names are derived from cx, so use that to our advantage. + for ($i = 0; $i<$cx_cnt; $i++) { + my $cxn = $cxs[$i]; + my $epa = "$cxn-A"; + my $epb = "$cxn-B"; + + $summary_text .= "$cxn:"; + my $tx = $endp_tx_rate{$epa}; + my $rx = $endp_rx_rate{$epa}; + + $tot_dl_bps += $rx; + $summary_text .= sprintf(" Download RX: %.3fMbps", $rx / 1000000); + $summary_text .= sprintf(" Upload TX: %.3fMbps", $tx / 1000000); + + $tx = $endp_tx_rate{$epb}; + $rx = $endp_rx_rate{$epb}; + + $tot_ul_bps += $rx; + $summary_text .= sprintf(" Upload RX: %.3fMbps", $rx / 1000000); + $summary_text .= sprintf(" Download TX: %.3fMbps", $tx / 1000000); + $summary_text .= "\n"; + } + $summary_text .= sprintf("Total Endpoint Upload RX: %.3fMbps Download RX: %.3fMbps\n\n", $tot_ul_bps / 1000000, $tot_dl_bps / 1000000); + $mini_summary_text .= sprintf("Total Endpoint Upload RX: %.3fMbps Download RX: %.3fMbps\n\n", $tot_ul_bps / 1000000, $tot_dl_bps / 1000000); + + # Port + $sp = `cat $tmpf`; + logf("$sp\n"); + @lines = split("\n", $sp); + # Append dummy line to make it easier to terminate the parse logic. + @lines = (@lines, "Shelf: 9999, Card: 9999, Port: 9999 Type: STA Alias: \n"); + + my %port_mac = (); + my %port_tx_rate_link = (); + my %port_tx_rate = (); + my %port_rx_rate = (); + my %port_ssid = (); + my %port_mode = (); + my %port_nss = (); + my %port_bandwidth = (); + my %port_channel = (); + my %port_ap = (); + my %port_rx_rate_link = (); + my %port_signal = (); + my %port_activity = (); + + my $port_text = ""; + my $s = -1; + my $c = -1; + my $p = -1; + + for ($i = 0; $i<@lines;$i++) { + my $line = $lines[$i]; + chomp($line); + #print "line: $line\n"; + if ($line =~ /Shelf:\s+(\d+).*Card:\s+(\d+).*Port:\s+(\d+)/) { + my $m1 = $1; + my $m2 = $2; + my $m3 = $3; + + if ($port_text ne "") { + # port_text holds output for port $s.$c.$p. Gather stats and store them in our hashes + if ($port_text =~ /DEV:\s+(\S+)/) { + $p = $1; + } + my $pkey = "$s.$c.$p"; + #print "pkey: $pkey\n"; + # We want: bps_rx,bps_tx,MAC,TX-Rate,Signal,Link-Activity,Channel,Bandwidth,NSS + if ($port_text =~ /.*MAC:\s+(\S+).*Tx-Rate:\s+(\S+).*bps_tx:\s+(\S+)\s+bps_rx:\s+(\S+).*/s) { + $port_mac{$pkey} = $1; + $port_tx_rate_link{$pkey} = $2; + $port_tx_rate{$pkey} = $3; + $port_rx_rate{$pkey} = $4; + } + else { + print "Did not find MAC etc in text: $port_text\n"; + exit 1; + } + + if ($port_text =~ /.*ESSID: (.*) Antenna.*/) { + $port_ssid{$pkey} = $1; + } + + # WiFi stuff should come from the Probed section + if ($port_text =~ /.*Probed:\s+(.*)/s) { + my $haystack = $1; + + # We want: Mode,NSS,Bandwidth,Channel,AP,RX-Rate,Signal,Link-Activity + if ($haystack =~ /.*Mode:\s+(\S+).* NSS:\s+(\S+).*Bandwidth: (\S+).*Channel:\s+(\S+).* AP:\s+(\S+).*RX-Rate: (\S+).*Signal: (\S+).*Link-Activity: (\S+).*/s) { + $port_mode{$pkey} = $1; + $port_nss{$pkey} = $2; + $port_bandwidth{$pkey} = $3; + $port_channel{$pkey} = $4; + $port_ap{$pkey} = $5; + $port_rx_rate_link{$pkey} = $6; + $port_signal{$pkey} = $7; + $port_activity{$pkey} = $8; + } + # Deal with no AP or Signal field reported. + elsif ($haystack =~ /.*Mode:\s+(\S+).* NSS:\s+(\S+).*Bandwidth: (\S+).*Channel:\s+(\S+).*RX-Rate: (\S+).*Link-Activity: (\S+).*/s) { + $port_mode{$pkey} = $1; + $port_nss{$pkey} = $2; + $port_bandwidth{$pkey} = $3; + $port_channel{$pkey} = $4; + $port_rx_rate_link{$pkey} = $5; + $port_signal{$pkey} = $6; + $port_activity{$pkey} = $7; + $port_ap{$pkey} = "NA"; + $port_signal{$pkey} = "-1"; + } + else { + print "Did not find probed wifi data, raw-text:\n$haystack\nFull port output:\n$port_text"; + exit 1; + } + } + } + + $port_text = "$line\n"; + $s = $m1; + $c = $m2; + $p = $m3; + } + else { + if ($port_text ne "") { + $port_text .= "$line\n"; + } + } + }# for + + $tot_dl_bps = 0; + $tot_ul_bps = 0; + + for ($i = 0; $i<@stations; $i++) { + my $sta_name = $stations[$i]; + my $pkey = "1.$resource.$sta_name"; + my $mac = $port_mac{$pkey}; + my $tx_rate_link = $port_tx_rate_link{$pkey}; + my $tx_rate = $port_tx_rate{$pkey}; + my $rx_rate = $port_rx_rate{$pkey}; + my $ssid = $port_ssid{$pkey}; + my $mode = $port_mode{$pkey}; + my $nss = $port_nss{$pkey}; + my $bandwidth = $port_bandwidth{$pkey}; + my $channel = $port_channel{$pkey}; + my $ap = $port_ap{$pkey}; + my $rx_rate_link = $port_rx_rate_link{$pkey}; + my $signal = $port_signal{$pkey}; + my $activity = $port_activity{$pkey}; + + $brief_log .= "Station Stats:\nMAC: $mac SSID: $ssid Mode: $mode NSS: $nss Bandwidth: $bandwidth\n"; + $brief_log .= "\tChannel: $channel AP: $ap Signal: $signal Activity: $activity TX-Link-Rate: $tx_rate_link RX-Link-Rate: $rx_rate_link\n"; + $brief_log .= "\tTX-Rate: ${tx_rate}bps RX-Rate: ${rx_rate}bps"; + + $summary_text .= "$sta_name:"; + $tot_dl_bps += $rx_rate; + $summary_text .= sprintf(" RX: %.3fMbps", $rx_rate / 1000000); + + $tot_ul_bps += $tx_rate; + $summary_text .= sprintf(" TX: %.3fMbps", $tx_rate / 1000000); + + $summary_text .= " NSS: $nss RX-Rate: $rx_rate_link TX-Rate: $tx_rate_link Signal: $signal\n"; + } + $summary_text .= sprintf("Total station TX: %.3fMbps RX: %.3fMbps\n\n", $tot_ul_bps / 1000000, $tot_dl_bps / 1000000); + + # Radio stats + $tot_dl_bps = 0; + $tot_ul_bps = 0; + my @rep_ports = uniq(@radios); + for ($i = 0; $i<@rep_ports; $i++) { + my $sta_name = $rep_ports[$i]; + my $pkey = "1.$resource.$sta_name"; + + my $mac = $port_mac{$pkey}; + my $tx_rate = $port_tx_rate{$pkey}; + my $rx_rate = $port_rx_rate{$pkey}; + my $mode = $port_mode{$pkey}; + my $nss = $port_nss{$pkey}; + + $brief_log .= "Radio Stats for $sta_name:\nMAC: $mac Mode: $mode NSS: $nss\n"; + $brief_log .= "\tTX-Rate: ${tx_rate}bps RX-Rate: ${rx_rate}bps"; + + $summary_text .= "$sta_name:"; + $tot_dl_bps += $rx_rate; + $summary_text .= sprintf(" RX: %.3fMbps", $rx_rate / 1000000); + $tot_ul_bps += $tx_rate; + $summary_text .= sprintf(" TX: %.3fMbps", $tx_rate / 1000000); + $summary_text .= " Mode: $mode NSS: $nss\n"; + } + $summary_text .= sprintf("Total Radio TX: %.3fMbps RX: %.3fMbps\n\n", $tot_ul_bps / 1000000, $tot_dl_bps / 1000000); + + # Upstream port + my $pkey = "1.$upstream_resource.$upstream_port"; + my $mac = $port_mac{$pkey}; + my $tx_rate = $port_tx_rate{$pkey}; + my $rx_rate = $port_rx_rate{$pkey}; + my $mode = $port_mode{$pkey}; + my $nss = $port_nss{$pkey}; + + $brief_log .= "Upstream Port Stats for $pkey:\nMAC: $mac"; + $brief_log .= " TX-Rate: ${tx_rate}bps RX-Rate: ${rx_rate}bps"; + + $summary_text .= "$pkey:"; + $summary_text .= sprintf(" RX: %.3fMbps", $rx_rate / 1000000); + $summary_text .= sprintf(" TX: %.3fMbps", $tx_rate / 1000000); + + # The code below works, and makes use of existing scripts to make it perhaps more + # clear to understand, but it is quite slow. So, will hand-code some more efficient + # reporting. --Ben +# my $tot_dl_bps = 0; +# my $tot_ul_bps = 0; +# # Our endp names are derived from cx, so use that to our advantage. +# for ($i = 0; $i<$cx_cnt; $i++) { +# my $cxn = $cxs[$i]; +# my $epa = "$cxn-A"; +# my $epb = "$cxn-B"; + +# my $ep_stats = `./lf_firemod.pl --endp_name $epa --stats_from_file $tmpfe --endp_vals RealRxRate,RealTxRate`; +# $brief_log .= "Endpoint Stats for $epa:\n$ep_stats\n\n"; +# $summary_text .= "$cxn:"; +# if ($ep_stats =~ /RealRxRate:\s+(\d+)/) { +# $tot_dl_bps += $1; +# $summary_text .= sprintf(" Download RX: %.3fMbps", $1 / 1000000); +# } +# if ($ep_stats =~ /RealTxRate:\s+(\d+)/) { +# $summary_text .= sprintf(" Upload TX: %.3fMbps", $1 / 1000000); +# } +# $ep_stats = `./lf_firemod.pl --endp_name $epb --stats_from_file $tmpfe --endp_vals RealRxRate,RealTxRate`; +# $brief_log .= "Endpoint Stats for $epb:\n$ep_stats\n\n"; +# if ($ep_stats =~ /RealRxRate:\s+(\d+)/) { +# $tot_ul_bps += $1; +# $summary_text .= sprintf(" Upload RX: %.3fMbps", $1 / 1000000); +# } +# if ($ep_stats =~ /RealTxRate:\s+(\d+)/) { +# $summary_text .= sprintf(" Download TX: %.3fMbps", $1 / 1000000); +# } +# $summary_text .= "\n"; +# } +# $summary_text .= sprintf("Total Endpoint Upload RX: %.3fMbps Download RX: %.3fMbps\n\n", $tot_ul_bps / 1000000, $tot_dl_bps / 1000000); +# $mini_summary_text .= sprintf("Total Endpoint Upload RX: %.3fMbps Download RX: %.3fMbps\n\n", $tot_ul_bps / 1000000, $tot_dl_bps / 1000000); + +# # Port +# $sp = `cat $tmpf`; +# logf("$sp\n"); + +# $tot_dl_bps = 0; +# $tot_ul_bps = 0; +# for ($i = 0; $i<@stations; $i++) { +# my $sta_name = $stations[$i]; +# my $sta_stats = `./lf_portmod.pl --card $resource --port_name $sta_name --stats_from_file $tmpf --show_port AP,ESSID,bps_rx,bps_tx,MAC,Mode,RX-Rate,TX-Rate,Signal,Link-Activity,Channel,Bandwidth,NSS`; +# $brief_log .= "Station Stats:\n$sta_stats\n\n"; +# $summary_text .= "$sta_name:"; +# if ($sta_stats =~ /bps_rx:\s+(\d+)/) { +# $tot_dl_bps += $1; +# $summary_text .= sprintf(" RX: %.3fMbps", $1 / 1000000); +# } +# if ($sta_stats =~ /bps_tx:\s+(\d+)/) { +# $tot_ul_bps += $1; +# $summary_text .= sprintf(" TX: %.3fMbps", $1 / 1000000); +# } +# if ($sta_stats =~ /NSS:\s+(\d+)/) { +# $summary_text .= " NSS: $1"; +# } +# if ($sta_stats =~ /RX-Rate:\s+(\S+)/) { +# $summary_text .= " RX-Rate: $1"; +# } +# if ($sta_stats =~ /TX-Rate:\s+(\S+)/) { +# $summary_text .= " TX-Rate: $1"; +# } +# if ($sta_stats =~ /Signal:\s+(\S+)/) { +# $summary_text .= " Signal: $1"; +# } +# $summary_text .= "\n"; +# } +# $summary_text .= sprintf("Total station TX: %.3fMbps RX: %.3fMbps\n\n", $tot_ul_bps / 1000000, $tot_dl_bps / 1000000); + +# # Radio stats +# $tot_dl_bps = 0; +# $tot_ul_bps = 0; +# my @rep_ports = uniq(@radios); +# for ($i = 0; $i<@rep_ports; $i++) { +# my $sta_name = $rep_ports[$i]; +# my $sta_stats = `./lf_portmod.pl --card $resource --port_name $sta_name --stats_from_file $tmpf --show_port bps_rx,bps_tx,MAC,Mode,NSS`; +# $brief_log .= "Radio Stats for $sta_name:\n$sta_stats\n\n"; +# $summary_text .= "$sta_name:"; +# if ($sta_stats =~ /bps_rx:\s+(\d+)/) { +# $tot_dl_bps += $1; +# $summary_text .= sprintf(" RX: %.3fMbps", $1 / 1000000); +# } +# if ($sta_stats =~ /bps_tx:\s+(\d+)/) { +# $tot_ul_bps += $1; +# $summary_text .= sprintf(" TX: %.3fMbps", $1 / 1000000); +# } +# if ($sta_stats =~ /Mode:\s+(\S+)/) { +# $summary_text .= " Mode: $1"; +# } +# $summary_text .= "\n"; +# } +# $summary_text .= sprintf("Total Radio TX: %.3fMbps RX: %.3fMbps\n\n", $tot_ul_bps / 1000000, $tot_dl_bps / 1000000); + +# # Upstream port +# my $p_stats = `./lf_portmod.pl --card $upstream_resource --port_name $upstream_port --stats_from_file $tmpf --show_port bps_rx,bps_tx,MAC,RX-Rate,TX-Rate`; +# $brief_log .= "Upstream Port Stats:\n$p_stats\n\n"; +# $summary_text .= "$upstream_port:"; +# if ($p_stats =~ /bps_rx:\s+(\d+)/) { +# $tot_dl_bps += $1; +# $summary_text .= sprintf(" RX: %.3fMbps", $1 / 1000000); +# } +# if ($p_stats =~ /bps_tx:\s+(\d+)/) { +# $tot_ul_bps += $1; +# $summary_text .= sprintf(" TX: %.3fMbps", $1 / 1000000); +# } +# if ($p_stats =~ /RX-Rate:\s+(\S+)/) { +# $summary_text .= " RX-Rate: $1"; +# } +# if ($p_stats =~ /TX-Rate:\s+(\S+)/) { +# $summary_text .= " TX-Rate: $1"; +# } + $summary_text .= "\n"; +} + +sub stop_all_cx { + $cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"set_cx_state all all STOPPED\""; + do_cmd($cmd); +} + +sub stop_all_my_cx { + my $i; + + for ($i = 0; $i<@cxs; $i++) { + my $cxn = $cxs[$i]; + $cmd = "./lf_firemod.pl --mgr $manager --action do_cmd --cmd \"set_cx_state default_tm $cxn STOPPED\""; + do_cmd($cmd); + } +} + +sub logf { + my $text = shift; + print LOGF $text; +} + +sub logp { + my $text = shift; + print LOGF $text; + print $text; # to std-out too +} + +sub logpb { + my $text = shift; + print LOGF $text; + print $text; # to std-out too + $brief_log .= "$text"; + $summary_text .= "$text"; # Even sparser summary output +} + +sub do_test_series { + my $desc = shift; + my $speed_mult = shift; + my $msg = "\n" . `date` . "Doing test series: $desc\n"; + + logpb($msg); + $mini_summary_text .= $msg; + + # First test case, 20 stations downloading, 3x3 mode. + logpb("\nDoing download test with 20 stations.\n"); + do_one_test(0, floor(($speed_dl_tot * $speed_mult) / 20), 20, $one_way_test_time, $desc); + # Upload 30 sec + logpb("\nDoing upload test with 20 stations.\n"); + do_one_test( floor(($speed_ul_tot * $speed_mult) / 20), 0, 20, $one_way_test_time, $desc); + # Upload/Download 1 minute sec + logpb("\nDoing upload/download test with $sta_max stations.\n"); + do_one_test(floor(($speed_ul_bi_tot * $speed_mult) / $sta_max), + floor(($speed_dl_tot * $speed_mult) / $sta_max), + $sta_max, + $bi_test_time, + $desc); +} + +sub do_cmd { + my $cmd = shift; + logp("$cmd\n"); + return system($cmd); +} + +sub uniq { + my %seen; + grep !$seen{$_}++, @_; +} + +sub incr_ip { + if ($ip eq "DHCP") { + return "DHCP"; + } + $ipn++; + return ipn2ip($ipn); +} + +sub ip2ipn { + return unpack 'N', inet_aton(shift); +} + +sub ipn2ip { + return inet_ntoa( pack 'N', shift ); +} + + +my @array = qw(one two three two three); +my @filtered = uniq(@array); diff --git a/lanforge/lanforge-scripts/json/check_ports.pl b/lanforge/lanforge-scripts/json/check_ports.pl new file mode 100755 index 000000000..c3fdb3933 --- /dev/null +++ b/lanforge/lanforge-scripts/json/check_ports.pl @@ -0,0 +1,78 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +use Time::HiRes qw(usleep); +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use JSON; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names flatten_list); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 +); + + +## +## M A I N +## + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port, +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; +my $rh_update = { + 'shelf'=>1, 'resource'=>1, 'port'=>'all', 'probe_flags'=>'0x9' +}; +my $uri = "/shelf/1"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'resources'); +my @links2= (); +my $ra_alias_links = []; +logg("\nRefreshing after setting up... "); +my $rh_response = json_post("/cli-json/nc_show_ports", $rh_update); + +# wait on ports up +my $ports_still_down = 1; +while ($ports_still_down > 0) { + $rh = json_request("/port/1/1/list?fields=_links,port,device,down"); + flatten_list($rh, 'interfaces'); + $ports_still_down=0; + for my $rh_p (values %{$rh->{'flat_list'}}) { + next unless $rh_p->{'device'} =~ /^sta/; + #print "$rh_p->{'device'} is $rh_p->{'down'} "; + $ports_still_down++ + if ($rh_p->{'down'}); + } + print "ports down: $ports_still_down "; + $rh_response = json_post("/cli-json/nc_show_ports", $rh_update); + sleep 1; +} + +# diff --git a/lanforge/lanforge-scripts/json/cx_test.pl b/lanforge/lanforge-scripts/json/cx_test.pl new file mode 100755 index 000000000..91e21fb29 --- /dev/null +++ b/lanforge/lanforge-scripts/json/cx_test.pl @@ -0,0 +1,318 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use Time::HiRes qw(usleep); +use JSON; +use lib '/home/lanforge/scripts'; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names flatten_list); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; +our $ssid; +our $security; +our $passphrase; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 +); + + +## +## M A I N +## + +my $des_resource = 6; + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port, + 'resource=i' => \$des_resource +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + +my $uri = "/shelf/1"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'resources'); +my @ports_up= (); + +# TODO: make this a JsonUtils::list_ports() +$uri = "/port/1/${des_resource}/list?fields=alias,device,down,phantom,port"; +logg("requesting $uri\n"); +$rh = json_request($uri); +#print Dumper($rh); +flatten_list($rh, 'interfaces'); +#print "\n- 1 -------------------------------------------------------------\n"; +#print Dumper($rh->{'flat_list'}); +#print "\n- 2 -------------------------------------------------------------\n"; +#print Dumper(\(keys %{$rh->{'flat_list'}})); +for my $rh_p (keys %{$rh->{'flat_list'}}) { + #print "\n- 1 -------------------------------------------------------------\n"; + #print Dumper($rh->{'flat_list'}->{$rh_p}); + #print "\n".ref ($rh->{'flat_list'}->{$rh_p}->{'down'}); + #print "\n".$rh->{'flat_list'}->{$rh_p}->{'down'}(); + #print "\n- 2 -------------------------------------------------------------\n"; + #.$rh->{'flat_list'}->{$rh_p}->{'down'}; + + my $onoff = $rh->{'flat_list'}->{$rh_p}->{'down'}; + #print "truth? $onoff\n"; + if ($onoff) { # should force a truthy value + push(@ports_up, $rh_p); + } +} +#print "\n- 3 -------------------------------------------------------------\n"; +#print Dumper(\@ports_up); +#print "\n- 4 -------------------------------------------------------------\n"; +# find first station +my $rh_sta; +#print "\n- 5 -------------------------------------------------------------\n"; +#print Dumper(\@ports_up); +#print "\n- 5 -------------------------------------------------------------\n"; +for my $rh_up (@ports_up) { + #print "\n- 6 -------------------------------------------------------------\n"; + #print Dumper($rh->{'flat_list'}); + #print "\n- 6 -------------------------------------------------------------\n"; + my $eid = $rh->{'flat_list'}->{$rh_up}->{'port'}; + print ("rhup: $rh_up; EID $eid\n"); + my @hunks = split(/[.]/, $eid); + if ($hunks[1]) { + $rh_sta = $rh_up; + } +} +if (!defined $rh_sta) { + die("Unable to find a virtual station. Is one up?"); +} + +# delete old CXes and old endpoints +# TODO: collect_cx_names +my $rh_cxlist = json_request("/cx/list"); + +my @cx_names = (); +for my $cx_name (sort keys %$rh_cxlist) { + #print " cx_name[$cx_name]"; + next if (ref $rh_cxlist->{$cx_name} ne "HASH"); + next if (!defined $rh_cxlist->{$cx_name}->{"name"}); + next if ($rh_cxlist->{$cx_name}->{"name"} eq "uri"); + push(@cx_names, $rh_cxlist->{$cx_name}->{"name"}); +} + +print "\nRemoving cx: "; +$uri = "/cli-json/rm_cx"; +for my $cx_name (sort @cx_names) { + print "$cx_name "; + $rh = { + "test_mgr" => "all", + "cx_name" => $cx_name + }; + json_post($uri, $rh); +} +sleep 1; + + +my $rh_endplist = json_request("/endp/list"); +print "\nRemoving endpoints: "; +flatten_list($rh_endplist, 'endpoint'); +#print Dumper($rh_endplist->{'flat_list'}); +#sleep 10; +my @endp_names = (); +for my $ep_name (keys %{$rh_endplist->{'flat_list'}}) { + next if (!defined $ep_name); + next if ($ep_name eq ""); + next if ((ref $ep_name) eq "ARRAY"); + next if (!defined $rh_endplist->{'flat_list'}->{$ep_name}->{"name"}); + next if ($rh_endplist->{'flat_list'}->{$ep_name}->{"name"} eq ""); + #print "epn:".Dumper($rh_endplist->{'flat_list'}->{$ep_name}->{"name"}); + push(@endp_names, $ep_name); +} + +$uri = "/cli-json/rm_endp"; +for my $ep_name (@endp_names) { + if (!defined $ep_name || $ep_name =~/^\s*$/ || (ref $ep_name) eq "ARRAY") { + print " skipping ep_name[$ep_name]"; + print Dumper(\$ep_name); + next; + } + print "[$ep_name] "; + #usleep(500000); + $rh = { "endp_name" => $ep_name }; + json_post($uri, $rh); + +} + +print "\nRefreshing..."; +my $h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints", $h); +sleep 1; +$h = {"test_mgr"=>"all", "cross_connect"=>"all"}; +json_request("/cli-json/show_cxe", $h); + +# assume resource 1, eth1 is present, and create an endpoint to it +# -A and -B are expected convention for endpoint names + +# create 10 endpoints +my $rh_ports = json_request("/port/1/$des_resource/list"); +flatten_list($rh_ports, 'interfaces'); + +my $rh_endp_A = { + 'alias' => 'udp_json', + 'shelf' => 1, + 'resource' => 1, + 'port' => 'b1000', # or eth1 + 'type' => 'lf_udp', + 'ip_port' => -1, + 'is_rate_bursty' => 'NO', + 'min_rate' => 100000, + 'min_pkt' => -1, + 'max_pkt' => -1, + 'payload_pattern' => 'increasing', + 'multi_conn' => 0 + }; + +my $rh_endp_B = { + 'alias' => 'udp_json', + 'shelf' => 1, + 'resource' => $des_resource, + 'port' => 'unset', + 'type' => 'lf_udp', + 'ip_port' => -1, + 'is_rate_bursty' => 'NO', + 'min_rate' => 12800, + 'min_pkt' => -1, + 'max_pkt' => -1, + 'payload_pattern' => 'increasing', + 'multi_conn' => 0 + }; +my $rh_cx = { + "alias" => 'udp_json', + "test_mgr" => 'default_tm', + 'tx_endp' => '', + 'rx_endp' => '' +}; + +$h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints", $h); +$h = {"test_mgr"=>"all", "cross_connect"=>"all"}; +json_request("/cli-json/show_cxe", $h); +sleep 1; +print "\nConstructing new Endpoints: "; +my $num_ports = scalar keys(%{$rh_ports->{'flat_list'}}); +my $num_cx = 0; +for my $rh_p (values %{$rh_ports->{'flat_list'}}) { + + last if ($num_cx >= ($num_ports-1)); + next if ($rh_p->{'alias'} !~ /^v*sta/); + + my $end_a_alias = "udp_json_$num_cx-A"; + my $end_b_alias = "udp_json_$num_cx-B"; + my $port_b = $rh_p->{'alias'}; + print "$port_b "; + $rh_endp_B->{'port'} = $port_b; + $rh_endp_B->{'alias'} = $end_b_alias; + $rh_endp_A->{'alias'} = $end_a_alias; + $num_cx++; + + json_post("/cli-json/add_endp", $rh_endp_A); + json_post("/cli-json/add_endp", $rh_endp_B); +} +print "\nRefreshing..."; +$h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints", $h); +sleep 1; +print "\nConstructing new CX: "; +$num_cx = 0; +for my $rh_p (values %{$rh_ports->{'flat_list'}}) { + last if ($num_cx >= ($num_ports-1)); + next if ($rh_p->{'alias'} !~ /^v*sta/); + + my $end_a_alias = "udp_json_${num_cx}-A"; + my $end_b_alias = "udp_json_${num_cx}-B"; + my $port_b = $rh_p->{'alias'}; + my $cx_alias = "udp_json_".$num_cx; + $rh_cx->{'alias'} = $cx_alias; + $rh_cx->{'tx_endp'} = $end_a_alias; + $rh_cx->{'rx_endp'} = $end_b_alias; + json_post("/cli-json/add_cx", $rh_cx); + print " $cx_alias"; + $num_cx++; +} +print "\nRefreshing..."; +$h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints", $h); + +$rh_cxlist = json_request("/cx/list"); +@cx_names = (); +for my $cx_name (sort keys %$rh_cxlist) { + next if (ref $rh_cxlist->{$cx_name} ne "HASH"); + next if (!defined $rh_cxlist->{$cx_name}->{"name"}); + push(@cx_names, $rh_cxlist->{$cx_name}->{"name"}); +} +for my $cx_alias (sort @cx_names) { + my $rh_cx_t = { + 'test_mgr' => 'default_tm', + 'cx_name' => $cx_alias, + 'milliseconds'=> 1000, + }; + json_post("/cli-json/set_cx_report_timer", $rh_cx_t); +} +print "\nRefreshing..."; +$h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints", $h); +sleep 1; +$h = {"test_mgr"=>"all", "cross_connect"=>"all"}; +json_request("/cli-json/show_cxe", $h); + +my $set_state = { + 'test_mgr' => 'default_tm', + 'cx_name' => 'udp_ex', + 'cx_state' => 'RUNNING' +}; +@cx_names = (); +$rh_cxlist = json_request("/cx/list"); +for my $cx_name (sort keys %$rh_cxlist) { + next if (ref $rh_cxlist->{$cx_name} ne "HASH"); + next if (!defined $rh_cxlist->{$cx_name}->{"name"}); + push(@cx_names, $rh_cxlist->{$cx_name}->{"name"}); +} +print "\nStarting: "; +for my $cxname (@cx_names) { + print " $cxname"; + $set_state->{'cx_name'} = $cxname; + json_post("/cli-json/set_cx_state", $set_state); +} +sleep 1; + +$set_state = { + 'test_mgr' => 'default_tm', + 'cx_name' => 'udp_ex', + 'cx_state' => 'STOPPED' +}; +print "\nStopping: "; +for my $cxname (@cx_names) { + $set_state->{'cx_name'} = $cxname; + print " $cxname"; + json_post("/cli-json/set_cx_state", $set_state); +} +print "...done\n"; + +# diff --git a/lanforge/lanforge-scripts/json/cx_test_allsta.pl b/lanforge/lanforge-scripts/json/cx_test_allsta.pl new file mode 100755 index 000000000..293c4d8c8 --- /dev/null +++ b/lanforge/lanforge-scripts/json/cx_test_allsta.pl @@ -0,0 +1,191 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use JSON; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; +our $ssid; +our $security; +our $passphrase; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 +); + + +## +## M A I N +## + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + +my $uri = "/shelf/1"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'resources'); +my @links2= (); +my $ra_alias_links = []; +# TODO: make this a JsonUtils::list_ports() +for $uri (@$ra_links) { + $uri =~ s{/resource}{/port}g; + $uri .= "/list"; + #logg("requesting $uri"); + $rh = json_request($uri); + #print Dumper($rh); + push( @$ra_alias_links, @{get_port_names($rh, 'interfaces')}); + push(@links2, @{get_links_from($rh, 'interfaces')}); + #logg("\nfound: "); + #logg(@links2); +} +#print Dumper($ra_alias_links); + +my $rh_existing_cx = json_request("/cx/list"); +#print Dumper($rh_existing_cx); + +my $rh_existing_cxnames = {}; +for my $eid (keys %$rh_existing_cx) { + next if ($eid !~ /^\d+\.\d+$/); + print "=================================================================================\n"; + print Dumper($rh_existing_cx->{$eid}); + print "=================================================================================\n"; + my $rh_cx = $rh_existing_cx->{$eid}; + print Dumper($rh_cx); + print "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==\n"; + my $cx_n = $rh_cx->{'name'}; + print " $cx_n \n"; + $rh_existing_cxnames->{$cx_n} = $rh_cx; +} + +# assume resource 1, eth1 is present, and create an endpoint to it +# -A and -B are expected convention for endpoint names +my $i=999; +for my $rh_alias_link (@$ra_alias_links) { + + $i+=1; + + if (defined ($rh_existing_cxnames->{"udp_$i"})) { + print " udp_$i already exists "; + next; + } + + my $resourceB_uri = $rh_alias_link->{'uri'}; + my ($resourceB) = $resourceB_uri =~ m{/port/1/(\d+)/}; + + if ($rh_alias_link->{'alias'} !~ /^sta/) { + print " skipping $rh_alias_link->{'alias'} "; + next; + } + my $rh_endp_A = { + 'alias' => "udp_$i-A", + 'shelf' => 1, + 'resource' => 1, + 'port' => 'b1000', # or eth1 + 'type' => 'lf_udp', + 'ip_port' => -1, + 'is_rate_bursty' => 'NO', + 'min_rate' => 1000000, + 'min_pkt' => -1, + 'max_pkt' => -1, + 'payload_pattern' => 'increasing', + 'multi_conn' => 0 + }; + json_post("/cli-json/add_endp", $rh_endp_A); + + print Dumper($rh_alias_link); + my $rh_endp_B = { + 'alias' => "udp_$i-B", + 'shelf' => 1, + 'resource' => $resourceB, + 'port' => $rh_alias_link->{'alias'}, + 'type' => 'lf_udp', + 'ip_port' => -1, + 'is_rate_bursty' => 'NO', + 'min_rate' => 12800, + 'min_pkt' => -1, + 'max_pkt' => -1, + 'payload_pattern' => 'increasing', + 'multi_conn' => 0 + }; + json_post("/cli-json/add_endp", $rh_endp_B); + + my $rh_cx ={ + 'alias' => "udp_$i", + 'test_mgr' => 'default_tm', + 'tx_endp' => "udp_$i-A", + 'rx_endp' => "udp_$i-B" + }; + json_post("/cli-json/add_cx", $rh_cx); + + $rh_cx = { + 'test_mgr' => 'default_tm', + 'cx_name' => "udp_$i", + 'milliseconds'=> 1000, + }; + json_post("/cli-json/set_cx_report_timer", $rh_cx); + +} +sleep (1); +$rh_existing_cx = json_request("/cx/list"); + +for my $eid (keys %$rh_existing_cx) { + next if ($eid !~ /\d+\.\d+/); + my $rh_cx = $rh_existing_cx->{$eid}; + my $cx_name = $rh_cx->{'name'}; + next if ($cx_name !~ /^udp_/); + + print "run $cx_name "; + my $set_state = { + 'test_mgr' => 'default_tm', + 'cx_name' => $cx_name, + 'cx_state' => 'RUNNING' + }; + json_post("/cli-json/set_cx_state", $set_state); +} +print "\nLetting things run..."; +sleep(2); +print "shutting them down..."; + +for my $eid (keys %$rh_existing_cx) { + next if ($eid !~ /\d+\.\d+/); + my $rh_cx = $rh_existing_cx->{$eid}; + my $cx_name = $rh_cx->{'name'}; + next if ($cx_name !~ /^udp_/); + print "q $cx_name "; + my $set_state = { + 'test_mgr' => 'default_tm', + 'cx_name' => $cx_name, + 'cx_state' => 'QUIESCE' + }; + json_post("/cli-json/set_cx_state", $set_state); +} +# diff --git a/lanforge/lanforge-scripts/json/cx_test_allsta_para.pl b/lanforge/lanforge-scripts/json/cx_test_allsta_para.pl new file mode 100755 index 000000000..0375aa7e9 --- /dev/null +++ b/lanforge/lanforge-scripts/json/cx_test_allsta_para.pl @@ -0,0 +1,94 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use JSON; +use Time::HiRes qw(usleep nanosleep); + +# using this is going to want high inotify instances, like mentioned here: +# sysctl fs.inotify.max_user_instances=256 +# https://forum.proxmox.com/threads/unable-to-create-new-inotify-object-too-many-open-files-at-usr-share-perl5.23700/ + +use Proc::Background; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; +our $ssid; +our $security; +our $passphrase; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 +); + + +## +## M A I N +## + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + +my $uri = "/shelf/1"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'resources'); +my @links2= (); +my $ra_alias_links = []; + +my $rh_existing_cx = json_request("/cx/list"); + +my $rh_existing_cxnames = {}; +for my $eid (keys %$rh_existing_cx) { + next if ($eid !~ /^\d+\.\d+$/); + print "EID[$eid]:"; + print "=================================================================================\n"; + print Dumper($rh_existing_cx->{$eid}); + print "=================================================================================\n"; + my $rh_cx = $rh_existing_cx->{$eid}; + print Dumper($rh_cx); + print "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==\n"; + my $cx_n = $rh_cx->{'name'}; + print " $cx_n "; + $rh_existing_cxnames->{$cx_n} = $rh_cx; +} +# $SIG{'CHLD'} = "wait_for_child_to_die"; # do we need this? + +my @pids=(); +for my $cx_name (keys %$rh_existing_cxnames) { + next if ($cx_name !~ /^udp_/); + my $proc = Proc::Background->new("./cx_test_helper.pl --cxname $cx_name"); + push(@pids, $proc->pid()); + print " cx_name $cx_name ".$proc->pid(); + usleep(1500); +} + +print "...done\n"; +# diff --git a/lanforge/lanforge-scripts/json/cx_test_helper.pl b/lanforge/lanforge-scripts/json/cx_test_helper.pl new file mode 100755 index 000000000..da3488936 --- /dev/null +++ b/lanforge/lanforge-scripts/json/cx_test_helper.pl @@ -0,0 +1,113 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use JSON; +use Linux::Inotify2; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; +our $ssid; +our $security; +our $passphrase; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 + --cxname {name of connection to toggle} +); + + +## +## M A I N +## + +our $cxname; +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port, + 'cxname=s' => \$::cxname +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + + +# using this is going to want high inotify instances, like mentioned here: +# sysctl fs.inotify.max_user_instances=256 +# https://forum.proxmox.com/threads/unable-to-create-new-inotify-object-too-many-open-files-at-usr-share-perl5.23700/ + + + +# wait until /tmp/startcx appears +our $toggle="startcx"; + +# create a new object +my $inotify_c = new Linux::Inotify2 + or die "Unable to create new inotify object: $!" ; +my $inotify_d = new Linux::Inotify2 + or die "Unable to create new inotify object: $!" ; + +# create watch +$inotify_c->watch ("/tmp", IN_CREATE, sub { + my $event = shift; + print("I see $event->{name}\n"); + return if ($event->{name} =~ /$toggle$/); +}) or die "watch creation failed" ; + +while() { + my @events = $inotify_c->read; + last; + #unless (@events > 0) { print "read error: $!"; last; } +} + +print "run $cxname "; +my $set_state = { + 'test_mgr' => 'default_tm', + 'cx_name' => $cxname, + 'cx_state' => 'RUNNING' + }; +json_post("/cli-json/set_cx_state", $set_state); + + +$inotify_d->watch ("/tmp", IN_DELETE, sub { + my $event = shift; + print("Delete seen for $event->{name}\n"); + return if ($event->{name} =~ /$toggle$/); +}) or die "watch creation failed" ; +while() { + my @events = $inotify_d->read; + last; + #unless (@events > 0) { print "read error: $!"; last; } +} + +print "quiesce $cxname "; +$set_state = { + 'test_mgr' => 'default_tm', + 'cx_name' => $cxname, + 'cx_state' => 'QUIESCE' + }; +json_post("/cli-json/set_cx_state", $set_state); +print "...done\n"; diff --git a/lanforge/lanforge-scripts/json/dut_test.pl b/lanforge/lanforge-scripts/json/dut_test.pl new file mode 100755 index 000000000..67d9e8363 --- /dev/null +++ b/lanforge/lanforge-scripts/json/dut_test.pl @@ -0,0 +1,193 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use Time::HiRes qw(usleep); +use JSON; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names flatten_list); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; +our $ssid; +our $security; +our $passphrase; + +my $usage = qq("$0 --action {add|list|show|annotate|del} + # list: lists all device names + # show: shows everything about device + # annotate: updates the note for the device + --note {"double quoted stuff"} + # del: + --name {Device Under Test name} + # when adding, use: + --name {Device Under Test name} + --img {picture name: c:\\stuff.png} + --sw_version {text} + --hw_version {text} + --mgt_ip {1.1.1.1} + --model_num {blanktronix345} + --serial_num {asdf1234} + --serial_port {1.2.ttyS0} + --api_id {0-127} + --lan_port {1.1.eth1} + --wan_port {1.1.eth2} + --ssid1 --passwd1 --ssid2 --passwd2 --ssid3 --passwd3 {stuff} +); + + +my $action; +my $name = ""; +my $notes = ""; +my $img_file = ""; +my $sw_version = ""; +my $hw_version = ""; +my $mgt_ip = ""; +my $model_num = ""; +my $serial_num = ""; +my $serial_port = ""; +my $api_id = ""; +my $lan_port = ""; +my $wan_port = ""; +my $ssid1 = ""; +my $ssid2 = ""; +my $ssid3 = ""; +my $passwd1 = ""; +my $passwd2 = ""; +my $passwd3 = ""; + +my $help; +## +## M A I N +## + +GetOptions( 'host=s' => \$::Host, + 'action=s' => \$action, + 'name=s' => \$name, + 'notes=s' => \$notes, + 'img_file=s' => \$img_file, + 'sw_version=s' => \$sw_version, + 'hw_version=s' => \$hw_version, + 'mgt_ip=s' => \$mgt_ip, + 'model_num=s' => \$model_num, + 'serial_num=s' => \$serial_num, + 'serial_port=s' => \$serial_port, + 'api_id=s' => \$api_id, + 'lan_port=s' => \$lan_port, + 'wan_port=s' => \$wan_port, + 'ssid1=s' => \$ssid1, + 'ssid2=s' => \$ssid2, + 'ssid3=s' => \$ssid3, + 'passwd1=s' => \$passwd1, + 'passwd2=s' => \$passwd2, + 'passwd3=s' => \$passwd3, + 'h|help' => \$help) +|| (print($usage) && exit(1)); + +if ($help) { + print $usage; + exit 0; +} +if (!defined $action) { + print $usage; + exit 1; +} +$::HostUri = "http://$Host:$Port"; +my $DEBUGURI = "?__debug=1"; +my $uri_args = ""; # ="$DEBUG_URI"; + +our $URI = "$::HostUri/dut"; +our $Post_URI = "$::HostUri/cli-json"; + +if ($action eq "list") { + my $uri = "$::URI/list"; + my $rh = json_request($uri); + flatten_list($rh, 'duts'); + for my $rh_dut (sort keys %{$rh->{'flat_list'}}) { + print "$rh_dut\n"; + } + exit 0; +} + +if ($action eq "show") { + die ("show what DUT? use --name:\n$usage") + unless (defined $name && $name ne ""); + my $uri = "$::URI/$name"; + my $rh = json_request($uri); + print Dumper($rh->{'dut'}); + die("unable to find DUT $name") + unless(defined $rh->{'dut'}); + print Dumper($rh->{'dut'}); + exit 0; +} + +my $varnames = q(name img_file sw_version hw_version mgt_ip model_num serial_num serial_port api_id lan_port wan_port ssid1 ssid2 ssid3 passwd1 passwd2 passwd3); + +if ($action eq "add") { + die ("show what DUT? use --name:\n$usage") + unless (defined $name && $name ne ""); + my $rh_post = {}; + for my $k (sort split(' ', $varnames)) { + my $v = eval("return \$$k;"); + if ((defined $v) && ($v ne "")) { + $rh_post->{$k} = $v ; + } + else { + $rh_post->{$k} = "NA"; + } + } + print Dumper($rh_post); + my $post_uri = "$::Post_URI/add_dut$DEBUGURI"; + json_post($post_uri, $rh_post); +} + + +if ($action eq "annotate") { + die ("show what DUT? use --name:\n$usage") + unless (defined $name && $name ne ""); + die ("what notes? use --notes:\n$usage") + unless (defined $notes); + + my $rh_post = {}; + my @note_lines=(); + if ($notes eq "") { + $notes = '[BLANK]'; + } + @note_lines = split(/\r?\n/, $notes); + + if ($note_lines[0] ne "[BLANK]") { + unshift(@note_lines, "[BLANK]"); + } + print Dumper(\@note_lines); + my $post_uri = "$::Post_URI/add_dut_notes${DEBUGURI}"; + for my $note_line (@note_lines) { + print Dumper(\$note_line); + my $rh = { + 'dut' => $name, + 'text' => $note_line + }; + json_post($post_uri, $rh); + } +} +# diff --git a/lanforge/lanforge-scripts/json/json_create_sta2.sh b/lanforge/lanforge-scripts/json/json_create_sta2.sh new file mode 100755 index 000000000..c5893db14 --- /dev/null +++ b/lanforge/lanforge-scripts/json/json_create_sta2.sh @@ -0,0 +1,108 @@ +#!/bin/bash +unset proxy +unset http_proxy +set -x +set -e +ajson_h="Accept: application/json" +cjson_h="Content-type: application/json" +dbg="" +#dbg="__debug=1&" +R=1 +W=wiphy0 +M="10200" +N="10202" +SSID="jedway-wpa2-x2048-4-1" +KEY="jedway-wpa2-x2048-4-1" + +# +# works for ifdown: +# set_port 1 8 sta8000 NA NA NA NA 1 NA NA NA NA 8388610 +# works for ifup: +# set_port 1 8 sta8000 NA NA NA NA 0 NA NA NA NA 8388611 +# +echo -n "Removing: " +for n in `seq $M $N`; do + n=${n:1} + echo -n "sta$n " + echo "shelf=1&resource=$R&port=sta$n" > /tmp/curl_data + curl -sq -H "$ajson_h" -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-form/rm_vlan ||: +done +echo "." + +sleep 2 +echo -n "Adding: " +for n in `seq $M $N`; do + n=${n:1} + echo -n "${dbg}shelf=1&resource=$R&radio=$W" > /tmp/curl_data + echo -n "&sta_name=sta$n" >> /tmp/curl_data + echo -n "&flags=68727874560&ssid=idtest-1100-wpa2&key=idtest-1100-wpa2" >> /tmp/curl_data + echo -n "&mac=xx:xx:xx:xx:*:xx&mode=0&rate=DEFAULT" >> /tmp/curl_data + curl -sq -H "$ajson_h" -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-form/add_sta ||: +done + +sleep 2 +for n in `seq $M $N`; do + n=${n:1} + echo -n "${dbg}shelf=1&resource=$R&port=sta$n" > /tmp/curl_data + echo -n '¤t_flags=2147483648&interest=16384' >> /tmp/curl_data + curl -sq -H "$ajson_h" -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-form/set_port ||: +done +echo "." + +sleep 1 +echo -n "Bringing ports up " +for n in `seq $M $N`; do + n=${n:1} + echo -n "${dbg}shelf=1&resource=$R&port=sta$n" > /tmp/curl_data + echo -n '¤t_flags=0&interest=8388611' >> /tmp/curl_data + curl -sq -H "$ajson_h" -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-form/set_port ||: +done +echo "." + +sleep 3 +echo -n "Querying ports " +for n in `seq $M $N`; do + curl -sq -H "$ajson_h" -X GET -o /tmp/response "http://localhost:8080/port/1/$R/sta$n" + echo -n "." + #if [ -s /tmp/response ]; then + # json_pp < /tmp/response + #fi +done +echo "...done." +exit + + + + +echo 'shelf=1&resource=3&port=sta3100' > /tmp/curl_data +curl -sq -H "$ajson_h" -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-form/rm_vlan + +sleep 1 +echo 'shelf=1&resource=3&radio=wiphy1&sta_name=sta3100&flags=1024&ssid=idtest-1100-wpa2&nickname=sta3100&key=idtest-1100-wpa2&mac=XX:XX:XX:XX:*:*&flags_mask=1024' > /tmp/curl_data +curl -sq -H "$ajson_h" -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-form/add_sta + + +sleep 1 +rm -f /tmp/response +curl -sq -H "$ajson_h" -X GET -o /tmp/response http://localhost:8080/port/1/3/sta3100 +if [ -s /tmp/response ]; then + json_pp < /tmp/response +fi + +sleep 2 +echo '{"shelf":1,"resource":3,"port":"sta3100"}' > /tmp/curl_data +curl -sq -H "$ajson_h" -H "$cjson_h" -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-json/rm_vlan + +sleep 2 +echo '{"shelf":1,"resource":3,"radio":"wiphy1","sta_name":"sta3100","flags":1024,"ssid":idtest-1100-wpa2","nickname":"sta3100","key":"idtest-1100-wpa2","mac":"XX:XX:XX:XX:*:XX","flags_mask":1024}' > /tmp/curl_data +curl -sq -H "$ajson_h" -H "$cjson_h" -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-json/add_sta + +sleep 1 +rm -f /tmp/response +curl -sq -H "$ajson_h" -X GET -o /tmp/response http://localhost:8080/port/1/3/sta3100 +if [ -s /tmp/response ]; then + json_pp < /tmp/response +fi + + +# diff --git a/lanforge/lanforge-scripts/json/json_port_test.sh b/lanforge/lanforge-scripts/json/json_port_test.sh new file mode 100755 index 000000000..9771e23aa --- /dev/null +++ b/lanforge/lanforge-scripts/json/json_port_test.sh @@ -0,0 +1,252 @@ +#!/bin/bash +set -e +unset proxy +unset http_proxy +Q='"' +q="'" +S='*' +application_json="application/json" +accept_json="Accept: $application_json" +accept_html='Accept: text/html' +accept_text='Accept: text/plain' +#accept_any="'Accept: */*'" # just dont use +content_plain='Content-Type: text/plain' +content_json="Content-Type: $application_json" +switches='-sqv' +function Kurl() { + echo ======================================================================================= + echo curl $switches $@ + echo ======================================================================================= + #set -x + curl $switches "$@" + #set +x + echo "" + echo ======================================================================================= +} +function Jurl() { + echo =J===================================================================================== + echo curl $switches -H "$accept_json" -H "$content_json" "$@" + echo =J===================================================================================== + set -x + curl $switches -H "$accept_json" -H "$content_json" "$@" + set +x + echo "" + echo =J===================================================================================== +} + +url="http://localhost:8080" + +if true; then + #echo 'shelf=1&resource=3&port=sta3000&mac=xx:xx:xx:xx:xx:*' > /tmp/curl_data + echo '{"shelf":1,"resource":1,"port":"eth3","current_flags":1, "interest":2}' > /tmp/curl_data + Jurl -X POST -d '@/tmp/curl_data' "http://localhost:8080/cli-json/set_port" +fi + + + +exit + + + + + +if false; then + Kurl $acept_json $url/resource/1/list + echo 'shelf=1&resource=3&port=sta3000&mac=xx:xx:xx:xx:xx:*' > /tmp/curl_data + curl -sqv -H 'Accept: application/json' -X POST -d '@/tmp/curl_data' http://localhost:8080/cli-form/set_port + + url="http://localhost:8080/cli" + echo 'cmd=scan_wifi+1+3+sta30000' > /tmp/curl_data + Kurl -X POST -d '@/tmp/curl_data' -H "$accept_json" "$url" + +# this is not supported +# url="http://localhost:8080/cli/scan_wifi" +# echo '1+3+sta30000' > /tmp/curl_data +# Kurl -X POST -d '@/tmp/curl_data' -H "$accept_json" "$url" + + + #Kurl -X GET "$url/help/set_wifi_radio?cli=1%203%20wiphy1%20NA%20-1%20NA%20NA%20NA%20NA%20NA%20NA%20NA%20NA%200x1%20NA" + #CMD: set_wifi_radio 1 3 wiphy1 NA -1 NA NA NA NA NA NA NA NA 0x1 NA + echo "shelf=1&resource=3&radio=wiphy1&channel=-1&antenna=3x3&flags=0x10" > /tmp/curl_data + Kurl -H "$accept_json" -X POST -d "@/tmp/curl_data" "$url/cli-form/set_wifi_radio" + + #CMD: add_sta 1 3 wiphy1 sta3000 0 idtest-1000-open NA [BLANK] AUTO NA 00:0e:8e:98:64:45 8 NA NA NA NA NA 0 + echo "shelf=1&resource=3&radio=wiphy1&sta_name=sta3000&flags=0&ssid=idtest-1000-open&key=[BLANK]&ap=AUTO&mac=00:0e:8e:98:64:45&mode=8&flags_mask=0" > /tmp/curl_data + Kurl -H "$accept_json" -X POST -d "@/tmp/curl_data" "$url/cli-form/add_sta" + + #CMD: set_port 1 3 sta3000 0.0.0.0 255.255.0.0 0.0.0.0 NA 2147483648 00:0e:8e:98:64:45 NA NA NA 8405038 1000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NONE + echo 'shelf=&resource=1&port=3&ip_addr=sta3000&netmask=0.0.0.0&gateway=255.255.0.0&cmd_flags=0.0.0.0&mac=2147483648&mtu=00:0e:8e:98:64:45&report_timer=8405038&flags2=1000&ipv6_dflt_gw=N&bypass_wdt=A' > /tmp/curl_data + Kurl -H "$accept_json" -X POST -d "@/tmp/curl_data" "$url/cli-form/set_port" + echo -n "shelf=1&resource=3&radio=wiphy1&mode=NA&channel=-1&country=NA&frequency=NA&frag_thresh=NA" > /tmp/curl_data + echo -n "&rate=NA&rts=NA&txpower=NA&mac=NA&antenna=NA&flags=1&flags_mask=1&const_tx=NA" >> /tmp/curl_data + echo -n "&pulse_width=NA&pulse_interval=NA&vdev_count=NA&peer_count=NA&stations_count=NA" >> /tmp/curl_data + echo -n "&rate_ctrl_count=NA&fwname=NA&fwver=NA&txdesc_count=NA&tids_count=NA&skid_limit=NA" >> /tmp/curl_data + echo -n "&active_peer_count=NA&tx_pulses=NA&pulse2_interval_us=NA&max_amsdu=NA&pref_ap=NA" >> /tmp/curl_data + Kurl -X GET -d "@/tmp/curl_data" $url/help/set_wifi_radio + + echo -n "http://cholla:8080/help/set_wifi_radio?" + echo -n "shelf=1&resource=3&radio=wiphy1&mac=02:04:05:06:07:11&flags=1&flags_mask=1" + +# -1=auto or -1=ANY +#CMD: set_wifi_radio 1 3 wiphy1 NA -1 NA NA NA NA NA NA NA NA 0x1 NA + echo -n "shelf=1&resource=3&radio=NA&mode=NA&channel=NA&country=NA&frequency=NA&frag_thresh=NA" > /tmp/curl_data + echo -n "&rate=NA&rts=NA&txpower=NA&mac=NA&antenna=NA&flags=NA&flags_mask=NA&const_tx=NA" >> /tmp/curl_data + echo -n "&pulse_width=NA&pulse_interval=NA&vdev_count=NA&peer_count=NA&stations_count=NA" >> /tmp/curl_data + echo -n "&rate_ctrl_count=NA&fwname=NA&fwver=NA&txdesc_count=NA&tids_count=NA&skid_limit=NA" >> /tmp/curl_data + echo -n "&active_peer_count=NA&tx_pulses=NA&pulse2_interval_us=NA&max_amsdu=NA&pref_ap=NA" >> /tmp/curl_data + url="http://localhost:8080/cli-form/set_wifi_radio" + Kurl -X POST -d "@/tmp/curl_data" $url +#CMD: add_sta 1 3 wiphy1 sta3000 0 idtest-1000-open NA [BLANK] AUTO NA 00:0e:8e:98:64:45 8 NA NA NA NA NA 0 +#CMD: set_port 1 3 sta3000 0.0.0.0 255.255.0.0 0.0.0.0 NA 2147483648 00:0e:8e:98:64:45 NA NA NA 8405038 1000 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NONE + + # help demo + url="http://localhost:8080/cli-form/add_vsta" + Kurl -X POST -d 'help=1' -H "$accept_json" "$url" + Kurl -X POST -d 'help=?' -H "$accept_json" "$url" + Kurl -X POST -d 'help=%63' -H "$accept_json" "$url" + #$::resource, $::sta_wiphy, $sta_name, "$flags", "$::ssid", "$::passphrase", $mac_addr, "$flagsmask", $wifi_m, $::bssid); + url="http://localhost:8080/cli-form/add_vsta" + echo 'shelf=1&resource=3&sta_wiphy=&sta_name=&flags=&ssid=&passphrase=&flagsmask=&wifi_m=&bssid=' > /tmp/curl_data + Kurl -X POST -d '@/tmp/curl_data' -H "$accept_json" "$url" + + url="http://localhost:8080/cli-form/add_sta" + echo 'shelf=1&resource=3&sta_wiphy=&sta_name=&flags=&ssid=&passphrase=&flagsmask=&wifi_m=&bssid=' > /tmp/curl_data + Kurl -X POST -d '@/tmp/curl_data' -H "$accept_json" "$url" + + #fmt_port_cmd($resource, $sta_name, $ip_addr, $mac_addr); + url="http://localhost:8080/cli-form/set_port" + echo 'shelf=1&resource=3&port=&ip_addr=&mac=' > /tmp/curl_data + Kurl -X POST -d '@/tmp/curl_data' -H "$accept_json" "$url" + + #if ($::admin_down_on_add) { + # my $cur_flags = 0x1; # port down + # my $ist_flags = 0x800000; # port down + # $sta1_cmd = fmt_cmd("set_port", 1, $resource, $sta_name, "NA", + # "NA", "NA", "NA", "$cur_flags", + # "NA", "NA", "NA", "NA", "$ist_flags"); + # 3doCmd($sta1_cmd); + #} + + echo 'cmd=set_port_alias&shelf=1&resource=2&port=sta333&vport=NA&alias=bob33' > /tmp/curl_data + Kurl -X POST --data "@/tmp/curl_data" -H "$accept_json" -H "$content_plain" "$url" + sleep 5 + echo 'cmd=set_port_alias&shelf=1&resource=2&port=sta333&vport=NA&alias=bob33' > /tmp/curl_data + Kurl -X POST --data "@/tmp/curl_data" -H "$accept_json" -H "$content_json" "$url" + + echo 'shelf=1&resource=3&port=12&vport=NA&alias-64=Ym9iMzMK' > /tmp/curl_data + Kurl -X POST --data "@/tmp/curl_data" -H "$accept_json" "$url" + + echo 'shelf=1&resource=3&port=12&vport=NA&alias=bob+33' > /tmp/curl_data + Kurl -X POST --data "@/tmp/curl_data" -H "$accept_json" "$url" + + url="http://localhost:8080/cli-form/" + Kurl -X POST -d 'cmd=gossip&message=hi+there' -H "$accept_json" "$url" + + url="http://localhost:8080/cli-form/scan_wifi" + echo 'shelf=1&resource=3&port=sta30000' > /tmp/curl_data + Kurl -X POST -d '@/tmp/curl_data' -H "$accept_json" "$url" + + # the protocol command can only send back errors + # udp--2.b2000-03.sta30000 + #url="http://localhost:8080/cli-form/getMAC" + #echo 'CX=udp--2.b2000-03.sta30000&AorB=A' > /tmp/curl_data + #Kurl -X POST --data "@/tmp/curl_data" -H "$accept_json" "$url" + +fi + +echo "" +echo "END" + +exit +echo ======================================================================================= +url="http://localhost:8080/port/1/2/2" + +Kurl -X GET $url +Kurl -X HEAD $url +Kurl -X PUT $url +Kurl -X DELETE $url + + + +port_fields=( + "Crypt" + "Beacon" + "MAC" + "QLEN" + "Misc" + "Phantom" + "TX Pkts" + "Login-Fail" + "IPv6 Gateway" + "Logout-OK" + "CX Time (us)" + "Connections" + "Mask" + "No CX (us)" + "Channel" + "RX Bytes" + "bps RX" + "TX Errors" + "RX-Rate" + "ANQP Time (us)" + "RX Miss" + "Bytes TX LL" + "SSID" + "TX Fifo" + "DHCP (ms)" + "Retry" + "Port" + "Bytes RX LL" + "RX Errors" + "4Way Time (us)" + "Logout-Fail" + "Pps RX" + "MTU" + "AP" + "RX Length" + "Parent Dev" + "bps RX LL" + "RX Fifo" + "RX Drop" + "Pps TX" + "Noise" + "IP" + "TX Wind" + "RX Frame" + "Key/Phrase" + "Signal" + "SEC" + "IPv6 Address" + "TX Abort" + "Device" + "Gateway IP" + "TX-Rate" + "CX Ago" + "RX Pkts" + "Reset" + "TX Crr" + "RX Over" + "TX Bytes" + "Activity" + "Collisions" + "Alias" + "bps TX" + "bps TX LL" + "Status" + "TX HB" + "Down" + "RX CRC" + "Login-OK" + ) +for name in "${port_fields[@]}"; do + #echo "field <$name>" + name2="${name// /%20}" + url="http://localhost:8080/port/1/2/2,3,4?fields=$name2,Port" + echo -n "..." + curl -si -H 'Accept: application/json' "$url" > /tmp/response + echo "$url" + grep 'HTTP/1.1' /tmp/response + tail -1 /tmp/response + echo "" + sleep 0.05 +done diff --git a/lanforge/lanforge-scripts/json/json_port_test2.sh b/lanforge/lanforge-scripts/json/json_port_test2.sh new file mode 100755 index 000000000..773bf0688 --- /dev/null +++ b/lanforge/lanforge-scripts/json/json_port_test2.sh @@ -0,0 +1,104 @@ +#!/bin/bash +set -e +unset proxy +unset http_proxy +Q='"' +q="'" +S='*' +application_json="application/json" +accept_json="Accept: $application_json" +accept_html='Accept: text/html' +accept_text='Accept: text/plain' +#accept_any="'Accept: */*'" # just dont use +content_plain='Content-Type: text/plain' +content_json="Content-Type: $application_json" +switches='-sq' +#switches='-sq' + +function Kurl() { + echo "=======================================================================================" + echo "curl $switches $@" + echo "=======================================================================================" + curl $switches "$@" | json_pp + echo "" + echo "=======================================================================================" +} + +function Jurl() { + echo "=J=====================================================================================" + echo "curl $switches -H $accept_json -H $content_json $@" + echo "=J=====================================================================================" + curl $switches -H "$accept_json" -H "$content_json" -X POST "$@" + echo "" + echo "=J=====================================================================================" +} + +url="http://jedtest.jbr:8080" +#url="http://127.0.0.1:8080" +data_file="/var/tmp/data.$$" +result_file="/var/tmp/result_file.$$" + +function PortDown() { + echo "{\"shelf\":1,\"resource\":3,\"port\":\"$1\",\"current_flags\":1, \"interest\":8388610}" > $data_file + curl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/set_port" + sleep 0.3 + for f in `seq 1 10`; do + echo "{\"shelf\":1,\"resource\":3,\"port\":\"$1\"}" > $data_file + curl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/nc_show_ports" + sleep 0.5 + curl $switches "$url/port/1/3/$1?fields=alias,ip,down" | json_reformat > $result_file + egrep '"down" *: "?true"?' $result_file && break || : + done +} + +function PortUp() { + #set_port 1 3 sta3101 NA NA NA NA 0 NA NA NA NA 8388610 + echo "{\"shelf\":1,\"resource\":3,\"port\":\"$1\",\"current_flags\":0, \"interest\":8388610}" > $data_file + curl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/set_port" + sleep 1 + for f in `seq 1 100`; do + echo "{\"shelf\":1,\"resource\":3,\"port\":\"$1\"}" > $data_file + #Jurl -d"@$data_file" "$url/cli-json/nc_show_ports" + curl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/nc_show_ports" + sleep 0.5 + curl $switches "$url/port/1/3/$1?fields=alias,ip,down" | json_reformat > $result_file + #cat $result_file + egrep '"down" : "?false"?' $result_file && break || : + done +} + +function CxToggle() { + echo "{\"test_mgr\":\"all\",\"cx_name\":\"$1\",\"cx_state\":\"$2\"}" > $data_file + curl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/set_cx_state" +} + +while true; do + for e in `seq 3000 3020` ; do + ea="udp:r3r2:$e" + eu="udp%3Ar3r2%3A${e}-A" + CxToggle "$ea" "STOPPED" + sleep 0.1 + curl $switches -H "$accept_json" "$url/endp/$eu?fields=name,run" | json_reformat > $result_file + sleep 0.1 + done + for sta in `seq 100 121`; do + stb=$(( $sta + 3000)) + PortDown "sta$stb" + done + for sta in `seq 100 121`; do + stb=$(( $sta + 3000)) + PortUp "sta$stb" + done + sleep 4 + for e in `seq 3000 3020` ; do + ea="udp:r3r2:$e" + eu="udp%3Ar3r2%3A${e}-A" + CxToggle "$ea" "RUNNING" + sleep 0.1 + curl $switches -H "$accept_json" "$url/endp/$eu?fields=name,run" | json_reformat > $result_file + sleep 0.1 + done + sleep 14 +done + +# diff --git a/lanforge/lanforge-scripts/json/json_port_test3.sh b/lanforge/lanforge-scripts/json/json_port_test3.sh new file mode 100755 index 000000000..b4e002e11 --- /dev/null +++ b/lanforge/lanforge-scripts/json/json_port_test3.sh @@ -0,0 +1,151 @@ +#!/bin/bash +set -e +unset proxy +unset http_proxy +Q='"' +q="'" +S='*' +series=(10000 10001 10002 11000 11001 11002 12000 12001 12002) +ra=1 +rb=2 +application_json="application/json" +accept_json="Accept: $application_json" +accept_html='Accept: text/html' +accept_text='Accept: text/plain' +#accept_any="'Accept: */*'" # just dont use +content_plain='Content-Type: text/plain' +content_json="Content-Type: $application_json" +headers="/var/tmp/headers.$$" +switches="-s -D $headers -o $result" +#switches='-sq' +data_file=/var/tmp/data.$$ +result=/var/tmp/result.$$ + +function Kurl() { + echo "=======================================================================================" + echo "curl $switches $@" + echo "=======================================================================================" + curl $switches "$@" | json_pp + echo "" + echo "=======================================================================================" +} + +function Jurl() { + echo "=J=====================================================================================" + echo "curl $switches -H $accept_json -H $content_json $@" + echo "=J=====================================================================================" + curl $switches -H "$accept_json" -H "$content_json" -X POST "$@" + echo "" + echo "=J=====================================================================================" +} + +function Kuurl() { + echo "URL: ${@:$#}" + echo "" > $result + echo "" > $headers + #curl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$@" ||: + set -x + curl "$@" ||: + set +x + grep 'HTTP/1.1 200' $headers || (echo "${@:$#}"; cat $headers; exit 1) +} + +HOST=${1:-jedtest.jbr} +url="http://${HOST}:8080" +#url="http://127.0.0.1:8080" + +function PortDown() { + switches="-s -D $headers -o $result" + echo "{\"shelf\":1,\"resource\":$ra,\"port\":\"$1\",\"current_flags\":1, \"interest\":8388610}" > $data_file + Kuurl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/set_port" + sleep 0.3 + for try in `seq 1 100`; do + echo "{\"shelf\":1,\"resource\":$ra,\"port\":\"$1\"}" > $data_file + cat $data_file + Kuurl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/nc_show_ports" + sleep 0.5 + Kuurl $switches "$url/port/1/$ra/$1?fields=alias,ip,down" + json_pp < $result || cat $result + grep '"down".*true' $result && break || : + done +} + +function PortUp() { + #set_port 1 3 sta3101 NA NA NA NA 0 NA NA NA NA 8388610 + echo "{\"shelf\":1,\"resource\":$ra,\"port\":\"$1\",\"current_flags\":0, \"interest\":8388610}" > $data_file + curl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/set_port" + sleep 1 + for try in `seq 1 100`; do + echo "{\"shelf\":1,\"resource\":$ra,\"port\":\"$1\"}" > $data_file + #Jurl -d"@$data_file" "$url/cli-json/nc_show_ports" + curl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/nc_show_ports" + sleep 0.5 + curl $switches "$url/port/1/$ra/$1?fields=alias,ip,down" + json_pp < $result || cat $result + grep '"down".*false' $result && break || : + done +} + +function CxToggle() { + echo "{\"test_mgr\":\"all\",\"cx_name\":\"$1\",\"cx_state\":\"$2\"}" > $data_file + cat $data_file + Kuurl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/set_cx_state" +} + +function CxCreate() { # alias, port + echo "{\"alias\":\"$1-A\",\"shelf\":1,\"resource\":$ra,\"port\":\"$2\",\"type\":\"lf_udp\",\"ip_port\":\"AUTO\",\"is_rate_bursty\":\"NO\",\"min_rate\":164000,\"min_pkt\":-1,\"max_pkt\":0}" > $data_file + cat $data_file + Kuurl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/add_endp" + + echo "{\"alias\":\"$1-B\",\"shelf\":1,\"resource\":$rb,\"port\":\"b2000\",\"type\":\"lf_udp\",\"ip_port\":\"AUTO\",\"is_rate_bursty\":\"NO\",\"min_rate\":64000,\"min_pkt\":-1,\"max_pkt\":0}" > $data_file + cat $data_file + Kuurl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/add_endp" + + echo "{\"alias\":\"$1\",\"test_mgr\":\"default_tm\",\"tx_endp\":\"$1-A\",\"rx_endp\":\"$1-B\"}" > $data_file + cat $data_file + Kuurl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/add_cx" + + echo "{\"endpoint\":\"$1-A\"}" > $data_file + Kuurl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/nc_show_endpoints" + + echo "{\"endpoint\":\"$1-B\"}" > $data_file + Kuurl $switches -H "$accept_json" -H "$content_json" -X POST -d"@$data_file" "$url/cli-json/nc_show_endpoints" +} + +# create some cx +for eidcx in "${series[@]}" ; do + CxCreate "udp-$eidcx" "vsta$eidcx" + sleep 1 + Kuurl $switches -H "$accept_json" "$url/endp/udp-$eidcx-A?fields=name,run" + Kuurl $switches -H "$accept_json" "$url/endp/udp-$eidcx-B?fields=name,run" + Kuurl $switches -H "$accept_json" "$url/cx/udp-$eidcx?fields=name,state" +done + +sleep 4 +for try in 1 2; do + for eidcx in "${series[@]}" ; do + sleep 1 + CxToggle "udp-$eidcx" "STOPPED" + Kuurl $switches -H "$accept_json" "$url/endp/udp-$eidcx-A?fields=name,run" + Kuurl $switches -H "$accept_json" "$url/endp/udp-$eidcx-B?fields=name,run" + Kuurl $switches -H "$accept_json" "$url/cx/udp-$eidcx?fields=name,state" + done + for sta in "${series[@]}"; do + PortDown "vsta$sta" + done + sleep 5 + for sta in "${series[@]}"; do + PortUp "vsta$sta" + done + sleep 5 + for eidcx in "${series[@]}" ; do + sleep 1 + CxToggle "udp-$eidcx" "RUNNING" + Kuurl $switches -H "$accept_json" "$url/endp/udp-$eidcx-A?fields=name,run" + Kuurl $switches -H "$accept_json" "$url/endp/udp-$eidcx-B?fields=name,run" + Kuurl $switches -H "$accept_json" "$url/cx/udp-$eidcx?fields=name,state" + done + sleep 20 +done +echo "DONE" +# \ No newline at end of file diff --git a/lanforge/lanforge-scripts/json/json_test4.sh b/lanforge/lanforge-scripts/json/json_test4.sh new file mode 100755 index 000000000..5e101797e --- /dev/null +++ b/lanforge/lanforge-scripts/json/json_test4.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e +unset proxy +unset http_proxy +Q='"' +q="'" +S='*' +application_json="application/json" +accept_json="Accept: $application_json" +accept_html='Accept: text/html' +accept_text='Accept: text/plain' +#accept_any="'Accept: */*'" # just dont use +content_plain='Content-Type: text/plain' +content_json="Content-Type: $application_json" +switches='-sqv' +#switches='-sq' +data_file=/var/tmp/data.$$ + +function Kurl() { + echo "=======================================================================================" + echo "curl $switches $@" + echo "=======================================================================================" + curl $switches "$@" | json_pp + echo "" + echo "=======================================================================================" +} + +function Jurl() { + echo "=J=====================================================================================" + echo "curl $switches -H $accept_json -H $content_json $@" + echo "=J=====================================================================================" + curl $switches -H "$accept_json" -H "$content_json" -X POST "$@" + echo "" + echo "=J=====================================================================================" +} + +#url="http://jed-f24m64-9119:8080" +url="http://127.0.0.1:8080" +while true; do + curl -sq -H "$accept_html" $url/help/ > /var/tmp/help.html + perl -ne "m{href='(/help/[^']+)'} && print \"\$1\n\";" /var/tmp/help.html > /var/tmp/help_cmds.txt + for f in `cat /var/tmp/help_cmds.txt`; do + curl --retry 10 -sq -H "$accept_html" "${url}$f" >/dev/null + done + + curl -sq -H "$accept_json" ${url}/resource/list | json_reformat > $data_file +#less $data_file + perl -ne 'm{"_links"\s*:\s*"([^ ]+)"} && print "$1\n";' $data_file > /var/tmp/resources.txt + + for f in `cat /var/tmp/resources.txt`; do + echo "$f" + (curl --retry 10 -sq -H "$accept_json" "${url}$f" |json_reformat) || exit 1 + done + + curl -sq -H "$accept_json" ${url}/port/list | json_reformat > $data_file +#less $data_file + perl -ne 'm{"_links"\s*:\s*"([^ ]+)"} && print "$1\n";' $data_file > /var/tmp/ports.txt + + for f in `cat /var/tmp/ports.txt`; do + echo "$f" + (curl --retry 10 -sq -H "$accept_json" "${url}$f" |json_reformat) || exit 1 + done +done + +# diff --git a/lanforge/lanforge-scripts/json/l4_test.pl b/lanforge/lanforge-scripts/json/l4_test.pl new file mode 100755 index 000000000..bd98e64b4 --- /dev/null +++ b/lanforge/lanforge-scripts/json/l4_test.pl @@ -0,0 +1,419 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use Time::HiRes qw(usleep); +use JSON; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names flatten_list); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; +our $ssid; +our $security; +our $passphrase; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 +); + +my $des_resource = 6; +#my $pat_port_type = '^eth\d[#]\d+'; +my $pat_port_type = '^v*sta\d+'; +## +## M A I N +## + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + +my $DEBUGURI = "?__debug=1"; +my $uri_args = ""; # ="$DEBUG_URI"; + +my $uri = "/shelf/1"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'resources'); +my @ports_up= (); + +# TODO: make this a JsonUtils::list_ports() +$uri = "/port/1/${des_resource}/list?fields=alias,device,down,phantom,port"; +#logg("requesting $uri"); +$rh = json_request($uri); +flatten_list($rh, 'interfaces'); +for my $rh_p (keys %{$rh->{'flat_list'}}) { + # truthy value evaluates better + my $onoff = $rh->{'flat_list'}->{$rh_p}->{'down'}; + print "$rh_p down? $onoff "; + if ($onoff) { + push(@ports_up, $rh_p); + } +} +# find first station +my $rh_sta; +for my $rh_up (@ports_up) { + my $eid = $rh->{'flat_list'}->{$rh_up}->{'port'}; + my @hunks = split(/[.]/, $eid); + if ($hunks[1]) { + $rh_sta = $rh_up; + } +} +if (!defined $rh_sta) { + die("Unable to find a virtual station. Is one up?"); +} + +# delete old CXes and old endpoints +# TODO: collect_l4cx_names + +my $rh_endplist = json_request("/layer4/list"); +print "\nRemoving L4: "; +my @endp_names = (); +#sleep 2; +#print "-------------------------------------------------------------------------\n"; +#print Dumper($rh_endplist); +#print "-------------------------------------------------------------------------\n"; +#sleep 2; + +if (defined $rh_endplist->{"endpoint"} + && (ref $rh_endplist->{"endpoint"} eq "HASH")) { + # indicates we only have one + push(@endp_names, $rh_endplist->{"endpoint"}->{"name"}); +} +elsif (defined $rh_endplist->{"endpoint"}) { + flatten_list($rh_endplist, 'endpoint'); + #print "FLAT LIST:\n"; + #print Dumper($rh_endplist->{'flat_list'}); + for my $ep_name (keys %{$rh_endplist->{'flat_list'}}) { + print "?$ep_name? "; + next if (!defined $ep_name); + next if ($ep_name eq ""); + next if ((ref $ep_name) eq "ARRAY"); + next if (!defined $rh_endplist->{'flat_list'}->{$ep_name}->{"name"}); + next if ($rh_endplist->{'flat_list'}->{$ep_name}->{"name"} eq ""); + #print "\nepn:".Dumper($rh_endplist->{'flat_list'}->{$ep_name}->{"name"}); + push(@endp_names, $ep_name); + } +} +if ((@endp_names < 1) && (defined $rh_endplist->{"endpoint"})) { + # check for mutated L4endp entries that only exist in EID form + #die "Unknown L4 endpoint state" + # if (!(defined $rh_endplist->{"endpoint"})); + die "No endpoint entries" + if (scalar @{$rh_endplist->{"endpoint"}} < 1); + for $rh (@{$rh_endplist->{"endpoint"}}) { + #print Dumper($rh); + my @k = keys(%$rh); + #print "$k[0] "; + push(@endp_names, $k[0]); + } +} +#print Dumper(\@endp_names); + + + +my @cx_names = (); +if (@endp_names > 0) { + for my $endp_name (@endp_names) { + next if ($endp_name =~ /^CX_D_/); + print " endp_name[$endp_name]"; + push(@cx_names, "CX_".$endp_name); + } +} +my $rh_req = { + "test_mgr" => "default_tm", + "suppress_preexec_method" => 1, + "suppress_preexec_cli" => 1, +}; +for my $cx_name (@cx_names) { + print "rm_cx $cx_name "; + $rh_req->{"cx_name"} = $cx_name; + print "rm_cx $cx_name "; + json_post("/cli-json/rm_cx", $rh_req); +} +print "\nRemoved ".scalar @cx_names." cx\n"; +my $rh_show_cxe = { "test_mgr"=>"all", "cross_connect"=>"all"}; +json_post("/cli-json/show_cxe${DEBUGURI}", $rh_show_cxe); +sleep 2; +print "\nRemoving ".scalar @endp_names; +$uri = "/cli-json/rm_endp${uri_args}"; +for my $ep_name (@endp_names) { + if (!defined $ep_name || $ep_name =~/^\s*$/ || (ref $ep_name) eq "ARRAY") { + #print " rm_endp [$ep_name]"; #skipping + #print Dumper(\$ep_name); + next; + } + print " -$ep_name "; + $rh = { "endp_name" => $ep_name }; + json_post($uri, $rh); +} +print "\nRefreshing..."; +my $h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints${uri_args}", $h); +sleep 1; +$h = {"test_mgr"=>"all", "cross_connect"=>"all"}; +json_request("/cli-json/show_cxe${uri_args}", $h); + + + + +# assume resource 1, eth1 is present, and create an endpoint to it +# -A and -B are expected convention for endpoint names + +## +## Create New Endpoints +## +my $rh_ports = json_request("/port/1/${des_resource}/list"); +flatten_list($rh_ports, 'interfaces'); + +my $rh_endp_A = { # actual endpoint + #'alias' => "untitled", + 'shelf' => 1, + 'resource' => $des_resource, + 'type' => 'l4_generic', + 'timeout' => '2000', + 'url_rate' => '600', + 'url' => 'dl http://idtest.candelatech.com/ /dev/null', + 'max_speed' => '1000000', + 'http_auth_type' => 0, + 'proxy_auth_type' => 512, + }; +my $rh_endp_B = { # dummy endpoints, # we don't actually need + #'alias' => "D_untitled", + 'shelf' => 1, + 'resource' => $des_resource, + # port + 'type' => 'l4_generic', + 'proxy_port' => 'NA', + 'timeout' => 0, + 'url_rate' => 0, + 'url' => ' ', + 'max_speed' => 'NA', + }; +my $rh_set_flags_a = { + # 'name' => + 'flag' => 'GetUrlsFromFile', + 'val' => 0, + }; +my $rh_set_flags_b = { + # 'name' => + 'flag' => 'unmanaged', + 'val' => 1, + }; +my $rh_add_cx = { + # "alias" =>, + 'test_mgr' => 'default_tm', + #'tx_endp' =>, + #'rx_endp' => +}; +print "\nConstructing new Endpoints B: "; +my $num_ports = scalar keys(%{$rh_ports->{'flat_list'}}); +my $num_cx = 0; +my $disp_num = $des_resource * 1000; +# create dummy port and set it unmanaged +my $create_b_side = 0; +if ($create_b_side) { + for my $rh_p (values %{$rh_ports->{'flat_list'}}) { + last if ($num_cx >= ($num_ports-1)); + next if ($rh_p->{'alias'} !~ /$pat_port_type/); + + # create dummy port and set it unmanaged + my $end_b_alias = "D_l4json${disp_num}"; + $rh_endp_B->{'port'} = $rh_p->{'alias'}; + $rh_endp_B->{'alias'} = $end_b_alias; + $num_cx++; + $disp_num++; + print " +$end_b_alias "; + json_post("/cli-json/add_l4_endp${uri_args}", $rh_endp_B); + } + sleep 1; + $num_cx = 0; + $disp_num = $des_resource * 1000; + print "\nSetting Endpoint flags: "; + for my $rh_p (values %{$rh_ports->{'flat_list'}}) { + last if ($num_cx >= ($num_ports-1)); + next if ($rh_p->{'alias'} !~ /$pat_port_type/); + my $end_b_alias = "D_l4json${disp_num}"; + $rh_set_flags_b->{'name'} = $end_b_alias; + $num_cx++; + $disp_num++; + print " ~$end_b_alias "; + json_post("/cli-json/set_endp_flag${uri_args}", $rh_set_flags_b); + } + sleep 1; +} +$num_cx = 0; +$disp_num = $des_resource * 1000; +print "\nAdding Endpoint A: "; +for my $rh_p (values %{$rh_ports->{'flat_list'}}) { + last if ($num_cx >= ($num_ports-1)); + next if ($rh_p->{'alias'} !~ /$pat_port_type/); + my $end_a_alias = "l4json${disp_num}"; + $rh_endp_A->{'port'} = $rh_p->{'alias'}; + $rh_endp_A->{'alias'} = $end_a_alias; + $num_cx++; + $disp_num++; + print " +$end_a_alias; "; + json_post("/cli-json/add_l4_endp${uri_args}", $rh_endp_A); +} +$num_cx = 0; +$disp_num = $des_resource * 1000; +print "\nSet_endp_flag: "; +for my $rh_p (values %{$rh_ports->{'flat_list'}}) { + last if ($num_cx >= ($num_ports-1)); + next if ($rh_p->{'alias'} !~ /$pat_port_type/); + my $end_a_alias = "l4json${disp_num}"; + $rh_set_flags_a->{'name'} = $end_a_alias; + $num_cx++; + $disp_num++; + print " ~$end_a_alias "; + json_post("/cli-json/set_endp_flag${uri_args}", $rh_set_flags_a); +} +print "\nRefreshing..."; +$h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints${uri_args}", $h); +sleep 4; +print "\nConstructing new CX: "; +$num_cx = 0; +$disp_num = $des_resource * 1000; +print Dumper($rh_ports->{'flat_list'}); +for my $rh_p (values %{$rh_ports->{'flat_list'}}) { + last if ($num_cx >= ($num_ports-1)); + next if ($rh_p->{'alias'} !~ /$pat_port_type/); + my $end_a_alias = "l4json${disp_num}"; + my $end_b_alias = ($create_b_side) ? "D_l4json${disp_num}" : "NA"; + my $cx_alias = "CX_l4json${disp_num}"; + $rh_add_cx->{'alias'} = $cx_alias; + $rh_add_cx->{'tx_endp'} = $end_a_alias; + $rh_add_cx->{'rx_endp'} = $end_b_alias; + $num_cx++; + $disp_num++; + print " $cx_alias "; + json_post("/cli-json/add_cx${uri_args}", $rh_add_cx); +} +$h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints", $h); +sleep 4; +$h = {"test_mgr"=>"all", "cross_connect"=>"all"}; +json_request("/cli-json/show_cxe", $h); +sleep 4; + +print "\nRefreshing..."; +$h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints", $h); +sleep 4; +$h = {"test_mgr"=>"all", "cross_connect"=>"all"}; +json_request("/cli-json/show_cxe", $h); +sleep 4; + +# wait for data to distribute + +my $num_unfinished = 1; +while ($num_unfinished > 0) { + $num_unfinished = 0; + my $rh_cx = json_request("/layer4/list"); + print "\n- 337 --------------------------------------------------------\n"; + print Dumper($rh_cx); + print "\n- 337 --------------------------------------------------------\n"; + flatten_list($rh_cx, "endpoint"); + @cx_names = sort keys %{$rh_cx->{'flat_list'}}; + for my $cx_alias (sort @cx_names) { + if ($cx_alias =~ /^1\./) { + print " -$cx_alias "; + $num_unfinished++ ; + } + } + print " Unfinished: $num_unfinished\n"; + sleep 1 if ($num_unfinished); +} +@endp_names = (); +my $rh_endp = json_request("/layer4/list"); +flatten_list($rh_endp, "endpoint"); + print "\n- 354 --------------------------------------------------------\n"; + print Dumper($rh_endp); + print "\n- 354 --------------------------------------------------------\n"; +@endp_names = sort keys %{$rh_endp->{'flat_list'}}; +@cx_names = (); +for my $endp_name (@endp_names) { + next if ($endp_name =~ m/^D_/); + if ($endp_name =~ m/^1\./) { + print " what? $endp_name "; + next; + } + push(@cx_names, "CX_${endp_name}"); +} + +my $rh_cx_t = { + 'test_mgr' => 'default_tm', + #'cx_name' => $cx_alias, + 'milliseconds'=> 1000, + }; +print "\nSetting timers: "; +for my $cx_alias (sort @cx_names) { + if ($cx_alias =~ /^\s*$/ || ref $cx_alias eq "ARRAY") { + print "BLANK CX_NAME: ".Dumper(\@cx_names); + next; + } + $rh_cx_t->{'cx_name'} = $cx_alias; + print " ~$cx_alias "; + json_post("/cli-json/set_cx_report_timer", $rh_cx_t); +} + +print "\nRefreshing..."; +$h = {"endpoint"=>"all"}; +json_request("/cli-json/nc_show_endpoints", $h); +sleep 1; +$h = {"test_mgr"=>"all", "cross_connect"=>"all"}; +json_request("/cli-json/show_cxe", $h); +sleep 1; + +$rh_cx_t = { + 'test_mgr' => 'default_tm', + #'cx_name' => $cx_alias, + 'cx_state'=> "RUNNING", + }; +print "\nStarting cx..."; +for my $cx_alias (sort @cx_names) { + $rh_cx_t->{'cx_name'} = $cx_alias; + print " +$cx_alias "; + json_post("/cli-json/set_cx_state", $rh_cx_t); +} +sleep 5; + +print "\nStopping cx..."; +$rh_cx_t = { + 'test_mgr' => 'default_tm', + #'cx_name' => $cx_alias, + 'cx_state'=> "STOPPED", + }; +for my $cx_alias (sort @cx_names) { + $rh_cx_t->{'cx_name'} = $cx_alias; + print " -$cx_alias "; + json_post("/cli-json/set_cx_state", $rh_cx_t); +} + +# diff --git a/lanforge/lanforge-scripts/json/port_test.pl b/lanforge/lanforge-scripts/json/port_test.pl new file mode 100755 index 000000000..3c98b114f --- /dev/null +++ b/lanforge/lanforge-scripts/json/port_test.pl @@ -0,0 +1,331 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +use Time::HiRes qw(usleep); +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use JSON; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names flatten_list); + +package main; +# Default values for ye ole cmd-line args. + +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; +our $use_ssid = "kedtest-wpa2"; +our $use_pass = "kedtest-wpa2"; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 + --ssid {ssid} + --pass {passwd} +); + + +my $num_sta = 3; +my $des_resource = 1; +## +## M A I N +## + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port, + 'ssid=s' => \$::use_ssid, + 'pass=s' => \$::use_pass, +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + +my $uri = "/shelf/1"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'resources'); +my @links2= (); +my $ra_alias_links = []; + +# TODO: make this a JsonUtils::list_ports() +for $uri (@$ra_links) { + $uri =~ s{/resource}{/port}g; + $uri .= "/list?fields=_links,device,alias,port"; + print "$uri\n"; + $rh = json_request($uri); + if (defined $rh->{'interfaces'}) { + flatten_list($rh, 'interfaces'); + #push(@$ra_alias_links, keys(%{$rh->{'flat_list'}})); + push( @$ra_alias_links, get_port_names($rh, 'interfaces')); + #push(@links2, keys(%{$rh->{'flat_list'}})); + push(@links2, @{get_links_from($rh, 'interfaces')}); + } +} + +# destroy stations +my @radios = (); +my @destroy_me = (); +for my $rh_alias_link (@$ra_alias_links) { + for my $rh_link (@$rh_alias_link) { + if ( ($rh_link->{'uri'} =~m{^/port/1/$des_resource/}) + && ($rh_link->{'device'} =~m{^sta})) { + push(@destroy_me, $rh_link); + } + + push(@radios, $rh_link) + if (($rh_link->{'uri'} =~m{^/port/1/$des_resource/}) + && ($rh_link->{'device'} =~m{^wiphy})); + } +} +logg("\nDestroying these: "); + +for my $rh_target (@destroy_me) { + my $alias = $rh_target->{'device'}; + my @hunks = split(/[\/]/, $rh_target->{'uri'}); + + # TODO: create JsonUtils::rm_vlan($eid, $alias) + # suppress_postexec used to reduce the update traffic concurrent with large set of deletions + my $rh_data = { + 'shelf'=>1, + 'resource'=>$hunks[3], + 'port'=>$alias + }; + logg(" $alias"); + my $rh_response = json_post("/cli-json/rm_vlan", $rh_data); + usleep (15000); +} +my $rh_update = { + 'shelf'=>1, 'resource'=>$des_resource, 'port'=>'all', 'probe_flags'=>'0x1' +}; +logg("\nRefreshing: "); +my $rh_response = json_post("/cli-json/nc_show_ports", $rh_update); +my $remaining = 1; +while ($remaining > 0) { + $rh = json_request("/port/1/$des_resource/list"); + flatten_list($rh, 'interfaces'); + $remaining = 0; + for my $name (keys %{$rh->{'flat_list'}}) { + $remaining ++ + if ($name =~ /^v*sta/); + } + print "Remaining stations: $remaining, "; + sleep 1; +} + + + +# this really should poll for ports to wait for them to disappear +sleep 3; + +my @new_stations = (); +logg("\nCreating new stations on these: "); +#print Dumper(\@radios); +my $rh_radio; +my $radio_name; +my $resource; +my $range; +my $radio_num; +my $radio_counter = 0; +$rh_response = json_post("/cli-json/nc_show_ports", $rh_update); + +# add_sta + ht20 -ht40 -ht80 -create_admin_down +# flags=142609408&mode=8 + +print "\nAdding stations...\n"; +for $rh_radio (@radios) { + $radio_name = $rh_radio->{'alias'}; + my @hunks = split(/[\/]/, $rh_radio->{'uri'}); + ($radio_num) = $radio_name =~ /wiphy(\d+)/; + $resource = $hunks[3]; + $range = ($resource * 1000) + ($radio_num * 100); + logg("\n/cli-json/add_sta on 1.$resource.$radio_name\n"); + for (my $i = $range; $i < ($range+$num_sta); $i++) { + # TODO: create JsonUtils::add_sta($eid, $alias...) + my $rh_data = { + 'shelf'=>1, + 'resource'=>$resource, + #'radio'=>'x'.$radio_name, # use to prompt radio not found error + 'radio'=>$radio_name, + 'sta_name'=>'sta'.$radio_counter, + #'alias'=>'vsta'.$i, # deprecated, use set_port + interest.set_alias + #'flags'=>68862086144, # has port-down set + 'flags'=>142609408, + 'ssid'=> $::use_ssid, + 'key'=> $::use_pass, + 'mac'=>'xx:xx:xx:xx:*:xx', + 'mode'=>0, + 'rate'=>'DEFAULT' + }; + logg(" sta$radio_counter"); + my $rh_response = json_post("/cli-json/add_sta", $rh_data); + usleep(300000); + $radio_counter +=1; + } + $rh_response = json_post("/cli-json/nc_show_ports", $rh_update); + sleep 1; +} # for +logg("\nUpdating aliases "); +$rh_response = json_post("/cli-json/nc_show_ports", $rh_update); +sleep 5; +$radio_counter = 0; +for $rh_radio (@radios) { + $radio_name = $rh_radio->{'alias'}; + my @hunks = split(/[\/]/, $rh_radio->{'uri'}); + ($radio_num) = $radio_name =~ /wiphy(\d+)/; + $resource = $hunks[3]; + $range = ($resource * 10000) + ($radio_num * 1000); + + for (my $i = $range; $i < ($range+$num_sta); $i++) { + print "sta$radio_counter:vsta$i "; + #my $eidname = "1.$resource.sta$radio_counter"; + + # set port up + dhcp + alias + my $rh_data = { + 'shelf'=>1, + 'resource'=>$resource, + 'port'=>'sta'.$radio_counter, + 'current_flags'=>2147483648, + 'interest'=>20480, + 'alias'=>'vsta'.$i + }; + my $rh_response = json_post("/cli-json/set_port", $rh_data); + $radio_counter+=1; + usleep(300000); + } +} +$rh_response = json_post("/cli-json/nc_show_ports", $rh_update); +sleep 5; +$radio_counter = 0; +for $rh_radio (@radios) { + $radio_name = $rh_radio->{'alias'}; + my @hunks = split(/[\/]/, $rh_radio->{'uri'}); + ($radio_num) = $radio_name =~ /wiphy(\d+)/; + $resource = $hunks[3]; + $range = ($resource * 10000) + ($radio_num * 1000); + for (my $i = $range; $i < ($range+$num_sta); $i++) { + print "sta$radio_counter:vsta$i "; + #my $eidname = "1.$resource.sta$radio_counter"; + + # set port up + dhcp + alias + my $rh_data = { + 'shelf'=>1, + 'resource'=>$resource, + 'port'=>'sta'.$radio_counter, + 'current_flags'=>0, + 'interest'=>8388608, + }; + my $rh_response = json_post("/cli-json/set_port", $rh_data); + $radio_counter+=1; + sleep 1; + } +} +logg("\nRefreshing after setting up... "); +$rh_response = json_post("/cli-json/nc_show_ports", $rh_update); +sleep 4; +# wait on ports up +my $ports_still_down = 1; +while ($ports_still_down > 0) { + # this logic has to see if port is phantom and if port is over-subscribed + $rh = json_request("/port/1/$des_resource/list?fields=_links,port,device,down,phantom,channel"); + flatten_list($rh, 'interfaces'); + $ports_still_down=0; + for my $rh_p (values %{$rh->{'flat_list'}}) { + next unless $rh_p->{'device'} =~ /^sta/; + #print "$rh_p->{'device'} Down: $rh_p->{'down'} Ph $rh_p->{'phantom'} Channel: $rh_p->{'channel'} \n"; + next if ($rh_p->{'phantom'}); # does not count as down + next if (!$rh_p->{'channel'}); # probably oversubscribed or misconfigured + $ports_still_down++ + if ($rh_p->{'down'}); + } + print "ports down: $ports_still_down "; + $rh_response = json_post("/cli-json/nc_show_ports", $rh_update); + sleep 4; +} +sleep 4; +my $port_uri = "/port/1/$des_resource?fields=_links,device,alias,port"; +# ports down +my $set_port = "/cli-json/set_port"; +logg("\nsetting ports down: "); +$rh = json_request($port_uri); +flatten_list($rh, 'interfaces'); +@links2 = (); +for my $k (keys %{$rh->{"flat_list"}}) { + print "\n -- 200 ---------------------------------------------------------------\n"; + print Dumper($rh->{"flat_list"}->{$k}); + print "\n -- 200 ---------------------------------------------------------------\n"; + my $Link = $rh->{"flat_list"}->{$k}->{"_links"}; + print "LINK $Link\n"; + push(@links2, $Link); +} + +for my $port_uri (@links2) { + print "URI: $port_uri\n"; + $rh = json_request($port_uri); + my $device = get_thru('interface', 'device', $rh); + next if ($device !~ /^sta/); + logg($device." "); + my $port = get_thru('interface', 'port', $rh); + my @hunks = split(/\./, $port); + my $resource = $hunks[1]; + my %post = ( + #'suppress_preexec_cli'=>'false', + #'suppress_preexec_method'=>'false', + #'suppress_postexec_cli'=>'false', + #'suppress_postexec_method'=>'false', + "shelf" => 1, + "resource" => 0+$resource, + "port" => $device, + 'suppress_postexec_cli'=>'true', + "current_flags" => 1, + "interest" => 8388610 + ); + my $rh_response = json_post($set_port, \%post); + usleep(10000); +} +logg(" updating "); +$rh_response = json_post("/cli-json/nc_show_ports", $rh_update); +sleep 1; +logg("\nsetting ports up "); +for my $port_uri (@links2) { + $rh = json_request($port_uri); + my $device = get_thru('interface', 'device', $rh); + next if ($device !~ /^sta/); + logg($device." "); + my $port = get_thru('interface', 'port', $rh); + my @hunks = split(/\./, $port); + my $resource = $hunks[1]; + # 'shelf=1&resource=2&port=vap2000&cmd_flags=0¤t_flags=0&interest=8388610' + my %post = ( + #'suppress_preexec_cli'=>'false', + #'suppress_preexec_method'=>'false', + #'suppress_postexec_cli'=>'false', + #'suppress_postexec_method'=>'false', + "shelf" => 1, + "resource" => 0+$resource, + "port" => $device, + 'suppress_postexec_cli'=>'true', + "current_flags" => 0, + "interest" => 8388610 + ); + my $rh_response = json_post($set_port, \%post); + usleep(10000); +} +logg(" updating "); +$rh_response = json_post("/cli-json/nc_show_ports", $rh_update); +# diff --git a/lanforge/lanforge-scripts/json/port_toggle_test.pl b/lanforge/lanforge-scripts/json/port_toggle_test.pl new file mode 100755 index 000000000..3eecf9485 --- /dev/null +++ b/lanforge/lanforge-scripts/json/port_toggle_test.pl @@ -0,0 +1,206 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use Time::HiRes qw(usleep); +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use JSON; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 +); + + +my $do_destroy = 0; + +## +## M A I N +## + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port, +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + +my $uri = "/shelf/1"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'resources'); +my @links2= (); +my $ra_alias_links = []; +# TODO: make this a JsonUtils::list_ports() +for $uri (@$ra_links) { + $uri =~ s{/resource}{/port}g; + $uri .= "/list"; + #logg("requesting $uri"); + $rh = json_request($uri); + push( @$ra_alias_links, @{get_port_names($rh, 'interfaces')}); + push(@links2, @{get_links_from($rh, 'interfaces')}); + #logg("\nfound: "); + #logg(@links2); +} + +# destroy stations on resource 3, 7, 8 +my @radios = (); +my @destroy_me = (); +for my $rh_alias_link (@$ra_alias_links) { + push(@destroy_me, $rh_alias_link) + if (($rh_alias_link->{'uri'} =~m{^/port/1/[3678]/}) + && ($rh_alias_link->{'alias'} =~m{^sta})); + push(@radios, $rh_alias_link) + if (($rh_alias_link->{'uri'} =~m{^/port/1/[3678]/}) + && ($rh_alias_link->{'alias'} =~m{^wiphy})); +} + +if ($do_destroy) { + logg("\nDestroying these: "); + for my $rh_target (@destroy_me) { + my $alias = $rh_target->{'alias'}; + my @hunks = split(/[\/]/, $rh_target->{'uri'}); + + # TODO: create JsonUtils::rm_vlan($eid, $alias) + my $rh_data = { + 'shelf'=>1, 'resource'=>$hunks[3], 'port'=>$alias + }; + logg(" $alias"); + json_post("/cli-json/rm_vlan", $rh_data); + usleep(150); + } + +# this really should poll for ports to wait for them to disappear + sleep 2; +} +my @new_stations = (); +my $rh_radio; +my $radio_name; +my $resource; +my $range; +my $radio_num; +my @stations=(); +logg("\nCreating new stations on these: ") if ($do_destroy); +for $rh_radio (@radios) { + $radio_name = $rh_radio->{'alias'}; + my @hunks = split(/[\/]/, $rh_radio->{'uri'}); + ($radio_num) = $radio_name =~ /wiphy(\d+)/; + $resource = $hunks[3]; + $range = ($resource * 1000) + ($radio_num * 100); + #logg("\n/cli-json/add_sta = "); + for (my $i = $range; $i < ($range+20); $i++) { + if ($resource == 3 || $resource >= 6) { + push(@stations, { 'resource'=>$resource, 'station'=> "sta$i"}); + } + if ($do_destroy) { + # TODO: create JsonUtils::add_sta($eid, $alias...) + my $rh_data = { + 'shelf'=>1, + 'resource'=>$resource, + 'radio'=>$radio_name, + 'sta_name'=>"sta$i", + 'flags'=>68862086144, # has port-down set + 'ssid'=>'idtest-1200-wpa2', + 'key'=>'idtest-1200-wpa2', + 'mac'=>'xx:xx:xx:xx:*:xx', + 'mode'=>0, + 'rate'=>'DEFAULT' + }; + logg(" sta$i"); + json_post("/cli-json/add_sta", $rh_data); + usleep(210); + } + } +} +if ($do_destroy) { + sleep 1; + logg("\nSetting dhcp: "); + for my $rh_station (@stations) { + # this sets dhcp + logg(" $rh_station->{'station'}"); + + my $rh_data = { + 'shelf'=>1, + 'resource'=>$rh_station->{'resource'}, + 'port'=>$rh_station->{'station'}, + 'cmd_flags'=>0, + 'current_flags'=>2147483648, + 'interest'=>16386 + }; + # TODO: create JsonUtils::set_dhcp($eid, $alias, $on_off) + json_post("/cli-json/set_port", $rh_data); + usleep(210); + } + sleep 1; +} + +my $set_port = "/cli-json/set_port"; +logg("\nsetting ports up "); + +for my $rh_station (@stations) { + logg(" $rh_station->{'station'}"); + my %post = ( + "shelf" => 1, "resource" => 0+$rh_station->{'resource'}, + "port" => $rh_station->{"station"}, + "current_flags" => 0, "interest" => 8388610 + ); + my $rh_response = json_post($set_port, \%post); +} + +sleep 1; +my $number_down = scalar @stations; +logg("\nwaiting to raise $number_down "); +while($number_down > 0) { + $number_down = scalar @stations; + for my $rh_station (@stations) { + my $url = "/port/1/$rh_station->{'resource'}/$rh_station->{'station'}?fields=device,down,ip"; + my $rh_obj = json_request($url); + if ($rh_obj->{'interface'}->{'ip'} ne "0.0.0.0") { + $number_down--; + } + #logg(" $number_down ".$rh_station->{'station'}); + } + logg(" ".((scalar @stations) - $number_down)." up,"); + last if ($number_down < 2); + sleep 3; + my $rh_response = json_post("/cli-json/nc_show_ports", {"shelf"=>1,"resource"=>"all","port"=>"all"}); +} + +# ports down +sleep 3; +logg("\nsetting ports down: "); +for my $rh_station (@stations) { + logg(" $rh_station->{'station'}"); + my %post = ( + "shelf" => 1, "resource" => 0+$rh_station->{'resource'}, + "port" => $rh_station->{"station"}, + "current_flags" => 1, "interest" => 8388610 + ); + my $rh_response = json_post($set_port, \%post); +} + +# diff --git a/lanforge/lanforge-scripts/json/set_port_aliases.pl b/lanforge/lanforge-scripts/json/set_port_aliases.pl new file mode 100755 index 000000000..bb8dda3be --- /dev/null +++ b/lanforge/lanforge-scripts/json/set_port_aliases.pl @@ -0,0 +1,183 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +use Time::HiRes qw(usleep); +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use JSON; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names flatten_list); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 +); + + +## +## M A I N +## + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port, +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + +my $uri = "/shelf/1"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'resources'); +my @links2= (); +my $ra_alias_links = []; + + +#print Dumper($rh_stations); + +#sub flatten_list { +# my $rh_list = shift; +# my $list_name = shift; +# my $rh_irefs = {}; +# for (my $i=0; $i < @{$rh_stations->{"interfaces"}}; $i++) { +# my @k = keys(%{$rh_stations->{"interfaces"}[$i]}); +# my $id = $k[0]; +# #print "ID[$id]\n"; +# $rh_irefs->{$id} = $rh_stations->{"interfaces"}[$i]->{$id}; +# } +# $rh_list->{"flat_list"} = $rh_irefs; +#} + +# TODO: make this a JsonUtils::list_ports() +for $uri (@$ra_links) { + $uri =~ s{/resource}{/port}g; + $uri .= "/list"; + #logg("requesting $uri"); + $rh = json_request($uri); + #print Dumper($rh); + push( @$ra_alias_links, @{get_port_names($rh, 'interfaces')}); + push(@links2, @{get_links_from($rh, 'interfaces')}); + #logg("\nfound: "); + #logg(@links2); +} +#print Dumper($ra_alias_links); + +# destroy stations on resource 3 +my @radios = (); +for my $rh_alias_link (@$ra_alias_links) { + push(@radios, $rh_alias_link) + if (($rh_alias_link->{'uri'} =~m{^/port/1/[3]/}) + && ($rh_alias_link->{'alias'} =~m{^wiphy})); +} +logg(" updating "); +my $rh_update = { + 'shelf'=>1, 'resource'=>'all', 'port'=>'all', 'probe_flags'=>'0x1' +}; +my $rh_response = json_post("/cli-json/nc_show_ports", $rh_update); + +# this really should poll for ports to wait for them to disappear +sleep 2; + +my @new_stations = (); +logg("\nCreating new stations on these: "); +#print Dumper(\@radios); +my $rh_radio; +my $radio_name; +my $resource; +my $range; +my $num_sta = 160; +my $radio_num; +my $radio_counter = 0; + + +logg(" updating "); +$rh_response = json_post("/cli-json/nc_show_ports", $rh_update); +sleep 2; +$radio_counter = 0; +for $rh_radio (@radios) { + $radio_name = $rh_radio->{'alias'}; + my @hunks = split(/[\/]/, $rh_radio->{'uri'}); + ($radio_num) = $radio_name =~ /wiphy(\d+)/; + $resource = $hunks[3]; + $range = ($resource * 10000) + ($radio_num * 1000); + + my $rh_stations = json_request("/port/1/$resource/all?fields=port,device,mac"); + flatten_list($rh_stations, "interfaces"); + + for (my $i = $range; $i < ($range+$num_sta); $i++) { + print "sta$radio_counter = vsta$i [ $range .. ".($range+$num_sta)."] 1/$resource/$radio_num $radio_name \n"; + my $portname = "sta$radio_counter"; + my $eidport = "1.$resource.$portname"; + if (defined $rh_stations->{"flat_list"}->{$eidport}) { + #my $macaddr = $rh_stations->{"flat_list"}->{$eidport}->{"mac"}; + my $eid = $rh_stations->{"flat_list"}->{$eidport}->{"port"}; + my @hunks = split(/[.]/, $eid); + die("port eid is bad: $eid") + if (!defined $hunks[2]); + my $portid = 0 + $hunks[2]; + my $rh_data = { + 'shelf'=>1, + 'resource'=>$resource, + 'port'=>$portname, + 'interest'=>4096, + #'suppress_preexec_cli'=>'true', + #'suppress_preexec_method'=>'true', + #'suppress_postexec_cli'=>'false', + #'suppress_postexec_method'=>'false', + 'alias'=>'vsta'.$i + }; # todo: set_alias + $rh_response = json_post("/cli-json/set_port", $rh_data); + #usleep(10000); + + # set port up + dhcp + $rh_data = { + #'suppress_preexec_cli'=>'true', + #'suppress_preexec_method'=>'true', + #'suppress_postexec_cli'=>'false', + #'suppress_postexec_method'=>'false', + 'shelf'=>1, + 'resource'=>$resource, + 'port'=>'sta'.$radio_counter, + 'cmd_flags'=>0, + 'current_flags'=>2147483648, + 'interest'=>16386 + }; + # TODO: create JsonUtils::set_dhcp($eid, $alias, $on_off) + # set_port - port up, enable dhcp + # current_flags=2147483648&interest=16386 + my $rh_response = json_post("/cli-json/set_port", $rh_data); + $radio_counter+=1; + usleep(10000); + } + else { + print " 1.$resource.sta$radio_counter not found "; + } + } +} +logg(" updating "); +$rh_response = json_post("/cli-json/nc_show_ports", $rh_update); +sleep 2; + +# diff --git a/lanforge/lanforge-scripts/json/show-chamber.sh b/lanforge/lanforge-scripts/json/show-chamber.sh new file mode 100755 index 000000000..68ac458f4 --- /dev/null +++ b/lanforge/lanforge-scripts/json/show-chamber.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# this is a test of the JSON add/show chamber api +echo 'name=cha&flags=OPEN' > /tmp/curl_data +curl -sqv -H 'Accept: application/json' -X POST -d '@/tmp/curl_data' http://cholla5:8080/cli-form/add_chamber + +echo 'name=cha' > /tmp/curl_data +curl -sqv -H 'Accept: application/json' -X POST -d '@/tmp/curl_data' http://cholla5:8080/cli-form/show_chamber + +#chamber [id] [angle] [flags] [table-speed-rpm] +#echo "cmd=chamber&arg1=cha&arg2=0&arg3=OPEN&arg5=3" > /tmp/curl_data +echo 'cmd=chamber&arg1=cha&arg2=0&arg3=open&arg5=3' +curl -sqv -H 'Accept: application/json' -X POST -d '@/tmp/curl_data' http://cholla5:8080/cli-form/admin + diff --git a/lanforge/lanforge-scripts/json/test_sta_mode.pl b/lanforge/lanforge-scripts/json/test_sta_mode.pl new file mode 100755 index 000000000..e284dd9eb --- /dev/null +++ b/lanforge/lanforge-scripts/json/test_sta_mode.pl @@ -0,0 +1,62 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use JSON; +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; +use LANforge::JsonUtils qw(err logg xpand json_request); + + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; + +my $usage = qq($0: --host [hostname] # hostname or IP to query + --port [port] # port like 8080 +); + +## +## M A I N +## +GetOptions +( + 'port=i' => \$::Port, + 'host=s' => \$::Host, +) || (print($usage) && exit(1)); + +$HostUri = "http://$Host:$Port"; + +my $uri = "/port/1/1/list"; +my $rh = json_request($uri); +#print Dumper($rh->{interfaces}); +for my $rh_e (@{$rh->{interfaces}}) { + my @keys = keys(%$rh_e); + my $rh_val = $rh_e->{$keys[0]}; + next if ($keys[0] !~ /sta/); + my $resp = json_request($rh_val->{_links}."?fields=alias,port,mode"); + print Dumper($resp->{interface}); + sleep 0.1; +} + +# diff --git a/lanforge/lanforge-scripts/json/vap_stations_example.pl b/lanforge/lanforge-scripts/json/vap_stations_example.pl new file mode 100755 index 000000000..30c31002d --- /dev/null +++ b/lanforge/lanforge-scripts/json/vap_stations_example.pl @@ -0,0 +1,70 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use Getopt::Long; +use JSON::XS; +use HTTP::Request; +use LWP; +use LWP::UserAgent; +use Data::Dumper; +use Time::HiRes qw(usleep); +use JSON; +use lib '/home/lanforge/scripts'; +use LANforge::JsonUtils qw(logg err json_request get_links_from get_thru json_post get_port_names flatten_list); + +package main; +# Default values for ye ole cmd-line args. +our $Resource = 1; +our $quiet = "yes"; +our $Host = "localhost"; +our $Port = 8080; +our $HostUri = "http://$Host:$Port"; +our $Web = LWP::UserAgent->new; +our $Decoder = JSON->new->utf8; +our $ssid; +our $security; +our $passphrase; + +my $usage = qq("$0 --host {ip or hostname} # connect to this + --port {port number} # defaults to 8080 +); + + +my $des_resource = 1; + +GetOptions +( + 'host=s' => \$::Host, + 'port=i' => \$::Port, + 'resource=i' => \$des_resource +) || (print($usage) && exit(1)); + +$::HostUri = "http://$Host:$Port"; + +my $uri = "/stations/list"; +my $rh = json_request($uri); +my $ra_links = get_links_from($rh, 'stations'); +# print(Dumper($ra_links)); + +my @attribs = ("ap", "signal", "tx rate", "rx rate", "capabilities"); +for my $sta_uri (@$ra_links) { + my $with_fields = "$sta_uri?fields=station+bssid,capabilities,rx+rate,tx+rate,signal,ap"; + $rh = json_request($with_fields); + #print(Dumper($rh)); + #print(Dumper($rh->{station})); + print("Station ", $rh->{station}->{'station bssid'}, "\n"); + for my $k (@attribs) { + print(" $k: ".$rh->{station}->{$k}."\n"); + } + print("\n"); +} + + + diff --git a/lanforge/lanforge-scripts/l3_vid_group.pl b/lanforge/lanforge-scripts/l3_vid_group.pl new file mode 100755 index 000000000..442b21e4f --- /dev/null +++ b/lanforge/lanforge-scripts/l3_vid_group.pl @@ -0,0 +1,454 @@ +#!/usr/bin/perl -w +=pod +Use this script to create a large number of L3 connections for emulating video +traffic. This script is going to assume that all the connections are going to +use the same traffic type and same traffic speed. This test will collect all the +L3 connections into a test group. +=cut + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; +use Data::Dumper; +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +our $quiet = "yes"; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; +our $resource = 1; + +our $action = ""; +our $vid_mode = "yt-sdr-1080p30"; +our $buffer_size = (3 * 1024 * 1024); +our $clear_group = -1; +my $cmd; +our $cx_name = ""; +our $upstream = ""; + +our $endp_type = "tcp"; +our $first_sta = ""; +my $log_cli = "unset"; # use ENV{LOG_CLI} elsewhere +our $num_cx = -1; +my $show_help = 0; +our $speed = 1000 * 1000 * 1000; +our $generic_test_grp; # we will manage our generic connections using this group (l3_video_em) +our $l3_test_grp; # the actual Layer 3 cx will live here, starting with _L3_ +our $use_ports_str = "NA"; +our $use_speeds_str = "NA"; +our $use_max_speeds = "NA"; + +our $usage = <<"__EndOfUsage__"; +Usage: $0 # create a large group of Layer 3 creations that emulate video traffic + --action -a { create | destroy | start | stop } + --buffer_size -b {bytes K|M} # size of emulated RX buffer, default 3MB + --clear_group -z # empty test group first + --cx_name -c {connection prefix} + --endp_type -t {tcp|udp|lf_tcp|lf_udp} + --first_sta -i {name} + --log_cli {1|filename} # log cli commands + --mgr -m {lanforge server} # default localhost + --mgr_port -p {lanforge port} # default 4002 + --num_cx -n {number} # default 1 + --resource -r {station resource} + --speed -s {bps K|M|G} # maximum speed of tx side, default 1Gbps + --stream --vid_mode -e {stream resolution name|list} # default yt-sdr-1080p30 + # list of streams maintained in l3_video_em.pl + --test_grp -g {test group name} # all connections placed in this group + # default is {cx_name}_tg for the Generic connections + # we manage Layer 3 connections in _L3_{cx_name}_tg + --upstream -u {port short-EID} # video transmitter port; + # use 1.1.eth1 or 1.2.br0 for example + # upstream port does not need to be on same resource +Examples: +# create 30 stations emulating 720p HDR 60fps transmitted from resource 2: + $0 --action create --buffer_size 8M --clear_group --cx_name yt1080p60.1 \\ + --endp_type udp --first_sta sta0000 --num_cx 30 \\ + --resource 2 --speed 200M --stream yt-hdr-720p60 --test_group yt60fps \\ + --upstream 1.2.br0 + +# start test group: + $0 -a start -g yt60fps + +# stop test group: + $0 -a stop -g yt60fps + +# add 30 more stations on resource 3 to group + $0 -a create -b 8M -c yt1080p60.3 -t udp -i sta0100 -n 30 -r 3 \\ + -s 200M -e yt-hdr-720p60 -g yt60fps -u 1.2.br0 + +# destroy test group + $0 -a destroy -g yt60fps +__EndOfUsage__ + +if (@ARGV < 2) { + print $usage; + exit 0; +} +our $debug = 0; +GetOptions +( + 'action|a=s' => \$::action, + 'buffer_size|b=s' => \$::buffer_size, + 'clear_group|z' => \$::clear_group, + 'cx_name|c=s' => \$::cx_name, + 'debug|d' => \$::debug, + 'endp_type|type|t=s' => \$::endp_type, + 'first_sta|i=s' => \$::first_sta, + 'help|h' => \$show_help, + + 'log_cli=s{0,1}' => \$log_cli, + 'manager|mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p=i' => \$::lfmgr_port, + 'num_cx|n=i' => \$::num_cx, + 'quiet|q=s' => \$::quiet, + 'resource|r=i' => \$::resource, + 'speed|s=i' => \$::speed, + 'stream|vid_mode|e' => \$::vid_mode, + 'test_group|test_grp|group|g=s' => \$::generic_test_grp, + 'upstream|u=s' => \$::upstream, + +) || die($::usage); + +if ($show_help) { + print $usage; + exit 0; +} + +if ($::debug) { + $ENV{DEBUG} = 1 if (!(defined $ENV{DEBUG})); +} + +if ($::quiet eq "0") { + $::quiet = "no"; +} +elsif ($::quiet eq "1") { + $::quiet = "yes"; +} + +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } +} + +our $utils = new LANforge::Utils; + +$::utils->connect($lfmgr_host, $lfmgr_port); +#$::utils->doCmd("log_level 8"); + +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ +# M A I N +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ + +# Apply defaults + +if (!(defined $::generic_test_grp) || ("" eq $::generic_test_grp) || ("NA" eq $::generic_test_grp)) { + # use cx_name as prefix + if (!(defined $::cx_name) || ("" eq $::cx_name) || ("NA" eq $::cx_name)) { + die("No test_grp or cx_name is defined. Bye."); + } + $::generic_test_grp = $::cx_name ."_tg"; +} +$::l3_test_grp = "_L3_".$::generic_test_grp; + +# get a list of test groups +my $ra_tg_list = $::utils->test_groups(); +print Dumper($ra_tg_list) if ($::debug); + +my $ra_l3_cx_names = $::utils->group_items($::l3_test_grp); +my $ra_generic_cx_names = $::utils->group_items($::generic_test_grp); + + + +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ +if (($::clear_group > 0) || ($::action eq "destroy")) { + if (@$ra_tg_list < 1) { + print "No test groups defined, bye."; + exit(1); + } + + my @matches = grep {/^$::generic_test_grp$/} @$ra_tg_list; + + print Dumper(\@matches) if ($::debug); + if (@matches < 1) { + print "No test group matching name [$::generic_test_grp], bye."; + exit(1); + } + print "will clear groups $::generic_test_grp and $::l3_test_grp\n"; + $::utils->doCmd("clear_group $::generic_test_grp"); + $::utils->doCmd("clear_group $::l3_test_grp"); +} + +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ +if ($::action eq "create") { + my $re = q(^TestGroup name:\s+).$::generic_test_grp.q(\s+[\[]); + my @matches = grep {/$re/} @$ra_tg_list; + print Dumper(\@matches) if ($::debug); + if (@matches < 1) { + print "Creating test group [$::generic_test_grp]..."; + $::utils->doCmd($::utils->fmt_cmd("add_group", $::generic_test_grp)); + print "Creating test group [$::l3_test_grp]..."; + $::utils->doCmd($::utils->fmt_cmd("add_group", $::l3_test_grp)); + } + + if (!(defined $::cx_name) or ("" eq $::cx_name)) { + $::cx_name = $::generic_test_grp."-"; + } + if (!(defined $::buffer_size) or ($::buffer_size < 0)) { + print ("Please set --buffer_size, bye."); + exit(1); + } + if (!(defined $::endp_type) or ("" eq $::endp_type)) { + print ("Please set --endp_type {tcp|udp}"); + } + elsif ($::endp_type eq "tcp") { + $::endp_type = "lf_tcp"; + } + elsif ($::endp_type eq "udp") { + $::endp_type = "lf_udp"; + } + if ($::num_cx < 1) { + print "How many connections should we create? --num_cx 1? Bye.\n"; + exit(1); + } + if (!(defined $::first_sta) or ("" eq $::first_sta)) { + print "Please set first station name: --first_sta 200; bye."; + exit(1); + } + if (!(defined $::upstream) or ("" eq $::upstream)) { + print "Please set your upstream port: --upstream 1.1.eth1; bye."; + exit(1); + } + elsif ($::upstream !~ /^1.\d+\.\S+$/) { + print "Upstream port should be named 1..\n EG: --upstream 1.1.eth1\nbye."; + exit(1); + } + + if ( ! -x "./lf_firemod.pl" ) { + print "I don't see ./lf_firemod.pl, bye."; + exit(1); + } + my $upstream_resource = $::resource; + my $rh_eid_map = $::utils->get_eid_map($::resource); + die("Unable to find keys in rh_eid_map") if ((keys(%$rh_eid_map)) < 1); + + my $rh_upstream_map = $rh_eid_map; + if ($::upstream !~ /^1\.$::resource\.\S+$/) { + $upstream_resource = (split('.', $::upstream))[1]; + if (!(defined $upstream_resource) || ("" eq $upstream_resource)) { + die("Problem with upstream resource name"); + } + $rh_upstream_map = $::utils->get_eid_map($upstream_resource); + } + #print Dumper($rh_eid_map); + + # build a list of ports -n ports long starting at -first_port + my @ports = (); + my $rh_first_dev = $::utils->find_by_name($rh_eid_map, $::first_sta); + die("Unable to find dev record for port $::first_sta on resource $::resource") + if ($rh_first_dev == -1); + my $parent_name = $rh_first_dev->{parent}; + die("Unable to find parent of $::first_sta, bye.") + if (!(defined $parent_name)); + my $ra_interfaces = $::utils->ports_on_radio($rh_eid_map, $parent_name); + while (@$ra_interfaces < $::num_cx) { + # hack wiphy names + my ($wi, $nu) = $parent_name =~ /^([a-z]+)(\d+)$/; + $nu = 1 + $nu; + $parent_name = "${wi}${nu}"; + my $ra_more = $::utils->ports_on_radio($rh_eid_map, $parent_name); + push(@$ra_interfaces, @$ra_more); + } + die("Unable to find any subinterfaces of $parent_name") + if (@$ra_interfaces < 1); + + # want a pattern that matches Qvlan and Mvlan patterns, not just stations + # things like eth1.3 or rd0#0 or r0b#0 + my ($prefix) = $::first_sta =~ /^(.*?[^0-9]+)\d+$/i; + #print "PREFIX IS $prefix\n"; + my @selected_list = (); + + foreach my $iface (sort @$ra_interfaces) { + #print "iface[$iface] "; + next if ($iface !~ /^$prefix/); + push(@selected_list, $iface); + last if (@selected_list >= $::num_cx); + } + if (@selected_list != $::num_cx) { + my $a = @selected_list; + print "Number of interfaces($a) does not match number of connections($::num_cx).\n" + ." You probably don't have as many interfaces as you think.\n"; + sleep 5; + } + + my @next_cmds = (); + for (my $i=0; $i < @selected_list; $i++) { + my $j = 10000 + $i; + my $cname = "_".$::cx_name . substr("$j", 1); + my $ports = join('.', 1, $::resource, $selected_list[$i]).",".$::upstream; + + #print "Connection name $name uses $ports\n"; + my $cmd = qq(/home/lanforge/scripts/lf_firemod.pl --mgr $::lfmgr_host --mgr_port $::lfmgr_port ) + .qq(--action create_cx --cx_name $cname --endp_type $::endp_type ) + .qq(--use_ports $ports --use_speeds 0,0 --report_timer 3000); + #print "CMD: $cmd\n"; + `$cmd`; + push(@next_cmds, "set_endp_flag $cname-A AutoHelper 1"); + push(@next_cmds, "set_endp_flag $cname-B AutoHelper 1"); + push(@next_cmds, $::utils->fmt_cmd("add_tgcx", $::l3_test_grp, $cname)); + } + sleep 1; + $::utils->doAsyncCmd("nc_show_endpoints all"); # this helps prepare us for adding next generic connections + print "adding L3 CX to $::l3_test_grp "; + for my $cmd (@next_cmds) { + print "."; + $::utils->doCmd($cmd); + } + print "done\n"; + print "Creating Generic connections for video emulation "; + $::utils->sleep_ms(20); + @next_cmds = (); + for (my $i=0; $i < @selected_list; $i++) { + my $j = 10000 + $i; + my $cname = "_".$::cx_name . substr("$j", 1); + my $ports = join('.', 1, $::resource, $selected_list[$i]).",".$::upstream; + + my $gname = $::cx_name . substr("$j", 1); + my $gnr_cmd = qq(/home/lanforge/scripts/l3_video_em.pl --mgr $::lfmgr_host --mgr_port $::lfmgr_port ) + .qq(--cx_name $cname --max_tx 1G --buf_size $::buffer_size ) + .qq(--stream $::vid_mode --quiet yes ); + + $cmd = qq(./lf_firemod.pl --mgr $::lfmgr_host --mgr_port $::lfmgr_port) + .qq( --action create_endp --endp_name $gname --endp_type 'generic') + .qq( --port_name ).$selected_list[$i] + .q( --endp_cmd ").$gnr_cmd.q("); + `$cmd`; + print "."; + $::utils->sleep_ms(20); + #print "adding CX_$gname to $::generic_test_grp\n"; + push(@next_cmds, $::utils->fmt_cmd("add_tgcx", $::generic_test_grp, "CX_".$gname)); + } + print "done\n"; + sleep 1; + $::utils->doAsyncCmd("nc_show_endpoints all"); # this helps prepare us for adding next generic connections + print "Adding generic connections to $::generic_test_grp "; + for my $cmd (@next_cmds) { + print "."; + $::utils->doCmd($cmd); + } + print "done\n"; + + exit 0; +} +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ +if ($::action eq "destroy") { + my @cmds = (); + if (@$ra_generic_cx_names < 1) { + print "No layer 3 connections in group [$::generic_test_grp], bye\n"; + } + else { + print "Removing generic connections "; + foreach my $cx_name (@$ra_generic_cx_names) { + push(@cmds, "rm_cx default_tm ".$cx_name); + } + foreach my $cx_name (@$ra_generic_cx_names) { + $cx_name =~ s/^CX_/D_/; + push(@cmds, "rm_endp ".$cx_name); + $cx_name =~ s/^D_//; + push(@cmds, "rm_endp ".$cx_name); + } + foreach my $cmd (@cmds) { + print "."; + $::utils->doCmd($cmd); + $::utils->sleep_ms(30); + } + print "done\n"; + } + $::utils->doCmd("rm_group $::generic_test_grp"); + + if (@$ra_l3_cx_names < 1) { + print "No layer 3 connections in group [$::l3_test_grp], bye\n"; + } + else { + print "Removing L3 endpoints "; + @cmds = (); + foreach my $cx_name (@$ra_l3_cx_names) { + push(@cmds, "rm_cx default_tm ".$cx_name); + } + foreach my $cx_name (@$ra_l3_cx_names) { + push(@cmds, "rm_endp ${cx_name}-A"); + push(@cmds, "rm_endp ${cx_name}-B"); + } + foreach my $cmd (@cmds) { + print "."; + $::utils->doCmd($cmd); + $::utils->sleep_ms(30); + } + print "done\n"; + } + $::utils->doCmd("rm_group $::l3_test_grp"); + exit 0; +} +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ +if ($::action eq "start") { + if (!(defined $::generic_test_grp) || ("" eq $::generic_test_grp)) { + print "Please specify test group to start: --test_grp foo; bye."; + exit(1); + } + + # collect all cx names in the test group and start up the + # video pulser on them + print "Starting connections..."; + $::utils->doCmd("start_group $::l3_test_grp"); + sleep 1; + $::utils->doCmd("start_group $::generic_test_grp"); + + exit 0; +} +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ +if ($::action eq "stop") { + if (!(defined $::generic_test_grp) || ("" eq $::generic_test_grp)) { + print "Please specify test group to stop: --test_grp foo; bye."; + exit(1); + } + + # collect all cx names in the test group and start up the + # video pulser on them + print "Stopping connections..."; + $::utils->doCmd("stop_group $::generic_test_grp"); + sleep 1; + $::utils->doCmd("stop_group $::l3_test_grp"); + exit 0; +} +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ +else { + die "What kind of action is [$::action]?"; +} + +# ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ +#END { +# if (defined $::utils->{telnet}) { +# print STDERR "reducing log level"; +# $::utils->doCmd("log_level 2"); +# } +#} \ No newline at end of file diff --git a/lanforge/lanforge-scripts/l3_video_em.pl b/lanforge/lanforge-scripts/l3_video_em.pl new file mode 100755 index 000000000..c97844b07 --- /dev/null +++ b/lanforge/lanforge-scripts/l3_video_em.pl @@ -0,0 +1,913 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +use Data::Dumper; +use POSIX; +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; +use Time::HiRes qw(usleep gettimeofday); +our $has_usleep = (defined &usleep) ? 1 : 0; + +my $NA ='NA'; +our $resource = 1; +our $upstream_res = 1; +our $quiet = "yes"; +our $silent = 0; +our $endp_name = ""; +our $speed = "-1"; +our $action = ""; +our $do_cmd = "NA"; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; +our $tx_style = ""; +our $cx_name = ""; +our $tx_side = "B"; +our $min_tx = 0; +our $max_tx = 920 * 1024 * 1024; # 920Mbps default +our $buf_size = 3 * 1024 * 1024; # 3MB default +our $log_cli = "unset"; # do not set to 0, it turns into logfile "./0" +our $stream_key = undef; +our $quit_when_const = 0; +our $sta = ""; +our $upstream = ""; +our $proto = "udp"; # for constant +our $est_fill_time_sec = 0; +our $last_fill_time_sec = 0; +our $begin_running = 1; # set to 0 to not start CX running; 0 is appropriate for batch creation or bufferfill + +# https://en.wikipedia.org/wiki/Standard-definition_television +# https://www.adobe.com/devnet/adobe-media-server/articles/dynstream_live/popup.html +# https://en.wikipedia.org/wiki/ISDB-T_International +# https://en.wikipedia.org/wiki/Frame_rate +# https://en.wikipedia.org/wiki/List_of_broadcast_video_formats +# https://blog.forret.com/2006/09/27/hd-720p-1080i-and-1080p/ +# Framerate is highly subjective in digital formats, because there are +# variable frame rates dictated by min- and max-frame rate. +our %stream_keys = ( + 'w' => 0, + 'width' => 0, + 'x' => 0, + 'h' => 1, + 'height' => 1, + 'y' => 1, + 'i' => 2, + 'interlaced' => 2, + 'audio' => 3, + 'audio_bps' => 3, + 'video' => 4, + 'video_bps' => 4, + 'stream' => 5, + 'stream_bps' => 5, + 'fps' => 6, + 'frames' => 6, + 'framerate' => 6, + ); + +our %avail_stream_res = ( + # nicname w, h, interlaced, audio, vid bps, tt bps framerate + "sqvga-4:3" => [ 160, 120, 0, 16000, 32000, 48000, 30], + "sqvga-16:9" => [ 160, 90, 0, 16000, 32000, 48000, 30], + "qvga-4:3" => [ 320, 240, 0, 16000, 32000, 48000, 30], + "qvga-16:9" => [ 320, 180, 0, 16000, 32000, 48000, 30], + "qcif-48k-4:3" => [ 144, 108, 0, 16000, 32000, 48000, 30], + "qcif-48k-16:9" => [ 192, 108, 0, 16000, 32000, 48000, 30], + "qcif-96k-4:3" => [ 192, 144, 0, 16000, 80000, 96000, 30], + "qcif-96k-16:9" => [ 256, 144, 0, 16000, 80000, 96000, 30], + "cif" => [ 352, 288, 0, 32000, 268000, 300000, 30], + "cif-300k-4:3" => [ 288, 216, 0, 32000, 268000, 300000, 30], + "cif-300k-16:9" => [ 384, 216, 0, 32000, 268000, 300000, 30], + "cif-500k-4:3" => [ 320, 240, 0, 32000, 468000, 500000, 30], + "cif-500k-16:9" => [ 384, 216, 0, 32000, 468000, 500000, 30], + "d1-800k-4:3" => [ 640, 480, 0, 32000, 768000, 800000, 30], + "d1-800k-16:9" => [ 852, 480, 0, 32000, 768000, 800000, 30], + "d1-1200k-4:3" => [ 640, 480, 0, 32000, 1168000, 1200000, 30], + "d1-1200k-16:9" => [ 852, 480, 0, 32000, 1168000, 1200000, 30], + "hd-1800k-16:9" => [ 1280, 720, 0, 64000, 1736000, 1800000, 59.94], + "hd-2400k-16:9" => [ 1280, 720, 0, 64000, 2272000, 2336000, 59.94], + + "108p4:3" => [ 144, 108, 0, 16000, 32000, 48000, 30], + "144p16:9" => [ 192, 144, 0, 16000, 80000, 96000, 30], + "216p4:3" => [ 288, 216, 0, 32000, 268000, 300000, 30], + "216p16:9" => [ 384, 216, 0, 32000, 268000, 300000, 30], + "240p4:3" => [ 320, 240, 0, 32000, 468000, 500000, 30], + + "360p4:3" => [ 480, 360, 0, 32000, 768000, 800000, 30], + "480i4:3" => [ 640, 480, 1, 32000, 768000, 800000, 30], + "480p4:3" => [ 640, 480, 0, 32000, 768000, 800000, 30], + "480p16:9" => [ 852, 480, 0, 32000, 1168000, 1200000, 30], + + # unadopted standard + #"720i" => [ 1280, 720, 1, 64000, 1736000, 1800000, 30], + # 0.92 megapixels, 2.76MB per frame + "720p" => [ 1280, 720, 0, 64000, 1736000, 1800000, 59.94], + + # https://support.google.com/youtube/answer/1722171?hl=en + # h.264 stream rates, SDR quality + "yt-sdr-360p30" => [ 640, 360, 0, 128000, 1000000, 1128000, 30], + "yt-sdr-480p30" => [ 852, 480, 0, 128000, 2500000, 2628000, 30], + "yt-sdr-720p30" => [ 1280, 720, 0, 384000, 5000000, 5384000, 30], + "yt-sdr-1080p30" => [ 1920, 1080, 0, 384000, 8000000, 8384000, 30], + "yt-sdr-1440p30" => [ 2560, 1440, 0, 512000, 16000000, 16512000, 30], + "yt-sdr-2160p30" => [ 3840, 2160, 0, 512000, 40000000, 40512000, 30], + + "yt-sdr-360p60" => [ 640, 360, 0, 128000, 1500000, 1628000, 60], + "yt-sdr-480p60" => [ 852, 480, 0, 128000, 4000000, 4128000, 60], + "yt-sdr-720p60" => [ 1280, 720, 0, 384000, 7500000, 7884000, 60], + "yt-sdr-1080p60" => [ 1920, 1080, 0, 384000, 12000000, 12384000, 60], + "yt-sdr-1440p60" => [ 2560, 1440, 0, 512000, 24000000, 24512000, 60], + "yt-sdr-2160p60" => [ 3840, 2160, 0, 512000, 61000000, 61512000, 60], + #"yt-hdr-360p60" => [ 1280, 720, 0, 32000, 1000000, 1800000, 60], # yt unsupported + #"yt-hdr-480p60" => [ 1280, 720, 0, 32000, 1000000, 1800000, 60], # yt unsupported + + "yt-hdr-720p30" => [ 1280, 720, 0, 384000, 6500000, 6884000, 30], + "yt-hdr-1080p30" => [ 1920, 1080, 0, 384000, 10000000, 10384000, 30], + "yt-hdr-1440p30" => [ 2560, 1440, 0, 512000, 20000000, 20512000, 30], + "yt-hdr-2160p30" => [ 3840, 2160, 0, 512000, 50000000, 50512000, 30], + + "yt-hdr-720p60" => [ 1280, 720, 0, 384000, 9500000, 9884000, 60], + "yt-hdr-1080p60" => [ 1920, 1080, 0, 384000, 15000000, 15384000, 60], + "yt-hdr-1440p60" => [ 2560, 1440, 0, 512000, 30000000, 30512000, 60], + "yt-hdr-2160p60" => [ 3840, 2160, 0, 512000, 75500000, 76012000, 60], + + "raw720p30" => [ 1280, 720, 0, 64000, 221120000, 221184000, 30], + "raw720p60" => [ 1280, 720, 0, 64000, 442304000, 442368000, 60], + + # frame size 6.2MB + # 1080i60 1920x1080 186MBps + "raw1080i" => [ 1920, 540, 1, 128000, 1486384000, 1486512000, 59.94], + "raw1080i30" => [ 1920, 540, 1, 128000, 1487872000, 1488000000, 30], + "raw1080i60" => [ 1920, 540, 1, 128000, 1487872000, 1488000000, 60], + + # 1080p60 1920x1080 373MBps, 6.2Mbps frame size + "raw1080p" => [ 1920, 1080, 0, 128000, 2975872000, 2976000000, 60], + + # Skype requirements below as listed on https://support.skype.com/en/faq/FA1417/how-much-bandwidth-does-skype-need + # ^--- indicates there is a minimum TX requirement for stations + # group calls range from 128k up to 512k up, roughly HQ-recommended, maybe 1280x720x15 + # https://www.quora.com/Does-Skype-support-1080p-HD-video-calls + # https://tomtalks.blog/2018/04/set-skype-for-business-to-record-meetings-at-1080p-and-30-fps/ + # Transmission quality is fundamentally different than YouTube -- it is constant TX that varies by + # the amount of compression available. Variation between minimum required bandwidth and recommended + # bandwidth is visible in packet captures. + # Actual capture resolutions depend on your camera and can be manipulated via settings, esp frame rate: + # https://superuser.com/questions/180690/how-to-reduce-the-skype-video-settings-to-work-with-an-older-computer + # https://lifehacker.com/how-to-get-better-quality-out-of-your-video-chats-5836186 + # https://docs.microsoft.com/en-us/skypeforbusiness/plan-your-deployment/clients-and-devices/video-resolutions + # ^--- This outlines a requirements for 4 core processors as requirement for Skype at 720p! + # Jed is roughly interpolating many of these values for this table + # nicname w, h, interlaced, audio, vid bps, tt bps framerate + "skype-vox-min" => [ 0, 0, 0, 30000, 0, 30000, 0 ], + "skype-vox-rcmd" => [ 0, 0, 0, 100000, 0, 100000, 0 ], + # screen sharing falls into min requirement + "skype-vid-min" => [ 424, 240, 0, 30000, 98000, 128000, 15 ], + "skype-vid-rcmd" => [ 640, 360, 0, 100000, 200000, 300000, 30 ], + "skype-vid-hq-min" => [ 960, 540, 0, 100000, 300000, 400000, 15 ], + "skype-vid-hq-rcmd" => [ 1280, 720, 0, 100000, 400000, 500000, 30 ], + "skype-vid-hd-min" => [ 1920, 1080, 0, 100000, 1100000, 1200000, 15 ], + "skype-vid-hd-rcmd" => [ 1920, 1080, 0, 100000, 1400000, 1500000, 30 ], + "skype-vid-grp3-min" => [ 640, 480, 0, 30000, 482000, 512000, 15 ], + "skype-vid-grp3-rcmd" => [ 1280, 720, 0, 100000, 900000, 2000000, 15 ], + "skype-vid-grp5-min" => [ 640, 360, 0, 30000, 1700000, 2000000, 15 ], + "skype-vid-grp5-rcmd" => [ 1280, 720, 0, 100000, 3700000, 4000000, 15 ], + "skype-vid-grp7-min" => [ 640, 360, 0, 30000, 3700000, 4000000, 15 ], + "skype-vid-grp7-rcmd" => [ 1280, 720, 0, 100000, 7700000, 8000000, 15 ], + +); + +our $avail_stream_desc = join(", ", keys(%avail_stream_res)); +our $resolution = "yt-sdr-1080p30"; +my $list_streams = undef; + +our $usage = "$0: # modulates a Layer 3 CX to emulate a video server + # Expects an existing L3 connection + --mgr {hostname | IP} + --mgr_port {ip port} + --tx_style { constant | bufferfill | L4 } + # constant: for variable-br constant streaming, like Skype. UDP or TCP. + # bufferfill: for framebuffer transmission, like YouTube, that monitors and throttles an existing Layer-3 connection + # High cpu load and imprecise technique, but uses a single constant connection. UDP or TCP + # L4: for framebuffer transmission, like YouTube, but using more precise Layer-4 URL fetching pattern. + # This repeats the same curl fetch over and over, creating a new connection every time. More efficient, TCP only. + --cx_name {name} + --tx_side {A|B} # which side is emulating the server, + # default $::tx_side + --max_tx {speed in bps [K|M|G]} # use this to fill buffer + --min_tx {speed in bps [K|M|G]} # use when not filling buffer, default 0 + --buf_size {size[K|M|G]} # fill a buffer at max_tx for this long + --stream_res {$avail_stream_desc} + --list_streams # show stream bps table and exit + # default $resolution + --log_cli {0|1} # use this to record cli commands + --quiet {0|1|yes|no} # print CLI commands + --silent # do not print status output + --quit_when_const # quits connection when constant tx detected + --sta {1.1.sta0 or 1.sta0} # use with L4 or constant + --upstream {1.1.eth1 or 1.eth1} # use with L4 or constant; will create HTTP service on port if necessary + --proto {udp|tcp} # use with constant tx style + --begin_running {0|1} # bufferfill does not get created running, but constant and L4 do, overrides this + + Example: + 1) create the L3 connection: + ./lf_firemod.pl --resource 1 --action create_endp bursty-udp-A --speed 0 --endp_type lf_udp --port_name eth1 --report_timer 500 + ./lf_firemod.pl --resource 1 --action create_endp bursty-udp-B --speed 0 --endp_type lf_udp --port_name eth2 --report_timer 500 + ./lf_firemod.pl --resource 1 --action create_cx --cx_name bursty-udp --cx_endps bursty-udp-A,bursty-udp-B + $0 --cx_name bursty-udp --stream 720p --buf_size 8M --max_tx 40M + + 2) Create a Layer-4 connection: + $0 --tx_style L4 --cx_name hunker --stream yt-sdr-1080p30 --buf_size 3M --port 1.sta0000 +"; + +my $show_help = undef; +our $debug = 0; + +$::stream_key = $resolution; +GetOptions +( + 'help|h' => \$show_help, + 'quiet|q=s' => \$::quiet, + 'debug|d' => \$::debug, + 'silent+' => \$::silent, + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p:i' => \$::lfmgr_port, + 'resource|r:i' => \$::resource, + 'log_cli:s{0,1}' => \$log_cli, + 'tx_style|style:s' => \$::tx_style, + 'cx_name|e=s' => \$::cx_name, + 'tx_side|side|s:s' => \$::tx_side, + 'max_tx=s' => \$::max_tx, + 'min_tx:s' => \$::min_tx, + 'buf_size|buf=s' => \$::buf_size, + 'stream_res|stream=s' => \$::stream_key, + 'list_streams+' => \$list_streams, + 'quit_when_const' => \$::quit_when_const, + 'sta=s' => \$::sta, + 'upstream|up|u=s' => \$::upstream, + 'proto=s' => \$::proto, + 'begin_running' => \$::begin_running, +) || die($!); + + +if ($show_help) { + print $usage; + exit 0; +} + +if ($list_streams) { + print "Predefined Video Streams\n"; + print "=" x 72, "\n"; + print " Stream W H Audio+Video\n"; + my %sortedkeys = (); + foreach my $oldkey (keys(%::avail_stream_res)) { + my $ra_row = $::avail_stream_res{$oldkey}; + my $x = 10000000 + int(@$ra_row[$::stream_keys{x}]); + my $y = 10000000 + int(@$ra_row[$::stream_keys{y}]); + my $b = 10000000000 + int(@$ra_row[$::stream_keys{video_bps}]); + my $newkey = "${b}_${x}_${y}_${oldkey}"; + $sortedkeys{$newkey} = $oldkey; + } + foreach my $sorted_key (sort(keys(%sortedkeys))) { + my $key = $sortedkeys{$sorted_key}; + my $ra_row1 = $::avail_stream_res{$key}; + my $x = @$ra_row1[$::stream_keys{x}]; + my $y = @$ra_row1[$::stream_keys{y}]; + my $bps = int(@$ra_row1[$::stream_keys{stream_bps}]); + my $bps_sum = int(@$ra_row1[$::stream_keys{video_bps}]) + int(@$ra_row1[$::stream_keys{audio_bps}]); + #my $warning = ""; + printf("[ %15s ] %4s x %4s using %8s kbps", $key, $x, $y, ($bps/1000)); + if ($bps != $bps_sum) { + print " Invalid BPS $bps, correct to $bps_sum"; + } + print "\n"; + } + exit 0; +} + +if ($::quiet eq "0") { + $::quiet = "no"; +} +elsif ($::quiet eq "1") { + $::quiet = "yes"; +} + +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + #print "LOG_CLI now 1\n"; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + #print "LOG_CLI now $log_cli\n"; + } + } +} + +#my @sigkeys = keys %SIG; +#print join(";", sort @sigkeys); +# ABRT;ALRM;BUS;CHLD;CLD;CONT;FPE;HUP;ILL;INT;IO;IOT;KILL; +# NUM32;NUM33;NUM35;NUM36;NUM37;NUM38;NUM39;NUM40;NUM41;NUM42;NUM43;NUM44;NUM45;NUM46;NUM47;NUM48;NUM49; +# NUM50;NUM51;NUM52;NUM53;NUM54;NUM55;NUM56;NUM57;NUM58;NUM59;NUM60;NUM61;NUM62;NUM63; +# PIPE;POLL;PROF;PWR;QUIT;RTMAX;RTMIN;SEGV;STKFLT;STOP;SYS;TERM;TRAP;TSTP;TTIN;TTOU;UNUSED; +# URG;USR1;USR2;VTALRM;WINCH;XCPU;XFSZ;__DIE__;__WARN__ +# +# install signal handlers for stopping connections +$SIG{ABRT} = \&cleanexit; +$SIG{HUP} = \&cleanexit; +$SIG{INT} = \&cleanexit; +$SIG{KILL} = \&cleanexit; +$SIG{PIPE} = \&cleanexit; # <- this is how we're terminated, no output message seen +$SIG{SEGV} = \&cleanexit; +$SIG{STOP} = \&cleanexit; +$SIG{TERM} = \&cleanexit; +$SIG{QUIT} = \&cleanexit; + +# ======================================================================== +sub cleanexit { + my ($msg) = @_; + if (!(defined $msg) || ("" eq $msg)) { + $msg = 'no msg'; + } + if ((defined $::cx_name) && ("" ne $::cx_name)) { + if (defined $::utils->telnet) { + if ($::stop_cx_on_exit) { + print STDERR "\nStopping $::cx_name: $msg\n"; + $::utils->doAsyncCmd($::utils->fmt_cmd("set_cx_state", "all", $::cx_name, "STOPPED")); + } + else { + print STDERR ("CX '$::cx_name' will not be stopped.") unless $::silent; + } + } + else { + print STDERR ("No telnet session remains, CX '$::cx_name' will not be stopped."); + } + } + exit 0; +} + +# ======================================================================== +# ======================================================================== +sub rxbytes { + my ($endp) = @_; + die ("called rxbytes with no endp name, bye") + unless((defined $endp) && ("" ne $endp)); + + my @lines = split("\n", $::utils->doAsyncCmd("nc_show_endpoints $endp")); + #Rx Bytes: Total: 0 Time: 60s Cur: 0 0/s + my $bytes = 0; + my @matches = grep {/^\s+Rx Bytes/} @lines; + if (@matches < 1) { + warn "rx-bytes not found for [$endp]\n"; + print join("\n> ", @lines), "\n"; + return 0; + } + ($bytes) = $matches[0] =~ /Rx Bytes:\s+Total: (\d+)/; + if (!(defined $bytes)) { + warn "no rx-bytes match for [$endp]\n"; + print "="x72, "\n"; + print $matches[0], "\n"; + print "="x72, "\n"; + print join("\n> ", @lines), "\n"; + return 0; + } + return $bytes; +} +# ======================================================================== +# look for any TX/RX rates associated with station +sub get_txrx_rate { + my ($lf_host, $lf_port, $rez, $cxnam, $rx_sid) = @_; + my $rxendp = "${cxnam}-${rx_sid}"; + my $cmd = "./lf_firemod.pl --mgr $lf_host --mgr_port $lf_port -r $rez " + ."--action show_endp --endp_name $rxendp --endp_vals EID"; + print "GET_TXRX: $cmd\n"; + my @lines = `$cmd`; + chomp(@lines); + my @matches = grep {/EID:/} @lines; + return -1 if (@matches < 1); + + my ($discard1, $port_eid) = split(/:\s*/, $matches[0]); + my $max_rate = 0; + if (!(defined $port_eid) || ("" eq $port_eid)) { + print STDERR "Unable to determine port eid, unable to update max_tx\n"; + return -1; + } + # find tx/rx rate + my ($discard2, $rez2, $portid) = split(/[.]/, $port_eid); + $cmd = "lf_portmod.pl --mgr $lf_host --mp $lf_port --resource $rez2" + ." --port_name $portid --show_port Probed-TX-Rate,Probed-RX-Rate"; + @lines = `$cmd`; + chomp(@lines); + my $rate = 0; + for my $line (@lines) { + my @hunks = split(/:\s*/, $line); + if (@hunks > 1) { + $rate = $::utils->expand_unit_str($hunks[1]); + } + $max_rate = $rate if ($rate > $max_rate); + } + #if ($max_rate > 0) { + # print "Adjusting max-rate closer to $max_rate\n"; + #} + return $max_rate +} # ~get_txrx_rate() + +# ======================================================================== + +sub txbytes { + my ($endp, $check_exit) = @_; + die ("called txbytes with no endp name, bye") + unless((defined $endp) && ("" ne $endp)); + + my @lines = split("\n", $::utils->doAsyncCmd("nc_show_endpoints $endp")); + #Tx Bytes: Total: 0 Time: 60s Cur: 0 0/s + my $bytes = 0; + my @matches = grep {/^L4Endp \[/} @lines; + my $is_4 = (@matches > 0)? 1 : 0; + + if ($is_4) { + @matches = grep {/^\s+Bytes Written/} @lines; + if (@matches < 1) { + warn "bytes-written not found for [$endp]\n"; + print join("\n> ", @lines), "\n"; + return 0; + } + ($bytes) = $matches[0] =~ /Bytes Written:\s+Total: (\d+)/; + } + else { + @matches = grep {/^\s+Tx Bytes/} @lines; + if (@matches < 1) { + warn "tx-bytes not found for [$endp]\n"; + print join("\n> ", @lines), "\n"; + return 0; + } + ($bytes) = $matches[0] =~ /Tx Bytes:\s+Total: (\d+)/; + } + + if (!(defined $bytes)) { + warn "no tx-bytes match for [$endp]\n"; + print "="x72, "\n"; + print $matches[0], "\n"; + print "="x72, "\n"; + print join("\n> ", @lines), "\n"; + return 0; + } + # we want to exit if connection indicates stopped + if ($check_exit) { + @matches = grep { /Endpoint .*?NOT_RUNNING, .*/ } @lines; + if (@matches > 0) { + #print "Endpoint has stopped, exiting\n"; + cleanexit("Endpoint has stopped, exiting\n"); + } + } + return $bytes; +} + +# ======================================================================== +# M A I N +# ======================================================================== + +if ($::quiet eq "1" ) { + $::quiet = "yes"; +} + +# Configure our utils. +our $utils = new LANforge::Utils(); +$::utils->connect($::lfmgr_host, $::lfmgr_port); + +die ("Please provide buffer size") + unless((defined $buf_size) && ("" ne $buf_size)); +if ($buf_size =~ /[kmg]$/i) { + my($n) = $buf_size =~ /(\d+)/; + if ($buf_size =~ /k$/i) { + $buf_size = $n * 1024; + } + elsif ($buf_size =~ /m$/i) { + $buf_size = $n * 1024 * 1024; + } + elsif ($buf_size =~ /g$/i) { + $buf_size = $n * 1024 * 1024 * 1024; + } + else { + die("Whhhhhuuuuuut?"); + } +} + +die("Please specify max tx bps") + unless("" ne $::max_tx); +if ($::max_tx =~ /[kmg]$/i) { + my($n) = $::max_tx =~ /(\d+)/; + if ($::max_tx =~ /k$/i) { + $::max_tx = $n * 1000; + } + elsif ($::max_tx =~ /m$/i) { + $::max_tx = $n * 1000 * 1000; + } + elsif ($::max_tx =~ /g$/i) { + $::max_tx = $n * 1000 * 1000 * 1000; + } + else { + die("Whhhhhuuuuuut?"); + } +} +if ($::min_tx =~ /[kmg]$/i) { + my($n) = $::min_tx =~ /(\d+)/; + if ($::min_tx =~ /k$/i) { + $::min_tx = $n * 1000; + } + elsif ($::min_tx =~ /m$/i) { + $::min_tx = $n * 1000 * 1000; + } + elsif ($::min_tx =~ /g$/i) { + $::min_tx = $n * 1000 * 1000 * 1000; + } + else { + die("Whhhhhuuuuuut?"); + } +} + + +my @hunks = (); +my @lines = (); +my @matches = (); + +if ((defined $::sta) && ("" ne $::sta)) { + if ($::sta =~ /\./) { + @hunks = split(/\./, $::sta); + $::sta = $hunks[-1]; + if (("$::resource" ne $hunks[-2])) { + print "Mismatch between station resource(${hunks[-2]}) and declared resource($::resource), bye.\n"; + exit(1); + } + @lines = split(/\r?\n/, $::utils->doAsyncCmd("nc_show_port 1 $::resource $::sta")); + @matches = grep {/^Shelf: 1,/} @lines; + if (@matches < 1) { + print "Cannot find port $::resource.$::sta, bye\n"; + exit(1); + } + } +} + +my $endp = $::cx_name."-".$::tx_side; # change me if L4 +@hunks = (); +if ($::tx_style =~ /^l(ayer)?[-_]?4$/i ) { + $::tx_style = "L4"; +} +if ($::tx_style =~ /^const(ant)?$/i) { + $::tx_style = "constant"; +} +if (($::tx_style eq "L4") || ($::tx_style eq "constant")) { + if (!(defined $::sta) || ("" eq $::sta)) { + print "L4 and constant connections needs a station, bye\n"; + exit 1; + } + if (!(defined $::upstream) || ("" eq $::upstream)) { + print "L4 and constant connection needs an upstream port, bye\n"; + exit 1; + } + if ($::upstream !~ /[.]/) { + $::upstream_res = $::resource; + } + else { + @hunks = split(/[.]/, $::upstream); + $::upstream = $hunks[-1]; + $::upstream_res = $hunks[-2]; + } + @lines = split(/\r?\n/, $::utils->doAsyncCmd("nc_show_port 1 $::upstream_res $::upstream")); + @matches = grep {/^Shelf: 1,/} @lines; + if (@matches < 1) { + print "Cannot find upstream port $::upstream_res.$::upstream, bye\n"; + exit(1); + } + if ($::sta =~ /\./) { + @hunks = split(/[.]/, $::sta); + $::sta = $hunks[-1]; + die("resource ${hunks[-2]} for station $::sta is not listed resource: $::resource, bye.") + if ($hunks[-2] ne $::resource); + } +} +else { + $::begin_running = 0; +} + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# Layer-3 constant setup +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + +if ($::tx_style =~ /constant/) { + if ($::stream_key !~ /^skype-/) { + print "Using 'constant' tx-style only makes sense when emulating Skype calls. Please choose different stream.\n"; + exit 1; + } + if ($::stream_key =~ /grp\d+/) { + print "group calls not implemented presently\n"; + exit 1; + } + else { + print "Call upload requirements still under development.\n"; + } + + # if someone sets stream resolution to "-min$", that's not a cap that Skype respects, skype will + # search for more bandwidth...stream max will be /-rcmd$/, min will be /-min$/ + if ($::stream_key =~ /-min$/) { + $::stream_key =~ s/-min/-rcmd/; + } +} +die ("Please provide cx_name") + unless((defined $::cx_name) && ("" ne $::cx_name)); + +my $stream_bps = 0; +die("Unknown stream key $::stream_key") + unless(exists $::avail_stream_res{$::stream_key}); + +$stream_bps = @{$::avail_stream_res{$::stream_key}}[$::stream_keys{stream_bps}]; + +my $drain_time_sec = 0; +my $drain_wait_sec = 0; +my $stream_kbps = 0; + +if ($::tx_style =~ /constant/) { + my $stream_min = $::stream_key; + $stream_min =~ s/-rcmd/-min/; + + $::min_tx = @{$::avail_stream_res{$stream_min}}[$::stream_keys{stream_bps}]; + $::max_tx = @{$::avail_stream_res{$::stream_key}}[$::stream_keys{stream_bps}]; + $stream_bps = $::max_tx; + $stream_kbps = $stream_bps / 1000; +} +else { + # estimated fill time is probably not going to be accurate because + # there's no way to know the txrate between the AP and station. + $::est_fill_time_sec = (8 * $::buf_size) / ($::max_tx * 0.5); + my $drain_time_sec = (8 * $::buf_size) / $stream_bps; + my $drain_wait_sec = $drain_time_sec - $est_fill_time_sec; + + if ($drain_wait_sec <= 0) { + my $stream_kbps = $stream_bps / 1000; + print "Warning: constant transmit! Raise max_tx to at least $stream_kbps Kbps\n"; + $drain_wait_sec = 0; + } + + my $buf_kB = $::buf_size / 1024; + print "Filling $::stream_key $buf_kB KB buffer est ${est_fill_time_sec}sec, empties in ${drain_time_sec} sec\n" + unless($::silent); +} +$stream_kbps = $stream_bps / 1000; + +# check for cx if we're bufferfill +my $cx_exists = 0; +@lines = split("\r?\n", $::utils->doAsyncCmd($::utils->fmt_cmd("show_cx", "all", $::cx_name))); +@matches = grep {/Could not find/} @lines; +$cx_exists = 1 if (@matches == 0); + +if (($::tx_style eq "bufferfill") && !$cx_exists) { + print "Tx_style bufferfill requires your connection already exists, bye.\n"; + exit 1; +} + +if (($::tx_style =~ /constant/) && !$cx_exists) { + my $cmd = "./lf_firemod.pl --mgr $::lfmgr_host --mgr_port $::lfmgr_port --action create_cx " + ."--cx_name $::cx_name --use_ports $::sta,$::upstream --use_speeds 128000,$::max_tx " + ."--speed $::min_tx --max_speed $::max_tx --endp_type udp --report_timer 3000"; + my $result = `$cmd`; + print "x"x72, "\n"; + print $result, "\n"; + print "x"x72, "\n"; +} + +print "Stopping and configuring $::cx_name\n" unless($silent); +if (($::tx_style eq "L4") && ($::cx_name !~ /^CX_/)) { + $::cx_name = "CX_$::cx_name"; +} +$::utils->doCmd($::utils->fmt_cmd("set_cx_state", "all", $::cx_name, "STOPPED")); + +my @reports = (); +my $fill_starts = 1; +my $fill_stops = 0; +my $tt_bytes = 0; +my $ave_fill_bytes = 0; +my ($starttime_sec, $starttime_usec) = gettimeofday(); +$starttime_sec = $starttime_sec + ($starttime_usec / 1000000); +my $begin = $starttime_sec; +my $last_report_sec = $starttime_sec; +my $report_period_sec = 6; +my $check_if_stopped = 0; +my $cmd =""; +my $res = 1; +my $port = "Unknown"; +my $type = $::proto; +our $stop_cx_on_exit = 1; + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# Layer-4 setup +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if ($::tx_style eq "L4") { + # check that the upstream port has http enabled + $::stop_cx_on_exit = 0; + my $cmd = "./lf_portmod.pl --mgr $::lfmgr_host --mgr_port $::lfmgr_port --port_name $::upstream --show_port Current,IP"; + my @lines = `$cmd`; + chomp(@lines); + + if ($lines[0] !~ / SVC-HTTPD/m ) { + print "Enabling HTTP on $::upstream...\n"; + #"set_port 1 1 eth1 NA NA NA NA 0 NA NA NA NA 134217730 " # <--- and to turn off + $cmd = $::utils->fmt_cmd("set_port", 1, $::resource, $::upstream, + "NA", "NA", "NA", "NA", 35184372088832, "NA", "NA", "NA", "NA", 134217730); + $::utils->doCmd($cmd); + sleep(1); + } + my $ip = "0.0.0.0"; + if ($lines[1] =~ /^IP:\s+([^ ]+)$/) { + $ip = $1; + } + else { + print "Unable to find IP address for upstream port, bye."; + exit 1; + } + my ($short_cx) = $::cx_name =~ /CX_(\S+)/; + my $tmp_ep1 = $short_cx; + my $tmp_ep2 = "D_$short_cx"; + + $endp = $tmp_ep1; # L4 endpoints are not '-A', '-B' + my $timeout = 2000; # ms + die("Invalid drain time: $drain_time_sec") + if ($drain_time_sec <= 0); + my $url_rate = floor(600 / $drain_time_sec); + + my $short_size = $::buf_size; + while ($short_size > 1024) { + $short_size = floor($short_size / 1024); + } + my $url = "dl http://".$ip."/".$short_size."m.bin /dev/null"; + #print "URL $url\n"; + #sleep 10; + + # do not need to add dummy endpoint + $::utils->doCmd($::utils->fmt_cmd( + "add_l4_endp", $tmp_ep1, 1, $::resource, $::sta, "l4_generic", 0, $timeout, $url_rate, $url, ' ')); + #sleep 1; + $cmd = $::utils->fmt_cmd("add_cx", $::cx_name, "default_tm", $tmp_ep1, "NA"); + $::utils->doAsyncCmd($cmd); +} + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# Layer-3 constant bufferfill +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +#print "Stopping and configuring $::cx_name\n" unless($silent); +#$::utils->doCmd($::utils->fmt_cmd("set_cx_state", "all", $::cx_name, "STOPPED")); + +@lines = split("\r?\n", $::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_endp", $endp))); +@matches = grep {/ Shelf: 1, Card: /} @lines; +# create a L3 connection +if ($::tx_style =~ /constant/) { + $::stop_cx_on_exit = 0; + + die ("No matches for show endp $endp") + unless($matches[0]); + ($res, $port, $type) = $matches[0] =~ /, Card: (\d+)\s+Port: (\d+)\s+Endpoint: \d+ Type: ([^ ]+)\s+/; + if (!(defined $res) || !(defined $port) || !(defined $type)) { + die("Unable to determine endpoint [$endp], bye"); + + } + $cmd = $::utils->fmt_cmd("add_endp", $endp, 1, $res, $port, $type, + $NA, # ip_port + $NA, # is_rate_bursty + $::min_tx, # min_rate + (($::tx_style eq "bufferfill") ? $::min_tx : $::max_tx) # max_rate + ); + print "CMD[$cmd]\n"; + sleep 5; + $::utils->doAsyncCmd($cmd); +} + + + +# +# start CX +# +# avoid a stampede of scripts starting at the same time +my $rand_start_delay = rand(7); + if (! $::debug) { + print "Random start delay: $rand_start_delay...\n"; + $::utils->sleep_sec($rand_start_delay); +} + +if (!(defined $endp) || !(defined $res) || !(defined $port) || !(defined $type) || !(defined $::max_tx) || !(defined $::min_tx)) { + die("Unable to continue, missing values in: endp($endp) res($res) port($port) type($type) max_tx($::max_tx)"); +} + +if ($::tx_style !~ /bufferfill/) { + if ($::begin_running) { + $cmd = $::utils->fmt_cmd("set_cx_state", "all", $::cx_name, "RUNNING"); + #print "Starting $::cx_name: $cmd\n" unless($silent); + $::utils->doCmd($cmd); + print "started $::cx_name\n"; + } + cleanexit("Done with setup on $::tx_style $::cx_name\n"); +} + +$cmd = $::utils->fmt_cmd("add_endp", $endp, 1, $res, $port, $type, $NA, $NA, $::max_tx, $::max_tx); +$::utils->doAsyncCmd($cmd); + +my $startbytes = txbytes($endp, $check_if_stopped); +my @delta_reports = (); + +do { + ($starttime_sec, $starttime_usec) = gettimeofday(); + my $starttime = $starttime_sec + ($starttime_usec / 1000000 ); + if (($starttime - $begin) > 20) { + $check_if_stopped = 1; + } + my $bytes = 0; + my $num_checks = 0; + my $prev_bytes = 0; + # this might not be + while($bytes < ($buf_size + $startbytes)) { + $num_checks++; + my ($delta1_sec, $delta1_usec) = gettimeofday(); + $prev_bytes = $bytes; + $bytes = txbytes($endp, $check_if_stopped); + my ($delta2_sec, $delta2_usec) = gettimeofday(); + my $rx_side = ($::tx_side eq "A") ? "B" : "A"; + my $updated_txbps = get_txrx_rate($::lfmgr_host, $lfmgr_port, $::resource, $::cx_name, $rx_side); + if ($updated_txbps > 0) { + $::max_tx = $updated_txbps; + $::est_fill_time_sec = (8 * $::buf_size) / ($::max_tx * 0.5); + $drain_wait_sec = $drain_time_sec - $::est_fill_time_sec; + } + $delta1_sec = $delta1_sec + ($delta1_usec/1000000); + $delta2_sec = $delta2_sec + ($delta2_usec/1000000); + #push(@delta_reports, sprintf(" Sent %d B, d %.5f",($bytes-$prev_bytes), ($delta2_sec - $delta1_sec))); + push(@delta_reports, sprintf(" Sent %d B/ %.5f bps;", + ($bytes-$prev_bytes), + ($bytes-$prev_bytes)/($delta2_sec - $starttime) )); + last if ($bytes > ($buf_size + $startbytes)); + + # if we're taking unreasonably long, let's just escape + if (($delta2_sec - $starttime) > (12 * $last_fill_time_sec)) { + push(@reports, sprintf("Likely overfill detected, txsec: %.4f", ($delta2_sec - $starttime))); + last; + } + #push(@delta_reports, "z"); + $::utils->sleep_ms(200); + #$::utils->sleep_ms( 5 * ($delta2_sec - $delta1_sec)); + } + # startbytes is only needed on iteration 0 + $startbytes = 0; + my ($finishtime_sec, $finishtime_usec) = gettimeofday(); + $finishtime_sec = ($finishtime_sec + ($finishtime_usec / 1000000)); + $last_fill_time_sec = $finishtime_sec - $starttime_sec; + $tt_bytes += $bytes; + + $drain_wait_sec = $drain_time_sec - $last_fill_time_sec; + push(@reports, sprintf("## drain_wait_seconds: %.4f; est fill: %.4f; actual fill %.4f; dev: %.4f", + $drain_wait_sec, $est_fill_time_sec, $last_fill_time_sec, ($est_fill_time_sec - $last_fill_time_sec ))); + push(@reports, "deltas: ".join(',', @delta_reports)); + + if ($::quit_when_const && ($fill_stops > 1) && ($drain_wait_sec <= 0)) { + # this is a failure condition, we are misconfigured or overloaded + cleanexit("Constant TX Quit: Wait $drain_wait_sec = Drain $drain_time_sec - Fill time $last_fill_time_sec;\n" + .join("\n", @reports)); + } + + #push(@reports, " deltas: ".join(',', @delta_reports)); + @delta_reports = (); + + #if ($drain_wait_sec > 0) { # we don't really want to never stop, that's not useful + $cmd = $::utils->fmt_cmd("add_endp", $endp, 1, $res, $port, $type, $NA, $NA, $::min_tx, $::min_tx); + $::utils->doCmd($cmd); + $fill_stops++; + #$ave_fill_bytes = $tt_bytes / $fill_stops; + #push(@reports, "# $fill_starts fills for ave ${ave_fill_bytes}B/fill"); + + $::utils->sleep_sec($drain_wait_sec); + $startbytes = txbytes($endp, $check_if_stopped); + push(@reports, "Setting max_tx to $::max_tx"); + $cmd = $::utils->fmt_cmd("add_endp", $endp, 1, $res, $port, $type, $NA, $NA, $::max_tx, $::max_tx); + $::utils->doCmd($cmd); + $fill_starts++; + #} + if (($finishtime_sec - $last_report_sec) >= $report_period_sec) { + print (join("\n", @reports), "\n"); + @reports = (); + $last_report_sec = $finishtime_sec; + } +} while(1); + +# \ No newline at end of file diff --git a/lanforge/lanforge-scripts/label-printer/README.md b/lanforge/lanforge-scripts/label-printer/README.md new file mode 100644 index 000000000..572b96219 --- /dev/null +++ b/lanforge/lanforge-scripts/label-printer/README.md @@ -0,0 +1,48 @@ +**label-printer.py** + +This is a small python webserver intended to run on a testing network resource where the `lf_kinstall.pl` script can post build machine information to. A useful place to install this script would be on an APU2 being used as a VPN gateway. + +Use these commands to install **LaTeX** and **html2ps**: + `apt install perlmagick libwww-perl libhtml-parser-perl libpaper-utils ghostscript weblint-perl texlive-base postscript-viewer xhtml2ps html2ps` + +Consider these commands to install printer drivers: + +**Dymo LabelWriter-450:** + `apt install printer-driver-dymo` + +**Brother QL-800:** + `$ sudo apt install brother-cups-wrapper-common brother-cups-wrapper-extra brother-lpr-drivers-common brother-lpr-drivers-extra` + +Download drivers here: +​ https://support.brother.com/g/b/downloadtop.aspx?c=us&lang=en&prod=lpql800eus + +Install driver using: + `$ sudo dpkg -i dymo*.dpkg` + +Use these commands to install the script: + + $ sudo cp label-printer.py /usr/local/bin + $ sudo chmod a+x /usr/local/bin/label-printer.py + + $ sudo cp label-printer.service /lib/systemd/system + $ sudo systemctl add-wants multi-user.target label-printer.service + $ sudo systemctl daemon-reload + $ sudo systemctl restart label-printer.service + +At this point, if you use `ss -ntlp` you should see this script listening on port 8082. + +If you are running `ufw` on your label-printer host, please use this command to allow +traffic to port 8082: + +`$ sudo ufw allow 8082/tcp` +`$ sudo ufw reload` + +**Using kinstall to print labels:** + +Dymo LabelWriter: + `$ ./lf_kinstall.pl --print-label http://192.168.9.1:8082/ --printer LabelWriter-450` + +Brother QL-800: + `$ ./lf_kinstall.pl --print-label http://192.168.9.1:8082/ --printer QL-800` + +Note that the QL-800 printer driver installs an *example* printer queue named **QL800** which is misleading. Please ignore that. When the printer is plugged in, a **QL-800** printer queue will be created, which is the true queue to print to. diff --git a/lanforge/lanforge-scripts/label-printer/label-printer-test.bash b/lanforge/lanforge-scripts/label-printer/label-printer-test.bash new file mode 100755 index 000000000..08531c0d1 --- /dev/null +++ b/lanforge/lanforge-scripts/label-printer/label-printer-test.bash @@ -0,0 +1,10 @@ +#!/bin/bash + +#printer="LabelWriter-450" +printer="QL-800" + +curl -v -d "printer=${printer}&model=lf0350&mac=00:0e:84:33:44:55:66&hostname=vm-atlas20&serial=zoso1234" http://localhost:8082/ +sleep 4 +curl -v -d "printer=${printer}&model=lf0350&mac=00:0e:84:33:44:55:66&hostname=&serial=" http://localhost:8082/ +sleep 4 +curl -v -d "printer=${printer}&model=lf0350&mac=00:0e:84:33:44:55:66" http://localhost:8082/ diff --git a/lanforge/lanforge-scripts/label-printer/label-printer.py b/lanforge/lanforge-scripts/label-printer/label-printer.py new file mode 100755 index 000000000..2e4bafb86 --- /dev/null +++ b/lanforge/lanforge-scripts/label-printer/label-printer.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +""" +This is a small python webserver intended to run on a testing network resource +where the lf_kinstall.pl script can post build machine information to. A useful +place to install this script would be on an APU2 being used as a VPN gateway. + +Use these commands to install LaTeX and html2ps + apt install perlmagick libwww-perl libhtml-parser-perl libpaper-utils ghostscript weblint-perl texlive-base postscript-viewer xhtml2ps html2ps + +Consider these commands to install printer drivers: + +Dymo LabelWriter-450: + apt install printer-driver-dymo +Brother QL-800: + # apt install brother-cups-wrapper-common brother-cups-wrapper-extra brother-lpr-drivers-common brother-lpr-drivers-extra + Download drivers here: + https://support.brother.com/g/b/downloadtop.aspx?c=us&lang=en&prod=lpql800eus + * download driver + * install driver using + $ sudo dpkg -i dymo*.dpkg + +Use these commands to install the script: + + $ sudo cp label-printer.py /usr/local/bin + $ sudo chmod a+x /usr/local/bin/label-printer.py + + $ sudo cp label-printer.service /lib/systemd/system + $ sudo systemctl add-wants multi-user.target label-printer.service + $ sudo systemctl daemon-reload + $ sudo systemctl restart label-printer.service + +At this point, if you use `ss -ntlp` you should see this script listening on port 8082. + +If you are running ufw on your label-printer host, please use this command to allow +traffice to port 8082: +$ sudo ufw allow 8082/tcp +$ sudo ufw reload + +Using kinstall to print labels: +Dymo LabelWriter: + $ ./lf_kinstall.pl --print-label http://192.168.9.1:8082/ --printer LabelWriter-450 + +Brother QL-800: + $ ./lf_kinstall.pl --print-label http://192.168.9.1:8082/ --printer QL800 + + +""" +import os +import logging +import math +from datetime import datetime +from http import server +from http.server import HTTPServer, BaseHTTPRequestHandler +from ssl import wrap_socket +from urllib.parse import urlparse, parse_qs +import pprint +from pprint import pprint + +class LabelPrinterRequestHandler(BaseHTTPRequestHandler): + def do_HEAD(self): + self.send_response(200); + self.send_header("Content-type", "text/html") + self.end_headers() + + def do_GET(self): + self.send_response(200); + self.send_header("Content-type", "text/html") + self.end_headers() + + def popstr(self, param): + #print ("""TYPE : "%s" """%type(param)) + #print ("""TYPE0: "%s" """%type(param[0])) + if type(param) is list: + #print("""LIST: "%s", """%param[0]) + return str(param[0]) + return str(param) + + def do_POST(self): + html_filename = "/tmp/label.html" + printer = "" + hostname = ""; + mac_address = ""; + model = ""; + serial = ""; + + length = int(self.headers['Content-Length']) + field_data = self.rfile.read(length).decode("utf-8") + print("Field_data: %s\n"%field_data); + fields = parse_qs(field_data) + #pprint(fields) + + #for name in fields: + # print("""key %s: "%s" %s""" % (name, self.popstr(fields[name]), type(fields[name]))) + if "printer" in fields: + printer = self.popstr(fields["printer"]) + if (printer is None) or ("" == printer): + err_msg = "printer empty or unset" + self.send_resonse(400) + self.send_header("X-Error", err_msg) + self.end_headers() + self.wfile.write(b"
  • %s\n"%err_msg) + return + else: + err_msg = "printer not submitted" + self.send_response(400) + self.send_header("X-Error", err_msg) + self.end_headers(); + self.wfile.write(b"
  • %s\n" % err_msg); + return + + if "mac" in fields: + mac_address = self.popstr(fields["mac"]) + if (mac_address is None) or ("" == mac_address): + err_msg = "mac address empty or unset" + self.send_resonse(400) + self.send_header("X-Error", err_msg) + self.end_headers() + self.wfile.write(b"
  • %s\n"%err_msg) + return + else: + err_msg = "mac address not submitted" + self.send_response(400) + self.send_header("X-Error", err_msg) + self.end_headers(); + self.wfile.write(b"
  • %s\n" % err_msg); + return + + if "model" in fields: + model = self.popstr(fields["model"]) + if (model is None) or (model == ""): + err_msg = "model name not submitted" + self.send_reponse(400) + self.send_header("X-Error", err_msg) + self.end_headers() + self.wfile.write(b"
  • %s\n"%err_msg) + return + else: + err_msg = "model name not submitted" + self.send_response(400) + self.send_header("X-Error", err_msg) + self.wfile.write(b"
  • %s\n" % err_msg); + return + + if "hostname" in fields: + hostname = self.popstr(fields["hostname"]) + else: + suffix = mac_address[-5:].replace(":", "") + hostname = "%s-%s"%(model, suffix) + + if "serial" in fields: + serial = self.popstr(fields["serial"]) + else: + serial = hostname + + now = datetime.now() + datestr = now.strftime("%Y-%m") + + self.send_response(200); + self.send_header("Content-type", "text/html") + self.end_headers() + label_html = self.html_template(model_=model, mac_=mac_address, hostname_=hostname, serial_=serial, datestr_=datestr) + + if os.path.exists(html_filename): + try: + os.remove(html_filename) + except: + err_msg = "unable to remove html file" + self.send_response(400) + self.send_header("X-Error", err_msg) + self.wfile.write(b"
  • %s\n" % err_msg); + return + + try: + file = open(html_filename, "w") + file.write(label_html) + file.close() + except: + err_msg = "unable to write html file" + self.send_response(400) + self.send_header("X-Error", err_msg) + self.wfile.write(b"
  • %s\n" % err_msg); + return + + self.print_pdf(printer=printer, html=html_filename) + + self.wfile.write(b"Success\n") + + def html_template(self, model_="unset", mac_="unset", hostname_="unset", serial_="unset", datestr_="unset"): + template = """ + + + + + + + + + + + + + + + +
    Model:%s (%s)
    MAC:%s
    Hostname:%s
    Serial:#%s
    """ % (model_, datestr_, mac_, hostname_, serial_) + return template + + def print_pdf(self, printer, html ): + """ + + :param printer: + :param html: + :return: + """ + """ Below is shell script that worked: + +w_inches="3.45" +h_inches="1.125" +w_points=`echo "scale=0; ($w_inches * 72)/1" | bc -l` +w_px=`echo "scale=0; ($w_inches * 720)/1" | bc -l` +h_points=`echo "scale=0; ($h_inches * 72)/1" | bc -l` +h_px=`echo "scale=0; ($h_inches * 720)/1" | bc -l` + +echo "Page size in Points: $w_points x $h_points" +echo "Page size in pixels: $w_px x $h_px" + +rm -f label.pdf + +set -x +html2ps -L label.html \ +| gs -o label.pdf \ + -g${h_px}x${w_px} \ + -sDEVICE=pdfwrite \ + -dFIXEDMEDIA \ + -dPDFFitPage \ + -dFitPage \ + -c '<> setpagedevice' \ + -f - +""" + w_inches = 3.45 + h_inches = 1.125 + w_points = math.floor(w_inches * 72) + w_px = math.floor(w_inches * 720) + h_points = math.floor(h_inches * 72) + h_px = math.floor(h_inches * 720) + pdf_file = "/tmp/label.pdf" + pageoffset = "<> setpagedevice" + geometry = "-g{}x{}".format(h_px, w_px) + if os.path.exists(pdf_file): + try: + os.remove(pdf_file) + except: + err_msg = "unable to remove html file" + self.send_response(400) + self.send_header("X-Error", err_msg) + self.wfile.write(b"
  • %s\n" % err_msg); + return + + cmd = """html2ps -L "%s" | gs -o "%s" %s -sDEVICE=pdfwrite -dFIXEDMEDIA -dPDFFitPage -dFitPage -c '%s' -f -""" \ + % (html, pdf_file, geometry, pageoffset) + print("CMD: "+cmd) + try: + os.system(cmd) + os.system("""lp -d "%s" -- "%s" """%(printer, pdf_file)) + except: + err_msg = "trouble printing pdf" + self.send_response(500) + self.send_header("X-Error", err_msg) + self.wfile.write(b"
  • %s\n" % err_msg); + return + + + + +def __main__(): + logging.info("Main Method. Creating CGI Handler") + httpd = HTTPServer(('', 8082), LabelPrinterRequestHandler) + print("Starting LabelPrinter service...") + httpd.serve_forever() + +if __name__ == "__main__": + __main__() + diff --git a/lanforge/lanforge-scripts/label-printer/label-printer.service b/lanforge/lanforge-scripts/label-printer/label-printer.service new file mode 100644 index 000000000..6add80fa4 --- /dev/null +++ b/lanforge/lanforge-scripts/label-printer/label-printer.service @@ -0,0 +1,14 @@ +# Candelatech Label Printer service + +[Unit] +Description=Simple Label Printer service on port 8082 + +[Service] +Type=simple +Restart=on-failure +RemainAfterExit=yes +ExecStart=/usr/bin/python3 /usr/local/bin/label-printer.py + +[Install] +WantedBy=multi-user.target + diff --git a/lanforge/lanforge-scripts/lcheck.sh b/lanforge/lanforge-scripts/lcheck.sh new file mode 100755 index 000000000..cee63c378 --- /dev/null +++ b/lanforge/lanforge-scripts/lcheck.sh @@ -0,0 +1,36 @@ +#!/bin/bash +#set -x +set -e +if [ -z "$1" ]; then + lic_file="/home/lanforge/license.txt" +else + lic_file="$1" +fi +if [ ! -f "$lic_file" ]; then + echo "File not found [$lic_file]" + exit 1 +fi +declare -a lic_lines +IFS=$'\n' lic_lines=(`cat $lic_file`) + +if [[ ${#lic_lines[@]} -lt 2 ]]; then + echo "Nothing found in [$lic_file]" + exit 1 +fi +NOW=`date +%s` +num_expired=0 +for line in "${lic_lines[@]}"; do + [[ "$line" = "" ]] && continue; + IFS=$' ' hunks=($line) + lastbit=${hunks[4]} + [[ "$lastbit" = "" ]] && echo "Unable to determine timecode for ${hunks[0]}" && continue + delta=$(( $lastbit - $NOW )) + if [[ $delta -lt 86200 ]] && [[ $delta -gt 0 ]] ; then + echo "${hunks[0]} expires today!" + elif [[ $delta -le 0 ]]; then + echo "${hunks[0]} expired" + ((num_expired++)) || true + fi +done +exit $num_expired +# diff --git a/lanforge/lanforge-scripts/lf_associate_ap.pl b/lanforge/lanforge-scripts/lf_associate_ap.pl new file mode 100755 index 000000000..c31350588 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_associate_ap.pl @@ -0,0 +1,1811 @@ +#!/usr/bin/perl -w +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## LANforge server script for associating virtual stations +## to an arbitrary SSID. You have options for creating a series +## of Layer-3 connections per station created. Support for various +## security modes for stations: wep, wpa, wpa2. +## +## Install: +## copy this script to /home/lanforge/scripts +## +## Preparation: +## This script expects a free radio (like wiphy0) to create +## wifi stations on. It also expects an upstream wired port to +## make tcp connections to. These ports should be able to +## communicate with each other. +## +## Usage Overview: +## Use -h to show options. +## There are two activities that this script presently performs: +## +## Step1: create 1 wifi station, pass traffic to the upstream port +## back and forth, and then disassociate. This activity could +## be split into several steps if testing traffic up- or +## down-stream only is desired. +## +## Step2: create many wifi stations, wait until we see IPs appear +## on them and then disassociate all of them. This activity could +## also be modified to test for wifi association instead of address +## aquisition. The present example uses static address assignment. +## +## add: create and delete WiFi Virtual Radios. Also has option to +## create station on specified virtual radio. +## +## (C) 2020, Candela Technologies Inc. support@candelatech.com +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +package main; +use strict; +use warnings; +use diagnostics; +use Carp; +#$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +#$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +use POSIX qw(ceil floor); +use Scalar::Util; #::looks_like_number; +use Getopt::Long; + +no warnings 'portable'; # Support for 64-bit ints required +use Socket; +#use Data::Dumper; +our $binsleep = 0; +if ( -x "/bin/sleep" || -x "/usr/bin/sleep") { + $::binsleep = 1; +} + +sub altsleep { + my ($time) = @_; + if ($::binsleep) { + `sleep $time`; + } + elsif ( $time < 1) { + sleep(1); + } + elsif (int($time) != $time) { + $time += 1.0; + $time = int($time); + sleep($time); + } + else { + sleep($time); + } +} + +# Un-buffer output +$| = 1; +use Cwd qw(getcwd); +my $cwd = getcwd(); + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use List::Util qw(first); +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +#use Net::Telnet (); + +our $num_stations = 1; +our $netmask = "255.255.0.0"; +our $default_ip_addr = "DHCP"; # or IP +my $log_cli = "unset"; # use ENV{'LOG_CLI'} + +# the upstream port should have an IP in same subnet range +# and we're assuming the port is on the same resource (1). +our $upstream_port = "eth1"; # Step 1 upstream port +our $sta_wiphy = "wiphy0"; # physical parent (radio) of virtual stations +our $phy_channel = ""; # channel number +our $phy_antenna = ""; # number of antennas, 0 means all +our %wiphy_bssids = (); +our $admin_down_on_add = 0; +our $ssid; +our $first_sta = "sta100"; +our $passphrase = ''; +our $change_mac = 0; +our $min_tx = "10000000"; +our $max_tx = "SAME"; +our $security = "open"; +our $xsec = ""; # extra 802.1* options: use-11u,use-11u-internet,use-dot1x +our %sec_options = ( + "open" => 0x0, + "wpa" => 0x10, + "wep" => 0x200, + "wpa2" => 0x400, + "no-ht40" => 0x800, # Disable ht-40 + "use-scan-ssid" => 0x1000, # Enable SCAN-SSID flag in wpa_supplicant. + "use-pasv-scan" => 0x2000, # Use passive scanning (don't send probe requests). + "no-sgi" => 0x4000, # Disable SGI (Short Guard Interval). + "use-radio-migration" => 0x8000, # OK-To-Migrate (Allow migration between LANforge radios) + "use-more-debug" => 0x10000, # Verbose-Debug: more info in wpa-supplicant and hostapd logs. + "use-11u" => 0x20000, # Enable 802.11u (Interworking) feature. + "use-11u-auto" => 0x40000, # Enable 802.11u (I...) Auto-internetworking. Always enabled currently. + "use-11u-internet" => 0x80000, # AP Provides access to internet (802.11u I...) + "use-11u-x-steps" => 0x100000, # AP requires additional step for access (802.11u I...) + "use-11u-emrg-advert" => 0x200000, # AP claims emergency services reachable (802.11u I...) + "use-11u-emrg-unauth" => 0x400000, # AP provides Unauthenticated emergency services (802.11u I...) + "use-hs20" => 0x800000, # Enable Hotspot 2.0 (HS20) feature. Req WPA-2. + "no-dgaf" => 0x1000000, # AP: Disable DGAF (used by HotSpot 2.0). + "use-dot1x" => 0x2000000, # Use 802.1x (RADIUS for AP). + "use-11r-pmska" => 0x4000000, # Enable PMSKA caching for WPA2 (Rel to 802.11r). + "no-ht80" => 0x8000000, # Disable HT80 (for AC chipset NICs only) + "use-ibss" => 0x20000000, # Station should be in IBSS mode. + "use-osen" => 0x40000000, # Enable OSEN protocol (OSU Server-only Auth) + "disable_roam" => 0x80000000, # Disable automatic station roaming based on scan results. + "ht160_enable" => 0x100000000, # Enable HT160 mode. + "disable_fast_reauth" => 0x200000000, # Disable fast_reauth option for virtual stations. + "mesh_mode" => 0x400000000, # Station should be in MESH mode. + "power_save_enable" => 0x800000000, # Station should enable power-save. May not work in all drivers/configurations. + "create_admin_down" => 0x1000000000, # Station should be created admin-down. + "wds-mode" => 0x2000000000, # WDS station (sort of like a lame mesh), not supported on ath10k + "no-supp-op-class-ie" => 0x4000000000, # Do not include supported-oper-class-IE in assoc requests. May work around AP bugs. + "txo-enable" => 0x8000000000, # Enable/disable tx-offloads, typically managed by set_wifi_txo command + "wpa3" => 0x10000000000, # Enable WPA-3 (SAE Personal) mode. + "use-bss-transition" => 0x80000000000, # Enable BSS transition. + "disable-twt" => 0x100000000000, # Disable TWT mode +); +our %ieee80211w_options = ( + "disabled" => 0, + "optional" => 1, + "required" => 2, + "0" => 0, + "1" => 1, + "2" => 2 +); +our $ieee80211w = "NA"; + +our $cx_type = "tcp"; +our %cx_types = ( + "tcp" => "lf_tcp", + "udp" => "lf_udp", + "tcp6" => "lf_tcp6", + "udp6" => "lf_udp6", +); +our %antenna_table = ( + 0 => 0, + "0" => 0, + -1 => 0, + '0' => 0, + '-1' => 0, + 'ALL' => 0, + + '1' => 1, + '1x1' => 1, + 'A' => 1, + + '2' => 4, + '2x2' => 4, + 'AB' => 4, + + '3' => 7, + '3x3' => 7, + 'ABC' => 7, + + '4' => 8, + '4x4' => 8, + 'ABCD' => 8, + ); + +our $duration = 30; # seconds to transmit in step 1 +our $db_preload = ""; # use for loading before station creation +our $db_save = ""; # use for saving a scenario that we just ran +our $db_postload = ""; # use for cleanup after running/saving a scenario +our $poll_time = 5; # seconds +our $traffic_type = "separate"; # separate: download then upload, concurrent: at same time +our $default_mac_pat = "xx:xx:xx:*:*:xx"; +our $mac_pattern = $::default_mac_pat; +our $gateway = "NA"; +our %wifi_modes = ( + "a" => "1", + "b" => "2", + "g" => "3", + "abg" => "4", + "abgn" => "5", + "bgn" => "6", + "bg" => "7", + "abgnAC" => "8", + "anAC" => "9", + "an" => "10", + "bgnAC" => "11", + "abgnAX" => "12", + "bgnAX" => "13", + "anAX" => "14" +); +our $wifi_mode =""; +our $bssid = ""; +my $mode_list = join("|", sort keys %wifi_modes); + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +## Usage # +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +my $usage = qq($0 [--mgr {host-name | IP}] + [--mgr_port {ip port}] # use if on non-default management port + [--resource {resource}] # use if multiple lanforge systems; defaults to 1 + [--quiet { yes | no }] # debug output; -q + [--log_cli] # enables CLI command printing to STDOUT + # same effect when setting env var LOG_CLI=STDOUT + ## AP selection + [--radio {name}] # e.g. wiphy2 + [--channel {channel}] # e.g. 52, 161, 153 + # please check the LANforge GUI to verify resulting selection + # center channels might be selected differently than you intended + [--antenna {1,2,3,4}] # select number of antennas + [--ssid {ssid}] # e.g. jedtest + [--bssid {aa:bb:cc:00:11:22, or DEFAULT} # AP BSSID to connect to + [--security {open|wep|wpa|wpa2|wpa3}] # station authentication type, Default is open + [--xsec {comma,separated,list} ] # dot1x, 11u, other features, read script {to set flags same as in add_sta} + [--passphrase {...}] # Set security too if you want to enable security + [--wifi_mode {$mode_list}] + [--ieee80211w {disabled,optional,required}] # protected management frames (wpa2-ent/wpa3) also { NA, 0, 1, 2 } + + ## station configuration + [--num_stations {$num_stations}] # Defaults to 1 + [--first_sta {$first_sta}] + [--first_ip {DHCP | DHCP6 | DHCP,DHCP6 |}] + # use DHCP,DHCP6 to enable both DHCP and DHCP6 + [--netmask {$netmask}] + [--gateway {$gateway}] + [--change_mac {0|1}] + # If this is set to 0, then we will not change MAC if the station already exists. + # This is now the default behaviour. + [--mac-pattern {$default_mac_pat}] + # xx : uses parent radio octet + # [0-9a-f] : use this value for octet + # * : generates random octet + # Use quotes around this argument! EG: + # --mac_pattern '00:xx:*:*:xx:xx' + + ## connection configuration + [--cxtype {tcp/tcp6/udp/udp6}] # use a tcp/udp connection, default tcp + [--upstream {name|$upstream_port}] + # could be AP or could be port on LANforge + # connected to WAN side of AP + [--bps-min {$min_tx}] # minimum tx bps + [--bps-max {SAME|bps-value}] # maximum tx bps, use SAME or omit for SAME + [--duration {$duration}] # connection duration, seconds, default 60 + [--poll-time {$poll_time}] # nap time between connection displays + [--action {step1,step2,add,del,del_all_phy}] + # step1: creates stations and L3 connections + # step2: does bringup test + # add: creates station on specified radio, or radio if no stations are requested + # del: Delete the specified port. + # del_all_phy: Delete all interfaces with the specified parent device. + + [--traffic_type {separate|concurrent}] + # for step1: separate does download then upload + # concurrent does upload and download at same time + + [--admin_down_on_add] + # when creating stations, create them admin-down + + [--db_preload {scenario name}] + # load this database before creating stations + # option intended as a cleanup step + + [--db_save {name}] + # save the state of this test scenario after running the + # connections, before --db_postload + + [--db_postload {scenario name}] + # load this database after running connections, + # option intended as a cleanup step + + ## virtual radio configuration + [--vrad_chan {channel}] + [--port_del {name}] # deletes port given + +Examples: +## connecting to an open AP, at 2Mbps, for 20 minutes + $0 --action step1 --radio wiphy0 --ssid ap-test-01 \\ + --bps-min 2000000 --duration 1200 --upstream eth1 + + $0 --action step2 --radio wiphy2 --ssid jedtest \\ + --first_sta sta100 --first_ip DHCP --num_stations 3 \\ + --security wpa2 --passphrase jedtest1 --mac_pattern 'xx:xx:xx:*:*:*' + Note: mac_pattern is NOT a regex, it is octet based tokens: + * = rand(256) + xx = parent mac octet + You can specify a numeric mac address (d0:01:00:00:af:ff) and it can get incremented + +## using a second lanforge system to connect to wpa2 AP: + $0 --mgr 192.168.100.1 --resource 2 --radio wiphy2 \\ + --ssid jedtest --passphrase 'asdf1234' \\ + --num_stations 10 --first_sta sta400 \\ + --first_ip DHCP --upstream eth1 --action step1 + +## (Windows) using a beginning database and saving the resulting database: + C:\\Users\\bob> cd "c:\\Program Files (x86)\\LANforge-Server\\scripts" + C:\\Program Files (x86)\\LANforge-Server\\scripts>perl lf_associate_ap.pl --mgr jedtest \\ + --resource 2 --radio wiphy2 --first_ip DHCP \\ + --duration 10 --bps-min 10k --bps-max 20M --cxtype tcp \\ + --ssid jedtest --passphrase jedtest1 --security wpa2 \\ + --first_sta 300 --db_preload Radio2 --db_save run_results --num_stations 3 + +## connecting to wpa AP: +$0 --mgr 192.168.100.1 --radio wiphy0 \\ + --ssid jedtest --passphrase 'asdf1234' --security wep \\ + --num_stations 10 --first_sta sta400 \\ + --first_ip DHCP --upstream eth1 --action step1 + +## creating and deleting a virtual radio: + $0 --mgr 192.168.100.1 --resource 2 \\ + --radio vphy1 --vrad_chan 36 --action add + + $0 --mgr 192.168.100.1 --resource 2 \\ + --port_del vrad1 --action del + +## Adding a station to a new or existing virtual radio: + $0 --mgr 192.168.100.1 --resource 2 \\ + --radio vphy1 --first_sta sta0 --first_ip DHCP --ssid my_ssid --action add + +## Add lots of stations to a radio + + $0 --mgr ben-ota-1 --resource 2 --action add --radio wiphy0 --ssid Lede-ventana \\ + --first_sta sta100 --first_ip DHCP --num_stations 63 + +## Delete all virtual devices on wiphy0 + + $0 --mgr ben-ota-1 --resource 2 --action del_all_phy --port_del wiphy0 + +## Create a station and set Flags + + $0 --mgr localhost --radio wiphy1 --ssid sushant-AP --action add --num_stations 1 \\ + --xsec use-bss-transition --first_ip DHCP +); + +my $shelf_num = 1; + +# Default values for cmd-line args. +our $report_timer = 1000; # milliseconds +our $test_mgr = "default_tm"; # name of test manager +our $resource = 1; # might be referred to as card_id +our $resource2 = 1; # might be referred to as card_id +our $begin_ip = $default_ip_addr; + +# sta_names is a set of names and static IP addresses to assign them. +# As many stations as are in the set will be created. If you want to use +# DHCP, replace the ip with "DHCP". +# example +# %sta_names = ( +# "sta1" => "192.168.0.1", +# "sta2" => "NEXT" +# "sta2" => "DHCP" +#); +our %sta_names = (); +our %cx_names = (); +our $quiet = "yes"; # debugging +our $action = "step1"; # default action +our $lfmgr_host = "localhost"; # LANforge manager IP + +# Virtual radio defaults. +our $vrad_chan = -1; # default channel (AUTO) + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# Nothing to configure below here, most likely. +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +my $lfmgr_port = 4001; # LANforge manager port +our $quiesce_sec = 3; # pretty standard + +=pod +this fmt_cmd subroutine is now disabled, please use utils->fmt_cmd +=cut + +sub db_exists { + my $db_name = shift; + die ("::db_exists: called with blank database name. Did you mean EMPTY?") if ($db_name eq ""); + print "Looking for database $db_name ..."; + my @db_names = split("\n", $::utils->doAsyncCmd("show_dbs")); + my @match = grep { /^$db_name\/$/ } @db_names; + return 1 if (@match > 0); + + print "Warning! Scenario $db_name not found among: ".join(", ", @db_names)."\n"; + return 0; +} + +sub load_db { + my $db_name = shift; + die ("::load_db: called with blank database name. Did you mean EMPTY?") if ($db_name eq ""); + print "Loading database $db_name ..."; + $::utils->doCmd(fmt_cmd("load", $db_name, "overwrite")); + + for (my $i = 20 ; $i>0; $i--) { + sleep(1); + my $up = 0; + my $has_tx_bytes = 0; + my $sta_cnt = 0; + my $prev_cnt = 0; + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_ports", 1, $::resource, "ALL")); + my @status = split("\n", $status); + + foreach (@status){ + if (/^Shelf: 1, Card: \d+\s+Port: \d+\s+Type: STA\s+/) { + $sta_cnt++; + print "sta_cnt $sta_cnt up $up has_tx %has_tx_bytes\n"; + } + if ($sta_cnt > $prev_cnt) { + if ( /IP: \d+\.\d+\.\d+\.\d+ / && !/IP: 0\.0\.0\.0 /) { + $up++; + } + if ( /Txb: \d+ / && !/Txb: 0 / ) { + $has_tx_bytes ++; + } + $prev_cnt = $sta_cnt if ( /^\s*$/ ); + } + } # ~foreach + } + print " done\n"; +} + +sub save_db { + my $db_name = shift; + die ("::save_db: called with blank database name. Please debug.") if ($db_name eq ""); + print "Saving database $db_name ..."; + if (db_exists($db_name)==1) { + print "Warning: will over-write database $db_name! "; + } + $::utils->doCmd($::utils->fmt_cmd("save", $db_name)); + print " done\n"; +} + +sub get_radio_bssid { + my $radio_name = shift; + die ("::get_radio_bssid: blank radio name. Please debug.") if ($radio_name eq ""); + + return $::wiphy_bssids{ $radio_name } + if (exists($::wiphy_bssids{ $radio_name })); + + #print "* looking up $radio_name for bssid..."; + my @status_lines = split("\n", $::utils->doAsyncCmd($::utils->fmt_cmd("show_port", 1, $::resource, $radio_name))); + my @mac_lines = grep { /\s+MAC:\s+[^ ]+/ } @status_lines; + die ("::get_radio_bssid: failed to find radio bssid, no MAC lines") + if (@mac_lines < 1); + + my ($parent_bssid) = $mac_lines[0] =~ /\s+MAC:\s+([^ ]+)/; + die ("::get_radio_bssid: failed to find radio bssid, MAC was empty") + if ($parent_bssid eq ""); + + $::wiphy_bssids{ $radio_name } = $parent_bssid; + #print $parent_bssid."\n"; + + return $parent_bssid; +} + +sub new_mac_from_pattern { + my $parent_mac = shift; + my $pattern = shift; + die ("::new_mac_pattern: blank parent_mac. Please debug.") if ($parent_mac eq ""); + die ("::new_mac_pattern: blank pattern. Please debug.") if ($pattern eq ""); + + if (($pattern !~ /x+/i) && ($pattern !~ /[*]+/)) { + return $pattern; # this lacks pattern tokens + } + + my @parent_hunks = split(":", $parent_mac); + my @pattern_hunks = split(":", $pattern); + + die ("::new_mac_pattern: parent_mac needs to be colon-separated. Please debug.") if (@parent_hunks != 6); + die ("::new_mac_pattern: pattern needs to be colon-separated. Please debug.") if (@pattern_hunks != 6); + + my @new_hunks = (); + for (my $i=0; $i < 6; $i++) { + if ($pattern_hunks[$i] =~ /xx/i) { + $new_hunks[ $i ] = $parent_hunks[ $i ]; + } + elsif ($pattern_hunks[$i] =~ /[*]+/) { + my $r=int(rand(255)); + if ($i == 0) { + $r |= 0x002; # sets the 'locally administered bit' + $r &= 0x0FE; + # use if this upstream routers squash local admin bit macs + # $r &= 0x0DF; + } + $new_hunks[ $i ] = sprintf("%02X", $r); + } + else { + $new_hunks[ $i ] = $pattern_hunks[ $i ]; + } + } + #print "####### new_mac_from_pattern: [$parent_mac][$pattern] -> ".lc(join(":", @new_hunks))."\n"; + return lc(join(":", @new_hunks)); +} # ~new_mac_pattern + +sub new_random_mac { + my $rv = "00:"; + for (my $i=0; $i<5; $i++) { + $rv.=sprintf("%02X",int(rand(255))).(($i<4)?':':''); + } + #print "new_random_mac $rv\n"; + return $rv; +} + +sub fmt_vsta_cmd { + my ($resource, $sta_wiphy, $sta_name, $flags, $ssid, $passphrase, $mac, $flags_mask, $wifi_m, $bssid ) = @_; + die("fmt_vsta_cmd wants sta_wiphy name, bye.") unless($sta_wiphy); + my $key = "[BLANK]"; + my $ap = "DEFAULT"; + if ((defined $bssid) && ($bssid ne "")) { + $ap = $bssid; + } + my $cfg_file = "NA"; + my $mode = 8; # default to a/b/g/n/AC + my $rate = "NA"; + my $amsdu = "NA"; + my $ampdu_factor = "NA"; + my $ampdu_density = "NA"; + my $sta_br_id = "NA"; + $key = $passphrase if ($passphrase ne ""); + + if ($wifi_m ne "") { + if (exists $::wifi_modes{$wifi_m}) { + $mode = $::wifi_modes{$wifi_m}; + } + else { + print "Wifi Mode [$wifi_m] not recognised. Please use:\n"; + print join(", ", sort keys %::wifi_modes); + exit 1; + } + } + + $flags = "+0" if ($flags == 0); # perl goes funny on zeros + $flags_mask = "+0" if ($flags_mask == 0); + $flags = "NA" if ($flags eq ""); + + $::ieee80211w = "NA" + if (!(defined $::ieee80211w) || ($::ieee80211w eq "")); + if ($::ieee80211w ne "NA") { + if ( exists $::ieee80211w_options{ $::ieee80211w }) { + $::ieee80211w = $::ieee80211w_options{ $::ieee80211w }; + } + elsif ((int($::ieee80211w) < 0) || (int($::ieee80211w) > 2)) { + print("\n* ieee80211w value outside of values {0, 1, 2} or {disabled, optional, required} -- being set to NA\n"); + $::ieee80211w = "NA"; + } + # print("\n* ieee80211w value set to $::ieee80211w \n"); + } + + return $::utils->fmt_cmd("add_sta", 1, $resource, $sta_wiphy, $sta_name, "$flags", + "$ssid", "NA", "$key", $ap, $cfg_file, $mac, + $mode, $rate, $amsdu, $ampdu_factor, $ampdu_density, + $sta_br_id, "$flags_mask", $::ieee80211w ); +} + +sub fmt_vrad_cmd { + my ($resource, $sta_wiphy, $vrad_chan ) = @_; + die("fmt_vrad_cmd requires sta_wiphy.") unless($sta_wiphy); + my $mode = "NA"; + my $country = "NA"; + my $frequency = "NA"; + my $frag_thresh = "NA"; + my $rate = "NA"; + my $rts = "NA"; + my $txpower = "NA"; + my $mac = "NA"; + my $antenna = "NA"; + my $flags = "0x1"; + my $flags_mask = "NA"; + return $::utils->fmt_cmd("set_wifi_radio", 1, $resource, $sta_wiphy, $mode, $vrad_chan, + $country, $frequency, $frag_thresh, $rate, $rts, $txpower, + $mac, "$antenna", "$flags", "$flags_mask" ); +} + +sub createEpPair { + my $sta_name = shift; + die("createEpPair: please pass station name, bye") unless(defined $sta_name && $sta_name ne ''); + die("createEpPair: please define upstream_port, bye") unless(defined $::upstream_port && $::upstream_port ne ''); + my $port_a = $sta_name; + my $port_b = $::upstream_port; + my $cx_name = $::cx_names{$sta_name}->{"cx"}; + my $ep1 = $::cx_names{$sta_name}->{"ep1"}; + my $ep2 = $::cx_names{$sta_name}->{"ep2"}; + my %min_pkt_szs = ( + 'tcp' => [ 1460, 1460 ], + 'tcp6' => [ 1460, 1460 ], + 'udp' => [ 1472, 1472 ], + 'udp6' => [ 1472, 1472 ] + ); + my %max_pkt_szs = ( + 'tcp' => [ 1460, 1460 ], + 'tcp6' => [ 1460, 1460 ], + 'udp' => [ 1472, 1472 ], + 'udp6' => [ 1472, 1472 ] + ); + $::cx_type = "tcp" if ($::cx_type eq ""); + print "\n cxtype [$::cx_type]\n" unless($::utils->isQuiet()); + if ( ! exists $::cx_types{$::cx_type} ) { + die( "Please choose connection type: ".join(", ", keys(%::cx_types))); + } + my $cxtype = $::cx_types{$::cx_type}; + my $rate_min = "+0"; # we will set these later + my $rate_max = "+0"; # using set_endp_tx_bounds + + die("createEpPair: wants cx_name, bye.") unless(defined $cx_name && $cx_name ne ''); + die("createEpPair: wants ep1 name, bye.") unless(defined $ep1 && $ep1 ne ''); + die("createEpPair: wants ep2 name, bye.") unless(defined $ep2 && $ep2 ne ''); + + my $cmd = $::utils->fmt_cmd("add_endp", $ep1, 1, $::resource, $port_a, $cxtype, + -1, "NA", "$rate_min", "$rate_max", "NA", + $min_pkt_szs{$::cx_type}[0], @{$max_pkt_szs{$::cx_type}}[0], + "increasing", "NO", "NA", "NA", "NA"); + print "EP1: $cmd\n" unless($::utils->isQuiet()); + $::utils->doCmd($cmd); + + $cmd = $::utils->fmt_cmd("add_endp", $ep2, 1, $::resource2, $port_b, $cxtype, + -1, "NA", "$rate_min", "$rate_max", "NA", + $min_pkt_szs{$::cx_type}[1], @{$max_pkt_szs{$::cx_type}}[1], + "increasing", "NO", "NA", "NA", "NA"); + print "EP2: $cmd\n" unless($::utils->isQuiet()); + $::utils->doCmd($cmd); + + # Now, add the cross-connect + $::utils->doCmd($::utils->fmt_cmd("add_cx", $cx_name, $::test_mgr, $ep1, $ep2)); + $::utils->doCmd($::utils->fmt_cmd("set_cx_report_timer", $::test_mgr, $cx_name, $::report_timer)); +} + +sub fmt_port_cmd { + my($resource, $port_id, $ip_addr, $mac_addr) = @_; + my $use_dhcp = ($ip_addr =~ /\bDHCP\b/) ? 1 : 0; + my $use_dhcp6 = ($ip_addr =~ /\bDHCP6\b/) ? 1 : 0; + my $ip = ($use_dhcp||$use_dhcp6) ? "0.0.0.0" : $ip_addr ; + $mac_addr = die("fmt_port_cmd requires mac_addr") if(!$mac_addr); # || $mac_addr eq "NA"); + #print "fmt_port_cmd: RES $resource PORT $port_id IP_A $ip_addr MAC $mac_addr -> $ip\n" unless($::quiet eq "yes"); + my $cmd_flags = 'NA'; #0; + my $cur_flags = 0; + $cur_flags |= 0x80000000 if ($use_dhcp); + $cur_flags |= 0x20000000000 if ($use_dhcp6); + #print "fmt_port_cmd: DHCP($use_dhcp) $cur_flags\n" unless($::quiet eq "yes"); + my $ist_flags = 0; + $ist_flags |= 0x2; # check current flags + $ist_flags |= 0x4 if ($ip ne "NA"); + $ist_flags |= 0x8 if ($::netmask ne "NA"); + $ist_flags |= 0x10 if (($::gateway ne "NA") || ($::gateway ne "") || ($::gateway ne "0.0.0.0")); + $ist_flags |= 0x20 if ($mac_addr ne "NA"); + $ist_flags |= 0x4000; # Always interested in DHCP, we either set it to DHCP or IP + $ist_flags |= 0x800000; # port up + $ist_flags |= 0x1000000; # Always interested in DHCP, we either set it to DHCP or IP + + + my $gw = "0.0.0.0"; + if (($::gateway ne "") || ($::gateway ne "") || ($::gateway ne "0.0.0.0")) { + $gw = $::gateway; + } + + my $dns_servers = "NA"; + my $dhcp_client_id = "NONE"; + my $flags2 = "NA"; + + # Ben suggests using $sta_name before using $port_id + $cur_flags = "+0" if(!$cur_flags); + $cmd_flags = "+0" if(!$cmd_flags); + $ist_flags = "+0" if(!$ist_flags); + my $cmd = $::utils->fmt_cmd("set_port", 1, $::resource, $port_id, $ip, $::netmask, + $gw, "$cmd_flags", "$cur_flags", + "$mac_addr", "NA", "NA", "NA", "$ist_flags", $::report_timer, "$flags2", + "NA","NA","NA","NA","NA","NA","NA","NA","NA","NA","NA","NA","NA", + $dns_servers, "NA", $dhcp_client_id); + print("fmt_port_cmd: ".$cmd) unless($::utils->isQuiet()); + return $cmd; +} + +sub fmt_port_down { + my($resource, $port_id, $ip_addr, $ip_mask) = @_; + die("fmt_port_down wants resource id, bye.") unless($resource); + die("fmt_port_down wants port_id id, bye.") unless($port_id); + die("fmt_port_down wants ip_addr id, bye.") unless($ip_addr); + die("fmt_port_down wants ip_mask id, bye.") unless($ip_mask); + + my $use_dhcp = ($ip_addr =~ /\bDHCP\b/) ? 1 : 0; + my $use_dhcp6 = ($ip_addr =~ /\bDHCP6\b/) ? 1 : 0; + my $ip = ($use_dhcp||$use_dhcp6) ? "0.0.0.0" : $ip_addr ; + my $cmd_flags = "NA"; + my $cur_flags = 0; + $cur_flags |= 0x1; # port down + my $ist_flags = 0; + $ist_flags |= 0x2; # check current flags + $ist_flags |= 0x800000; # port down + my $dhcp_id = "NONE"; + my $netmask = "$ip_mask"; + my $gw = (($::gateway eq "NA") || ($::gateway eq "") || ($::gateway eq "0.0.0.0")) ? "0.0.0.0" : $::gateway; + my $dns_servers = "NA"; + my $dhcp_client_id = "NONE"; + my $flags2 = "NA"; + + $cmd_flags = "+0" if(!$cmd_flags); # zeros are falsy in perl + $cur_flags = "+0" if(!$cur_flags); + $ist_flags = "+0" if(!$ist_flags); + my $cmd = $::utils->fmt_cmd("set_port", 1, $resource, $port_id, $ip_addr, + $netmask, $gw, "$cmd_flags", "$cur_flags", + "NA", "NA", "NA", "NA", "$ist_flags", $::report_timer, "$flags2", + "NA","NA","NA","NA","NA","NA","NA","NA","NA","NA","NA","NA","NA", + $dns_servers, "NA", $dhcp_client_id); + return $cmd; +} + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----# +# WiFi FLAGS # +# and please see the CLI users guide (flags can get updated) # +# http://www.candelatech.com/lfcli_ug.php # +# # +# 0x10 Enable WPA # +# 0x20 Use Custom wpa_supplicant config file. # +# 0x100 Use wpa_supplicant configured for WEP encryption. # +# 0x200 Use wpa_supplicant configured for WPA2 encryption. # +# 0x400 Disable HT-40 even if hardware and AP support it. # +# 0x800 Enable SCAN-SSID flag in wpa_supplicant. # +# 0x1000 Enable PCSC (used by WPA-SIM) # +# 0x2000 Disable SGI (Short Guard Interval). # +# 0x4000 OK-To-Migrate (Allow migration between LANforge radios) # +# 0x8000 Verbose-Debug: Increase debug info in wpa-supplicant and hostapd logs. # +# 0x10000 Enable 802.11u (Interworking) feature. # +# 0x20000 Enable 802.11u (Interworking) Auto-internetworking feature. # +# 0x40000 AP Provides access to internet (802.11u Interworking) # +# 0x80000 AP requires additional step for access (802.11u Interworking) # +# 0x100000 AP claims emergency services reachable (802.11u Interworking) # +# 0x200000 AP provides Unauthenticated emergency services (802.11u Interworking) # +# 0x400000 Enable Hotspot 2.0 (HS20) feature. Requires WPA-2. # +# 0x800000 AP: Disable DGAF (used by HotSpot 2.0). # +# 0x1000000 Use 802.1x (RADIUS for AP). # +# 0x2000000 Enable oportunistic PMSKA caching for WPA2 (Related to 802.11r). # +# # +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----# +sub new_wifi_station { + my $sta_name = shift; + die("new_wifi_station wants station name, bye") unless(defined $sta_name && $sta_name ne ''); + my $ip_addr = shift; + die("new_wifi_station wants ip_address, bye") unless(defined $ip_addr && $ip_addr ne ''); + my $rh_results = shift; + die("new_wifi_station wants hash ref to place results, bye.") unless(defined $rh_results); + my $wifi_m = shift; + my $num_in_series = shift; # use this to add to non-patterned mac-address + my $mac_addr = ""; + + #print "## new-wifi-station, sta-name: $sta_name change-mac: $change_mac" unless($::utils->isQuiet()); + + if (! $::change_mac) { + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("show_port", 1, $::resource, $sta_name)); + if ($status =~ /MAC:\s+(\S+)\s+/) { + $mac_addr = $1; + } + } + #print "new_wifi_station->mac_addr: $mac_addr\n"; + if ($mac_addr eq "") { + # Couldn't find it, or we want to change the mac + #print "## calculating new mac-addr.." unless($::utils->isQuiet()); + my $parent_mac = get_radio_bssid($::sta_wiphy); + die("new_wifi_station: unable to find bssid of parent radio") if ($parent_mac eq ""); + $mac_addr = new_mac_from_pattern($parent_mac, $::mac_pattern); + #print "OLD MAC $::mac_pattern NEW MAC $mac_addr\n"; + if (($mac_addr eq $::mac_pattern) && ($num_in_series > 0)) { + $mac_addr = $::utils->mac_add($::mac_pattern, $num_in_series); + } + #print "OLD MAC $::mac_pattern NEWER MAC $mac_addr\n"; + #print "new_wifi_station->new_mac_from_pattern: $mac_addr\n"; + } + + #print "## $sta_name $mac_addr; " unless($::utils->isQuiet()); + my $flags = +0; # set this to string later + my $flagsmask = +0; # set this to string later + + # To set zero value set the bit in flags to zero. + # Set the flagsmask value to 1 if you want the value to be set to 1 or 0. + # NOTE: This script is used to change things, not just create them, so it is not + # always wrong to not set passphrase since it could be set already. + if ($::passphrase eq "") { + $::passphrase = "NA"; + #if($::security ne "open") { + # die("Passphrase not set when --security [$::security] chosen. Please set passphrase."); + #} + } + if ( ! exists($::sec_options{$::security})) { + die( "Unknown security option [{$::security}]"); + } + $flags |= $::sec_options{$::security}; + # This doesn't work since 'open' maps to 0x0 and we need to also disable + # flags that might be set previously. + # $flagsmask |= $::sec_options{$::security}; + # We are always configuring security to one thing or another, so we need to + # mask all of the bits properly. + $flagsmask |= (0x10 | 0x200 | 0x400 | 0x10000000000); + $flagsmask |= 0x1000000000 if ($::admin_down_on_add); + + if (defined $::xsec && "$::xsec" ne "") { + for my $sec_op (split(',', $::xsec)) { + next if (!defined $::sec_options{$sec_op}); + + $flags |= $::sec_options{$sec_op}; + $flagsmask |= $::sec_options{$sec_op}; + } + } + $flags = "+0" if ( $flags == 0); + $flagsmask = "+0" if ( $flagsmask == 0); + # perform the station create first, then assign IP as necessary + my $sta1_cmd = fmt_vsta_cmd($::resource, $::sta_wiphy, $sta_name, + "$flags", "$::ssid", "$::passphrase", + $mac_addr, "$flagsmask", $wifi_m, $::bssid); + $::utils->doCmd($sta1_cmd); + #$::utils->sleep_ms(20); + $sta1_cmd = fmt_port_cmd($resource, $sta_name, $ip_addr, $mac_addr); + $::utils->doCmd($sta1_cmd); + #$::utils->sleep_ms(20); + #$::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_port", 1, $::resource, $sta_name)); + if ($::admin_down_on_add) { + my $cur_flags = 0x1; # port down + my $ist_flags = 0x800000; # port down + $sta1_cmd = $::utils->fmt_cmd("set_port", 1, $resource, $sta_name, "NA", + "NA", "NA", "NA", "$cur_flags", + "NA", "NA", "NA", "NA", "$ist_flags"); + $::utils->doCmd($sta1_cmd); + #$::utils->sleep_ms(20); + } + + #if ($sleep_amt > 0) { + # sleep $sleep_amt; + #} + my $data = [ $mac_addr, $sta_name, $sta1_cmd ]; + $rh_results->{$sta_name} = $data; +} + +sub new_wifi_radio { + my $cmd = fmt_vrad_cmd($::resource, $::sta_wiphy, $::vrad_chan ); + $::utils->doCmd($cmd); +} + +sub delete_port { + if (defined $::port_del) { + print "deleting port $::port_del\n" unless($::utils->isQuiet()); + $::utils->doCmd($::utils->fmt_cmd("rm_vlan", 1, $::resource, $::port_del)); + $::utils->sleep_ms(20); + } +} + +sub get_sta_state { + my($rs_status) = @_; + die("is_assoc_state: wants ref to status string") unless($rs_status); + my @lines = split(/\r?\n/, $$rs_status); + my $careful = 0; + my $name = "unknown"; + my $ip = "0.0.0.0"; + my $assoc = "unknown"; + my $first; + my $freq; + my @hunks; + my $mac; + my $gw; + my $mask; + my $channel; + my $mode; + my $probed_seen = 0; + for my $line (@lines) { + $first = "_"; + my($key) = $line =~ m/^\s*([^:]+:)\s+/; + #print "{{{$key}}} $line\n"; + next if ($line =~ /^\s*$/); + next if ($line =~ /RSLT:/); + last if ($line =~ /default@/); + + $probed_seen++ if ($line =~ /Probed/); + if ($key && $key eq "MAC:" ) { + @hunks = split(/: /, $line); + $mac = (split(/ /, $hunks[1]))[0]; + $name = (split(/ /, $hunks[2]))[0]; + next; + } + if ($key && $key eq "IP:") { + @hunks = split(/: /, $line); + $ip = (split(/ /, $hunks[1]))[0]; + $mask = (split(/ /, $hunks[2]))[0]; + $gw = (split(/ /, $hunks[3]))[0]; + next; + } + if ($probed_seen && ($line =~ /Mode:/)) { + @hunks = split(/: /, $line); + $careful = 1; + $mode = (split(/ /, $hunks[2]))[0]; + next; + } + if( $probed_seen && $careful && ($key eq "Channel:")) { + @hunks = split(/: /, $line); + #print Dumper(\@hunks); + $channel = (split(/ /, $hunks[1]))[0]; + $freq = (split(/ /, $hunks[3]))[0]; + if ((@hunks > 3) && (defined $hunks[4])) { + $assoc = (split(/ /, $hunks[4]))[0]; + } + + } + } + my %rv = ( + "assoc" => $assoc, + "freq" => $freq, + "ip" => $ip, + "mask" => $mask, + "gw" => $gw, + "mac" => $mac, + "mode" => $mode, + "name" => $name ); + #print Dumper(\%rv); + return %rv; +} + +sub awaitStationRemoval { + my $old_sta_count = (keys %::sta_names); + print "Waiting for $old_sta_count stations to be removed..."; + while( $old_sta_count > 0 ) { + $old_sta_count = (keys %::sta_names); + for my $sta_name (sort(keys %::sta_names)) { + print " $sta_name,"; + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("show_port", 1, $::resource, $sta_name)); + $old_sta_count-- if( $status =~ m/Could not find/); + } + if ($old_sta_count > 0) { + #print "$old_sta_count..."; + sleep 1; + } + } + print " Old stations removed\n"; +} + +#~expand to multiple cross-connects +sub removeOldCrossConnects { + print "Removing old cross-connects, and endpoints ...\n"; + for my $sta_name (sort(keys(%::sta_names))) { + my $cx_name = $::cx_names{$sta_name}->{"cx"}; + my $ep1 = $::cx_names{$sta_name}->{"ep1"}; + my $ep2 = $::cx_names{$sta_name}->{"ep2"}; + $::utils->doCmd("rm_cx $::test_mgr $cx_name"); + $::utils->doCmd("rm_endp $ep1"); + $::utils->doCmd("rm_endp $ep2"); + print " $cx_name ($ep1 - $ep2)..."; + } + print " done.\n"; +} + +sub removeOldStations { + print "Deleting ports:"; + foreach my $sta_name (reverse sort(keys %::sta_names)) { + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("show_port", 1, $::resource, $sta_name)); + if ($status =~ /Type:/) { + # It exists, remove it + # + print "...$sta_name "; + $::utils->doCmd($::utils->fmt_cmd("rm_vlan", 1, $::resource, $sta_name)); + } + } + sleep(1); + # force a refresh on them so phantom doesn't show + foreach my $sta_name (reverse sort(keys %::sta_names)) { + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_port", 1, $::resource, $sta_name)); + } + print " done.\n"; +} + +sub awaitNewStations { + print "Waiting for stations to associate..."; + my $new_sta_count = keys(%::sta_names); + my $found_stations = 0; + while( $new_sta_count > $found_stations ) { + $found_stations = 0; + my @are_assoc = (); + my @not_assoc = (); + for my $sta_name (sort(keys(%::sta_names))) { + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("show_port", 1, $::resource, $sta_name)); + #print "STATUS: $status\n\n"; + my %sta_status = get_sta_state(\$status); + #print " $sta_name ".$sta_status{"assoc"}; + if( $sta_status{"assoc"} !~ /NA|Not-Associated|unknown/) { + push(@are_assoc, $sta_name); + } + else { + push(@not_assoc, $sta_name); + } + } #~foreach sta + $found_stations = @are_assoc; + print " $found_stations/$new_sta_count seen to associate\n"; + if ( $found_stations != $new_sta_count ){ + print " Associated:".join(", ", @are_assoc)."\n"; + print " Pending :".join(", ", @not_assoc)."\n"; + } + sleep 1; + } # ~while +} + +sub endpointReport { + my $ep = shift; + my ($ep_name, $tx_rate, $rx_rate,$rx_bps); + die("endpointReport: should be passed name of endpoint, bye.") unless ( $ep ne '' ); + my $blob = $::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_endpoints", "$ep"), "\n"); + #print "BLOB: $blob\n\n\n"; + ( $ep_name ) = ($blob =~ m/^Endpoint \[(.*?)\] /mg); + ( $tx_rate ) = ($blob =~ m/(Tx Bytes: .*$)/mg); + ( $rx_rate ) = ($blob =~ m/(Rx Bytes: .*$)/mg); + print "$ep_name:\t$tx_rate\n\t\t$rx_rate\n"; +} + +sub showEndpoints { + for my $sta_name (sort(keys(%::sta_names))) { + my $ep1 = $::cx_names{$sta_name}->{ep1}; + my $ep2 = $::cx_names{$sta_name}->{ep2}; + endpointReport($ep1); + endpointReport($ep2); + } +} + +sub createEndpointPairs { + print "\nCreating connections: "; + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + print " $cx ($sta_name - $::upstream_port), "; + createEpPair($sta_name); + } + print "done.\n"; +} + +sub evalUnits { + my $val = shift; + + if ($val =~ /^\d+$/) { + return +0+$val; + } + my $pow = 1; + if ($val =~ /(\d+)(\w+)/) { + my $pref = $1; + my $suff = $2; + if ($suff =~ /[Kk][Bbps]*$/) { + $pow = 1000; + } + elsif ($suff =~ /[Mm][Bbps]*$/) { + $pow = 1000000; + } + elsif ($suff =~ /[Gg][Bbps]*$/) { + $pow = 1000000000; + } + if ($pref == 0 || $pow == 0) { + print "Warning: speed coeficients [$pref,$pow] appear suspicious\n"; + } + my $speed =0 + ($pref * $pow); + #print ">>>> setting speed to $speed <<<<\n"; + return $speed; + } + print "Warning: speed[$val] appears suspicious\n"; + return $val; +} + +sub adjustForSimultaneous { + my $no_rate = "+0"; + if (lc($::min_tx) eq "same" ) { + die "--min_tx may not be 'same', please provide a number or formatted unit in K, M or G"; + } + my $rate_min = evalUnits($::min_tx); + #print "rate_min now: $rate_min\n"; + $::max_tx = $::min_tx if (lc($::max_tx) eq "same" ) ; + my $rate_max = evalUnits($::max_tx); + #print "rate_max now: $rate_max\n"; + print "Adjusting cx min/max tx for concurrent test: "; + + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + my $ep1 = $::cx_names{$sta_name}->{"ep1"}; + my $ep2 = $::cx_names{$sta_name}->{"ep2"}; + + #print "UPLOAD: ".$::utils->fmt_cmd("set_endp_tx_bounds", $ep1, "$rate_min", "$rate_max")."\n"; + #print "UPLOAD: ".$::utils->fmt_cmd("set_endp_tx_bounds", $ep2, "$no_rate", "$no_rate")."\n"; + + $::utils->doCmd($::utils->fmt_cmd("set_endp_tx_bounds", $ep1, "$rate_min", "$rate_max")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_tx_bounds", $ep2, "$rate_min", "$rate_max")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_quiesce", $ep1, "$::quiesce_sec")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_quiesce", $ep2, "$::quiesce_sec")); + } + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + $::utils->doCmd($::utils->fmt_cmd("set_cx_state", $::test_mgr, $cx, "RUNNING")); + print " $cx..."; + } + print "done.\n"; +} # ~adjustForDuplex + + +# adjust the transmit rate up for endpoint 1, and down for endpoint 2 +sub adjustForUpload { + my $no_rate = "+0"; + if (lc($::min_tx) eq "same" ) { + die "--min_tx may not be 'same', please provide a number or formatted unit in K, M or G"; + } + my $rate_min = evalUnits($::min_tx); + $::max_tx = $::min_tx if (lc($::max_tx) eq "same" ) ; + + my $rate_max = evalUnits($::max_tx); + + print "Adjusting cx min/max tx for upload test: "; + + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + my $ep1 = $::cx_names{$sta_name}->{"ep1"}; + my $ep2 = $::cx_names{$sta_name}->{"ep2"}; + + #print "UPLOAD: ".fmt_cmd("set_endp_tx_bounds", $ep1, "$rate_min", "$rate_max")."\n"; + #print "UPLOAD: ".fmt_cmd("set_endp_tx_bounds", $ep2, "$no_rate", "$no_rate")."\n"; + + $::utils->doCmd($::utils->fmt_cmd("set_endp_tx_bounds", $ep1, "$rate_min", "$rate_max")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_tx_bounds", $ep2, "$no_rate", "$no_rate")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_quiesce", $ep1, "$::quiesce_sec")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_quiesce", $ep2, "$::quiesce_sec")); + } + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + $::utils->doCmd($::utils->fmt_cmd("set_cx_state", $::test_mgr, $cx, "RUNNING")); + print " $cx..."; + } + print "done.\n"; +} # ~adjustForUpload + + +sub printShowEndpointStats { + my $lines = shift; + for my $line (split(/\n/, $lines)) { + if ($line =~ m/RealRxRate:/) { + my ($bps_rx) = ($line =~ m/RealRxRate: (\d+)bps /); + + if ($bps_rx >=1000000) { + $bps_rx = ceil($bps_rx / 1000000)."M"; + } + elsif ($bps_rx >= 1000) { + $bps_rx = ceil($bps_rx / 1000)."K"; + } + print " ${bps_rx}bps"; + } + if ($line =~ m/Tx Bytes:/) { + my ($tx) = ($line =~ m/Total:\s+(\d+)/); + if($tx >=(1024*1024)) { + $tx = ceil($tx / (1024*1024))."M"; + } + elsif ($tx >= 1024) { + $tx = ceil($tx / 1024)."K"; + } + print " / ${tx}B\t"; + } + } +} + +sub awaitTransfers { + my $begin_time = time; + my $end_time = $begin_time + $::duration; + my $lines; + #my $print_nap = 3; + my $passes = 0; + + for my $sta_name (sort(keys(%::sta_names))) { + my $ep1 = $::cx_names{$sta_name}->{"ep1"}; + my $ep2 = $::cx_names{$sta_name}->{"ep2"}; + print " $ep1 Rx-bps/Tx-B \t$ep2 Rx-bps/Tx-B |" + } + print "\n"; + while( time < $end_time ) { + sleep $::poll_time; + #if($passes == 0) { + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + my $ep1 = $::cx_names{$sta_name}->{"ep1"}; + my $ep2 = $::cx_names{$sta_name}->{"ep2"}; + + $lines = $::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_endpoints", "$ep1"), "\n"); + printShowEndpointStats($lines); + $lines = $::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_endpoints", "$ep2"), "\n"); + printShowEndpointStats($lines); + print " |"; + } + #$passes = $print_nap + 1; + print "\n"; + #} + #$passes--; + } +} # ~awaitUploads + +sub adjustForDownload { + my $no_rate = "+0"; + + if (lc($::min_tx) eq "same" ) { + die "--min_tx may not be 'same', please provide a number or formatted unit in K, M or G"; + } + my $rate_min = evalUnits($::min_tx); + $::max_tx = $::min_tx if (lc($::max_tx) eq "same" ) ; + my $rate_max = evalUnits($::max_tx); + + print "Adjusting tx_rate for download..."; + for my $sta_name (sort(keys(%::sta_names))) { + my $ep1 = $::cx_names{$sta_name}->{"ep1"}; + my $ep2 = $::cx_names{$sta_name}->{"ep2"}; + + #print "Download: ".fmt_cmd("set_endp_tx_bounds", $ep1, "$no_rate", "$no_rate")."\n"; + #print "Download: ".fmt_cmd("set_endp_tx_bounds", $ep2, "$rate_min", "$rate_max")."\n"; + + $::utils->doCmd($::utils->fmt_cmd("set_endp_tx_bounds", $ep1, "$no_rate", "$no_rate")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_tx_bounds", $ep2, "$rate_min", "$rate_max")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_quiesce", $ep1, "$::quiesce_sec")); + $::utils->doCmd($::utils->fmt_cmd("set_endp_quiesce", $ep2, "$::quiesce_sec")); + } + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + $::utils->doCmd($::utils->fmt_cmd("set_cx_state", $::test_mgr, $cx, "RUNNING")); + print " $cx..." + } + print " done\n"; +} # ~adjustForUpload + +sub quiesceConnections { + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + $::utils->doCmd($::utils->fmt_cmd("set_cx_state", $::test_mgr, $cx, "QUIESCE")); + } +} + +sub resetCounters { + for my $sta_name (sort(keys(%::sta_names))) { + my $cx = $::cx_names{$sta_name}->{"cx"}; + $::utils->doCmd("clear_cx_counters $cx"); + } + $::utils->doCmd("clear_endp_counters all"); +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## +## Create a virtual station and associate it with and SSID, +## then pass traffic to and from it. +## +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub doStep_1 { + my $sta_name = (sort(keys %::sta_names))[0]; + + + removeOldCrossConnects(); + sleep(2); + removeOldStations(); + sleep(1); + awaitStationRemoval(); + + sleep 1; + my $cmd; + my %results1 = (); + + # make sure that ::num_station and ::sta_names is consistent + + if ($::num_stations != (keys %::sta_names)) { + die "Unexpected difference between number of station names and num_stations, did num_stations not get set?"; + } + + # create stations + print " Creating new stations: "; + my $i = 0; + for $sta_name (sort(keys %::sta_names)) { + # sta, ip, rh, $ip_addr + print " $sta_name "; + new_wifi_station( $sta_name, $::sta_names{$sta_name}, \%results1, $::wifi_mode, $i); + altsleep(0.12); + altsleep(0.6) if (($i % 5) == 0); + $i++; + } + sleep(1); + #print "**************************************************\n"; + foreach my $sta_name (sort(keys %::sta_names)) { + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_port", 1, $::resource, $sta_name)); + } + sleep(1); + #print "**************************************************\n"; + + print " Created $::num_stations stations\n"; + sleep(1); + + my $new_sta_count = keys %results1; + my $found_stations = 0; + awaitNewStations(); + sleep 1; + + # we create a pair of connection endpoints and + # a cross-connect between them for every station + createEndpointPairs(); + sleep 5; + + if ($::traffic_type eq "separate") { + adjustForUpload(); + print " started uploads.\n"; + awaitTransfers(); + quiesceConnections(); + sleep 1+$::quiesce_sec; # the STOPPED signal might report short on packets because + # there might be queued packets in the backlog. If you need + # more precise readings, use the QUIESCE command which waits + # a specified number of seconds for all connections to close + + showEndpoints(); + resetCounters(); + # adjust the transmit rate down for endpoint 1, and up for endpoint 2 + adjustForDownload(); + print "\nStarted download...\n"; + awaitTransfers(); + } + elsif ($::traffic_type eq "concurrent") { + adjustForSimultaneous(); + print "\nStarted concurrent traffic...\n"; + awaitTransfers(); + } + else { + print "Unkown traffic_type $::traffic_type, exiting.\n"; + exit(1); + } + quiesceConnections(); + sleep 1+$::quiesce_sec; + showEndpoints(); +} # ~step1 + + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## +## Create a series of stations and associate them to +## the SSID. Then disassociate them. +## +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub doStep_2 { + my %results2 = (); + # delete previous stations + print "Removing old stations..."; + for my $sta_name (sort(keys %::sta_names)) { + # if we have a port eid for this station, let's delete the port so we can start fresh + my $del_cmd = $::utils->fmt_cmd("rm_vlan", 1, $::resource, $sta_name); + print "$sta_name " unless($::utils->isQuiet()); + $::utils->doCmd($del_cmd); + } + # poll until they are gone + my $old_sta_count = (keys(%::sta_names)); + while( $old_sta_count > 0 ) { + $old_sta_count = (keys(%::sta_names)); + sleep 1; + for my $sta_name (sort(keys %::sta_names)) { + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("show_port", 1, $::resource, $sta_name)); + #print ">>status>>$status\n"; + $old_sta_count-- if( $status =~ /Could not find/); # ?? + } + } + print "Old stations removed.\n"; + print "Creating new stations..."; + + # create new stations + my $i = 0; + for my $sta_name (sort(keys %::sta_names)) { + die("misconfiguration! ") if( ref($sta_name) eq "HASH"); + my $ip = $::sta_names{$sta_name}; + print "$sta_name " unless($::utils->isQuiet()); + new_wifi_station( $sta_name, $ip, \%results2, $::wifi_mode, $i); + $i++; + + # Uncomment to diagnose connection results. The IPs assigned + # are unlikely to appear instantly, but the mac and entity id + # used internally by LANforge will be set. + #my $ra_data = $results{$sta_name}}; + #my $mac = $results{$sta_name}[0]; + #my $eid = $results{$sta_name}[1]; + #print "created $sta_name, mac $mac, EID: $eid\n"; + #print "CMD: ".$results{$sta_name}[2]."\n\n"; + } + sleep 1; + my $num_stations = (keys %::sta_names); + print "Created $num_stations stations.\nPolling for association: "; + # we can view IP assignment as well as station association + my $num_assoc = 0; + my $num_ip = 0; + my $begin_time = time; + my %assoc_state = (); + for my $sta_name (sort(keys %::sta_names)) { + $assoc_state{$sta_name} = {}; + } + my $port; + + #while($num_ip < $num_stations) { # if we just cared about IPs + + while($num_assoc < $num_stations) { + sleep 1; + $num_assoc = 0; + $num_ip = 0; + for my $sta_name (sort(keys %::sta_names)) { + my $status = $::utils->doAsyncCmd($::utils->fmt_cmd("show_port", 1, $::resource, $sta_name)); + my %state = get_sta_state(\$status); + #print $state{"name"}.": ".$state{"assoc"}." "; + $num_assoc++ if( $state{"assoc"} !~ /NA|Not-Associated|unknown/); + #print $state{"ip"}."/".$state{"mask"}." gw:".$state{"gw"}."\n"; + $num_ip++ if($state{"ip"} ne "0.0.0.0" ); + } + + print "$num_assoc stations associated, $num_ip stations with IPs\n"; + } + my $end_time = time; + my $delta = $end_time - $begin_time; + + print "Association took about $delta seconds\n"; + print "Bringing those stations down now: "; + for my $sta_name (keys %::sta_names) { + my $cmd = fmt_port_down($::resource, $sta_name, "0.0.0.0", "0.0.0.0"); #$::netmask + $::utils->doCmd($cmd); + print "$sta_name " unless ($::utils->isQuiet()); + } + print "...stations down. Done.\n" +} +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## +## Create a station +## +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub doAdd { + # create virtual station + #print "## doAdd: sta_wiphy[$::sta_wiphy]\n"; + if ($::num_stations > 0 && defined $::sta_wiphy) { + my %results2 = (); + print "creating stations:"; + my $i = 0; + for my $sta_name (sort(keys %::sta_names)) { + die("misconfiguration! ") if( ref($sta_name) eq "HASH"); + my $ip = $::sta_names{$sta_name}; + print " $sta_name"; + new_wifi_station( $sta_name, $ip, \%results2, $::wifi_mode, $i); + if (($i % 10) == 9) { + $::utils->sleep_ms(120); + } + else { + $::utils->sleep_ms(30); + } + $i++; + } + print " done\n"; + } + elsif (defined $::sta_wiphy) { + print "Creating virtual radio: $::sta_wiphy.\n"; + new_wifi_radio(); + } + else { + print "Please define a radio with --radio\n"; + exit(1); + } +}# doAdd + +sub doDelWiphyVdevs { + if (defined $::port_del) { + # List ports on the resource in question, delete anything that has port_del for + # a parent. + my $q; + for ($q = 0; $q < 5; $q++) { + my @ports = $::utils->getPortListing(1, $::resource); + my $found = 0; + my $i; + for ($i = 0; $i<@ports; $i++) { + my $dev = $ports[$i]->dev(); + my $parent = $ports[$i]->parent(); + if ($parent eq $::port_del) { + print "deleting port $dev\n" unless($::utils->isQuiet()); + $::utils->doCmd($::utils->fmt_cmd("rm_vlan", 1, $::resource, $dev)); + $found++; + } + } + + if ($found == 0) { + last; + } + sleep(10); + } + } +} + +sub doDel { + if (defined $::port_del) { + #delete any port listed + delete_port(); + } +}# doDel + +sub ip2ipn { + return unpack 'N', inet_aton(shift); +} +sub ipn2ip { + return inet_ntoa( pack 'N', shift ); +} + +sub initStationAddr { + die("Zero stations cannot be very useful, bye.") if ($::num_stations < 1); + if ($::num_stations > 200 ) { + println("Over 200 stations is unlikely to work on one machine, expect over-subscription behavior."); + sleep 2; + } + + my $ip; + my $ip_obj; + if ($::first_ip =~ /^DHCP/){ + $ip = $::first_ip; + } + else { + $ip = $::first_ip; + } + + # often people create own stations at sta0 or sta1 and + # those are really hard to sort in the Ports Tab. We shall + # start with sta100 by default. Separate the numeric suffix + # use that as offset + my $offset = 100; + if ($::first_sta =~ /^.*?(\d+)\s*$/) { + $offset = $1; + } + for( my $i=0; $i < $::num_stations ; $i++ ) { + my $suffix = 0 + $i + $offset; + my $name; + if ($i == 0) { + $name = $::first_sta; + } + else { + $name = sprintf("sta%03d", $suffix); + } + my $ep_name1 = sprintf("ep-A%03d", $suffix); + my $ep_name2 = sprintf("ep-B%03d", $suffix); + my $cx_name = sprintf("cx-%03d", $suffix); + + if ($ip ne 'DHCP' && $i > 0) { + my $val = ip2ipn($ip); + #print "ip[$ip] to int[$val]\n"; + $ip = ipn2ip( 1 + $val); + #print "ip[$ip] from int[".(1+$val)."]\n"; + } + + $::sta_names{$name} = $ip; + $::cx_names{$name} = { + ep1 => $ep_name1, + ep2 => $ep_name2, + cx => $cx_name + }; + } +} # ~initStationAddr + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## +## Set phy channel, antennas +## +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub set_channel { + my $res = shift; + my $phy = shift; + my $chan = shift; + + die("set_channel: unset resource") unless ((defined $res) && ("" ne $res)); + die("set_channel: unset radio") unless ((defined $phy) && ("" ne $phy)); + die("set_channel: unset channel") unless ((defined $chan) && ("" ne $chan)); + + my $mode = 'NA'; + my $cmd = $::utils->fmt_cmd("set_wifi_radio", 1, $res, + $phy, + $mode, + $chan); + $::utils->doAsyncCmd($cmd); + sleep 1; +} + +sub set_antenna { + my $res = shift; + my $phy = shift; + my $ant = shift; + die("set_channel: unset resource") unless ((defined $res) && ("" ne $res)); + die("set_channel: unset radio") unless ((defined $phy) && ("" ne $phy)); + die("Antenna mode [$ant] does not exist.") + if (! exists $::antenna_table{$ant}); + my $mode = 'NA'; + my $chan = $::phy_channel; + if ($chan eq "") { + $chan = "NA"; + } + my $country = 'NA'; + my $freq = 'NA'; #'0xFFFF' will override channel + my $frag = 'NA'; + my $rate = 'NA'; + my $rts = 'NA'; + my $txpower = 'NA'; + my $mac = 'NA'; + + my $antenna = $::antenna_table{$ant}; + #print "ANTENNA: $ant -> $antenna\n"; + my $cmd = $::utils->fmt_cmd("set_wifi_radio", 1, $::resource, + $phy, + $mode, + $chan, + $country, + $freq, + $frag, + $rate, + $rts, + $txpower, + $mac, + $antenna); + $::utils->doAsyncCmd($cmd); + sleep 1; +} + + + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## +## M A I N +## +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + +my $help; + +if (@ARGV < 2) { + print $usage; + exit 0; +} +GetOptions +( + 'mgr|m=s' => \$::lfmgr_host, + 'lf_mgr_port|lf_port|mgr_port|p=i' => \$lfmgr_port, + 'resource|r=i' => \$::resource, + 'resource2|r2=i' => \$::resource2, + 'quiet|q=s' => \$::quiet, + 'radio|o=s' => \$::sta_wiphy, + 'channel|chan=i' => \$::phy_channel, + 'antenna|ant=s' => \$::phy_antenna, + 'ssid|s=s' => \$::ssid, + 'security=s' => \$::security, + 'xsec=s' => \$::xsec, + 'passphrase|password|pass|h=s' => \$::passphrase, + 'first_ip|b=s' => \$::first_ip, + 'first_sta|c=s' => \$::first_sta, + 'num_stations|num_sta|n=i' => \$::num_stations, + 'netmask|k=s' => \$::netmask, + 'gateway|g=s' => \$::gateway, + 'change_mac=i' => \$::change_mac, + 'mac-pattern|mac_pattern=s' => \$::mac_pattern, + 'cxtype|x=s' => \$::cx_type, + 'bps_min|bps-min|y=s' => \$::min_tx, + 'bps_max|bps-max|z=s' => \$::max_tx, + 'duration|e=i' => \$::duration, + 'upstream|t=s' => \$::upstream_port, + 'action|a=s' => \$action, + 'db_preload=s' => \$::db_preload, + 'db_save=s' => \$::db_save, + 'db_postload=s' => \$::db_postload, + 'poll_time|poll-time=i' => \$::poll_time, + 'wifi_mode|mode=s' => \$::wifi_mode, + 'bssid=s' => \$::bssid, + 'traffic_type=s' => \$::traffic_type, + 'vrad_chan=i' => \$::vrad_chan, + 'port_del=s' => \$::port_del, + 'admin_down_on_add' => \$::admin_down_on_add, + 'ieee80211w=s' => \$::ieee80211w, + 'log_cli=s{0,1}' => \$log_cli, # use ENV{LOG_CLI} elsewhere + 'help|?' => \$help, +) || (print($usage) && exit(1)); + +if ($help) { + print($usage) && exit(0); +} +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; + +if ($::quiet eq "0") { + $::quiet = "no"; +} +elsif ($::quiet eq "1") { + $::quiet = "yes"; +} + +# Open connection to the LANforge server. +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } +} + +# Configure our utils. +our $utils = new LANforge::Utils(); +print "Connecting to $lfmgr_host, $lfmgr_port\n"; +$::utils->connect($lfmgr_host, $lfmgr_port); + +if ($db_postload ne "" && db_exists($::db_postload)==0) { + print("Scenario [$::db_postload] does not exist. Please create it first."); + exit(1); +} + +if ($::db_preload ne "") { + if(db_exists($::db_preload)==1) { + print "Loading scenario $::db_preload..."; + load_db($::db_preload); + print " done\n"; + } + else { + print("Scenario [$::db_postload] does not exist. Please create it first."); + exit(1); + } +} + +if (!($action =~ /del/)) { # Below steps are unrelated to deleting objects + if(!defined $::first_ip || $::first_ip eq '') { + print("Please specify the first IP for stations. You may choose DHCP, DHCP6, or DHCP,DHCP6 or an IP that will be incremented.\n"); + exit(1); + } + + if(! $::ssid ) { + print("Please configure SSID for stations to associate with.\n"); + exit(1); + } + if(! $::sta_wiphy ) { + print("Please specify the base radio port for the wifi stations. ".$usage ); + exit(1); + } +} + +if(! $action ) { + print("Please specify which test action we want: + step1: connect one station and pass upload and download traffic + step2: connect 10 wifi stations and disconnect. + add: create virtual radio.\n + del: Delete virtual radio.\n"); + exit(1); +} + +if (!($action =~ /del/)) { # Below steps are unrelated to deleting objects + if(0 == keys(%::sta_names)) { + initStationAddr(); + } + if(! %sta_names ) { + print("Please configure station list to test with.\n"); + exit(1); + } +} + +if ($action =~ /step|add/) { + if ($::phy_channel ne "") { + set_channel($::resource, $::sta_wiphy, $::phy_channel); + } + if ($::phy_antenna ne "") { + set_antenna($::resource, $::sta_wiphy, $::phy_antenna); + } +} + +# take first station and associate it or fail +if ($action eq "step1" ) { + if ($traffic_type !~ /^(concurrent|separate)$/ ) { + print("Please specify concurrent or separate as traffic_type.\n"); + exit(1); + } + doStep_1(%sta_names, $::ssid, $sta_wiphy); + if ($db_save ne "") { + save_db($::db_save); + } + if ($::db_postload ne "") { + load_db($::db_postload); + } + +} +elsif( $action eq "step2" ) { + doStep_2(%sta_names, $::ssid, $sta_wiphy); + if ($::db_postload ne "") { + load_db($::db_preload); + } +} +elsif ($action eq "add" ) { + doAdd(); + if ($::db_postload ne "") { + load_db($::db_preload); + } +} +elsif ($action eq "del" ) { + doDel(); + if ($::db_postload ne "") { + load_db($::db_preload); + } +} +elsif ($action eq "del_all_phy" ) { + doDelWiphyVdevs(); + if ($::db_postload ne "") { + load_db($::db_preload); + } +} +elsif ($action eq "show_port") { + print $::utils->doAsyncCmd($::utils->fmt_cmd("nc_show_port", 1, $resource, (sort(keys %sta_names))[0])) . "\n"; +} + +exit(0); diff --git a/lanforge/lanforge-scripts/lf_attenmod.pl b/lanforge/lanforge-scripts/lf_attenmod.pl new file mode 100755 index 000000000..f1172ddfd --- /dev/null +++ b/lanforge/lanforge-scripts/lf_attenmod.pl @@ -0,0 +1,127 @@ +#!/usr/bin/perl + +# This program is used to modify the LANforge attenuator (through +# the LANforge manager/server processes. + +# Written by Candela Technologies Inc. +# Udated by: +# +# + +use strict; + +# Un-buffer output +$| = 1; + +use lib '/home/lanforge/scripts'; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; +my $shelf_num = 1; + +# Default values for ye ole cmd-line args. + + +my $resource = 1; +my $quiet = "yes"; +my $atten_serno = ""; +my $atten_idx = ""; +my $atten_val = ""; +my $action = "show_atten"; +my $do_cmd = "NA"; +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + + +my $fail_msg = ""; +my $manual_check = 0; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $usage = "$0 --action { show_atten | set_atten | do_cmd } ] + [--mgr {host-name | IP}] + [--mgr_port {ip port}] + [--cmd {lf-cli-command text}] + [--atten_serno {serial-num}] + [--atten_idx { attenuator-module-index | all}] + [--atten_val {0-950 dDbm}] + [--quiet { yes | no }] + +Example: + $0 --mgr 192.168.100.138 --action set_atten --atten_serno 3 --atten_idx all --atten_val 550\n"; + +my $i = 0; +my $show_help = 0; + +if (@ARGV < 2) { + print $usage; + exit 0; +} +GetOptions +( + 'help|h' => \$show_help, + 'atten_serno|s=s' => \$atten_serno, + 'atten_idx|i=s' => \$atten_idx, + 'atten_val|v=s' => \$atten_val, + 'action|a=s' => \$action, + 'cmd|c=s' => \$do_cmd, + 'mgr|m=s' => \$lfmgr_host, + 'mgr_port|p=i' => \$lfmgr_port, + 'resource|r=i' => \$resource, + 'quiet|q=s' => \$quiet, + +) || (print($usage) && exit(1)); + +if ($show_help) { + print $usage; + exit 0; +} + +if ($do_cmd ne "NA") { + $action = "do_cmd"; +} + +if (!(($action eq "show_atten") || + ($action eq "set_atten") || + ($action eq "do_cmd"))) { + die("Invalid action: $action\n$usage\n"); +} + +if ($action eq "set_atten") { + if ((length($atten_serno) == 0) || + (length($atten_val) == 0) || + (length($atten_idx) == 0)) { + print "ERROR: Must specify atten_serno, atten_idx, and atten_val when setting attenuator.\n"; + die("$usage"); + } +} + +# Open connection to the LANforge server. +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->connect($lfmgr_host, $lfmgr_port); +if ($quiet eq "yes") { + $utils->cli_send_silent(1); # Do show input to CLI + $utils->cli_rcv_silent(1); # Repress output from CLI ?? +} +else { + $utils->cli_send_silent(0); # Do show input to CLI + $utils->cli_rcv_silent(0); # Repress output from CLI ?? +} + +if ($action eq "show_atten") { + print $utils->doAsyncCmd("show_atten $shelf_num $resource $atten_serno"); +} +elsif ($action eq "set_atten") { + print $utils->doAsyncCmd("set_atten $shelf_num $resource $atten_serno $atten_idx $atten_val") . "\n"; +} +elsif ($action eq "do_cmd") { + print $utils->doAsyncCmd("$do_cmd") . "\n"; +} +else { + die("Unknown action: $action\n$usage\n"); +} + +exit(0); diff --git a/lanforge/lanforge-scripts/lf_auto_wifi_cap.pl b/lanforge/lanforge-scripts/lf_auto_wifi_cap.pl new file mode 100755 index 000000000..f6a878597 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_auto_wifi_cap.pl @@ -0,0 +1,345 @@ +#!/usr/bin/perl -w + +# This program is used to automatically run LANforge-GUI WiFi Capacity tests. + +# Written by Candela Technologies Inc. +# Udated by: +# +# + +use strict; +use warnings; +use Carp; + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; +use Cwd; + +use constant NA => "NA"; +use constant NL => "\n"; +use constant shelf_num => 1; + +# Default values for ye ole cmd-line args. +our $use_existing_sta = 0; +our $use_existing_cfg = 0; +our $resource = 1; +our $upstream_resource = -1; +our $quiet = "yes"; +our $radio = ""; # wiphy0 +our $ssid = "my-ssid"; +our $num_sta = 64; +our $speed_ul = 0; +our $ul_ps_rate = 0; +our $speed_dl = 100000000; +our $dl_ps_rate = 0; +our $endp_type = "mix"; +our $percent_tcp = 50; +our $first_ip = "DHCP"; +our $upstream = "eth1"; +our $increment = 5; +our $duration = 30; +our $test_name = "lanforge-wifi-capacity-test"; +our $rpt_timer = 1000; +our $settle_timer = 10000; # 10 seconds + +our $fail_msg = ""; +our $manual_check = 0; +our $gui_host = "127.0.0.1"; +our $gui_port = 7777; +our $lfmgr_host = "127.0.0.1"; +our $lfmgr_port = 4001; +our @test_text = (); +our $use_pdu_mix = "false"; +our $pdu_percent = "pps"; +our @pdu_mix = (); +our $use_stations = ""; +our @user_stations = (); +our $multicon = -1; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +our $usage = "$0 + [--mgr {host-name | IP}] + [--mgr_port {ip port}] + [--resource {number}] + [--upstream_resource {number}] + [--gui_host {LANforge gui_host (127.0.0.1)}] + [--gui_port {LANforge gui_port (7777)}] + [--radio {name,name2,..}] example: wiphy0,wiphy1 + [--speed_dl {speed in bps}] + [--dl_ps_rate {(0) total download rate, 1 download rate per station}] + [--speed_ul {speed in bps}] + [--ul_ps_rate {(0) total upload rate, 1 upload rate per station}] + [--ssid {ssid}] + [--num_sta {num-stations-per-radio}] # For each radio. + [--use_existing_sta ] # Assume stations are already properly created and do not re-create. + [--use_existing_cfg ] # Use existing station config + [--upstream {upstream-port-name (eth1)}] + [--first_ip {first-ip-addr | DHCP}] + [--percent_tcp {percent_tcp for mixed traffic type}] + [--increment {station-bringup-increment (5)}] + [--duration {bringup-step-duration (30)}] + [--report_timer {milisecond report timer for created the endpoints (1000)}] + [--endp_type { udp, tcp, mix } + [--use_pdu_mix { true | (false) }] + [--pdu_percent { bps | (pps) }] + [--pdu_mix { pdu-size:%, pdu-size:%, ... }] + [--use_station { sta1,sta2, ... }] # Specify which stations to use. + [--test_name { my-test-name}] + [--test_text { 'my-test
    over the air
    funky-hardware-x
    OS z'}] + [--multicon { -1: auto, 0 none, 1 new process, 2+ new process + multiple streams} + [--quiet { yes | no }] + +Example: + +./lf_auto_wifi_cap.pl --mgr ben-ota-1 --resource 2 --radio wiphy0 --speed_dl 500000000 --ssid Lede-ventana --num_sta 64 --upstream eth1 --first_ip DHCP --percent_tcp 50 --increment 1,5,10,20,30,40,50,64 --duration 15 --report_timer 3000 --endp_type mix --test_name ventana-mix-dl --test_text 'Ventana LEDE, WLE900VX
    over-the-air to LANforge station system 5 feet away
    LAN to WiFi traffic path' --multicon 1 + +"; + +my $i = 0; +my $help = 0; + +GetOptions +( + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port=i' => \$::lfmgr_port, + 'gui_host=s' => \$::gui_host, + 'gui_port=i' => \$::gui_port, + 'resource=i' => \$::resource, + 'upstream_resource=i' => \$::upstream_resource, + 'radio=s' => \$::radio, + 'speed_ul=i' => \$::speed_ul, + 'ul_ps_rate=i' => \$::ul_ps_rate, + 'speed_dl=i' => \$::speed_dl, + 'dl_ps_rate=i' => \$::dl_ps_rate, + 'ssid=s' => \$::ssid, + 'num_sta=i' => \$::num_sta, + 'use_existing_sta' => \$::use_existing_sta, + 'use_existing_cfg' => \$::use_existing_cfg, + 'upstream=s' => \$::upstream, + 'first_ip=s' => \$::first_ip, + 'percent_tcp=i' => \$::percent_tcp, + 'increment=s' => \$::increment, + 'duration=i' => \$::duration, + 'report_timer=i' => \$::rpt_timer, + 'settle_timer=i' => \$::settle_timer, + 'endp_type=s' => \$::endp_type, + 'test_name=s' => \$::test_name, + 'multicon=i' => \$::multicon, + 'test_text=s' => \$::test_text, + 'use_pdu_mix=s' => \$::use_pdu_mix, + 'pdu_percent=s' => \$::pdu_percent, + 'pdu_mix=s' => \@::pdu_mix, + 'use_station=s' => \$::use_stations, + 'quiet|q=s' => \$::quiet, + 'help' => \$::help, +) || die("$::usage"); + +if ($::help) { + print $::usage; + exit(0); +} + +if ($use_stations ne "") { + @user_stations = split(",", $use_stations); +} + +if ($upstream_resource == -1) { + $upstream_resource = $resource; +} + +our $mgr_telnet = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); +$::mgr_telnet->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); +$::mgr_telnet->waitfor("/btbits\>\>/"); + +# Configure our utils. +our $utils = new LANforge::Utils(); +$utils->telnet($::mgr_telnet); # Set our telnet object. +if ($utils->isQuiet()) { + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + $utils->cli_send_silent(0); + } + else { + $utils->cli_send_silent(1); # Do not show input to telnet + } + $utils->cli_rcv_silent(1); # Repress output from telnet +} +else { + $utils->cli_send_silent(0); # Show input to telnet + $utils->cli_rcv_silent(0); # Show output from telnet +} +$utils->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); + +my @radios = split(/,/, $::radio); +my $starting_sta = 500; +my $first_sta = $starting_sta; + +if (@radios == 0) { + print ("No radios specified, doing nothing.\n"); + exit(1); +} + +if (!$::use_existing_sta) { + # Clean ports on these radios. + for ($i = 0; $i<@radios; $i++) { + my $r = $radios[$i]; + print "Deleting virtual devices on resource $::resource radio: $r\n"; + system("./lf_associate_ap.pl --mgr $::lfmgr_host --mgr_port $::lfmgr_port --resource $::resource --action del_all_phy --port_del $r"); + } +} + +if (!$::use_existing_cfg) { + # Create/Set stations on these radios. + for ($i = 0; $i<@radios; $i++) { + my $r = $radios[$i]; + + print "Creating/Setting $::num_sta virtual stations on resource $::resource radio: $r\n"; + system("./lf_associate_ap.pl --mgr $::lfmgr_host --mgr_port $::lfmgr_port --resource $::resource " + ." --action add --radio $r --ssid $::ssid " + ." --first_sta sta$first_sta --first_ip $::first_ip " + ." --num_stations $::num_sta --admin_down_on_add"); + $first_sta += $::num_sta; + } +} + +my $cwd = cwd(); +my $wifi_cap_fname = "wifi_auto_cap_" . $$ . ".txt"; + +# Create temporary wifi capacity config file. +open(CAP, ">$wifi_cap_fname") or die ("Can't open $wifi_cap_fname for writing.\n"); + +print CAP "__CFG VERSION 1\n"; +print CAP "__CFG SEL_PORT 1 $::upstream_resource $::upstream\n"; + +our @sta_list = (); +if (@user_stations) { + # Use all existing stations on the specified radio + for ($i = 0; $i<@user_stations; $i++) { + my $sta_name = $user_stations[$i]; + print CAP "__CFG SEL_PORT 1 $::resource $sta_name\n"; + push(@sta_list, "$sta_name"); + } +} +else { + for ($i = $starting_sta; $i<$first_sta; $i++) { + print CAP "__CFG SEL_PORT 1 $::resource sta$i\n"; + push(@sta_list, "sta$i"); + } +} + +print CAP "__CFG STA_INCREMENT $::increment\n"; +print CAP "__CFG DURATION " . ($::duration * 1000) . "\n"; +print CAP "__CFG RPT_TIMER " . ($::rpt_timer) . "\n"; +print CAP "__CFG BEFORE_CLEAR " . ($::settle_timer) . "\n"; + +my $proto = 0; +if ($endp_type eq "tcp") { + $proto = 1; +} +# 2 is layer-4, this script does not support that currently. +elsif ($endp_type eq "mix") { + $proto = 3; +} +print CAP "__CFG PROTOCOL $proto\n"; +print CAP "__CFG DL_RATE_SEL $::dl_ps_rate\n"; +print CAP "__CFG DL_RATE $::speed_dl\n"; +print CAP "__CFG UL_RATE_SEL $::ul_ps_rate\n"; +print CAP "__CFG UL_RATE $::speed_ul\n"; +print CAP "__CFG PRCNT_TCP " . ($::percent_tcp * 10000) . "\n"; +print CAP "__CFG MULTI_CONN $::multicon\n"; +print CAP "__CFG USE_MIX_PDU $::use_pdu_mix\n"; + +my $pps = "false"; +my $bps = "false"; +if ($pdu_percent eq "pps") { + $pps = "true"; +} +elsif ($pdu_percent eq "bps") { + $bps = "true"; +} +print CAP "__CFG PDU_PRCNT_PPS $pps\n"; +print CAP "__CFG PDU_PRCNT_BPS $bps\n"; + +#my @pdu_mix_ln = split(/,/, $::pdu_mix); +for ($i = 0; $i < @::pdu_mix; $i++) { + print CAP "__CFG PDU_MIX_LN " . $::pdu_mix[$i] . "\n"; +} + +my @test_texts = split(/
    /, $::test_text); +for ($i = 0; $i < @test_texts; $i++) { + print CAP "__CFG NOTES_TEXT_LN " . $test_texts[$i] . "\n"; +} + +# Things not specified will be left at defaults. + +close(CAP); +my $accounted_for = 0; +while ($accounted_for < $::num_sta) { + my @show_lines = split("\n", $::utils->doAsyncCmd("nc_show_ports 1 $::resource ALL")); + sleep 2; + $accounted_for = 0; + for my $sta_name (@sta_list) { + my @sta_matches = grep { / $sta_name /} @show_lines; + if (@sta_matches > 0) { + #print " found: ".join("\n", @sta_matches)."\n"; + $accounted_for++; + } + } # ~for each station + print " $accounted_for/$::num_sta up\n"; +} +# sleep 2s extra because one station might not be quite done setting up whereas +# 10 station will overall less time per station +sleep 2; + +# Send command to GUI to start this test. +# Something like: wifi_cap run "ventana-mix-dl" "/tmp/ventana-dl-0003" +our $gui_telnet = new Net::Telnet(Prompt => '/#/', + Timeout => 60); +$::gui_telnet->open(Host => $::gui_host, + Port => $::gui_port, + Timeout => 10); +$::gui_telnet->waitfor("/#/"); + +my $output_dname = "$::test_name" . "_" . time(); +my $output_fname = "$cwd/$output_dname"; +my $cmd = "wifi_cap run \"$cwd/$wifi_cap_fname\" \"$output_fname\"\n"; +print "Sending GUI command to start the capacity test -:$cmd:-\n"; +my @rslt = $::gui_telnet->cmd($cmd); +$::gui_telnet->close(); + +print "GUI result: " . join(@rslt, "\n"); +print "Waiting for test to complete...\n"; +# Wait until test is done. +while (1) { + if (-f "$output_fname/index.html") { + print "Found $output_fname/index.html, wait one more minute to be sure images are written.\n"; + last; + } + sleep(10); +} + +# Could still take a bit to complete writing out the images... +sleep(60); + +print "Finished, see report at: $output_fname/index.html\n"; + +system("tar -cvzf $output_dname.tar.gz $output_dname"); + +# Notes on possible LEDE/OpenWRT DUT cleanup +# rm /etc/dhcp.leases and reboot to clean leases. diff --git a/lanforge/lanforge-scripts/lf_cmc_macvlan.pl b/lanforge/lanforge-scripts/lf_cmc_macvlan.pl new file mode 100755 index 000000000..cf596f854 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_cmc_macvlan.pl @@ -0,0 +1,802 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, and custom_tcp +# across 1 real port and many macvlan ports on 2 machines. +# It then continously starts and stops the connections. + +# Un-buffer output +$| = 1; + +use strict; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf = 1; + +# This sets up connections between 2 LANforge machines +#my $lf1 = 1; my $lf2 = 2; my @lf1_ports = (5); my @lf2_ports = (5); + +# This sets up connections between 2 ports of a single machine; +my $lf1 = 1; my $lf2 = 1; my @lf1_ports = (2); my @lf2_ports = (3); + + +my $ip_base = "172.1"; +my $ip_lsb = 2; +my $ip_c = 2; +my $msk = "255.255.0.0"; +my $mac_prefix = "00:0b:6b:30"; # NOTE: For use with CMC unit, this MAC must be within + # the range that the CMC unit supports, and the MACs + # must match the VSTA MACs in external mode 2. +my $mac_prefix2 = "00:00:00:00"; # For second machine. +my $mac_lsb = 01; # Starting least-significant byte of the MAC. +my $mac_lsb2 = 05; # Starting second least significant byte of the MAC. + +my $num_macvlans = 64; + + + +# If zero, will have one of EACH of the cx types on each port. +#my $one_cx_per_port = 1; +my $one_cx_per_port = 0; + +#my @cx_types = ("", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4"); +#my @min_pkt_szs = (64, 1, 1, 1, 1, 0); +#my @max_pkt_szs = (1514, 12000, 13000, 2048, 2048, 0); + +# Good for testing with CMC 'EE' unit. +my @cx_types = ("lf_udp", "lf_tcp", "l4", "voip"); +my @min_pkt_szs = (1, 1, 0, 0); +my @max_pkt_szs = (12000, 13000, 0, 0); + +# Layer-4 only +#my @cx_types = ("l4", "l4"); +#my @min_pkt_szs = (0, 0); +#my @max_pkt_szs = (0, 0); + +# VOIP only +#my @cx_types = ("voip"); +#my @min_pkt_szs = (0); +#my @max_pkt_szs = (0); + + +my $peer_to_peer_voip = 1; # Don't register with SIP proxy, but just call peer to peer. +my $max_voip = 3; # These are expensive, cannot run too many on most machines/networks. +my @src_sound_files = ("media/male_voice_8khz.wav"); + +# URL will be acted on from machine $lf1 +#my $l4_url = "http://172.1.5.75"; +my $l4_url = "http://www.yahoo.com"; + +my $min_rate = 9000; +my $max_rate = 12000; + +my $test_mgr = "ben_tm"; + +my $loop_max = 100; +my $start_stop_iterations = 100; +my $run_for_time = 1200; # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Run for XX seconds..then will be stopped again +my $report_timer = 5000; # 8 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); +my $cur_voip = 0; + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 45); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + initToDefaults(); + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + addMacVlans(); + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + my $rl = 0; + for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + + }# For some amount of start_stop iterations... +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub addMacVlans { + my $i; + my $q; + + my $v; + + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $pnum1 = $lf1_ports[$q]; + my $pnum2 = $lf2_ports[$q]; + for ($i = 0; $i<$num_macvlans; $i++) { + + $mac_lsb++; + if ($mac_lsb > 255) { + $mac_lsb2++; + $mac_lsb = 0; + } + + my $s2 = $shelf+10; + my $c2 = $lf1+10; + my $p2 = $pnum1+10; + my $mc = sprintf("$mac_prefix:%02x:%02x", $mac_lsb2, $mac_lsb); + doCmd("add_mvlan $shelf $lf1 $pnum1 $mc"); + + if ($lf2 ne "") { + $c2 = $lf2+10; + $p2 = $pnum2+10; + #$mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + $mc = sprintf("$mac_prefix2:%02x:%02x", $mac_lsb2, $mac_lsb); + doCmd("add_mvlan $shelf $lf2 $pnum2 $mc"); + + # Throttle ourself so we don't over-run the poor LANforge system. + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + $since_throttle = 0; + } + } + } + } + + doCmd("probe_ports"); + + # Wait untill we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = 0; $i<$num_macvlans; $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($lf2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } + + +}#addMacVlans + + +# Wait untill the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + my $wait_for_phantom = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + if ($ports[$i]->isPhantom()) { + # Wait a bit..hopefully it will go away. + if ($wait_for_phantom++ < 20) { + print "Sleeping a bit, found a phantom port."; + sleep(5); + doCmd("probe_ports"); + $found_one = 1; + } + } + else { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + } + } + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = "set_port $shelf $lf1 $tmp $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($lf2 ne "") { + $cmd = "set_port $shelf $lf2 $tmp2 $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + } + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $tmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf1 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$q"); + $since_throttle = 0; + } + + } + + $ip_lsb++; + + if ($lf2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $tmp2); + $pname = $p1->{dev}; + + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf2 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pname\#$q"); + $since_throttle = 0; + } + } + }# If we have an LF-2 defined. + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + + my $voip_phone = 3000; # Start here and count on up as needed. + my $rtp_port = 10000; # Starting RTP port. + my $sound_file_idx = 0; + + my @all_ports1 = @lf1_ports; + my $j; + my $pname; + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + + my @all_ports2 = @lf2_ports; + if ($lf2 ne "") { + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + } + } + + print "About to start endpoints, all_ports1:\n" . join(" ", @all_ports1) . + "\nall_ports2: " . join(" ", @all_ports2) . "\n\n"; + + if ($one_cx_per_port) { + my $j = 0; + my $cxcnt = 0; + for ($j ; $j<@all_ports1; $j++) { + my $i = $cxcnt % @cx_types; + $cxcnt++; + + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "l4e-${ep}-TX"; + $ep++; + my $ep2 = "D_l4e-${ep}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . + "dl $l4_url /tmp/$ep1' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "l4-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + elsif ($cxt eq "voip") { + # Create VOIP endpoint + if ($cur_voip < $max_voip) { + my $ep1 = "rtpe-${ep}-TX"; + $ep++; + my $ep2 = "rtpe-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_voip_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep2"; + doCmd($cmd); + + $cmd = "set_voip_info $ep2 NA 5 60 NA NA NA NA NA NA NA /dev/null 20000"; + doCmd($cmd); + + $cmd = "set_endp_flag $ep2 SavePCM 0"; + doCmd($cmd); + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep2 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 BindSIP 1"; + doCmd($cmd); + } + + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + doCmd($cmd); + + $cmd = "add_voip_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep2"; + doCmd($cmd); + + $cmd = "set_voip_info $ep1 NA 5 60 NA NA NA NA NA NA NA /dev/null 20000"; + doCmd($cmd); + + $cmd = "set_endp_flag $ep1 SavePCM 0"; + doCmd($cmd); + + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep1 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep1 BindSIP 1"; + doCmd($cmd); + } + + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + # Now, add the cross-connects + my $cx_name = "rtp-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + $cur_voip++; + } + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "l3e-${ep}-TX"; + $ep++; + my $ep2 = "l3e-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "l3-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for all ports + }#one_cx_per_port + else { + my $j = 0; + for ($j ; $j<@all_ports1; $j++) { + for ($i = 0; $i<@cx_types; $i++) { + my $cxt = $cx_types[$i]; + + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "l4e-${ep}-TX"; + $ep++; + my $ep2 = "D_l4e-${ep}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . + "dl $l4_url /tmp/$ep1' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "l4-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + elsif ($cxt eq "voip") { + # Create VOIP endpoint + if ($cur_voip < $max_voip) { + + my $ep1 = "RTPE-${ep}-TX"; + $ep++; + my $ep2 = "RTPE-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_voip_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep2"; + doCmd($cmd); + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + $cmd = "set_voip_info $ep2 NA 5 60 NA NA NA NA NA NA NA /dev/null 20000"; + doCmd($cmd); + + $cmd = "set_endp_flag $ep2 SavePCM 0"; + doCmd($cmd); + + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep2 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 BindSIP 1"; + doCmd($cmd); + } + + + my $cmd = "add_voip_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep2"; + doCmd($cmd); + + $cmd = "set_voip_info $ep1 NA 5 60 NA NA NA NA NA NA NA /dev/null 20000"; + doCmd($cmd); + + $cmd = "set_endp_flag $ep1 SavePCM 0"; + doCmd($cmd); + + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep1 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep1 BindSIP 1"; + doCmd($cmd); + } + + + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + # Now, add the cross-connects + my $cx_name = "rtp-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + $cur_voip++; + } + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "l3e-${ep}-TX"; + $ep++; + my $ep2 = "l3e-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "l3-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for cx types + }#for each port + }# each cx per port + +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_create_bcast.pl b/lanforge/lanforge-scripts/lf_create_bcast.pl new file mode 100755 index 000000000..0cb9910c2 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_create_bcast.pl @@ -0,0 +1,304 @@ +#!/usr/bin/perl -w + +# This program creates a UDP broadcast connection + +# Written by Candela Technologies Inc. +# Udated by: +# +# + +use strict; +use warnings; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +# Default values for ye ole cmd-line args. +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; + +our $resource = 1; +our $quiet = "yes"; +our $tx_bps = 512000; +our $socket_buf = 512000; +our $cx_name = ""; +our $endp_a = ""; +our $endp_b = ""; +our $port_a = ""; +our $mac_a = ""; +our $mac_b = "FF FF FF FF FF FF"; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +sub logg { + return if ($::quiet eq "yes"); + foreach (@_) { + print "* ".$_."\n"; + } +} + +# [--port_b {eth0}] +our $port_b = "eth0"; + +our $usage = qq($0 ## creates a UDP broadcast connection + [--mgr {host-name | IP}] + [--mgr_port {ip port}] + [--resource {number}] + [--quiet { yes | no }] + [--cx_name {cx name}] + [--tx_bps { transmit bps }] + [--port_a {eth1}] + [--mac_addr_a {mac address}] + [--ip_a {ip addr}] + [--netmask {255.255.255.0}] + [--dest_ip {ip.255}] + [--socket_buf {512000}] + [--tx_bps {512000}] + +Examples: +# set broadcast endpoint +$0 --mgr jedtest \\ + --resource_a 1 \\ + --cx_name cx3eth0 \\ + --port_a eth1 \\ + --mac_a 00:00:00:32:23:11 \\ + --ip_a 10.26.1.2 \\ + --broadcast 10.26.1.255 \\ + --netmask 255.255.255.0 \\ + --socket_buf 512000 \\ + --tx_bps 512000 +); + +GetOptions +( + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p=i' => \$::lfmgr_port, + 'resource|r=i' => \$::resource, + 'quiet|q=s' => \$::quiet, + 'cx_name|c=s' => \$::cx_name, + 'port_a|a=s' => \$::port_a, + 'mac_addr_a|mac_a=s' => \$::mac_a, + 'tx_bps=i' => \$::tx_bps, + 'socket_buf=i' => \$::socket_buf +) || die("$::usage"); + +sub fmt_cmd { + my $rv; + my $mod_hunk; + for my $hunk (@_) { + die("fmt_cmd called with empty space or null argument, bye.") unless(defined $hunk && $hunk ne ''); + die("rv[${rv}]\n --> fmt_cmd passed an array, bye.") if (ref($hunk) eq 'ARRAY'); + die("rv[${rv}]\n --> fmt_cmd passed a hash, bye.") if (ref($hunk) eq 'HASH'); + $mod_hunk = $hunk; + $mod_hunk = "0" if ($hunk eq "0" || $hunk eq "+0"); + + if( $hunk eq "" ) { + #print "hunk[".$hunk."] --> "; + $mod_hunk = 'NA'; + #print "hunk[".$hunk."]\n"; + #print "fmt_cmd: warning: hunk was blank, now NA. Prev hunks: $rv\n" + } + $rv .= ( $mod_hunk =~m/ +/) ? "'$mod_hunk' " : "$mod_hunk "; + } + chomp $rv; + print "cmd formatted to: $rv\n" unless($::quiet eq "yes"); + return $rv; +} + +die "please specify --mgr \n$::usage" + if ((! defined $::lfmgr_host) || "$::lfmgr_host" eq ""); + +die "please specify --resource\n$::usage" + if ((! defined $::resource) || "$::resource" eq ""); + +die "please specify --mgr_port\n$::usage" + if ((! defined $::lfmgr_port) || "$::lfmgr_port" eq ""); + +die "please specify --port_a\n$::usage" + if ((! defined $::port_a) || "$::port_a" eq ""); + +die "please specify --cx_name\n$::usage" + if ((! defined $::cx_name) || "$::cx_name" eq ""); + +die "please specify --tx_bps\n$::usage" + if ((! defined $::cx_name) || "$::cx_name" eq ""); + + +$endp_a = $::cx_name."-A"; +$endp_b = $::cx_name."-B"; + + +# Open connection to the LANforge server. +our $t = new Net::Telnet( Prompt => '/default\@btbits\>\>/', + Timeout => 20); +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +our $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +if ($::quiet eq "yes") { + $utils->cli_send_silent(1); # Do show input to CLI + $utils->cli_rcv_silent(1); # Repress output from CLI ?? +} +else { + $utils->cli_send_silent(0); # Do show input to CLI + $utils->cli_rcv_silent(0); # Repress output from CLI ?? +} + +$resource = 1; +$mac_a = ""; + +my @lines = split("\n", $::utils->doAsyncCmd(fmt_cmd("nc_show_ports", "1", "$resource", "$port_a"))); +my @hunks = grep {/MAC/} @lines; +if ( @hunks < 1) { + die("Unable to get mac addresses for port $port_a"); +} + +($mac_a) = $hunks[0] =~ /MAC: ([^ ]+)/; +$mac_a =~ y/:/ /; + +die "please specify --mac_a since endp_a does not report it" + if ((! defined $::mac_a) || "$::mac_a" eq "" || "$::mac_a" =~ /\s*(00[: ]){5}00\s*/); + +#print "MAC is now [$::mac_a]\n"; + +my $rx_buf_size=512000; # default is 0, expresses OS min: 64B +my $tx_buf_size=512000; # default is 0, expresses OS min: 64B +# list of commands +our @endp_a_list = ( + qq(add_endp $endp_a 1 $resource $port_a custom_ether -1 NO $tx_bps 0 NO 64 64 CUSTOM NO 32 0 0), + qq(set_endp_flag $endp_a ReplayOverwriteDstMac 1), + # this sets the broadcast MAC address + qq(set_endp_details $endp_a $rx_buf_size $tx_buf_size 4294967295 0 'ff ff ff ff ff ff' 0 0 0 0 10000 0 NA NA NA 0.0.0.0 0), + qq(set_endp_quiesce $endp_a 3), + # this sets the source MAC + qq(set_endp_addr $endp_a '$mac_a' AUTO 0 0), + qq(set_endp_flag $endp_a ReplayLoop 0), + qq(set_endp_flag $endp_a EnableTcpNodelay 0), + qq(set_endp_flag $endp_a EnableRndSrcIP 0), + qq(set_endp_flag $endp_a EnableConcurrentSrcIP 0), + qq(set_endp_flag $endp_a EnableLinearSrcIP 0), + qq(set_endp_flag $endp_a EnableLinearSrcIPPort 0), + qq(set_endp_flag $endp_a QuiesceAfterRange 0), + qq(set_endp_flag $endp_a QuiesceAfterDuration 0), + # does this require recompilation? + qq(set_endp_payload $endp_a CUSTOM ff ff ff ff ff ff 00 90 0b 29 06 f9 08 00 45 00 00 32 53 f5 40 00 40 11 cf 91 0a 1a 01 02 0a 1a 01 ff 00 00 00 00 00 00 e8 9b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00), + qq(set_endp_tos $endp_a DONT-SET 0), + qq(set_script $endp_a NA NA NONE 'NA' 0 0), + qq(set_endp_proxy $endp_a NO), + qq(rm_thresholds $endp_a all), + qq(set_endp_report_timer $endp_a 5000), + qq(set_endp_flag $endp_a ClearPortOnStart 0), +); +our @endp_b_list = ( + # this is how an *unmanaged port* appears to be created + qq(add_endp $endp_b 1 0 eth0 custom_ether -1 NO 56000 0 NO 64 64 CUSTOM NO 32 0 0), + qq(set_endp_flag $endp_b ReplayOverwriteDstMac 0), + # dest mac address + qq(set_endp_details $endp_b 0 0 4294967295 0 '$mac_a' 0 0 0 0 10000 0 NA NA NA 0.0.0.0 0), + qq(set_endp_quiesce $endp_b 3), + qq(set_endp_flag $endp_b unmanaged 1), + qq(set_endp_addr $endp_b '00 00 00 00 00 00 ' AUTO 0 0), + qq(set_endp_flag $endp_b ReplayLoop 0), + qq(set_endp_flag $endp_b EnableTcpNodelay 0), + qq(set_endp_flag $endp_b EnableRndSrcIP 0), + qq(set_endp_flag $endp_b EnableConcurrentSrcIP 0), + qq(set_endp_flag $endp_b EnableLinearSrcIP 0), + qq(set_endp_flag $endp_b EnableLinearSrcIPPort 0), + qq(set_endp_flag $endp_b QuiesceAfterRange 0), + qq(set_endp_flag $endp_b QuiesceAfterDuration 0), + qq(set_endp_payload $endp_b CUSTOM 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00), + qq(set_endp_tos $endp_b DONT-SET 0), + qq(set_script $endp_b NA NA NONE 'NA' 0 0), + qq(set_endp_proxy $endp_b NO), + qq(rm_thresholds $endp_b all), + qq(set_endp_report_timer $endp_b 5000), + qq(set_endp_flag $endp_b ClearPortOnStart 0) +); + +$::utils->doAsyncCmd( fmt_cmd("rm_cx", "all", $cx_name)); +sleep(1); +$::utils->doAsyncCmd( fmt_cmd("rm_endp", "$endp_a")); +$::utils->doAsyncCmd( fmt_cmd("rm_endp", "$endp_b")); +sleep(1); +my $cmd; +logg("creating endp_a:"); +for $cmd (@endp_a_list) { + logg(" ".$cmd."\n"); + $::utils->doAsyncCmd( $cmd ); +} +logg("creating endp_b"); +for $cmd (@endp_b_list) { + logg(" ".$cmd."\n"); + $::utils->doAsyncCmd( $cmd ); +} +sleep 1; +$::utils->doAsyncCmd(fmt_cmd("add_cx", $cx_name, "default_tm", "$endp_a", "$endp_b")); + + +######################################################################## +=pod +### REFERENCE OF COMMANDS +add_endp $endp_a 1 1 eport_a custom_ether -1 NO 512000 0 NO 64 64 CUSTOM NO 32 0 0 + set_endp_flag $endp_a ReplayOverwriteDstMac 1 + set_endp_details $endp_a 0 0 4294967295 0 'ff ff ff ff ff ff ' 0 0 0 0 10000 0 NA NA NA 0.0.0.0 0 + set_endp_quiesce $endp_a 3 + set_endp_addr $endp_a '00 90 0b 29 06 f9 ' AUTO 0 0 + set_endp_flag $endp_a ReplayLoop 0 + set_endp_flag $endp_a EnableTcpNodelay 0 + set_endp_flag $endp_a EnableRndSrcIP 0 + set_endp_flag $endp_a EnableConcurrentSrcIP 0 + set_endp_flag $endp_a EnableLinearSrcIP 0 + set_endp_flag $endp_a EnableLinearSrcIPPort 0 + set_endp_flag $endp_a QuiesceAfterRange 0 + set_endp_flag $endp_a QuiesceAfterDuration 0 + set_endp_payload $endp_a CUSTOM ff ff ff ff ff ff 00 90 0b 29 06 f9 08 00 45 00 00 32 53 f5 40 00 40 11 cf 91 0a 1a 01 02 0a 1a 01 ff 00 00 00 00 00 00 e8 9b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + set_endp_tos $endp_a DONT-SET 0 +set_script $endp_a NA NA NONE 'NA' 0 0 + set_endp_proxy $endp_a NO +rm_thresholds $endp_a all +set_endp_report_timer $endp_a 5000 + set_endp_flag $endp_a ClearPortOnStart 0 +add_endp $endp_b 1 0 eth0 custom_ether -1 NO 56000 0 NO 64 64 CUSTOM NO 32 0 0 + set_endp_flag $endp_b ReplayOverwriteDstMac 0 + set_endp_details $endp_b 0 0 4294967295 0 '00 90 0b 29 06 f9 ' 0 0 0 0 10000 0 NA NA NA 0.0.0.0 0 + set_endp_quiesce $endp_b 3 + set_endp_flag $endp_b unmanaged 1 + set_endp_addr $endp_b '00 00 00 00 00 00 ' AUTO 0 0 + set_endp_flag $endp_b ReplayLoop 0 + set_endp_flag $endp_b EnableTcpNodelay 0 + set_endp_flag $endp_b EnableRndSrcIP 0 + set_endp_flag $endp_b EnableConcurrentSrcIP 0 + set_endp_flag $endp_b EnableLinearSrcIP 0 + set_endp_flag $endp_b EnableLinearSrcIPPort 0 + set_endp_flag $endp_b QuiesceAfterRange 0 + set_endp_flag $endp_b QuiesceAfterDuration 0 + set_endp_payload $endp_b CUSTOM 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + set_endp_tos $endp_b DONT-SET 0 +set_script $endp_b NA NA NONE 'NA' 0 0 + set_endp_proxy $endp_b NO +rm_thresholds $endp_b all +set_endp_report_timer $endp_b 5000 + set_endp_flag $endp_b ClearPortOnStart 0 +report 'lf_reports' NO NO NO NO +=cut + + diff --git a/lanforge/lanforge-scripts/lf_curl.sh b/lanforge/lanforge-scripts/lf_curl.sh new file mode 100755 index 000000000..d9ad336ad --- /dev/null +++ b/lanforge/lanforge-scripts/lf_curl.sh @@ -0,0 +1,143 @@ +#!/bin/bash +if [ -f /home/lanforge/lanforge.profile ]; then + . /home/lanforge/lanforge.profile +else + echo "/home/lanforge/lanforge.profile not found, bye." + exit 1 +fi +CURL=`which curl` +IP=`which ip` +#echo $CURL; +[ -f lib_vrf.bash ] && . ./lib_vrf.bash + +SOURCE_IP="" +SOURCE_PORT="" +DEST_HOST="" +OUT_FILE=/dev/null +NUM_LOOPS=1 + +help="$0 options: + -d {destination_url} + -h # this help + -i {source ip} + -n {number of times, 0 = infinite} + -o {output file prefix, /dev/null is default} + -p {source port} + -v # verbose curl option -# +E.G.: + $0 -i 10.0.0.1 -p eth1 -o /tmp/output -d http://example.com/ +becomes + curl -sq. ~Lk --interface 10.0.0.1 -o /tmp/output_eth1 http://example.com/ + +Best if used from lf_generic_ping.pl to construct commands referencing this script: + ./lf_generic_ping.pl --mgr cholla-f19 -r 2 -n curl_ex_ --match 'eth2#' --cmd 'lf_curl.sh -o /tmp/curl_%p.out -i %i -d %d -p %p' --dest http://localhost/ +" +LFCURL='' +while getopts ":d:vhi:n:o:p:" OPT ; do + #echo "OPT[$OPT] OPTARG[$OPTARG]" + case $OPT in + h) + echo $help + exit 0 + ;; + d) + DEST_HOST="$OPTARG" + ;; + i) + PORT_IP="$OPTARG" + if [[ $CURL = ~/local/bin/curl ]] || [[ $CURL = /home/lanforge/local/bin/curl ]]; then + LFCURL=1 + fi + ;; + n) + NUM_LOOPS=$OPTARG + ;; + o) + OUT_FILE="$OPTARG" + ;; + p) + PORT="$OPTARG" + SOURCE_PORT="--interface $OPTARG" + ;; + v) + PROGRESS='-#' + ;; + *) + echo "Unknown option [$OPT] [$OPTARG]" + ;; + esac +done + +if [[ -z "$DEST_HOST" ]]; then + echo "$help" + exit 1 +fi + +if [[ x$OUT_FILE != x/dev/null ]] && [[ x$SOURCE_PORT != x ]] ; then + OUT_FILE="-o ${OUT_FILE}_${SOURCE_PORT}" +elif [[ $OUT_FILE = /dev/null ]]; then + OUT_FILE="-o ${OUT_FILE}" +fi + +VRF='' +NUM_GOOD=0 +LB='#' +L_SOURCE_PORT="$PORT" +if [[ $PORT = *$LB* ]] && [[ $PORT != *@* ]]; then + L_SOURCE_PORT="${PORT}@${PORT//#*/}" +fi +if [[ ${#IFNAMES[@]} -lt 1 ]]; then + [[ x$PROGRESS != x ]] && echo "NO VRF PORTS: ${#IFNAMES[@]}" +else + [[ x$PROGRESS != x ]] && echo "SOME VRF PORTS: ${#IFNAMES[@]}" + if [[ x${IFNAMES[$L_SOURCE_PORT]} = x ]]; then + [[ x$PROGRESS != x ]] && echo "No vrf port detected for $L_SOURCE_PORT" + else + [[ x$PROGRESS != x ]] && echo "VRF port: ${IFNAMES[$L_SOURCE_PORT]}" + VRF=1 + fi +fi + +if [[ $VRF = 1 ]]; then + SOURCE_IP='' +elif [[ $LFCURL = 1 ]]; then + SOURCE_IP="--dns-ipv4-addr $OPTARG --interface $OPTARG" +else + SOURCE_IP="--interface $OPTARG" +fi + + +STD_O="/tmp/lf_curl_so.$$" +if [[ x$PROGRESS = x ]]; then + VERB="-s" + STD_E="/tmp/lf_curl_se.$$" +else + VERB="" + STD_E="" +fi +CCMD="$CURL $VERB -Lk --connect-timeout 2 --max-time 10 $PROGRESS \ +-D /tmp/lf_curl_h.$$ $OUT_FILE $SOURCE_IP $DEST_HOST" + +if [[ x$VRF != x ]]; then + CCMD="$IP vrf exec ${IFNAMES[$L_SOURCE_PORT]} $CCMD" +fi + +for N in `seq 1 $NUM_LOOPS`; do + if [[ x$PROGRESS = x ]]; then + $CCMD > $STD_O &> $STD_E + else + echo "Running $CCMD" + $CCMD + fi + if [[ $? > 0 ]]; then + echo "Failed $DEST_HOST" + [ -f /tmp/lf_curl_se.$$ ] && head -1 /tmp/lf_curl_se.$$ + else + NUM_GOOD=$(( $NUM_GOOD +1)) + [ -f /tmp/lf_curl_so.$$ ] && head -1 /tmp/lf_curl_so.$$ + [ -f /tmp/lf_curl_h.$$ ] && head -1 /tmp/lf_curl_h.$$ + fi + sleep 1 +done +echo "Finished $NUM_LOOPS, $NUM_GOOD successful" +# diff --git a/lanforge/lanforge-scripts/lf_cycle_wanlinks.pl b/lanforge/lanforge-scripts/lf_cycle_wanlinks.pl new file mode 100755 index 000000000..3e2e9f86e --- /dev/null +++ b/lanforge/lanforge-scripts/lf_cycle_wanlinks.pl @@ -0,0 +1,52 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# Load different databases, turn on/off packet capturing. + +use strict; + +# Un-buffer output +$| = 1; + +my $i = 0; +my $nm = "VRWL-1.1.000"; +my $im = "./lf_icemod.pl --quiet=2"; +my $cap_for = 10; + +while (1) { + print "Doing round: $i\n"; + printAndExec("$im --load db1"); + printAndExec("$im --cx $nm --state running"); + save_captures(); + printAndExec("$im --load db2"); + printAndExec("$im --cx $nm --state running"); + save_captures(); + $i++; +} + + +sub save_captures { + my $i; + for ($i = 0; $i<5; $i++) { + printAndExec("$im --endp $nm-A --pcap /tmp/endp-a"); + printAndExec("$im --endp $nm-B --pcap /tmp/endp-b"); + sleep($cap_for); + printAndExec("$im --endp $nm-A --pcap off"); + printAndExec("$im --endp $nm-B --pcap off"); + printAndExec("rm -fr /tmp/endp-a/"); + printAndExec("rm -fr /tmp/endp-b/"); + } +} + + +sub printAndExec { + my $cmd = $_[0]; + + print "$cmd\n"; + # NOTE: If you use the single back-ticks here, it will hang, probably some + # signal problem...never figured out why really (ERESTARTSYS) was the error + # that perl hung on... --Ben + system("$cmd"); +} diff --git a/lanforge/lanforge-scripts/lf_endp_script.pl b/lanforge/lanforge-scripts/lf_endp_script.pl new file mode 100755 index 000000000..e5b6a827a --- /dev/null +++ b/lanforge/lanforge-scripts/lf_endp_script.pl @@ -0,0 +1,255 @@ +#!/usr/bin/perl -w + +# This program is used to create a hunt-script +# used for matrix load emulation on LANforge +# (C) Candela Technologies 2015 + +use strict; +use warnings; +#use Carp; +#$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +use constant NA => "NA"; +use constant NL => "\n"; +use constant shelf_num => 1; + +# Default values for ye ole cmd-line args. +our $resource = 1; +our $quiet = "yes"; +our $endp_name = ""; +our $action = ""; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; + +our $script_name = undef; +our $script_type = ""; +our $flags = ""; +our $loops = 0; +our $private = ""; +our $group_action = "ALL"; +our $log_cli = "unset"; # use ENV{LOG_CLI} elsewhere + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +=pod +Below is an example of a set_script for script name bunny +set_script hunt-sta-A bunny 4096 ScriptHunt '60000 1000 50000,100000,500000,20,56000,30000,1,100000, 60,128,256,512,1024,1280,1460,1472,1514 60,128,256,512,1024,1280,1460,1472,1514 100,300,400,600,800,955 NONE' ALL 0 +which should follow this syntax: + + endp: hunt-sta-A + name: bunny + flags: 4096 + type: ScriptHunt + private: '60000 1000 50000,100000,500000,20,56000,30000,1,100000, 60,128,256,512,1024,1280,1460,1472,1514 60,128,256,512,1024,1280,1460,1472,1514 100,300,400,600,800,955 NONE' + group_action: ALL + loop_count: 0 + +The private syntax is very opaque + ScriptHunt syntax is: run_duration pause_duration constraints payload_sizes_a payload_sizes_b attenuations attenuator + run_duration 60000 + pause_duration 1000 + constraints 50000,100000,500000,20,56000,30000,1,100000, + payload_sizes_a 60,128,256,512,1024,1280,1460,1472,1514 60,128,256,512,1024,1280,1460,1472,1514 100,300,400,600,800,955 + payload_sizes_b NONE + attenuations ? + attenuator ? +=cut + + +our $usage = qq<$0 ... + [--action { set_script|start_cx|quiece_cx|stop_cx|show_report|del_script } ] + set_script: configure a cx with script parameters set in script_type, script_flags + show_port: show script report for cx + del_script: remove script from cx + start_cx: start traffic on a connection (thus starting script) + quiece_cx: stop transmitting traffic and wait a period before stopping connection recieve + stop_cx: stop transmit and recieve immediately + # --action start_cx --cx_name bunbun + [--mgr {host-name | IP}] + [--mgr_port {ip port}] + [--resource {number}] + [--quiet { yes | no }] + [--endp_name {endpoint name}] + [--cx_name {endpoint name}] + [--script_type {2544|Hunt|WanLink|Atten} ] + 2544 - RFC 2544 type script + Hunt - Hunt for maximum speed with constraints + WanLink - iterate thru wanlink settings + Atten - use with attenuators + [--flags - see LF CLI User Guide script flags for set_port] + [--script_name - script name] + [--loops - how many time to loop before stopping; (0 is infinite)] + [--private - the nested script-type parameters in a single string] + [--log_cli {1|filename}] + +Please refer to LANforge CLI Users Guide: http://www.candelatech.com/lfcli_ug.php#set_script + +Examples: +# add a script to an endpoint +$0 --action set_script --script_type Hunt \\ + --script_name bunny --endp_name cx3eth0 -loops 1 --flags 4096 \\ + --private '60000 1000 50000,100000,500000,20,56000,30000,1,100000, 60,128,256,512,1024,1280,1460,1472,1514 60,128,256,512,1024,1280,1460,1472,1514 100,300,400,600,800,955 NONE' + +# start the cx to start the script: +$0 --action start_cx --cx_name hunt-sta + +# quiesce the cx +$0 --action quiece_cx --cx_name hunt-sta + +# show the report +$0 --action show_report --endp_name hunt-sta-A + +# stop the cx +$0 --action stop_cx --cx_name hunt-sta + +# remove endpoint script +$0 --action del_script --endp_name hunt-sta-A +>; + +my $i = 0; +my $cmd; +die($::usage) if (@ARGV < 2); + +GetOptions +( + 'action|a=s' => \$::action, + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p=i' => \$::lfmgr_port, + 'resource|r=i' => \$::resource, + 'quiet|q=s' => \$::quiet, + 'endp_name|e=s' => \$::endp_name, + 'cx_name|c=s' => \$::cx_name, + 'script_type|t=s' => \$::script_type, + 'flags|f=i' => \$::flags, + 'script_name|n=s' => \$::script_name, + 'loops|l=i' => \$::loops, + 'private|b=s' => \$::private, + 'log_cli=s{0,1}'=> \$log_cli, +) || die("$::usage"); + + +die("please specify action\n$usage") + if (!defined $::action || $::action eq ""); + +if ($::action eq "set_script" + || $::action eq "show_report" + || $::action eq "del_script") { + die("please specify endpoint name\n$usage") + if (!defined $::endp_name || $::endp_name eq ""); +} +if ($::action eq "set_script" + || $::action eq "del_script") { + + die("please specify script name\n$usage") + if (!defined $::script_name || $::script_name eq ""); +} + +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } +} + +# Open connection to the LANforge server. + +our $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +our $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +if ($::quiet eq "yes") { + $utils->cli_send_silent(1); # Do show input to CLI + $utils->cli_rcv_silent(1); # Repress output from CLI ?? +} +else { + $utils->cli_send_silent(0); # Do show input to CLI + $utils->cli_rcv_silent(0); # Repress output from CLI ?? +} + +$::utils->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); +our %script_types = ( + "2544" => "Script2544", + "Atten" => "ScriptAtten", + "Hunt" => "ScriptHunt", + "Script2544" => "Script2544", + "ScriptAtten" => "ScriptAtten", + "ScriptHunt" => "ScriptHunt", + "ScriptWanLink"=> "ScriptWL", + "ScriptWL" => "ScriptWL", + "WanLink" => "ScriptWL", +); + +if ($::action eq "start_cx" + || $::action eq "stop_cx" + || $::action eq "quiece_cx") { + die("Please state cx_name") + if ( !defined $::cx_name || $::cx_name eq "" ); +} + +if ($::action eq "set_script") { + my $scr_type = $::script_types{ $::script_type }; + die("Unknown script type [$::script_type]") + if ( !defined $::script_type + || !defined $scr_type + || $::script_type eq "" + || $scr_type eq "" ); + die("Cannot use blank action.") + if (! defined $::private || $::private eq ""); + + $cmd = $::utils->fmt_cmd("set_script", $::endp_name, "$::script_name", $::flags, $scr_type, "$::private", $::group_action, $::loops); + $::utils->doAsyncCmd($cmd); +} +elsif ($::action eq "show_report") { + $cmd = $::utils->fmt_cmd("show_script_results", $::endp_name); + $::utils->doAsyncCmd($cmd); +} +elsif ($::action eq "del_script") { + $cmd = $::utils->fmt_cmd("set_script", $::endp_name, "$::script_name", "0", "NA", "NONE"); + $::utils->doAsyncCmd($cmd); +} +elsif ($::action eq "start_cx") { + $cmd = $::utils->fmt_cmd("set_cx_state", "ALL", $::cx_name, "RUNNING"); + $::utils->doAsyncCmd($cmd); +} +elsif ($::action eq "quiece_cx") { + $cmd = $::utils->fmt_cmd("set_cx_state", "ALL", $::cx_name, "QUIESCE"); + $::utils->doAsyncCmd($cmd); +} +elsif ($::action eq "stop_cx") { + $cmd = $::utils->fmt_cmd("set_cx_state", "ALL", $::cx_name, "STOPPED"); + $::utils->doAsyncCmd($cmd); +} +else { + die( "Unknown action.\n$usage"); +} + + + +#eof diff --git a/lanforge/lanforge-scripts/lf_firemod.pl b/lanforge/lanforge-scripts/lf_firemod.pl new file mode 100755 index 000000000..da4f764da --- /dev/null +++ b/lanforge/lanforge-scripts/lf_firemod.pl @@ -0,0 +1,852 @@ +#!/usr/bin/perl -w + +# This program is used to create, show, and modify existing connections +# and get some basic information from LANforge. +# (C) 2020 Candela Technologies Inc. +# +# + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use if (-e "/home/lanforge/scripts"), lib => "/home/lanforge/scripts"; +use lib "../"; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +our $NA = 'NA'; +our $NL = "\n"; +our $shelf_num = 1; + +# Default values for ye ole cmd-line args. +our $resource = 1; +our $quiet = "yes"; +our $endp_name = ""; +our $endp_cmd = ""; +our $port_name = ""; +our $speed = "-1"; +our $action = ""; +our $do_cmd = "NA"; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; +our $endp_vals = undef; +our $ip_port = "-1"; # let lf choose +our $multicon = "0"; #no multicon + +# For creating multicast endpoints +our $endp_type = undef; #"mc_udp"; this needs to be explicit +our $mcast_addr = "224.9.9.9"; +our $mcast_port = "9999"; +our $max_speed = "0"; +our $rcv_mcast = "YES"; +our $min_pkt_sz = "-1"; +our $max_pkt_sz = "0"; +our $pkts_to_send = ""; +our $use_csums = "NO"; # Use LANforge checksums in payload? +our $ttl = 32; +our $report_timer = 5000; +our $tos = ""; +our $lat1 = ""; +our $lat2 = ""; +our $arm_pps = ""; +our $arm_cpu_id = "NA"; +# For cross connects +our $cx_name = ""; +our $cx_endps = ""; +our $list_cx_name = "all"; +our $test_mgr = "default_tm"; +our $list_test_mgr = "all"; +our $stats_from_file = ""; + +our $fail_msg = ""; +our $manual_check = 0; + +our @known_endp_types = qw(generic lf_tcp lf_tcp6 lf_udp lf_udp6 mc_udp mc_udp6); +our @known_tos = qw(DONT-SET LOWCOST LOWDELAY RELIABILITY THROUGHPUT BK BE VI VO); + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $endp_type_str = join(' | ', @::known_endp_types); +my $tos_str = join(' | ', @::known_tos); + +our $usage = <<"__EndOfUsage__"; +$0 [ --action { + create_cx | create_endp | create_arm | + delete_cx | delete_cxe | delete_endp | do_cmd | + list_cx | list_endp | list_ports | + set_endp | show_cx | show_endp | show_port | + start_cx | start_endp | stop_cx | stop_endp + } ] + [--arm_pps {packets per second}] + [--cmd {lf-cli-command text}] + [--cx_name {connection name}] + [--cx_endps {endp1},{endp2}] + [--endp_cmd {generic-endp-command}] + [--endp_name {name}] + [--endp_type { $endp_type_str }] + [--endp_vals {key,key,key,key}] + # show_endp output can be narrowed with key-value arguments + # Examples: + # --action show_endp --endp_vals MinTxRate,DestMAC,Avg-Jitter + # Not available: Latency,Pkt-Gaps, or rows below steps-failed. + # Special Keys: + # --endp_vals tx_bytes (Tx Bytes) + # --endp_vals rx_bytes (Rx Bytes) + [--ip_port {-1 (let LF choose, AUTO) | 0 (let OS choose, ANY) | specific IP port}] + [--max_pkt_sz {maximum payload size in bytes}] + [--pkts_to_send {Packets (PDUs) to send before stopping the connection. 0 means infinite}] + [--max_speed {speed in bps, 0 means same as mininum value}] + [--min_pkt_sz {minimum payload size in bytes}] + [--mcast_addr {multicast address, for example: 224.4.5.6}] + [--mcast_port {multicast port number}] + [--mgr {host-name | IP}] + [--mgr_port {ip port}] + [--multicon {0 (no multi-conn, Normal) | number of connections (TCP only)}] + [--port_name {name}] + [--quiet { yes | no }] + [--rcv_mcast {yes (receiver) | no (transmitter)}] + [--report_timer {miliseconds}] + [--resource {number}] + [--speed {speed in bps}] + [--stats_from_file {file-name}] + # Read 'show-endp' ouput from a file instead of direct query from LANforge. + # This can save a lot of time if we already have the output available. + [--tos { $tos_str },{priority}] + [--use_csums {yes | no, should we checksum the payload}] + [--use_ports {port-A,port-B} # Example: 1.1.eth1,1.2.sta0 + # notation is .. + # if this option is present, you can skip the create_endp action + [--use_speeds {speed-A,speed-B} # Example: 54000,1000000 + # if this option is present, you can skip the create_endp action + [--log_cli {1|filename}] + [--test_mgr {default_tm|all|other-tm-name}] + [--ttl {time-to-live}] + +Example: + $0 --action set_endp --endp_name udp1-A --speed 154000 + + $0 --action normalize_latency --lat1 "latency-buckets info for endpA" --lat2 "latency-buckets-info for endpB" + + $0 --action create_endp --endp_name mcast_xmit_1 --speed 154000 \\ + --endp_type mc_udp --mcast_addr 224.9.9.8 --mcast_port 9998 \\ + --rcv_mcast NO --port_name eth1 \\ + --min_pkt_sz 1072 --max_pkt_sz 1472 --pkts_to_send 9999\\ + --use_csums NO --ttl 32 \\ + --quiet no --report_timer 1000 + + $0 --action create_endp --endp_name bc1 --speed 256000 \\ + --endp_type lf_tcp --tos THROUGHPUT,100 --port_name rd0#1 + + $0 --action create_endp --endp_name ping1 --port_name sta0 --endp_cmd \"lfping -p deadbeef000 -I sta0 8.8.4.4\" + --endp_type generic + + $0 --action list_cx --test_mgr all --cx_name all + + # Unlikely example of creating a CX from differently named endpoints: + $0 --action create_cx --cx_name L301 \\ + --cx_endps ep_rd0a,ep_rd1a --report_timer 1000 + + # Or, skip explicit endpoint creation with the using-ports feature: + $0 --action create_cx --cx_name banana \\ + --use_ports sta0,eth1 --use_speeds 15000,10000000 --report_timer 1200 + + Example of creating an Armageddon connection: + $0 --action create_arm --endp_name arm01-A --port_name eth1 \\ + --arm_pps 80000 --min_pkt_sz 1472 --max_pkt_sz 1514 --tos LOWDELAY,100 + + $0 --mgr jedtest --action create_cx --cx_name arm-01 --cx_endps arm01-A,arm01-B + + Example of creating endpoints and joining them: + $0 --mgr localhost --action create_endp --endp_name test1a --speed 10000000 \\ + --endp_type lf_tcp --port_name eth5 --ip_port 0 --multicon 10 + + $0 --mgr localhost --resource 3 --action create_endp --endp_name test1b --speed 0 \\ + --endp_type lf_tcp --port_name wlan2 --multicon 1 + + $0 --mgr localhost --action create_cx --cx_name test1 --cx_endps test1a,test1b + + $0 -m vm-48e4 -p 4001 --action create_cx --cx_name testo --endp_type tcp \\ + --use_ports r0b,r1b --use_speeds 9600,9600 --report_timer 250 +__EndOfUsage__ + +my $i = 0; +my $cmd; + +my $log_cli = "unset"; # use ENV{LOG_CLI} elsewhere +my $show_help = 0; +our $use_ports_str = "NA"; +our $use_speeds_str = "NA"; +our $use_max_speeds = "NA"; +our @use_ports = (); +our @use_speeds = (); + +if (@ARGV < 2) { + print $usage; + exit 0; +} + +our $debug = 0; + +GetOptions +( + 'action|a=s' => \$::action, + 'arm_pps=i' => \$::arm_pps, + 'cmd|c=s' => \$::do_cmd, + 'cx_endps=s' => \$::cx_endps, + 'cx_name=s' => \$::cx_name, + 'debug|d' => \$::debug, + 'help|h' => \$show_help, + 'ip_port=i' => \$::ip_port, + 'endp_cmd=s' => \$::endp_cmd, + 'endp_name|e=s' => \$::endp_name, + 'endp_type=s' => \$::endp_type, + 'endp_vals|o=s' => \$::endp_vals, + 'log_cli=s{0,1}' => \$log_cli, + 'manager|mgr|m=s' => \$::lfmgr_host, + 'max_speed=s' => \$::speed, + 'min_pkt_sz=s' => \$::min_pkt_sz, + 'max_pkt_sz=s' => \$::max_pkt_sz, + 'pkts_to_send=s' => \$::pkts_to_send, + 'mcast_addr=s' => \$::mcast_addr, + 'mcast_port=s' => \$::mcast_port, + 'lfmgr_port|mgr_port|port|p=i' => \$::lfmgr_port, + 'multicon=i' => \$::multicon, + 'port_name=s' => \$::port_name, + 'quiet|q=s' => \$::quiet, + 'rcv_mcast=s' => \$::rcv_mcast, + 'report_timer=i' => \$::report_timer, + 'resource|r=i' => \$::resource, + 'speed|s=i' => \$::speed, + 'stats_from_file=s' => \$::stats_from_file, + 'ttl=i' => \$::ttl, + 'use_csums=s' => \$::use_csums, + 'use_ports=s' => \$::use_ports_str, + 'use_speeds=s' => \$::use_speeds_str, + 'use_max_speeds=s' => \$::use_max_speeds, + 'test_mgr=s' => \$::test_mgr, + 'tos=s' => \$::tos, + 'lat1=s' => \$::lat1, + 'lat2=s' => \$::lat2, + +) || die("$::usage"); + +if ($show_help) { + print $usage; + exit 0; +} + +# Convert some TOS values that the server likely doesn't understand. +if ($tos eq "BK") { + $tos = 64; +} +elsif ($tos eq "BE") { + $tos = 96; +} +elsif ($tos eq "VI") { + $tos = 128; +} +elsif ($tos eq "VO") { + $tos = 192; +} + +use Data::Dumper; + +if ($::debug) { + $ENV{DEBUG} = 1 if (!(defined $ENV{DEBUG})); +} + +if ($::quiet eq "0") { + $::quiet = "no"; +} +elsif ($::quiet eq "1") { + $::quiet = "yes"; +} + +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } +} + +if ($::do_cmd ne "NA") { + $::action = "do_cmd"; +} +our @valid_actions = qw( + create_arm create_cx create_endp normalize_latency + delete_cx delete_cxe delete_endp do_cmd + list_cx list_endp list_ports + set_endp show_cx show_endp show_port start_endp stop_endp + ); + +if (($::action eq "") && ((defined $::endp_vals) && ("$::endp_vals" ne ""))) { + $::action = "show_endp"; +} + +if (! (grep {$_ eq $::action} @::valid_actions )) { + die("Invalid action: $::action\n$::usage\n"); +} +our @actions_needing_endp = qw( + create_arm create_endp + delete_endp + set_endp start_endp stop_endp + ); +if (grep {$_ eq $::action} @actions_needing_endp) { + if (length($::endp_name) == 0) { + print "ERROR: Must specify endp_name.\n"; + die("$::usage"); + } +} +if ($::quiet eq "1" ) { + $::quiet = "yes"; +} + +# Open connection to the LANforge server. +our $utils = new LANforge::Utils(); +if (!defined $::stats_from_file || ("" eq $::stats_from_file)) { + $::utils->connect($lfmgr_host, $lfmgr_port); +} + +if (grep {$_ eq $::action} split(',', "show_endp,set_endp,create_endp,create_arm,list_endp,normalize_latency")) { + + $::max_speed = $::speed if ($::max_speed eq "-1"); + if ($::action eq "normalize_latency") { + my $val = $::utils->normalize_latency($::lat1, $::lat2); + print("Normalized-Latency: $val\n"); + } + elsif ($::action eq "list_endp") { + $::utils->cli_rcv_silent(0); + $::quiet = "no"; + my @lines = split(/\r?\n/, $::utils->doAsyncCmd("nc_show_endpoints all")); + for my $line (@lines) { + if ($line =~ /^([A-Z]\w+)\s+\[(.*?)\]/) { + print "** $line\n"; + } + } + } + elsif ($::action eq "show_endp") { + if ((defined $::endp_vals) && ("$::endp_vals" ne "")) { + my %option_map = (); + my $option = ''; + for $option (split(',', $::endp_vals)) { + #print "OPTION[$option]\n"; + #next if ($option =~ /\s/); + my $oopt = $option; + $option_map{ $option } = ''; + + if ($option =~ /rx[_-]pps/ ) { $option = "Rx-Pkts-Per-Sec"; } + if ($option =~ /tx[_-]pps/ ) { $option = "Tx-Pkts-Per-Sec"; } + if ($option =~ /rx[_-]pkts/ ) { $option = "Rx-Pkts-Total"; } + if ($option =~ /tx[_-]pkts/ ) { $option = "Tx-Pkts-Total"; } + if ($option =~ /rx[_-]bps/ ) { $option = "Rx-Bytes-bps"; } + if ($option =~ /tx[_-]bps/ ) { $option = "Tx-Bytes-bps"; } + + if ($option =~ /^Rx[ _-]Bytes$/ ) { $option = "Rx-Bytes-Total"; } + if ($option =~ /^Tx[ _-]Bytes$/ ) { $option = "Tx-Bytes-Total"; } + if ($option =~ /^Rx[ _-]Pkts$/ ) { $option = "Rx-Pkts-Total"; } + if ($option =~ /^Tx[ _-]Pkts$/ ) { $option = "Tx-Pkts-Total"; } + + if ($option =~ /^EID$/i ) { + $option_map{ "Shelf" } = ''; + $option_map{ "Card" } = ''; + $option_map{ "Port" } = ''; + } + + # we don't know if we're armageddon or layer 3 + if ($option =~ /tx_bytes/ ) { + $option_map{ "Tx-Bytes-Total" } = ''; + $option = "Bytes Transmitted"; + } + elsif ($option =~ /rx_b(ps|ytes)/ ) { + $option_map{ "Rx-Bytes-Total" } = ''; + $option = "Bytes Rcvd"; + } + elsif ($option =~ /tx_packets/) { + $option_map{ "Tx-Pkts-Total" } = ''; + $option = "Packets Transmitted"; + } + elsif ($option =~ /rx_packets/) { + $option_map{ "Rx-Pkts-Total" } = ''; + $option = "Packets Rcvd"; + } + if ($oopt ne $option) { + $option_map{ $option } = ''; + } + } + # options are reformatted + + my $i; + my @lines = (); + if ($stats_from_file ne "") { + @lines = split(/\r?\n/, get_stats_from_file($stats_from_file, $endp_name)); + } + else { + @lines = split(/\r?\n/, $::utils->doAsyncCmd("nc_show_endp $endp_name")); + } + my $rh_value_map = $::utils->show_as_hash(\@lines); + + if ($debug && ($quiet ne "yes")) { + my @keyz = sort keys(%$rh_value_map); + print Dumper(\@keyz); + } + + for my $option (keys %option_map) { + my $val = '-'; + + if ($option =~ /^EID$/i) { + $val = "1.".$rh_value_map->{"Card"}.".".$rh_value_map->{"Port"}; + } + elsif (defined $rh_value_map->{$option}) { + $val = $rh_value_map->{$option}; + + if ($option =~ /Latency/) { + $val = $rh_value_map->{$option}; + $option_map{"Normalized-Hdr"} = $::utils->normalize_bucket_hdr(17); + $option_map{"Latency"} = $val; + } + elsif ($option =~ /Pkt-Gaps/) { + $val = $rh_value_map->{$option}; + $option_map{"Normalized-Hdr"} = $::utils->normalize_bucket_hdr(17); + $option_map{"Pkt-Gaps"} = $val; + } + elsif ($option =~ /RX-Silence/) { + $val = $rh_value_map->{$option}; + $option_map{"Normalized-Hdr"} = $::utils->normalize_bucket_hdr(17); + $option_map{"RX-Silence"} = $val; + } + } + else { + $val = 'not found'; + } + $option_map{ $option } = $val; + } + + for $option ( sort keys %option_map ) { + #print("Checking option: $option\n"); + print $option.": ".$option_map{ $option }."\n"; + } + } + else { + if ($stats_from_file ne "") { + print get_stats_from_file($stats_from_file, $endp_name); + } + else { + print $::utils->doAsyncCmd("nc_show_endp $::endp_name"); + } + } + } + elsif ($::action eq "create_arm") { + die("Must choose packets per second: --arm_pps\n$::usage") + if (! defined $::arm_pps || $::arm_pps eq ""); + + $::min_pkt_sz = "1472" if ($::min_pkt_sz eq "-1"); + $::max_pkt_sz = $::min_pkt_sz if ($::max_pkt_sz eq "-1"); + my $ip_port = "-1"; # let lf choose + $cmd = $::utils->fmt_cmd("add_arm_endp", $::endp_name, $shelf_num, $::resource, + $::port_name, "arm_udp", $::arm_pps, + $::min_pkt_sz, $::max_pkt_sz, $::arm_cpu_id, $::tos); + $::utils->doCmd($cmd); + + $cmd = "set_endp_report_timer $::endp_name $::report_timer"; + $::utils->doCmd($cmd); + } + elsif ($::action eq "create_endp") { + create_endp($::endp_name, $::resource, $::port_name, $::endp_type, $::speed, $::max_speed); + } + else { + # Set endp + if ($speed ne "NA") { + # Read the endpoint in... + #my $endp1 = new LANforge::Endpoint(); + #$::utils->updateEndpoint($endp1, $endp_name); + + # Assume Layer-3 for now + $cmd = $::utils->fmt_cmd("add_endp", $endp_name, $::NA, $::NA, $::NA, + $::NA, $::NA, $::NA, $speed, $max_speed); + #print("cmd: $cmd\n"); + $::utils->doCmd($cmd); + } + } +} +elsif ($::action eq "start_endp") { + $cmd = "start_endp $::endp_name"; + $::utils->doCmd($cmd); +} +elsif ($::action eq "stop_endp") { + $cmd = "stop_endp $::endp_name"; + $::utils->doCmd($cmd); +} +elsif ($::action eq "delete_endp") { + $cmd = "rm_endp $::endp_name"; + $::utils->doCmd($cmd); +} +elsif ($::action eq "show_port") { + print $::utils->doAsyncCmd("nc_show_port 1 $::resource $::port_name") . "\n"; +} +elsif ($::action eq "do_cmd") { + print $::utils->doAsyncCmd("$::do_cmd") . "\n"; +} +elsif ($::action eq "list_ports") { + my @ports = $::utils->getPortListing($::shelf_num, $::resource); + my $i; + for ($i = 0; $i<@ports; $i++) { + my $cur = $ports[$i]->cur_flags(); + #print "cur-flags -:$cur:-\n"; + + print $ports[$i]->dev(); + if ($cur =~ /LINK\-UP/) { + print " link=UP"; + } + else { + print " link=DOWN"; + } + # Guess speed..need better CLI output API for more precise speed. + if ($cur =~ /10G\-FD/) { + print " speed=10G"; + } + elsif ($cur =~ /1000\-/) { + print " speed=1G"; + } + elsif ($cur =~ /100bt\-/) { + print " speed=100M"; + } + elsif ($cur =~ /10bt\-/) { + print " speed=10M"; + } + else { + print " speed=UNKNOWN"; + } + print "\n"; + } +} +elsif ($::action eq "list_cx") { + $::cx_name = $::list_cx_name if ($::cx_name eq ""); + $::test_mgr = $::list_test_mgr if ($::test_mgr eq ""); + + my $cmd = $::utils->fmt_cmd("show_cxe", $::test_mgr, $::cx_name ); + my @lines = split(/\r?\n/, $::utils->doAsyncCmd($cmd)); + my $out = ''; + my $num_ep = 0; + for my $line (@lines) { + print " |||$line\n" if ($::debug); + if ($line =~ /\s*WAN_LINK CX:\s+([^ ]+)\s+id:.*$/ ) { + $out .= "WL $1"; + } + if ($line =~ /^WanLink\s+\[([^ ]+)\] .*$/ ) { + $out .= ", wanlink $1"; + $num_ep++; + } + if ($line =~ /^L4Endp\s+\[([^ ]+)\]/) { + $num_ep++; + } + if ($line =~ /^L4_GENERIC\s+CX:\s+([^ ]+)\s+id:/) { + $out .= "L4 $1"; + } + if ($line =~ /^\s*(WanLink|LANFORGE.*? CX):\s+([^ ]+) .*$/ ) { + $out .= "CX $2"; + } + if ($line =~ /^ARM_.*? CX:\s+([^ ]+) .*$/ ) { + $out .= "CX $1"; + } + if ($line =~ /^(Endpoint|ArmEndp) \[([^ \]]+)\].*$/) { + $out .= ", endpoint $2"; + $num_ep++; + } + if (($line =~ /^ *$/) && ($num_ep > 0)) { + print "$out\n"; + $out = ''; + $num_ep = 0; + } + } +} +elsif ($::action eq "show_cx") { + # require a cx_name + die("Please specify cx_name\n$::usage") if (length($::cx_name) < 1); + if (length($::test_mgr) <1) { + $::test_mgr = "default_tm"; + } + my $cmd = $::utils->fmt_cmd("show_cxe", $::test_mgr, $::cx_name ); + #print "$cmd\n"; + print $::utils->doAsyncCmd($cmd)."\n"; +} +elsif ($::action eq "create_cx") { + # require cx_name, test_mgr, two endpoints + my $end_a = ""; + my $end_b = ""; + my $port_a = ""; + my $port_b = ""; + my $speed_a = 0; + my $speed_b = 0; + my $max_speed_a = $::max_speed; + my $max_speed_b = $::max_speed; + + if ("NA" ne $::use_ports_str) { + ($port_a, $port_b) = split(',', $::use_ports_str); + if (!(defined $port_a) || !(defined $port_b)) { + die("Error with port names. Please format as short EIDs: 1.1.sta0000,1.2.eth1"); + } + die("Please name your cross connect: --cx_name\n$::usage") + if ($::cx_name eq ""); + $end_a = "${main::cx_name}-A"; + $end_b = "${main::cx_name}-B"; + $::cx_endps = "$end_a,$end_b"; + } + elsif ((defined $::cx_endps) && ("" ne $::cx_endps)) { + ($end_a, $end_b) = split(/,/, $::cx_endps); + die("Specify two endpoints like: tcp123-A,tcp123-B \n$::usage") + if ((length($end_a) < 1) || (length($end_b) < 1)); + } + else { + die("please use --cx_endps a,b or --use_ports portA,portB"); + } + + if ("NA" ne $::use_speeds_str) { + ($speed_a, $speed_b) = split(',', $::use_speeds_str); + $max_speed_a = $speed_a; + $max_speed_b = $speed_b; + } + if ("NA" ne $::use_max_speeds) { + ($max_speed_a, $max_speed_b) = split(',', $::use_speeds_str); + } + + # create endpoints + my $resource_a = $::resource; + my $resource_b = $::resource; + my $shelf; + my @hunks; + if ($port_a =~ /1\.\d+\.\S+/) { + @hunks = split(/[.]/, $port_a); + ($shelf, $resource_a, $port_a) = @hunks; + } + if ($port_b =~ /1\.\d+\.\S+/) { + @hunks = split(/[.]/, $port_b); + ($shelf, $resource_b, $port_b) = @hunks; + } + print ("end_a[$end_a] resource_a[$resource_a] port_a[$port_a], type[$::endp_type] speed[$speed_a] max[$max_speed_a]\n"); + print ("end_b[$end_b] resource_b[$resource_b] port_b[$port_b], type[$::endp_type] speed[$speed_b] max[$max_speed_b]\n"); + create_endp($end_a, $resource_a, $port_a, $::endp_type, $speed_a, $max_speed_a); + create_endp($end_b, $resource_b, $port_b, $::endp_type, $speed_b, $max_speed_b); + + my $cmd = $::utils->fmt_cmd("add_cx", $::cx_name, $::test_mgr, $end_a, $end_b); + $::utils->doCmd($cmd); + my $cxonly = $::NA; + $cmd = $::utils->fmt_cmd("set_cx_report_timer", $::test_mgr, $::cx_name, $::report_timer, $cxonly); + $::utils->doCmd($cmd); +} +elsif ($::action eq "delete_cx") { + # require cx_name + die("Which test manager?: --test_mgr\n$::usage") if ($::test_mgr eq ""); + die("Which cross connect? --cx_name\n$::usage") if ($::cx_name eq ""); + $::utils->doCmd($::utils->fmt_cmd("rm_cx", $::test_mgr, $::cx_name)); +} +elsif ($::action eq "delete_cxe") { + # require cx_name + die("Which test manager?: --test_mgr\n$::usage") if ($::test_mgr eq ""); + die("Which cross connect? --cx_name\n$::usage") if ($::cx_name eq ""); + $::utils->doCmd($::utils->fmt_cmd("rm_cx", $::test_mgr, $::cx_name)); + $::utils->doCmd($::utils->fmt_cmd("rm_endp", "$::cx_name-A")); + $::utils->doCmd($::utils->fmt_cmd("rm_endp", "$::cx_name-B")); +} +else { + die("Unknown action: $::action\n$::usage\n"); +} + +exit(0); +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub create_endp { + my ($my_endp_name, $my_resource, $my_port_name, $my_endp_type, $my_speed, $my_max_speed) = @_; + + + die("Must choose endpoint protocol type: --endp_type\n$::usage") + if (! defined $::endp_type|| $::endp_type eq ""); + + if ($my_endp_type eq "tcp") { + $my_endp_type = "lf_tcp"; + } + if ($my_endp_type eq "udp") { + $my_endp_type = "lf_udp"; + } + + if ($my_endp_type ne "NA") { + die("Endpoint protocol type --endp_type must be among " + .join(', ', @::known_endp_types)."\n".$::usage) + if (! grep {$_ eq $my_endp_type } @::known_endp_types); + } + + if ($my_endp_type eq "generic") { + if ($::endp_cmd eq "") { + die("Must specify endp_cmd if creating a generic endpoint.\n"); + } + $cmd = $::utils->fmt_cmd("add_gen_endp", $my_endp_name, 1, $my_resource, + $my_port_name, "gen_generic"); + $::utils->doCmd($cmd); + + # Create the dummy + #my $dname = "D_" . $::endp_name; + #$cmd = $::utils->fmt_cmd("add_gen_endp", $dname, shelf_num, $::resource, + # $::port_name, "gen_generic"); + #$::utils->doCmd($cmd); + + $cmd = "set_gen_cmd " . $my_endp_name . " " . $::endp_cmd; + $::utils->doCmd($cmd); + + $cmd = "set_endp_report_timer $my_endp_name $::report_timer"; + $::utils->doCmd($cmd); + + $::cx_name = "CX_" . $my_endp_name; + $cmd = "add_cx $::cx_name $::test_mgr $my_endp_name"; + $::utils->doCmd($cmd); + + my $cxonly = $::NA; + $cmd = $::utils->fmt_cmd("set_cx_report_timer", $::test_mgr, $::cx_name, $::report_timer, $cxonly); + $::utils->doCmd($cmd); + } + elsif ($my_endp_type eq "mc_udp") { + # For instance: + # add_endp mcast-xmit-eth1 1 3 eth1 mc_udp 9999 NO 9600 0 NO 1472 1472 INCREASING NO 32 0 0 + # set_mc_endp mcast-xmit-eth1 32 224.9.9.9 9999 NO + # Assume Layer-3 for now + + $cmd = $::utils->fmt_cmd("add_endp", $my_endp_name, 1, $my_resource, + $my_port_name, $my_endp_type, $::mcast_port, $::NA, + $my_speed, $my_max_speed, $::NA, $::min_pkt_sz, + $::max_pkt_sz, "increasing", $::use_csums, $::ttl, "0", "0"); + $::utils->doCmd($cmd); + + $cmd = $::utils->fmt_cmd("set_mc_endp", $::endp_name, $::ttl, $::mcast_addr, $::mcast_port, $::rcv_mcast); + $::utils->doCmd($cmd); + + if ($pkts_to_send ne "") { + $cmd = $::utils->fmt_cmd("set_endp_details", $::endp_name, "NA", "NA", "NA", $::pkts_to_send); + $::utils->doCmd($cmd); + } + + $cmd = "set_endp_report_timer $::endp_name $::report_timer"; + $::utils->doCmd($cmd); + } + elsif (grep { $_ eq $my_endp_type} split(/,/, "lf_udp,lf_tcp,lf_udp6,lf_tcp6,NA")) { + if ($::use_ports_str ne "NA") { + ($::port_name,) = split(',', $::use_ports_str); + } + die("Which port is this? --port_name") + if (!defined $::port_name || $port_name eq "" || $port_name eq "0" ); + + if ($::use_speeds_str ne "NA") { + ($::speed,) = split(',', $::use_speeds_str); + } + die("Please set port speed: --speed") + if ($::speed eq "-1"|| $::speed eq $::NA); + + if ($::min_pkt_sz =~ /^\s*auto\s*$/i) { + $::min_pkt_sz = "-1"; + } + if ($::max_pkt_sz =~ /^\s*same\s*$/i ) { + $::max_pkt_sz = "0"; + } + elsif ($::max_pkt_sz =~ /^\s*auto\s*$/i) { + $::max_pkt_sz = "-1"; + } + + # Assume Layer-3 for now + my $bursty = $::NA; + my $random_sz = $::NA; + my $payld_pat = "increasing"; + $::ttl = $::NA; + my $bad_ppm = "0"; + $cmd = $::utils->fmt_cmd("add_endp", $my_endp_name, 1, $my_resource, + $my_port_name, $my_endp_type, $::ip_port, $bursty, + $my_speed, $my_max_speed, + $random_sz, $::min_pkt_sz, $::max_pkt_sz, + $payld_pat, $::use_csums, $::ttl, + $bad_ppm, $::multicon); + $::utils->doCmd($cmd); + + if ($pkts_to_send ne "") { + $cmd = $::utils->fmt_cmd("set_endp_details", $::endp_name, "NA", "NA", "NA", $::pkts_to_send); + $::utils->doCmd($cmd); + } + + $cmd = "set_endp_report_timer $my_endp_name $::report_timer"; + $::utils->doCmd($cmd); + + if ($::tos ne "") { + my($service, $priority) = split(',', $::tos); + if (!$priority) { + $priority = "NA"; + } + $::utils->doCmd("set_endp_tos $my_endp_name $service $priority"); + } + } + else { + die( "ERROR: Endpoint type: $::endp_type is not currently supported."); + } +} # ~create_endp() + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub get_stats_from_file { + my $fname = shift; + my $endp_name = shift; + + open(F, "<$fname") or die("Can't open $fname for reading: $!\n"); + + my $endp_text = ""; + my $ep = ""; + + my @lines = (); + while ( my $line = ) { + @lines = (@lines, $line); + } + # Append dummy line to make it easier to terminate the parse logic. + @lines = (@lines, "Endpoint [________] (NOT_RUNNING)\n"); + + my $i; + for ($i = 0; $i<@lines;$i++) { + my $line = $lines[$i]; + chomp($line); + if (($line =~ /Endpoint\s+\[(.*)\]/) || + ($line =~ /WanLink\s+\[(.*)\]/) || + ($line =~ /ArmEndp\s+\[(.*)\]/) || + # TODO: Layer-4 ? + ($line =~ /VoipEndp\s+\[(.*)\]/)) { + + my $m1 = $1; + #print "Found starting line: $line name: $m1 endp_name: $endp_name\n"; + if ($endp_text ne "") { + # See if existing endp entry matches? + if ($ep eq $endp_name) { + return $endp_text; + } + } + $endp_text = "$line\n"; + $ep = $m1; + } + else { + if ($endp_text ne "") { + $endp_text .= "$line\n"; + } + } + } + return ""; +} diff --git a/lanforge/lanforge-scripts/lf_generic_ping.pl b/lanforge/lanforge-scripts/lf_generic_ping.pl new file mode 100755 index 000000000..a7453c6c4 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_generic_ping.pl @@ -0,0 +1,409 @@ +#!/usr/bin/perl -w +# +# Use this script to generate a batch of Generic lfping endpoints +# +# Examples: +# ./lf_generic_ping.pl --mgr 192.168.1.100 --resource 1 --dest 10.1.1.1 -i wlan0 -i sta1 -i eth1 +# You should be able to place 1000 interfaces in the list +# +# Or all interfaces on a radio +# ./lf_generic_ping.pl --mgr $mgr --resource 1 --dest 10.1.1.1 --radio wiphy0 +# +# Or all macvlan on an ethernet port +# ./lf_generic_ping.pl --mgr $mgr --resource 1 --dest 10.1.1.1 --parent eth1 +# +# Or all interfaces matching a prefix: +# ./lf_generic_ping.pl -m $mgr -r 1 -d 10.1.1.1 --match sta3 +# +# The default name will be lfping_$endp name, use the --name +# switch to alter the generic endpoint name, This allows multiple +# generic connections to be created per port: +# +# for n in one two three four five six seven eight nine ten; do +# ./lf_generic_ping.pl -m 192.168.1.100 -r 1 -d 10.1.1.1 --match sta -name $n +# done +package main; +use strict; +use warnings; +use diagnostics; +use Carp; +use Data::Dumper; +$SIG{ __DIE__ } = sub { Carp::confess( @_ )}; +$SIG{ __WARN__ } = sub { Carp::confess( @_ )}; +use Getopt::Long; +use Cwd qw(getcwd); +my $cwd = getcwd(); + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use List::Util qw(first); +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils qw(fmt_cmd); +use Net::Telnet (); + +our $dest_ip_addr = "0.0.0.0"; +our $log_cli = "unset"; # use ENV{'LOG_CLI'} + +our $usage = qq(Usage: +$0 --mgr {host-name | IP} + --mgr_port {ip port} + --resource {resource} + --dest {destination IP} + --interface|-intf|-int|-i {source interface} + # You should be able to place 1000 interfaces in the list + --radio {wiphy} | --parent {eth} + --match {simple prefix, no stars or questions marks} + --cmd {"double quoted command"} # can contain special parameters + --name {prefix to name connection, appended with padded number} + + Examples: + $0 --mgr localhost --resource 1 --dest 192.168.0.1 -i wlan0 -i sta3000 --name "wlan_ping" + This will match just sta3000 + + All interfaces on a parent radio or MAC VLANs on parent Ethernet port: + $0 --mgr localhost --resource 1 --dest 192.168.0.1 --radio wiphy0 + This will match all stations whos parent is wiphy0: sta3 wlan0 + + $0 --mgr localhost --resource 1 --dest 192.168.0.1 --parent eth1 + This will match all MAC VLANs with parent eth1: eth1#0 eth1#1 eth1#2 + + All interfaces matching a prefix: + $0 -m localhost -r 1 -d 192.168.0.1 --match sta3 + This will match sta3 sta30 sta31 sta3000 + + Please don't put single quotes in a command. A command can have these expansions: + %d destination ip or hostname + %i port IPv4 address + %p port name + + Example with curl wrapper provides better feedback to LANforge: + $0 --mgr cholla-f19 -r 2 -n curl_ex_ --match 'eth2#' \\ + --cmd './scripts/lf_curl.sh -n 10 -o /tmp/curl_%p.out -i %i -p %p -d %d' --dest http://localhost/ + + Example curl command doesn't provide good feedback to LANforge: + $0 --cmd "curl -sqL --dns-ipv4-addr %i --dns-interface %p \\ + --interface %p --localaddr %i -o /tmp/results-%p http://%d/" + + The default name of the generic endpoints given will be "lfping_[port]". + You can create multiple generic connections per port by altering + the endpoint name with the --name switch. + Example of creating multiple connections per port in a loop: + for n in one two three four five six seven eight nine ten; do + $0 -m localhost -r 1 -d 10.1.1.1 --match sta -name \$n + done + + Example iperf3 server on eth1, 10.1.1.2: + iperf3 --forceflush --format k --precision 4 -s \\ + --bind_dev %p -i 1 --pidfile /tmp/lf_helper_iperf_%p.pid + + Example iperf3 client on sta0 as 10.1.1.3: + iperf3 --forceflush --format k --precision 4 -c %d -t 60 --tos 0 -b 1K \\ + --bind_dev %p -i 1 --pidfile /tmp/lf_helper_iperf_%p.pid + + If only a few of your generic commands start, check journalctl for + errors containing: 'cgroup: fork rejected by pids controller' + You want to set DefaultTasksMax=65535 in /etc/systemd/system.conf + then do a systemctl daemon-reload. + https://www.novell.com/support/kb/doc.php?id=7018594 +); + +our $shelf_num = 1; +our $report_timer = 1000; +our $test_mgr = "default_tm"; +our $resource = 1; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; +our $quiet = "yes"; +our $quiesce = 3; +our $clear_on_start = 0; +our $dest_ip; +our @interfaces = (); +our $radio = ''; +our $pattern = ''; +our $name_pref = "lfping"; +our $ref_cmd = ''; # user supplied command +our $ref_name = ''; +our $verbose = ((defined $ENV{'DEBUG'}) && ($ENV{'DEBUG'} eq "1")) ? 1:0; +my $help; + +if (@ARGV < 2) { + print $usage; + exit 0; +} +GetOptions +( + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p=i' => \$lfmgr_port, + 'resource|r=i' => \$::resource, + 'quiet|q' => \$::quiet, + 'verbose|v' => \$::verbose, + 'radio|parent|o=s' => \$::radio, + 'match=s' => \$::pattern, + 'interface|intf|int|i=s' => \@::interfaces, + 'dest_ip|dest|d=s' => \$::dest_ip, + 'name_pref|name|n=s' => \$::name_pref, + 'cmd|c=s' => \$::ref_cmd, + 'help|h|?' => \$help, +) || (print($usage), exit(1)); + +#print "radio: $::radio, match: $::pattern, $::quiet, $::resource, $::dest_ip\n"; + +if ($help) { + print($usage) && exit(0); +} +if ($::quiet eq "0") { + $::quiet = "no"; +} +elsif ($::quiet eq "1") { + $::quiet = "yes"; +} + +# Open connection to the LANforge server. +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } +} +our $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +our $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. + +if ($utils->isQuiet()) { + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + $utils->cli_send_silent(0); + } + else { + $utils->cli_send_silent(1); # Do not show input to telnet + } + $utils->cli_rcv_silent(1); # Repress output from telnet +} +else { + $utils->cli_send_silent(0); # Show input to telnet + $utils->cli_rcv_silent(0); # Show output from telnet +} +$utils->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); + +our @ports_lines = split("\n", $::utils->doAsyncCmd("nc_show_ports 1 $::resource ALL")); +chomp(@ports_lines); +our %eid_map = (); +my ($eid, $card, $port, $type, $mac, $dev, $parent, $ip); +foreach my $line (@ports_lines) { + # collect all stations on that radio add them to @interfaces + if ($line =~ /^Shelf: /) { + $card = undef; $port = undef; + $type = undef; $parent = undef; + $eid = undef; $mac = undef; + $dev = undef; + $ip = undef; + } + + # careful about that comma after card! + # NO EID for Shelf: 1, Card: 1, Port: 2 Type: WIFI-Radio Alias: + ($card, $port, $type) = $line =~ m/^Shelf: 1, Card: (\d+),\s+Port: (\d+)\s+Type: (\w+)/; + if ((defined $card) && ($card ne "") && (defined $port) && ($port ne "")) { + $eid = "1.${card}.${port}"; + my $rh_eid = { + eid => $eid, + type => $type, + parent => undef, + dev => undef, + }; + $::eid_map{$eid} = $rh_eid; + #print "\nfound eid $eid\n"; + } + #elsif ($line =~ /^Shelf/) { + # #print "NO EID for $line\n"; + #} + + if (!(defined $eid) || ($eid eq "")) { + #print "NO EID for $line\n"; + next; + } + ($mac, $dev) = $line =~ / MAC: ([0-9:a-fA-F]+)\s+DEV: (\S+)/; + if ((defined $mac) && ($mac ne "")) { + #print "$eid MAC: $line\n"; + $::eid_map{$eid}->{mac} = $mac; + $::eid_map{$eid}->{dev} = $dev; + } + + ($parent) = $line =~ / Parent.Peer: (\S+) /; + if ((defined $parent) && ($parent ne "")) { + #print "$eid PARENT: $line\n"; + $::eid_map{$eid}->{parent} = $parent; + } + + ($ip) = $line =~ m/ IP: *([^ ]+) */; + if ((defined $ip) && ($ip ne "")) { + #print "$eid IP: $line\n"; + $::eid_map{$eid}->{ip} = $ip; + } +} # foreach + +#foreach $eid (keys %eid_map) { +# print "eid $eid "; +#} + + +if (defined $::radio) { + while (my ($eid, $rh_eid) = each %::eid_map) { + if ((defined $rh_eid->{parent}) && ($rh_eid->{parent} eq $::radio)) { + push(@interfaces, $rh_eid->{dev}); + } + } +} + +if (defined $::pattern && $pattern ne "") { + my $pat = $::pattern; + $pat =~ s/[+]//g; + # collect all stations on that resource add them to @interfaces + while (my($eid, $rh_eid) = each %::eid_map) { + if ((defined $rh_eid->{dev}) && ($rh_eid->{dev} =~ /$pat/)) { + push(@interfaces, $rh_eid->{dev}); + } + } +} + +if (@interfaces < 1) { + print STDERR "One or more interfaces required.\n"; + print $usage; + exit(1); +} + +print "Creating generic lfping endpoints using these interfaces: \n"; +print " ".join(", ", @interfaces)."\n"; + +=pod +Example of generic created by GUI: + add_gen_endp test-1 1 3 sta3000 gen_generic + set_gen_cmd test-1 lfping -p deadbeef -I sta3000 10.41.1.2 + set_endp_quiesce test-1 3 + set_endp_report_timer test-1 1000 + set_endp_flag test-1 ClearPortOnStart 0 + add_gen_endp D_test-1 1 3 sta3000 gen_generic + set_endp_flag D_test-1 unmanaged 1 + set_endp_quiesce D_test-1 3 + set_endp_report_timer D_test-1 1000 + set_endp_flag D_test-1 ClearPortOnStart 0 + +Parameters that can be replaced: + %d destination ip or hostname + %i port IPv4 address + %p port name + + curl -sqL --dns-ipv4-addr %i --dns-interface %p --interface %p --localaddr %i -o /dev/null http://%d/ + +=cut +sub create_generic { + my ($name, $port_name, $eid)=@_; + #print "= 1 =====================================================\n"; + #print Dumper($eid); + my $endp_name = "${name_pref}_${port_name}"; + my $type = "gen_generic"; + my $rh_idr = $::eid_map{$eid}; + my $port_ip = $rh_idr->{'ip'}; + #print Dumper($rh_idr); + #print Dumper($rh_idr->{'ip'}); + #print "$endp_name PORT_IP $port_ip \n"; + #print "= 2 =====================================================\n"; + my $ping_cmd = "lfping -I $port_name $::dest_ip"; + if ((defined $::ref_cmd) && ($::ref_cmd ne "")) { + $ping_cmd = $::ref_cmd; + my $d_ip = ''; + $d_ip = $::dest_ip if (defined $::dest_ip && $::dest_ip ne ""); + $ping_cmd =~ s/%d/$::dest_ip/g if ($ping_cmd =~ /%d/); + + if (defined $port_name && $port_name ne "") { + $ping_cmd =~ s/%p/$port_name/g if ($ping_cmd =~ /%p/); + } + else { + print "no name for port $port_name\n"; + return; + } + + if (defined $port_ip && $port_ip ne "") { + $ping_cmd =~ s/%i/$port_ip/g if ($ping_cmd =~ /%i/); + } + else { + print "no ip for port $port_name\n"; + return; + } + } + $::command_map{$eid} = $ping_cmd; + + print "CMD: $ping_cmd\n" if ($::verbose); + + $::utils->doCmd($::utils->fmt_cmd("add_gen_endp", $endp_name, 1, $::resource, $port_name, $type)); + $::utils->doCmd("set_gen_cmd $endp_name $ping_cmd"); + $::utils->doCmd("set_endp_quiesce $endp_name $::quiesce"); + $::utils->doCmd("set_endp_flag $endp_name ClearPortOnStart $::clear_on_start"); + $::utils->doCmd("set_endp_report_timer $endp_name $::report_timer"); + + # we also need to add the opposite unmanaged endpoint + $::utils->doCmd("add_gen_endp D_$endp_name 1 $::resource $port_name gen_generic"); + $::utils->doCmd("set_endp_flag D_$endp_name unmanaged 1"); + $::utils->doCmd("set_endp_quiesce D_$endp_name $::quiesce"); + $::utils->doCmd("set_endp_flag D_$endp_name ClearPortOnStart $::clear_on_start"); + $::utils->doCmd("set_endp_report_timer D_$endp_name $::report_timer"); + + # tie the knot with a CX + $::utils->doCmd("add_cx CX_$endp_name default_tm $endp_name D_$endp_name"); + $::utils->doCmd("set_cx_report_timer default_tm CX_$endp_name $::report_timer cxonly"); +} + +#print Dumper(\@interfaces); +#print Dumper(\%::eid_map); +our %command_map = (); +my @map_keys = sort keys %eid_map; +for my $port (sort @interfaces) { + my $endp_name = "${name_pref}_$port"; + my $matching_eid = ""; + #print "Searching for port $port "; + #while (my ($eid, $rh_pid) = each %eid_map) { + for my $eid (@map_keys) { + my $rh_pid = $eid_map{$eid}; + #print " $port/$rh_pid->{dev} "; + if ("$port" eq "$rh_pid->{dev}") { + #print " ** "; + $matching_eid = $eid; + last; + } + } + if ($matching_eid eq "") { + print "\nSkipping $port no eid [$matching_eid]\n"; + next; + } + #print "\n= 3 =====================================================\n"; + #print " $matching_eid => ".$eid_map{$matching_eid}->{dev}."\n"; + #print Dumper($eid_map{$matching_eid}); + #print "= 4 =====================================================\n"; + + + if (! (defined $eid_map{$matching_eid}->{ip}) + || $eid_map{$matching_eid}->{ip} eq "" + || $eid_map{$matching_eid}->{ip} eq "0.0.0.0") { + print "\nSkipping $port: ".$eid_map{$matching_eid}->{ip}."\n"; + sleep 1; + next; + } + create_generic($endp_name, $port, $matching_eid); +} +#print Dumper(\%command_map); + +# diff --git a/lanforge/lanforge-scripts/lf_gui_cmd.pl b/lanforge/lanforge-scripts/lf_gui_cmd.pl new file mode 100755 index 000000000..76efa6cd2 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_gui_cmd.pl @@ -0,0 +1,241 @@ +#!/usr/bin/perl -w + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# Written by Candela Technologies Inc. +# Updated by: greearb@candelatech.com +# +# + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +# Un-buffer output +$| = 1; + +use Net::Telnet (); +use Getopt::Long; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 3990; + +# Default values for ye ole cmd-line args. +my $port = ""; +my $cmd = ""; +my $ttype = ""; # Test type +my $tname = "lfgui-test"; +my $scenario = ""; +my $tconfig = ""; # test config +my $rpt_dest = ""; +my $show_help = 0; +my $verbosity = -1; +my @modifiers_key = (); +my @modifiers_val = (); + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $usage = qq($0 [--manager { hostname or address of LANforge GUI machine } ] + [--port {port name} ] # cli-socket port default 3990 + # careful, your cli-socket might be 3390! + [--ttype {test instance type} ] + # likely types: "cv", "WiFi Capacity", "Port Bringup", "Port Reset" + [--scenario {scenario name} ] + # Apply and build the scenario. + [--tname {test instance name} ] + [--tconfig {test configuration name, use defaults if not specified} ] + [--rpt_dest {Copy report to destination once it is complete} ] + [--cmd { command to send to the GUI } ] + [--verbosity { report verbosity 1 - 11 } ] + [--modifier " + +Example: + lf_gui_cmd.pl --manager localhost --port 3990 --ttype TR-398 --tname mytest --tconfig comxim --rpt_dest /var/www/html/lf_reports + lf_gui_cmd.pl --manager localhost --port 3990 --cmd \"help\" + lf_gui_cmd.pl --manager localhost --port 3990 --scenario 64sta +); + +if (@ARGV < 2) { + print "$usage\n"; + exit 0; +} + +GetOptions ( + 'help|h' => \$show_help, + 'manager|mgr|m=s' => \$lfmgr_host, + 'modifier_key=s' => \@modifiers_key, + 'modifier_val=s' => \@modifiers_val, + 'ttype=s' => \$ttype, + 'tname=s' => \$tname, + 'scenario=s' => \$scenario, + 'tconfig=s' => \$tconfig, + 'rpt_dest=s' => \$rpt_dest, + 'port=s' => \$port, + 'cmd|c=s' => \$cmd, + 'verbosity|v=i' => \$verbosity, +) || die("$usage"); + +if ($show_help) { + print $usage; + exit 0; +} + +my $lnk = @modifiers_key; +my $lnv = @modifiers_val; +if ($lnk != $lnv) { + print("ERROR: You must specify the same amount of modifers-key and modifiers-val entries.\n"); + exit(3); +} + +if ((defined $port) && ($port > 0)) { + $lfmgr_port = $port; +} + +# Open connection to the LANforge server. +my $t = new Net::Telnet(Prompt => '/lfgui\# /', + Timeout => 20); + +$t->open( Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/lfgui\# /"); + +if ($cmd ne "") { + print doCmd("$cmd"); +} + +if ($scenario ne "") { + print doCmd("cv apply '$scenario'"); + print doCmd("cv build"); + sleep(3); + + while (1) { + my $rslt = doCmd("cv is_built"); + print "Result-built -:$rslt:-\n"; + if ($rslt =~ /NO/) { + sleep(3); + } + else { + last; + } + } +} + +if ($ttype ne "") { + # Try several times in case system is currently busy cleaning up or similar. + my $i; + my $rslt; + for ($i = 0; $i<60; $i++) { + $rslt = doCmd("cv create '$ttype' '$tname'"); + print $rslt; + if ($rslt =~ /BUSY/) { + sleep(1); + } + else { + last; + } + } + if ($tconfig ne "") { + print doCmd("cv load '$tname' '$tconfig'"); + } + if ($verbosity >= 1) { + print doCmd("cv set '$tname' 'VERBOSITY' '$verbosity'"); + } + print doCmd("cv click '$tname' 'Auto Save Report'"); + + for ($i = 0; $i<@modifiers_key; $i++) { + my $k = $modifiers_key[$i]; + my $v = $modifiers_val[$i]; + print doCmd("cv set '$tname' '$k' '$v'"); + } + + $rslt = doCmd("cv click '$tname' 'Start'"); + print $rslt; + if ($rslt =~ /Could not find instance/) { + exit(1); + } + + while (1) { + my $rslt = doCmd("cv get '$tname' 'Report Location:'"); + #print "Result-:$rslt:-\n"; + if ($rslt =~ /^\s*Report Location:::(.*)/) { + my $loc = $1; + if ($loc eq "") { + # Wait longer + sleep(3); + } + else { + # Copy some place it can be seen easily? + print("LANforge GUI test complete, rpt-dest: $rpt_dest location: $loc\n"); + if ($rpt_dest ne "") { + if ($lfmgr_host eq "localhost" || $lfmgr_host eq "127.0.0.1") { + # Must be on the local system + my $cp = "cp -ar $loc $rpt_dest"; + print "Copy test results: $cp\n"; + system($cp); + } + else { + # Must be on remote system, try scp to get it. + my $cp = "scp -r lanforge\@$lfmgr_host:$loc $rpt_dest"; + print "Secure Copy test results: $cp\n"; + system($cp); + } + } + last; + } + } + else { + sleep(3); + } + } + + # Clean up our instance. This can take a while. + print doCmd("cv delete '$tname'"); + while (1) { + my $rslt = doCmd("cv exists '$tname'"); + print "Result-exists -:$rslt:-\n"; + if ($rslt =~ /YES/) { + sleep(3); + } + else { + last; + } + } + + # Wait a bit more, CV will likey be rebuilt now. + sleep(5); + + while (1) { + my $rslt = doCmd("cv is_built"); + print "Result-built -:$rslt:-\n"; + if ($rslt =~ /NO/) { + sleep(3); + } + else { + print("Chamber-View is (re)built, exiting.\n"); + last; + } + } +} + +exit(0); + +sub doCmd { + my $cmd = shift; + + print ">>>Sending:$cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor('/lfgui\#/'); + if ($rslt[@rslt-1] eq "lfgui\#") { + $rslt[@rslt-1] = ""; + } + return join("\n", @rslt); +} + diff --git a/lanforge/lanforge-scripts/lf_ice.pl b/lanforge/lanforge-scripts/lf_ice.pl new file mode 100755 index 000000000..4994d8033 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_ice.pl @@ -0,0 +1,375 @@ +#!/usr/bin/perl -w + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# Written by Candela Technologies Inc. +# Creates a WanLink with 128 WanPaths for performance testing. + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# Specify 'card' numbers for this configuration. +my $ice_card = 1; + +# The ICE ports, on ice_card +my $ice1 = 1; +my $ice2 = 2; + +my $test_mgr = "vanilla-ice"; # Couldn't resist! + +my $report_timer = 1000; # XX/1000 seconds + +# Default values for ye ole cmd-line args. +my $quiet = "no"; +my $init_to_dflts = "yes"; + +my $latency = 35; # miliseconds +my $jitter = 10; +my $reorder = 0; +my $smoothing_buffer = 20000; # XXk smoothing buffer +my $drop_freq = 0; +my $dup_freq = 0; +my $max_wlrate = 1000000000; +my $wl_kmode = 1; # Set to 0 for user-space mode, 1 for kernel mode + +# WanPath related settings. +my $max_wp_rate = 10000000; +my $wp_ip_base = "172.2.2"; +my $wp_ip_lcb = 2; +my $wp_ip_mask = "255.255.255.255"; +my $wp_lat = 10; +my $wp_jitter = 10; +my $wp_extra_buf = 512; +my $wp_reord = 0; +my $wp_dup = 0; +my $wp_drop = 0; + + +# Dest matches all +my $wp_dst = "0.0.0.0"; +my $wp_dst_mask = "0.0.0.0"; + +my $wp_count = 128; + +my $fail_msg = ""; +my $manual_check = 0; + +#my $cmd_log_name = "lf_ice.txt"; +#open(CMD_LOG, ">$cmd_log_name") or die("Can't open $cmd_log_name for writing...\n"); +#print "History of all commands can be found in $cmd_log_name\n"; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $usage = "$0 [--quiet {yes | no}] + [--init_to_dflts {yes | no}] + +Example: + $0 --init_to_dflts yes\n"; + + +GetOptions ( + 'mgr|m=s' => \$lfmgr_host, + 'port|p=i' => \$lfmgr_port, + 'quiet|q=s' => \$quiet, + 'init_to_dflts|d=s' => \$init_to_dflts, +) || die("$usage"); + + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +if ($quiet eq "yes") { + $utils->cli_rcv_silent(1); # Repress output from CLI ?? +} +else { + $utils->cli_rcv_silent(0); # Repress output from CLI ?? +} + + +my $dt = ""; + +if ($init_to_dflts eq "yes") { + initToDefaults(); + + # Now, add back the test manager we will be using + $utils->doCmd("add_tm $test_mgr"); + $utils->doCmd("tm_register $test_mgr default"); #Add default user + $utils->doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + setUpPorts(); +} + +# $utils->doCmd("log_level 63"); + + +# Create the connections we will be manipulating. +my $i = 0; +my $cmd = ""; + + +my $ep1 = "wan1-A"; +my $ep2 = "wan1-B"; + +@endpoint_names = (@endpoint_names, $ep1, $ep2); + +# Create the two LANforge-ICE endpoints. +$cmd = "add_wl_endp $ep1 $shelf_num $ice_card $ice1 $latency $max_wlrate"; +$utils->doCmd($cmd); +$cmd = "set_wanlink_info $ep1 $max_wlrate $latency $jitter $reorder $smoothing_buffer $drop_freq $dup_freq"; +$utils->doCmd($cmd); + +# Create the two LANforge-ICE endpoints. +$cmd = "add_wl_endp $ep2 $shelf_num $ice_card $ice2 $latency $max_wlrate"; +$utils->doCmd($cmd); +$cmd = "set_wanlink_info $ep2 $max_wlrate $latency $jitter $reorder $smoothing_buffer $drop_freq $dup_freq"; +$utils->doCmd($cmd); + +$utils->doCmd("set_endp_flag $ep1 KernelMode $wl_kmode"); +$utils->doCmd("set_endp_flag $ep2 KernelMode $wl_kmode"); + + + +# Add the ICE cross connect. +my $cx_name = "wanlink1"; +$cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; +$utils->doCmd($cmd); +$utils->doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + +@cx_names = (@cx_names, $cx_name); + +# Add the wanpaths +for ($i = 0; $i<$wp_count; $i++) { + # Add wanpath with specified source and ANY dest. + $cmd = "add_wanpath $ep1 wp$wp_ip_lcb $max_wp_rate $wp_lat $wp_jitter $wp_extra_buf $wp_reord $wp_drop $wp_dup ${wp_ip_base}.$wp_ip_lcb $wp_ip_mask $wp_dst $wp_dst_mask OFF 'NA' YES NO NO NO"; + $utils->doCmd($cmd); + # Add wanpath with specified dest and ANY source. + $cmd = "add_wanpath $ep2 wp$wp_ip_lcb $max_wp_rate $wp_lat $wp_jitter $wp_extra_buf $wp_reord $wp_drop $wp_dup 0.0.0.0 0.0.0.0 ${wp_ip_base}.$wp_ip_lcb $wp_ip_mask OFF 'NA' YES NO NO NO"; + $utils->doCmd($cmd); + + $wp_ip_lcb++; +} + + + +for ($i = 0; $i<@cx_names; $i++) { + my $nm = $cx_names[$i]; + $cmd = "set_cx_state $test_mgr $nm RUNNING"; + $utils->doCmd($cmd); +} + +sleep(24 * 60 * 60); # Run for one day + +# Stop cxs. +for ($i = 0; $i<@cx_names; $i++) { + my $nm = $cx_names[$i]; + $cmd = "set_cx_state $test_mgr $nm STOPPED"; + $utils->doCmd($cmd); +} + +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + $utils->doCmd("rm_cx $test_mgr all"); + $utils->doCmd("rm_endp YES_ALL"); + $utils->doCmd("rm_test_mgr $test_mgr"); + +}#initToDefaults + + +sub testFailed { + my $msg = shift; + my $should_fail = shift; + + if (defined($should_fail) && ($should_fail eq "YES")) { + print "\nGOOD: SUB-TEST FAILED correctly: $msg\n"; + $fail_msg .= "GOOD (should fail): $msg"; + } + else { + print "\nSUB-TEST FAILED: $msg\n"; + $fail_msg .= $msg; + + if ($manual_check) { + #$utils->doCmd("log_level 7"); + print "Press enter to continue with test: "; + ; + } + else { + die("FATAL ERROR: $fail_msg\n"); + } + } +}#testFailed + +sub setUpPorts { + + # Nothing to do at this point. + +}#setUpPorts + + +sub setUpPort { + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $ip = shift; + my $msk = shift; + my $gw = shift; + + my $cmd = "set_port $sn $cn $pn $ip $msk $gw NA NA NA"; + $utils->doCmd($cmd); + my $p1 = new LANforge::Port(); + # Tell the port what it is so it decodes the right one.. + $utils->updatePort($p1, $sn, $cn, $pn); + # Make sure the values we attempted to set actually worked. + verifyPortAttributes($p1, $sn, $cn, $pn, $ip, $msk, $gw); +}#setUpPort + + +sub verifyPortAttributes { + my $port = shift; + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $ip = shift; + my $msk = shift; + my $gw = shift; + + my $_sn = $port->shelf_id(); + my $_cn = $port->card_id(); + my $_pn = $port->port_id(); + my $_ipa = $port->ip_addr(); + + my $p = $port->toStringBrief(); + + $_sn eq $sn or testFailed("$p: Shelf id: $_sn does not match: $sn\n"); + $_cn eq $cn or testFailed("$p: Card id: $_cn does not match: $cn\n"); + $_pn eq $pn or testFailed("$p: Port id: $_pn does not match: $pn\n"); + $_ipa eq $ip or testFailed("$p: IP Address: $_ipa does not match: $ip\n"); + $port->ip_mask() eq $msk or testFailed("$p: IP Mask: " . $port->ip_mask() . " does not match: $msk\n"); + $port->ip_gw() eq $gw or testFailed("$p: IP Gateway: " . $port->ip_gw() . " does not match: $gw\n"); + + print "$p verified as correct!\n"; +}#verifyPortAttributes + + +sub verifyEndpointAttributes { + my $endp = shift; + my $name = shift; + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $type = shift; + my $ip_port = shift; + my $bursty = shift; + my $min_rate = shift; + my $max_rate = shift; + my $szrnd = shift; + my $min_pkt_sz = shift; + my $max_pkt_sz = shift; + my $pattern = shift; + my $using_csum = shift; + my $should_fail = shift; + + my $_sn = $endp->shelf_id(); + my $_cn = $endp->card_id(); + my $_pn = $endp->port_id(); + + my $p = $endp->toStringBrief(); + + $_sn eq $sn or testFailed("$p: Shelf id: $_sn does not match: $sn\n", $should_fail); + $_cn eq $cn or testFailed("$p: Card id: $_cn does not match: $cn\n", $should_fail); + $_pn eq $pn or testFailed("$p: Port id: $_pn does not match: $pn\n", $should_fail); + $endp->isOfType($type) or testFailed("$p: Type: " . $endp->ep_type() . " does not match: $type\n", $should_fail); + if ($ip_port ne -1) { + $endp->ip_port() eq $ip_port or testFailed("$p: IP-Port: " . $endp->ip_port() . + " does not match: $ip_port\n", $should_fail); + } + $endp->getBursty() eq $bursty or testFailed("$p: Bursty: " . $endp->getBursty() . + " does not match: $bursty\n", $should_fail); + + $endp->min_tx_rate() eq $min_rate or testFailed("$p: Min-Tx-Rate: " . $endp->min_tx_rate() . + " does not match: $min_rate\n", $should_fail); + $endp->max_tx_rate() eq $max_rate or testFailed("$p: Max-Tx-Rate: " . $endp->max_tx_rate() . + " does not match: $max_rate\n", $should_fail); + + if ($endp->isCustom()) { + ($endp->size_random() eq "NO") or testFailed("$p: Size-Random: " . $endp->size_random() . + " but we are CUSTOM!!\n", $should_fail); + } + else { + $endp->size_random() eq $szrnd or testFailed("$p: Size-Random: " . $endp->size_random() . + " does not match: $szrnd\n", $should_fail); + } + + if (! $endp->isCustom()) { + $endp->min_pkt_size() eq $min_pkt_sz or testFailed("$p: Min-Packet-Size: " . $endp->min_pkt_size() . + " does not match: $min_pkt_sz\n", $should_fail); + $endp->max_pkt_size() eq $max_pkt_sz or testFailed("$p: Max-Packet-Size: " . $endp->max_pkt_size() . + " does not match: $max_pkt_sz\n", $should_fail); + } + $endp->pattern() eq $pattern or testFailed("$p: Pattern: " . $endp->pattern() . + " does not match: $pattern\n", $should_fail); + $endp->checksum() eq $using_csum or testFailed("$p: Using-Checksum: " . $endp->checksum() . + " does not match: $using_csum\n", $should_fail); + +}#verifyEndpointAttributes + + +sub genRandomHex { + my $bytes = shift; + + my @tbl = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"); + my $i; + my $pld = ""; + for ($i = 0; $i<$bytes; $i++) { + $pld .= $tbl[(rand() * 1000.0) % 16] . $tbl[(rand() * 1000.0) % 16]; #Generate some hex the hard way! + if ($i != ($bytes - 1)) { + $pld .= " "; + } + } + + return $pld; +}#genRandomHex diff --git a/lanforge/lanforge-scripts/lf_icemod.pl b/lanforge/lanforge-scripts/lf_icemod.pl new file mode 100755 index 000000000..37ffeed39 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_icemod.pl @@ -0,0 +1,275 @@ +#!/usr/bin/perl -w + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# Written by Candela Technologies Inc. +# Updated by: greearb@candelatech.com +# +# + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +our $q = q('); +our $Q = q("); +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +my $shelf_num = 1; +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; +my $report_timer = 1000; # XX/1000 seconds + +# Default values for ye ole cmd-line args. +my $port = ""; +my $endp_name = ""; +my $speed = ""; +my $latency = ""; +my $max_jitter = ""; +my $reorder_freq = ""; +my $extra_buffer = ""; +my $drop_pm = ""; +my $dup_pm = ""; +my $jitter_freq = ""; +my $min_drop_amt = ""; +my $max_drop_amt = ""; +my $min_reorder_amt = ""; +my $max_reorder_amt = ""; +my $max_lateness = ""; +my $switch = ""; +my $pcap = ""; +my $load = ""; +my $state = ""; +my $cx = ""; +our $quiet = 0; +my $description = ""; +my $fail_msg = ""; +my $manual_check = 0; +my $cpu_id = "NA"; +my $wle_flags = 0; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $usage = qq($0 [--manager { hostname or address of LANforge manager } ] + [--resource { resource number } ] + [--port {port name} ] + [--endp_name { name } ] + [--description { ${Q}stuff in quotes${Q} } ] + [--cx { name } ] + [--speed { speed in bps } ] + [--latency { 0 - 1000000 } # in milliseconds ] + [--max_jitter { 0 - 1000000 } # in milliseconds ] + [--reorder_freq { 0 - 1000000 } # packets per million ] + [--extra_buffer { -1 - 1000000 } # extra bytes to buffer, -1: AUTO, units of 1024 ] + [--drop_pm { 0 - 1000000 } # drop packets per million ] + [--dup_pm { 0 - 1000000 } # duplication packets per million ] + [--jitter_freq { 0 - 10000000 } # jitter these many packets per million ] + [--min_drop_amt { 1 - 1000 } # drop at least this many packets in a row, default 1 + [--max_drop_amt { 1 - 1000 } # drop at most this many packets in a row, default 1 + [--min_reorder_amt { 1 - 1000 } # reorder at least this many packets, default 1 + [--max_reorder_amt { 1 - 1000 } # reorder at most this many packets, default 10 + [--max_lateness { -1 - 1000000 } # maximum amount of unintentional delay before pkt is dropped -1=AUTO + [--switch new_cx_to_run ] # activate named CX + [--pcap { dir-name | off } ] # specify a packet capture to replay + [--load { db-name } ] # load a database + [--state { running | switch | quiesce | stopped | deleted } ] + +Example: + lf_icemod.pl --manager lanforge1 --new_endp t1-A --speed 256000 --drop_pm 100 --latency 35 --description ${Q}link one${Q} + lf_icemod.pl --mgr lanforge1 --new_cx "t1" --endps t1-A,t1-B + lf_icemod.pl --mgr lanforge1 --endp_name t1-A --speed 154000 --drop_pm 10000 --latency 35 + lf_icemod.pl --mgr 192.168.100.223 --switch t3 + lf_icemod.pl --state running --cx t3 + lf_icemod.pl --pcap /tmp/endp-a --endp_name t1-A + lf_icemod.pl --load my_db +); + +if (@ARGV < 2) { + print "$usage\n"; + exit 0; +} + +my $i = 0; +my $show_help; +my $resource = 1; +my $new_endp = ""; +my $new_cx = ""; +my $endps = ""; + +GetOptions ( + 'help|h' => \$show_help, + 'manager|mgr|m=s' => \$lfmgr_host, + 'card|resource|r=i' => \$resource, + 'endp_name|e=s' => \$endp_name, + 'desc|description=s' => \$description, + 'cx|c=s' => \$cx, + 'speed|s=i' => \$speed, + 'latency|l=i' => \$latency, + 'max_jitter=i' => \$max_jitter, + 'reorder_freq=i' => \$reorder_freq, + 'extra_buffer=i' => \$extra_buffer, + 'drop_pm|d=i' => \$drop_pm, + 'dup_pm=i' => \$dup_pm, + 'jitter_freq|j=i' => \$jitter_freq, + 'min_drop_amt=i' => \$min_drop_amt, + 'max_drop_amt=i' => \$max_drop_amt, + 'min_reorder_amt=i' => \$min_reorder_amt, + 'max_reorder_amt=i' => \$max_reorder_amt, + 'max_lateness=i' => \$max_lateness, + 'switch|w=s' => \$switch, + 'new_endp=s' => \$new_endp, + 'new_cx=s' => \$new_cx, + 'endps=s' => \$endps, + 'port=s' => \$port, + 'pcap|p=s' => \$pcap, + 'load|o=s' => \$load, + 'state|a=s' => \$state, + 'wle_flags=i' => \$wle_flags, + 'quiet|q=i' => \$quiet, +) || die("$usage"); + +if ($show_help) { + print $usage; + exit 0; +} + +# Open connection to the LANforge server. +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); + +$t->open( Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +my $dt = ""; + +my $utils = new LANforge::Utils(); +$utils->connect($lfmgr_host, $lfmgr_port); + +my $cmd; + +$speed = "NA" if ($speed eq ""); +$latency = "NA" if ($latency eq ""); +$max_jitter = "NA" if ($max_jitter eq ""); +$reorder_freq = "NA" if ($reorder_freq eq ""); +$extra_buffer = "NA" if ($extra_buffer eq ""); +$drop_pm = "NA" if ($drop_pm eq ""); +$dup_pm = "NA" if ($dup_pm eq ""); +$pcap = "NA" if ($pcap eq ""); +$jitter_freq = "NA" if ($jitter_freq eq ""); +$min_drop_amt = "NA" if ($min_drop_amt eq ""); +$max_drop_amt = "NA" if ($max_drop_amt eq ""); +$min_reorder_amt = "NA" if ($min_reorder_amt eq ""); +$max_reorder_amt = "NA" if ($max_reorder_amt eq ""); +$max_lateness = "NA" if ($max_lateness eq ""); + + + +if (($load ne "") && ($load ne "NA")) { + $cmd = "load $load overwrite"; + $utils->doCmd($cmd); + my @rslt = $t->waitfor("/LOAD-DB: Load attempt has been completed./"); + if (!($quiet & 0x1)) { + print @rslt; + print "\n"; + } + exit(0); +} + +if (($new_cx ne "") && ($new_cx ne "NA")) { + die("please set the endpoints for new wanlink cx; $usage") + unless ((defined $endps) && ($endps ne "")); + + die("please specify two endpoints joined by a comma: end1-A,end1-B; $usage") + unless ($endps =~ /^\S+,\S+$/); + my @ends= split(',', $endps); + $cmd = $utils->fmt_cmd("add_cx", $new_cx, "default_tm", $ends[0], $ends[1]); + $utils->doCmd($cmd); + exit(0); +} + +if (($new_endp ne "") && ($new_endp ne "NA")) { + die("please set the resource for new wanlink endpoint; $usage") + unless ((defined $resource) && ($resource ne "")); + die("please set latency for new wanlink endpoint; $usage") + unless ((defined $latency) && ($latency ne "")); + die("please set drop_pm for new wanlink endpoint; $usage") + unless ((defined $drop_pm) && ($drop_pm ne "")); + die("please set port for new wanlink endpoint; $usage") + unless ((defined $port) && ($port ne "")); + + $wle_flags = "NA" if (($wle_flags == 0) || ($wle_flags eq "")); + $cpu_id = "NA" if ($cpu_id eq ""); + $description = "NA" if ($description eq ""); + + $cmd = $utils->fmt_cmd("add_wl_endp", $new_endp, 1, $resource, $port, + $latency, $speed, $description, $cpu_id, $wle_flags); + $utils->doCmd($cmd); + + $cmd = $utils->fmt_cmd("set_wanlink_info", $new_endp, $speed, $latency, + $max_jitter, $reorder_freq, $extra_buffer, $drop_pm, $dup_pm, $pcap, + $jitter_freq, $min_drop_amt, $max_drop_amt, $min_reorder_amt, + $max_reorder_amt, $max_lateness ); + $utils->doCmd($cmd); + exit(0); +} + +if (($switch ne "") && ($switch ne "NA")) { + $cmd = "set_cx_state all $switch SWITCH"; + $utils->doCmd($cmd); + exit(0); +} + +if ((length($endp_name) == 0) && (length($cx) == 0)) { + print "ERROR: Must specify endp or cx name.\n"; + die("$usage"); +} + +if ((defined $pcap) && ($pcap ne "")&& ($pcap ne "NA")) { + print STDERR "pcap has value??? [$pcap]\n"; + if ($pcap =~ /^OFF$/i) { + $cmd = "set_wanlink_pcap $endp_name off"; + } + else { + $cmd = "set_wanlink_pcap $endp_name ON $pcap"; + } + $utils->doCmd($cmd); + exit(0); +} + +if (($state ne "") || ($state ne "NA")){ + $cmd = "set_cx_state all $cx $state"; + $utils->doCmd($cmd); + exit(0); +} + + +die ("requires endp_name to be set") + unless ((defined $endp_name) && ($endp_name ne "")); +# Assumes that the endpoint already exists. +$cmd = Utils::fmt_cmd("set_wanlink_info", $endp_name, $speed, $latency, + $max_jitter, $reorder_freq, $extra_buffer, $drop_pm, $dup_pm, $pcap, + $jitter_freq, $min_drop_amt, $max_drop_amt, $min_reorder_amt, + $max_reorder_amt, $max_lateness ); +$utils->doCmd($cmd); + +exit(0); diff --git a/lanforge/lanforge-scripts/lf_l4_auth.pl b/lanforge/lanforge-scripts/lf_l4_auth.pl new file mode 100755 index 000000000..9e399a2b5 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_l4_auth.pl @@ -0,0 +1,285 @@ +#!/usr/bin/perl -w +#-----------------------------------------------------------------------# +# This program is used to create layer-4 connections with # +# IP4 addresses correlated to username/password combinations # +# and get some basic information from LANforge. # +# # +# Written by Candela Technologies Inc. # +#-----------------------------------------------------------------------# +package main; +use strict; +use warnings; +use Carp; +$| = 1;# Un-buffer output + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use Getopt::Long; +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use POSIX; +use constant NA => "NA"; +use constant NL => "\n"; +use constant shelf_num => 1; + +# Default values for ye ole cmd-line args. +our $quiet ="yes"; +our $resource = 1; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; +our $report_timer = 5000; +our $outfile_pref = "l4-out"; +our $l4timeout = 1000 * 60 * 1; # minutes +our $url_rate = 600; # urls/10min +our $test_mgr = "l4_connections"; +our $port_range = undef; +our $auth_pref = undef; +our $target_url = undef; +our $port_name = undef; +our $first_port = undef; +our $last_port = undef; +our $user_pref = undef; +our $pass_pref = undef; + +#-----------------------------------------------------------------------# +# Nothing to configure below here, most likely. # +#-----------------------------------------------------------------------# + +our $usage = "\nUsage: $0 --mgr {host-name | IP} + --mgr_port {ip port} + --resource {number} + --report_timer {milliseconds} + --quiet {yes|no} + --timeout {millis} # url timeout in milliseconds ($::l4timeout ms) + --url_rate {per 10 min) # requests per 10 minutes ($::url_rate) + --port_range {first-last} # eg rd0#0-rd0#99 < keep name short! + --auth_pref {1-4 chars,1-4 chars} # u,p appended with last octet: u101 p101 + --target_url {http://hostname/path} # http(s) urls will be rewritten to + # http://hostname/path?user=u&pass=p + --outfile_pref {l4-out} # found in /home/lanforge/l4logs + +Example: + + $0 --port_range rd2#0-rd2#99 --auth_pref u,p \ + --target_url 'http://10.99.0.2/index.html' + + $0 --mgr 192.168.101.1 --mgr_port 4001 --resource 1 \\ + --port_range rd0#0-rd0#25 --report_timer 1000 \\ + --auth_pref bob,pas \\ + --target_url 'https://10.99.0.2/index.html' \\ + --outfile_pref 'req_log' \\ + --url_rate 6000 \\ + --timeout 120000 + + (*) first create macvlans with a gateway inside a virtual router +"; + +GetOptions +( + 'quiet|q=s' => \$::quiet, + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p=i' => \$::lfmgr_port, + 'resource|r=i' => \$::resource, + 'port_range=s' => \$::port_range, + 'report_timer=i' => \$::report_timer, + 'auth_pref|ap=s' => \$::auth_pref, + 'target_url|u=s' => \$::target_url, + 'outfile_pref|op=s' => \$::outfile_pref, + 'timeout|to=i' => \$::l4timeout, + 'url_rate=i' => \$::url_rate, +) || die("$::usage"); + +if ( length($::port_range) < 1 + || length($::auth_pref) < 1 + || length($::target_url) < 1) { + die( "missing port_range, auth_pref, or target_url: $::usage"); +} +#print "PortRange: $::port_range\n"; +($::port_name, $::first_port, $::last_port) = $::port_range =~ /([[:alnum:]]+[^[:alnum:]])(\d+)-[[:alnum:]]+[^[:alnum:]](\d+)/; +#print "PortName[$::port_name] FirstPort[$::first_port] LastPort[$::last_port]\n"; +#print "AuthPrefix: $::auth_pref\n"; +($::user_pref, $::pass_pref) = $::auth_pref =~ /^\s*(\S+)\s*,\s*(\S+)\s*$/; +#print "UserPrefix[$::user_pref] PassPrefix[$::pass_pref]\n"; + + +if ( !defined($::port_name) || length($::port_name) < 1 + || !defined($::first_port) || length($::first_port)< 1 + || !defined($::last_port) || length($::last_port) < 1 + || !defined($::user_pref) || length($::user_pref) < 1 + || !defined($::pass_pref) || length($::pass_pref) < 1) { + die( "missing port_name, first_port, last_port, user_pref, or pass_pref: $::usage"); +} + +our ($schema, $host, $path) = $::target_url =~ /\s*(https?):\/\/([^\/]+)(\/?.*?)\s*$/; +#print "schema[$schema] host[$host] path[$path]\n"; + +#----------------------------------------------------------------------# +# Wait up to 20 seconds when requesting info from LANforge. +our $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); +$::t->open( Host => $::lfmgr_host, + Port => $::lfmgr_port, + Timeout => 10); +$::t->max_buffer_length(8 * 1024 * 1000); # 8 MB buffer +$::t->waitfor("/btbits\>\>/"); + +#-----------------------------------------------------------------------# +# compat # +#-----------------------------------------------------------------------# +if ( !defined *LANforge::Utils::fmt_cmd ) { + #*LANforge::Utils::fmt_cmd = sub { + sub LANforge::Utils::fmt_cmd { + my $self = shift; + my $rv; + for my $hunk (@_) { + $rv .= ( $hunk =~ / +/) ? "'$hunk' " : "$hunk "; + } + chomp $rv; + return $rv; + }; +} +# Configure our utils. +our $utils = new LANforge::Utils(); +$::utils->connect($lfmgr_host, $lfmgr_port); + + +#-----------------------------------------------------------------------# +# survey ports, complain if they are not present # +#-----------------------------------------------------------------------# + +our %port_ips = (); +our %port_quads = (); +our %port_urls = (); +our %port_download= (); +our %port_file = (); +my $method = 1; +my $match = '(IP: \S+)\s'; +my $tmp_quad; +my $tmp_ip; +for (my $i = $::first_port; $i <= $::last_port; $i++) { + my $tmp_name = $::port_name.$i; + my @txt = split(/\n/, $::utils->doAsyncCmd("nc_show_port 1 $::resource $tmp_name")); + if (my ($matched) = grep(/$match/, @txt)) { + #print "$::port_name$i: found it: $matched\n"; + ($tmp_ip) = $matched =~ /^\s+IP: ([0-9.]+)\s+MASK.*$/; + ($tmp_quad) = $matched =~ /^\s+IP: [0-9.]+\.([^. ]+)\s+MASK.*$/; + #print "tmp_quad $tmp_quad tmp_ip:$tmp_ip\n"; + } + $::port_quads{$i} = $tmp_quad; + $::port_ips{$i} = $tmp_ip; + #print "last_q[$tmp_quad]\n"; +} + + +#-----------------------------------------------------------------------# +# M A I N # +#-----------------------------------------------------------------------# +# for every port, build the following items: # +# - url with user:pass # +# - input file url like "dl $url $outfile-$i # +# - create l4 connection with 'use url file' # +#-----------------------------------------------------------------------# + +my $l4path="/home/lanforge/l4-urls"; +if ( !-d $l4path ) { + mkdir $l4path || die "cannot make $l4path"; +} + +our $use_url_file = 1; + +for (my $i = $::first_port; $i <= $::last_port; $i++) { + #print "port_quads:".$i."[".$::port_quads{$i}."]\n"; + my $tmp_quad = $::port_quads{$i}; + #print "tmp_quad[$tmp_quad]\n"; + + # style for basic/auth + #my $url = $::schema."://".$::user_pref.$tmp_quad.':'.$::pass_pref.$tmp_quad."@".$::host.$::path; + + #get style + my $url = $::schema.'://'.$::host.$::path.'?username='.$::user_pref.$tmp_quad.'&password='.$::pass_pref.$tmp_quad; + + print "url[$url]\n"; + $::port_urls{$i} = $url; + $::port_download{$i} = "dl $url $l4path/$outfile_pref-$port_name$i.txt\n"; + $::port_file{$i} = "$l4path/dl_$port_name$i.txt"; +} + +my $proxy_server = NA; +my $proxy_userpwd = NA; +my $ssl_cert_fname = "ca-bundle.crt"; +my $user_agent = NA; +my $proxy_auth_type = "0"; +my $http_auth = 3; # | 0x2; for digest +my $dns_cache_to = 60; #default +my $max_speed = 0; +my $block_size = NA; +my $smtp_from = NA; + +# create test-mgr +my @testmgrs = split(/\n/, $::utils->doCmd("show_tm all")); +if( my($tmmatches) = grep /$::test_mgr/, @testmgrs) { + #print "test_mgr:$tmmatches\n"; +} +else { + $::utils->doCmd("add_tm $::test_mgr"); +} + + + +for (my $i = $::first_port; $i <= $::last_port; $i++) { + # create dummy endpoint + my $tmp_ep1 = "L4_$port_name$i"; + my $tmp_ep2 = "D_L4_$port_name$i"; + my $cmd = $::utils->fmt_cmd( "add_l4_endp", $tmp_ep2, + shelf_num, $::resource, "$port_name$i", + "l4_generic", 0, 0, 0, ' ', ' '); + #print "cmd: $cmd\n"; + $::utils->doCmd($cmd); + $cmd = $cmd = "set_endp_flag $tmp_ep2 unmanaged 1"; + #print "cmd: $cmd\n"; + $::utils->doCmd($cmd); + #sleep(0.2); + + # create live endpoint + my $ip_addr = $::port_ips{$i}; + open(my $fh, ">", $::port_file{$i} ) || die "unable to create file $::port_file{$i}"; + print $fh $::port_download{$i}; + close $fh; + + # layer4 endpoint + my $url = ($::use_url_file) + ? $::port_file{$i} + : $::port_download{$i} + ; + $cmd = $::utils->fmt_cmd( "add_l4_endp", $tmp_ep1, + shelf_num, $::resource, "$port_name$i", + "l4_generic", 0, $::l4timeout, $::url_rate, + $url, $proxy_server, $proxy_userpwd, + $ssl_cert_fname, $user_agent, $proxy_auth_type, + $http_auth, $dns_cache_to, $max_speed, $block_size, + $smtp_from, "AUTO" ); + #print "cmd: $cmd\n"; + $::utils->doCmd($cmd); + #sleep(0.2); + if ($::use_url_file) { + $cmd = $::utils->fmt_cmd("set_endp_flag", "$tmp_ep1", "GetUrlsFromFile", 1); + $::utils->doCmd($cmd); + #sleep(0.2); + } + #$::utils->doCmd("set_cx_report_timer $::test_mgr $tmp_ep1 $report_timer"); + #sleep(0.2); + + my $cx_name = "CX_$tmp_ep1"; # was CX-L4- + $cmd = $::utils->fmt_cmd("add_cx", $cx_name, $test_mgr, $tmp_ep1, $tmp_ep2); + #print "cmd: $cmd\n"; + $::utils->doCmd($cmd); + #sleep(0.2); + $::utils->doCmd("set_cx_report_timer $::test_mgr $cx_name $report_timer"); +} + +# diff --git a/lanforge/lanforge-scripts/lf_l4_random_speeds.bash b/lanforge/lanforge-scripts/lf_l4_random_speeds.bash new file mode 100755 index 000000000..b050c630e --- /dev/null +++ b/lanforge/lanforge-scripts/lf_l4_random_speeds.bash @@ -0,0 +1,84 @@ +#!/bin/bash + +mgr="jedtest" +url="http://10.26.0.1/random.txt" +endp_list=("tg1" "tg2") +resource=2 +pause_sec=15 +max_speed=1000000000 +port=b2000 + +proxyport=NA +con_timeout=1000 +url_rate=6000 +url="dl $url /dev/null" +proxy_svr=NA +proxy_creds=NA +ssl_cert_fname=NA +user_agent=NA +# set proxy_auth_type=64 to enable gzip +proxy_auth_type=0 +http_auth_type=0 +dns_cache_timeout=0 +tftp_block_sz=NA +smtpfm=NA +sec_ip=NA + +function create_l4_endp() { + echo -n " $endp" + ./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd --cmd \ + "set_cx_state default_tm CX_$endp STOPPED" >/dev/null + ./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd --cmd \ + "add_l4_endp $endp 1 $resource $port l4_generic $proxyport $con_timeout $url_rate '$url' $proxy_svr $proxy_creds $ssl_cert_fname $user_agent $proxy_auth_type $http_auth_type $dns_cache_timeout $new_speed $tftp_block_sz $smtpfm $sec_ip" >/dev/null + ./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd --cmd \ + "add_cx CX_$endp default_tm $endp NA" >/dev/null + ./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd --cmd \ + "set_endp_report_timer $endp 1000" >/dev/null + ./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd --cmd \ + "set_cx_report_timer default_tm CX_$endp 1000" >/dev/null + ./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd --cmd \ + "set_cx_state default_tm CX_$endp RUNNING" >/dev/null +} + +# the nc_show_endpoints flushes cached endpoint settings +function ncshow() { + ./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd --cmd \ + "nc_show_endpoints $endp" > /dev/null +} + + +## +## M A I N +## + +# method uses random modulus of list of speeds +set_speeds=(72800 128300 435000) +echo "Using set of speeds: ${set_speeds[@]}... " +for i in `seq 1 10`; do + j=`expr $RANDOM % ${#set_speeds[@]}` + new_speed=${set_speeds[$j]} + echo -n " $new_speed bps:" + for endp in ${endp_list[@]}; do + create_l4_endp + ncshow + done + sleep $pause_sec +done +echo "" +echo "Now using random speeds lower than $max_speed... " +# use a random fraction of maximum speed +for i in `seq 1 5`; do + new_speed=`echo "scale=0; $max_speed / $RANDOM" | bc -l` + new_speed=`echo "$RANDOM + $new_speed" | bc -l` + echo -n " $new_speed bps:" + for endp in ${endp_list[@]}; do + create_l4_endp + ncshow + done + sleep $pause_sec +done + +sleep $pause_sec + +echo "" + diff --git a/lanforge/lanforge-scripts/lf_l4_reset.sh b/lanforge/lanforge-scripts/lf_l4_reset.sh new file mode 100755 index 000000000..b32e3c844 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_l4_reset.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# This script will reset any layer 4 connection that reaches 0 Mbps over last minute. +# Run this script from the /home/lanforge/scripts directory. + + +# Custom variables +# Use DB to set a database to load. +# Use mgr to have this script run on another system (replace localhost with ip or hostname). +# Use rate to change how often the script checks layer 4 endpoints (default is 60s). +DB="" +mgr="localhost" +napTime="30s" +min="0" + +### Should not need to change anything below this line! ### + +function show_help() { + echo "$0 -m -d -n -l " + echo " --mgr --delay --min --load " + echo "" + exit +} + +ARGS=`getopt -o d:l:m:n:h --long help,load:,delay:,mgr:,min: -- "$@"` + +while :; do + case "$1" in + -l|--load) DB="$2"; shift 2 ;; + + -d|--delay) napTime="$2"; shift 2 ;; + + -m|--mgr) mgr="$2"; shift 2 ;; + + -n|--min) min="$2"; shift 2 ;; + + --) shift; break;; + + -h|--help) + show_help + exit 1 ;; + *) break;; + esac +done + +echo -n "Options --mgr $mgr --delay $napTime --min $min" +if [[ $DB != "" ]]; then + echo -n "--db $DB" +fi +echo "" + +# Load DB (if provided above) +if [[ ! $DB = "" ]]; then + echo -n "Loading database $DB..." + ./lf_portmod.pl --manager $mgr --load $DB > /dev/null + sleep 10s + echo "...done" +fi +echo "Press Control-C to stop..." + +while : ; do + # List layer-4 cx + l4output=`./lf_firemod.pl --mgr $mgr --cmd "show_cx" \ + | grep "type: L4_GENERIC" | awk ' ''{print $3}' | cut -d "_" -f 2- \ + | sort | uniq` + + # We get all the statuses we can get because that it a lot faster + # than querying one status at a time + allStatuses=`./lf_firemod.pl --mgr $mgr --action show_endp` + + l4list=($l4output) + for i in "${l4list[@]}" + do + # if we call lf_firemod multiple times we have to wait on + # the manager and it ends up taking longer than our dwell time + # endp_status=`./lf_firemod.pl --mgr $mgr --action show_endp --endp_name` + + endp_status=`echo "$allStatuses" | awk "/L4Endp \[$i\]/{flag=1}/^\$/{flag=0}flag"` + + #echo '---------------------------------------' + #echo "$endp_status" + #echo '---------------------------------------' + + l4read=`echo "$endp_status" | awk '/Bytes Read:/ {print $8}'` + l4write=`echo "$endp_status" | awk '/Bytes Written:/ {print $8}'` + runChk=`echo "$endp_status" | grep '^L4Endp '` + runStat=`echo "$runChk" | sed 's/L4Endp \[.*\] (\(.*\))/\1/'` + + checkSpeed=0 + doL4Restart=0 + case "$runStat" in + "RUNNING") + checkSpeed=1 + ;; + "RUNNING, ALLOW_REUSE") + checkSpeed=1 + ;; + "NOT_RUNNING") + doL4Restart=1 + ;; + "NOT_RUNNING, WAIT_RESTART") + doL4Restart=1 + ;; + "NOT_RUNNING, ALLOW_REUSE") + ;; + *) + echo "Unknown case ${i}[$runStat]" + ;; + esac + if [[ x$checkSpeed = x1 ]]; then + #echo "l4read[$l4read] min[$min] l4write[$l4write]" + if (( $l4read <= $min )) && (( $l4write <= $min )); then + doL4Restart=1 + fi + fi + + #echo "restart[${doL4Restart}] $i l4read[$l4read] l4write[$l4write] $runChk" + + if (( $doL4Restart == 1 )); then + echo "Resetting $i at `date`" + ./lf_firemod.pl --mgr $mgr --cmd "set_cx_state all CX_$i STOPPED" > /dev/null + sleep 3s + ./lf_firemod.pl --mgr $mgr --cmd "set_cx_state all CX_$i RUNNING" > /dev/null + fi + done + + echo -n "." + sleep $napTime +done diff --git a/lanforge/lanforge-scripts/lf_log_parse.pl b/lanforge/lanforge-scripts/lf_log_parse.pl new file mode 100755 index 000000000..5c17083f7 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_log_parse.pl @@ -0,0 +1,22 @@ +#!/usr/bin/perl + +# Convert the timestamp in LANforge logs (it is in unix-time, miliseconds) +# to readable date. + +use strict; +use POSIX qw(strftime); + +while (<>) { + my $ln = $_; + chomp($ln); + if ($ln =~ /^(\d+):(.*)/) { + my $ts = $1; + my $rst = $2; + my $dt = strftime("%Y-%m-%d %H:%M:%S", localtime($ts / 1000)); + my $msec = $ts % 1000; + print "$dt $msec:$rst\n"; + } + else { + print "$ln\n"; + } +} diff --git a/lanforge/lanforge-scripts/lf_loop_traffic.sh b/lanforge/lanforge-scripts/lf_loop_traffic.sh new file mode 100755 index 000000000..2dd32935c --- /dev/null +++ b/lanforge/lanforge-scripts/lf_loop_traffic.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +if [ -z "$1" -o -z "$2" -o -z "$3" ]; then + echo "Usage: $0 " + echo " Layer-3 Name: preface with cx: for cross connect" + echo " preface with group: for test group" + exit 1 +fi + +MANAGER=${MANAGER:-localhost} +RESOURCE=${RESOURCE:-1} + +TRAFFIC_NAME="$1" +USING=wrong +if [[ $1 = cx:* ]]; then + USING=cx + TRAFFIC_NAME=${TRAFFIC_NAME#cx:} +elif [[ $1 = group:* ]]; then + USING=tg + TRAFFIC_NAME=${TRAFFIC_NAME#group:} +fi + +if [[ $USING = wrong ]]; then + echo "Please specify group using 'group:$TRAFFIC_NAME' or single connection using 'cx:$TRAFFIC_NAME'" + exit 1 +fi + +case $USING in +cx) + START="op_cx run" + STOP="op_cx stop" + ;; +tg) + START="op_group run" + STOP="op_group stop" + ;; +esac + +RUN_SEC="$2" +SLEEP_SEC="$3" +ACTION="STOPPED" + +function op_cx() { + ACTION="STOPPED" + if [[ $1 = run ]]; then + ACTION="RUNNING" + elif [[ $1 = quiesce ]]; then + ACTION="QUIESCE" + fi + ./lf_firemod.pl --mgr $MANAGER --resource $RESOURCE --quiet yes --action do_cmd --cmd "set_cx_state default_tm $TRAFFIC_NAME $ACTION" +} + +function op_group() { + ACTION="stop_group" + if [[ $1 = run ]]; then + ACTION="start_group" + elif [[ $1 = quiesce ]]; then + ACTION="quiesce_group" + fi + ./lf_firemod.pl --mgr $MANAGER --resource $RESOURCE --quiet yes --action do_cmd --cmd "$ACTION $TRAFFIC_NAME" +} + + +cd /home/lanforge/scripts +while :; do + $START + sleep $RUN_SEC + $STOP + sleep $SLEEP_SEC +done diff --git a/lanforge/lanforge-scripts/lf_macvlan.pl b/lanforge/lanforge-scripts/lf_macvlan.pl new file mode 100755 index 000000000..cb17467f3 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_macvlan.pl @@ -0,0 +1,1509 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, custom_tcp, l4 (http, https, ftp and fileIO) +# across real ports and MACVLAN ports on one or more machines. +# It then continously starts and stops the connections. + +# Un-buffer output +$| = 1; + +use strict; +#use Switch; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; + +my $script_speed = 25; # Increase to issue commands faster. +my $pause = 0; # Increase delay (seconds) if experiencing problems on slow systems. + +my $INIT = 0; # If true, removes all previous tests and ports!!! +my $create_only = 1; # If true, only create tests, i.e. do not automatically run them. + +my $mac_init = 0; # Set to 1 to start MAC address from zero when running looped test. +my $ip_init = 0; # Set to 1 to start IP addresses from zero when running looped test. +my $init_once = 0; # Set to 1 to only initialize test creation once. +my $init_net = 1; # Set to 0 to disable reconfiguring MAC and IP addresses. +my $init_tests = 1; # Set to 0 to disable reconfiguring tests. +my $first_run = 1; # Set to 0 to disable initial configurations. +my $name_id = 0; # First index of name of endpoints and CXs. +my $name_id_len = 0; # Override for length of $name_id. +my $loop_max = 100; +my $start_stop_loops = 2; +my $run_for_time = 120; # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Run for XX seconds..then will be stopped again +my $ignore_phys_ports = 1; # If true, just muck with mac-vlans. + +my $one_cx_per_port = 0; # If zero, will have one of EACH of the cx types on each port. + +my $cx_types_from_file = 0; # If true, will rotate through the @cx_types_files + # when creating tests instead of using @cx_types array. +#cx_types files must be CSV. +my @cx_types_files = ("/tmp/cx_type-foo.txt", "/tmp/cx_type-foo1.txt"); + +#my @cx_types = ("lf", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4", "voip"); +#my @min_pkt_szs = (64, 1, 1, 1, 1, 0); +#my @max_pkt_szs = (1514, 12000, 13000, 2048, 2048, 0); + +#my @cx_types = ("lf_udp"); +my @cx_types = ("lf_tcp"); +#my @cx_types = ("l4", "l4", "l4", "l4", "l4", "l4", "l4", "l4", "l4", "l4"); +#my @cx_types = ("l4"); +#my @cx_types = ( +#"lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp" +#,"l4"); + + +my $test_mgr = "ben_tm"; +my $report_timer = 8000; # Set report timer for all tests created in ms, i.e. 8 seconds + +my $lfmgr_host = undef; +my $lfmgr_port = 4001; + +my $shelf = 1; + +# This sets up connections. +my $lf1 = 1; # Minor Resource EID of first LANforge resource. +my $lf2 = ""; # Set to "" if we have no second machine. Or set to second Resource + # minor EID to create mac-vlans on it. + + +# Port pairs. These are the ports that should be talking to each other. +# i.e. the third column in lf1_ports talks to the third column in lf2_ports. +# EIDs or aliases can be used. +# Port pairs must match on each shelf - will enhance to allow any pair on each shelf. +#my @lf1_ports = (1); #, 2, 3); +#my @lf2_ports = (2); #, 2, 3); +my @lf1_ports = ( "eth0", "eth1"); +my @lf2_ports = (""); +my @ip_base = ( "192.168", "172.16"); +my @ip_c = ( 2 , 1 ); +my @ip_lsb = ( 2 , 2 ); +my @msk = ("255.255.0.0", "255.255.0.0"); +my @ip_gw = ("192.168.2.1", "172.16.1.1"); + +#my $ip_base1 = "172.16"; # Use this set. +#my $ip_c1 = 2; # +#my $ip_lsb1 = 2; # +#my $msk1 = "255.255.0.0"; # +#my $ip_gw1 = "0.0.0.0"; # + +my $mac1 = 0x00; # Starting MAC address 00:m5:m4:m3:m2:m1 where: +my $mac2 = 0x00; # m5 is shelf EID, m4 is card EID, m3 is $mac3, +my $mac3 = 0x00; # m2 is $mac2 and m1 is $mac1. + +my $mvl = 1; +my $start_mvlan = 0; +my $num_mvlans = 9; +my $num_cxs = 10; + +#my @min_rate = (100000);# bps +#my @max_rate = (100000);# bps +my @min_rate = (9600, 64000, 1000000, 9600); # bps +my @max_rate = (9600, 64000, 1000000, 1000000);# bps +#my @min_pkt_szs = (0); # bytes +#my @max_pkt_szs = (0); # bytes +my @min_pkt_szs = (40, 548, 1472, 40); # bytes +my @max_pkt_szs = (40, 548, 1472, 1472); # bytes + + +################ +# Layer-4 Only # +################ + +my $url_dl = 1; # If true, test will download from URL. False will upload to URL. +#my $l4_dl_path = "/tmp"; # Path to save downloaded file. +#my $l4_dl_path = "NUL"; # Windows equivalent of *nix /dev/null. +my $l4_dl_path = "/dev/null"; # Improve performance by saving downloaded file to /dev/null. + +#my @l4_urls = ( +# "http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +#,"http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +#,"http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +#,"http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +#,"http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +#); +my @l4_urls = ("http://192.168.100.3/index.html"); +#my @l4_urls = ("ftp://192.168.100.3/file"); + +my $urls_10m = 100; # How many URLs to process every 10 minutes. +my $l4_timeout = 10000; # How long to wait for a connection, in milliseconds. + + +############# +# VoIP Only # +############# +my $codec = "G711U"; # Other options: G711U, g729a, SPEEX, g726-16, g726-24, g726-32, g726-40 +my $jB_size = 1; # Set jitter buffer size in 20ms packets. Default value is 8 packets, 160ms. +my $tos = 0xBE; # Set ToS/QoS for VoIP can be decimal or 0xNN for hexadecimal but values will display in decimal in the GUI. + +my $mn_icg = 3; # minimum intercall gap +my $mx_icg = 3; # maximum intercall gap +my $min_call_duration = 0; # set to zero for 'file' +my $max_call_duration = 0; # Set to zero for 'file' + +my $no_send_rtp = 0; # Set to zero to send RTP traffic, 1 to suppress RTP +my $use_VAD = 0; # Set to zero to not use VAD, 1 to use VAD +my $vad_timer = 500; # how much silence (ms) before we start VAD (Silence Suppression) +my $vad_fs = 3000; # how often (ms) to force an rtp pkt send even if we are in VAD +my $use_PESQ = 0; # Set to 1 for PESQ, zero for not PESQ +my $pesq_server = "127.0.0.1"; +my $pesq_server_port = 3998; +my $vproto = "SIP"; # set $vproto = "H323"; for H.323 +my $bsip_port_a = "5066"; # Base SIP port for endpoint-A +my $bsip_port_b = "5067"; # Base SIP port for endpoint-B +my $i_sip_port_a = 0; # If zero, do not increment, otherwise increment by assigned value. +my $i_sip_port_b = 0; # If zero, do not increment, otherwise increment by assigned value. +my $brtp_port = "AUTO"; # Base RTP port +my $i_rtp_port = 0; # If zero, do not increment, otherwise increment by assgined value + +my $peer_to_peer_voip = 1; # Don't register with SIP proxy, but just call peer to peer. + +my @src_sound_files = ("media/female_voice_8khz.wav"); + +################ +# File-IO Only # +################ +my $fio_base = "/mnt/fio_base"; +my $fio_targ_dir = ""; +my $fsrw = "write"; + + +my $DEBUG = 0; +my $D_PAUSE = 3; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## +my $script_name = $0; + +# Parse cmd-line args +my $i; +for ($i = 0; $i<@ARGV; $i++) { + my $var = $ARGV[$i]; + if ($var =~ m/(\S+)=(.*)/) { + my $arg = $1; + my $val = $2; + handleCmdLineArg($arg, $val); + } + else { + handleCmdLineArg($var); + } +} + +if ($lfmgr_host eq undef) { + print "\nYou must define a LANforge Manager!!!\n\n" + . "For example:\n" + . "./$script_name mgr=localhost\n" + . "OR\n" + . "./$script_name mgr=192.168.1.101\n\n"; + printHelp(); + exit (1); +} + +my $foundL4 = 0; +for ($i = 0; $i<@cx_types; $i++) { + if ($cx_types[$i] eq "l4") { + $foundL4 = 1; + last; + } +} +if ($lf2 == "" && @lf1_ports < 2 && !$foundL4) { + die ("Must have more than one port with only one resource."); +} + + +if (!$num_mvlans) { $mvl=0; } + + +#if (!$start_mvlan && !$num_mvlan && $num_cxs) { +# die ("Must have either number of MACVLANs (num_mvl) or cross-connects (num_cxs) > 0."); +#} + +print + "\nStarting script with the following arguments:" + . "\ninit: $INIT" + . "\nmanager: $lfmgr_host\n" + . "\nlf1: $lf1\nlf2: $lf2\n" + . "\nlf1_ports: " . join(" ", @lf1_ports) + . "\nlf2_ports: " . join(" ", @lf2_ports) . "\n" + . "\nstart_macvlans: $start_mvlan" + . "\nnum_mvlans: $num_mvlans\n" + . "\nmin_rates: " . join(" ", @min_rate) + . "\nmax_rates: " . join(" ", @max_rate) + . "\nmin_pkt_sizes: " . join(" ", @min_pkt_szs) + . "\nmax_pkt_sizes: " . join(" ", @max_pkt_szs) . "\n" + . "\ncx_types: " . join(" ", @cx_types) + . "\nnum_cxs: $num_cxs\n" + . "\none_cx_per_port: $one_cx_per_port\n\n"; + +if ($DEBUG) { sleep ($D_PAUSE); } + + +# Determine total port and endpoint counts and make sorting by name easier in the GUI :P + +my @num = (); # Formatted index number for name sorting in GUI. +my $t_num = 0; +my $t_ports = 0; +my $ni=0; +my $nj=0; + +my @lf1orig_ports = @lf1_ports; +my @lf2orig_ports = @lf2_ports; +my $lf2orig = $lf2; + +if ($lf2 == "") { + $lf2 = $lf1; + if ($foundL4) { + @lf2_ports = undef; + } + else { + # Put every other port into @lf2_ports to fake out lf2 info which makes the + # script work later. + # TODO: Stop faking out too early since we end up probing the same ports multiple times + # because of $num_cxs. This needs to move to below utils->updatePort @all_ports1 + # and @all_ports2 + @lf1_ports = undef; + @lf2_ports = undef; + $i=0; + if ($mvl) { + for ($ni=0; $ni<@lf1orig_ports; $ni++) { + $lf1_ports[$i] = $lf1orig_ports[$ni]; + $lf2_ports[$i] = $lf1orig_ports[++$ni]; + $i++; + } + } else { + for ($nj=0; $nj<($num_cxs*2); $nj++) { + for ($ni=0; $ni<(@lf1orig_ports); $ni++) { + $lf1_ports[$i] = $lf1orig_ports[$ni]; + $lf2_ports[$i] = $lf1orig_ports[++$ni]; + $i++; + } + } + } # if mvl + } # if foundL4 +} # if lf2 = "" + +if ($DEBUG) { printArgs(); sleep ($D_PAUSE); } + +# Check that ip_base address pairs aren't the same. +for ($ni = 0; $ni<@ip_base; $ni++) { + if ($ip_base[$ni] == $ip_base[$ni+1]) { + die ("ERROR: Base IP addresses cannot be the same."); + } + $ni++; +} + +my @cxts = ("lf", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4", + "fileIONFS", "fileIOCIFS"); +my @t_cxts = (); +for ($ni=0; $ni<@cxts; $ni++) { + @t_cxts[$ni] = 0; +} + +if ($lf2orig ne "") { + if ($ignore_phys_ports) { + $t_ports = $num_mvlans; + } + else { + $t_ports = @lf1_ports + @lf2_ports + ($num_mvlans); + if (@lf2_ports eq undef) { $t_ports--; } + } +} +elsif ($num_mvlans) { + if ($ignore_phys_ports) { + $t_ports = $num_mvlans; + } + else { + $t_ports = @lf1_ports + ($num_mvlans); + } +} +else { + $t_ports = @lf1_ports + @lf2_ports; + if (@lf2_ports eq undef) { $t_ports--; } + $t_ports *= $num_cxs; +} + +my $t_cxtypes = @cx_types; +my $t_urls = @l4_urls; + +if (@min_rate != @max_rate ) { + die("Number of elements in min_rate does not match number of elements in max_rate."); +} +else { + my $t_rate = @min_rate + @max_rate; +} +if (@min_pkt_szs != @max_pkt_szs ) { + die("Number of elements in min_pkt_szs does not match number of elements in max_pkt_szs."); +} +else { + my $t_pkt_szs = @min_pkt_szs + @max_pkt_szs; +} + +for ($ni=0; $ni<@cx_types; $ni++) { + for ($nj=0; $nj<@cxts; $nj++) { + if ( $cx_types[$ni] eq $cxts[$nj] ) { + $t_cxts[$nj]++; + } + } +} + +for ($nj=0; $nj<@cxts; $nj++) { + if ( $cxts[$nj] eq "l4") { + $t_num += ($t_ports * (2 * ($t_cxts[$nj] * $t_urls))); + } + else { + $t_num += ($t_ports * (2 * $t_cxts[$nj])); + } +} +$t_num += $name_id; + +my $num_len; +if ($name_id_len) { + if (length($name_id) > $name_id_len || length($t_num) > $name_id_len) { + print "\nWARNING: id_len specifies a string length less that first_name_id or less that total number of endpoints\n"; + } + $num_len = $name_id_len; +} +else { + $num_len = length ($t_num); +} +$t_num -= $name_id; +my $i = 0; + +# !!! DO NOT Reimplement Switch/Case since the following will cause switch/case to fail: +# !!! for ($nj=0; $nj<($num_cxs / @lf1orig_ports); $nj++) { +# !!! Why? DUNNO! + +#switch ($num_len) { +# case 1 { +# for ($i ; $i<$t_num; $i++) { +# $num[$i] = sprintf("%01d", $name_id + $i); +# } +# } +# case 2 { +# for ($i ; $i<$t_num; $i++) { +# $num[$i] = sprintf("%02d", $name_id + $i); +# } +# } +# case 3 { +# for ($i ; $i<$t_num; $i++) { +# $num[$i] = sprintf("%03d", $name_id + $i); +# } +# } +# case 4 { +# for ($i ; $i<$t_num; $i++) { +# $num[$i] = sprintf("%04d", $name_id + $i); +# } +# } +# case 5 { +# for ($i ; $i<$t_num; $i++) { +# $num[$i] = sprintf("%05d", $name_id + $i); +# } +# } +# case 6 { +# for ($i ; $i<$t_num; $i++) { +# $num[$i] = sprintf("%06d", $name_id + $i); +# } +# } +# else { +# for ($i ; $i<$t_num; $i++) { +# $num[$i] = $name_id + $i; +# } +# } +#} + +if ($num_len == 1) { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%01d", $name_id + $i); + } +} +elsif ($num_len == 2) { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%02d", $name_id + $i); + } +} +elsif ($num_len == 3) { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%03d", $name_id + $i); + } +} +elsif ($num_len == 4) { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%04d", $name_id + $i); + } +} +elsif ($num_len == 5) { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%05d", $name_id + $i); + } +} +elsif ($num_len == 6) { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%06d", $name_id + $i); + } +} +else { + for ($i ; $i<$t_num; $i++) { + $num[$i] = $name_id + $i; + } +} + +if ($DEBUG > 99) { + $i = 0; + print "name_id: $name_id, t_num: $t_num, num_len: $num_len :-\n"; + for ($i ; $i<$t_num; $i++) { + print $num[$i] . " "; + } + print "\n"; + sleep ($D_PAUSE); +} +if ($DEBUG) { printArgs(); sleep ($D_PAUSE); } + +# Open connection to the LANforge server. +my $t = new Net::Telnet(Timeout => 15, + Prompt => '/default\@btbits\>\>/'); + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 45); + +$t->waitfor("/btbits\>\>/"); +$t->max_buffer_length(1024 * 1024 * 10); # 10M buffer + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + +my $dt = getDate(); +my $dt_start = $dt; + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = getDate(); + print "\n\n***** Starting $script_name loop: $loop at: $dt *****\n\n"; + + if (!$init_once) { + if ($INIT) { initToDefaults(); } + + if ($init_net) { + if ($mvl) { addMacVlans(); } # Add MACVLANs. + initIpAddresses(); # Add some IP addresses to the ports. + } + if ($init_tests) { + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); # Add default user + doCmd("tm_register $test_mgr default_gui"); # Add default GUI user + addCrossConnects(); # Add our endpoints. + } + } + elsif ($first_run) { + $first_run = 0; + if ($INIT) { initToDefaults(); } + + if ($init_net) { + if ($mvl) { addMacVlans(); } + initIpAddresses(); + } + if ($init_tests) { + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); # Add default user + doCmd("tm_register $test_mgr default_gui"); # Add default GUI user + addCrossConnects(); # Add our endpoints. + } + } + + $dt = getDate(); + print "\n\n*** Started $script_name script at : $dt_start ***\n" + . "*** Finished $script_name configuration at: $dt ***\n\n"; + sleep($D_PAUSE); + + if ($create_only == 1) { exit(0); } + + my $rl = 0; + for ($rl = 0; $rl<$start_stop_loops; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + + }# For some amount of start_stop iterations... +}# for some amount of loop iterations + +if ($DEBUG) { printArgs(); } + +$dt = getDate(); +print "Started $script_name script at : $dt_start\n"; +print "Completed $script_name script at: $dt\n\n"; +exit(0); +##################### +# END lf_macvlan.pl # +##################### +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + my $szs = 0; + my $r = 0; + my @all_ports1 = @lf1_ports; +# my @all_ports1 = @lf1orig_ports; This don't work + my @all_ports2 = (""); + my $j; + my $pname; + + if ($foundL4) { + my $p1 = new LANforge::Port(); + my $q; + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + for ($j = 0; $j<@lf1_ports; $j++) { + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + if ($ignore_phys_ports) { + for ($j = 0; $j<@lf1_ports; $j++) { + shift(@all_ports1); + } + } + # TODO: Fake out ports here to create num_cxs tests. + } + else { + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + my $q; + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + @all_ports2 = @lf2_ports; +# @all_ports2 = @lf2orig_ports; This don't work + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + my $q; + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + } + if ($ignore_phys_ports) { + for ($j = 0; $j<@lf1_ports; $j++) { + shift(@all_ports1); + } + for ($j = 0; $j<@lf2_ports; $j++) { + shift(@all_ports2); + } + } + # TODO: Fake out ports here to create num_cxs tests. + } # if foundL4 + + print "\n\n\nCreating endpoints on " . @all_ports1 . " ports:\nall_ports1: " . join(" ", @all_ports1); + +# if ($lf2orig ne "") { + print "\nCreating endpoints on " . @all_ports2 . " ports:\nall_ports2: " . join(" ", @all_ports2) . "\n\n\n"; +# } + + if ($DEBUG) { sleep($D_PAUSE); } + + if ($one_cx_per_port) { + my $j = 0; + my $cxcnt = 0; + my $fecnt = 0; + for ($j ; $j<@all_ports1; $j++) { + my $i = $cxcnt % @cx_types; + $cxcnt++; + + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "L4-${num[$ep]}"; +# $ep++; + my $ep2 = "D_L4-${num[$ep]}"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + if ($l4_dl_path = "/dev/null") { + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 $l4_timeout $urls_10m 'dl ${l4_urls[0]} $l4_dl_path' ' '"; + } + else { + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 $l4_timeout $urls_10m 'dl ${l4_urls[0]} $l4_dl_path/$ep1' ' '"; + } + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "L4-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + }# if L4 + elsif ($cxt eq "voip") { + + + + + + }# if VoIP + elsif (($cxt eq "fileIONFS") || ($cxt eq "fileIOCIFS")) { + # Create File-IO endpoint + + my $FST = "nfs"; + if ($cxt eq "fileIOCIFS") { + $FST = "cifs"; + } + + my $ep1 = "fe-${num[$fecnt]}"; + my $ep2 = "D_$ep1"; + $fecnt++; + $ep++; +# $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_file_endp $ep2 $shelf $lf1 " . $all_ports1[$j] + . " fe_generic $min_rate[$r] $max_rate[$r] $min_rate[$r] $max_rate[$r]" + . " increasing $fio_base/$ep2 $ep2"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_file_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " fe_generic $min_rate[$r] $max_rate[$r] $min_rate[$r] $max_rate[$r]" + . " increasing \'$fio_base/$FST" + . "_$all_ports1[$j]" . $fio_targ_dir . "\' $ep1"; + doCmd($cmd); + + $cmd = "set_fe_info $ep1 16384 16384 10 1000000 1000000 \'$fio_base/$FST" . "_$all_ports1[$j]" + . $fio_targ_dir . "\' $ep1 $fsrw"; + doCmd($cmd); + + if ($r < (@min_rate - 1)) { + $r++; + } + else { + $r = 0; + } + + # Now, add the cross-connects + my $cx_name = "L4-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + }# elsif FIO + else { + # Create L3 endpoint + + my $burst = "NO"; + if ($min_rate[$r] != $max_rate[$r]) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$szs] != $max_pkt_szs[$szs]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "L3e-${num[$ep]}tx"; + $ep++; + my $ep2 = "L3e-${num[$ep]}rx"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . $max_pkt_szs[$szs] . + " $pattern NO"; + doCmd($cmd); + + if ($lf2 ne "") { +# die("Must have lf2 defined if using non-l4 endpoints."); + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . + $max_pkt_szs[$szs] . " $pattern NO"; + } + else { + $cmd = "add_endp $ep2 $shelf $lf1 " . $all_ports1[($j)] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . $max_pkt_szs[$szs] . + " $pattern NO"; + } + doCmd($cmd); + + if ($szs < (@min_pkt_szs - 1)) { $szs++; } + else { $szs = 0; } + if ($r < (@min_rate - 1)) { $r++; } + else { $r = 0; } + + # Now, add the cross-connects + my $cx_name = "L3-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + }# else L3 + }#for all ports + }#one_cx_per_port = 1 + else { + my $j = 0; + my $n = 0; + my $fecnt = 0; + for ($j; $j<@all_ports1; $j++) { + for ($i = 0; $i<@cx_types; $i++) { + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + for ($n = 0; $n<@l4_urls; $n++) { + my $ep1 = "L4-${num[$ep]}"; +# $ep++; + my $ep2 = "D_L4-${num[$ep]}"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + if ($l4_dl_path = "/dev/null") { + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 $l4_timeout $urls_10m 'dl ${l4_urls[$n]} $l4_dl_path' ' '"; + } + else { + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 $l4_timeout $urls_10m 'dl ${l4_urls[$n]} $l4_dl_path/$ep1' ' '"; + } + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "L4-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } #for url_list + } + elsif ($cxt eq "voip") { + + + + + + }# if VoIP + elsif (($cxt eq "fileIONFS") || ($cxt eq "fileIOCIFS")) { + # Create File-IO endpoint + my $FST = "nfs"; + if ($cxt eq "fileIOCIFS") { + $FST = "cifs"; + } + + my $ep1 = "fe-${num[$fecnt]}"; + my $ep2 = "D_$ep1"; + $fecnt++; + $ep++; +# $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_file_endp $ep2 $shelf $lf1 " . $all_ports1[$j] + . " fe_generic $min_rate[$r] $max_rate[$r] $min_rate[$r] $max_rate[$r]" + . " increasing $fio_base/$ep2 $ep2"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_file_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " fe_generic $min_rate[$r] $max_rate[$r] $min_rate[$r] $max_rate[$r]" + . " increasing $fio_base/$FST" . "_$all_ports1[$j]" . $fio_targ_dir . " $ep1"; + doCmd($cmd); + + $cmd = "set_fe_info $ep1 16384 16384 10 1000000 1000000 $fio_base/$FST" + . "_$all_ports1[$j]" . $fio_targ_dir . " $ep1 $fsrw"; + doCmd($cmd); + + if ($r < (@min_rate - 1)) { $r++; } + else { $r = 0; } + + # Now, add the cross-connects + my $cx_name = "L4-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + # Create L3 endpoint + + my $burst = "NO"; + if ($min_rate[$r] != $max_rate[$r]) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$szs] != $max_pkt_szs[$szs]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "L3e-${num[$ep]}tx"; + $ep++; + my $ep2 = "L3e-${num[$ep]}rx"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . $max_pkt_szs[$szs] . + " $pattern NO"; + doCmd($cmd); + + + if ($lf2 ne "") { +# die("Must have lf2 defined if using non-l4 endpoints."); + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . + $max_pkt_szs[$szs] . " $pattern NO"; + } + else { + $cmd = "add_endp $ep2 $shelf $lf1 " . $all_ports1[$j+1] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . $max_pkt_szs[$szs] . + " $pattern NO"; + } + doCmd($cmd); + + if ($szs < (@min_pkt_szs - 1)) { $szs++; } + else { $szs = 0; } + if ($r < (@min_rate - 1)) { $r++; } + else { $r = 0; } + + # Now, add the cross-connects + my $cx_name = "L3-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for cx types + }#for each port + }##one_cx_per_port = 0 +}#addCrossConnects +sub initToDefaults { + # Clean up database if stuff exists + if ($DEBUG) { + print "\nsub initToDefaults\n"; + } + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + +my $lsb1 = sprintf("%d", $mac1); +my $lsb2 = sprintf("%d", $mac2); +my $lsb3 = sprintf("%d", $mac3); + +# Return a unique MAC address using last 3 octets +sub getNextMac { + $lsb1++; + if ($lsb1 > 255) { + $lsb2++; + $lsb1 = 0; + if ($lsb2 > 255) { + $lsb3++; + $lsb2 = 0; + if ($lsb3 > 255) { + print "*** WARNING, MAC address rolling over XX:YY:ZZ:ff:ff:ff ***\n"; + $lsb3 = 0; + } + } + } + $mac1 = sprintf("%02x", $lsb1); + $mac2 = sprintf("%02x", $lsb2); + $mac3 = sprintf("%02x", $lsb3); + return "$mac3:$mac2:$mac1"; +} # getNextMac + +sub addMacVlans { + if ($DEBUG) { + print "\nsub addMacVlans\n"; + } + if ($mac_init == 1 ) { + $lsb1 = sprintf("%d", $mac1); + $lsb2 = sprintf("%d", $mac2); + $lsb3 = sprintf("%d", $mac3); + } + my $i; + my $q; + my $pnum1; + my $pnum2; + my $throttle = $script_speed; + my $since_throttle = 0; + for ($i = $start_mvlan; $i<($num_mvlans + $start_mvlan); $i++) { + for ($q = 0; $q<@lf1_ports; $q++) { + + $pnum1 = $lf1_ports[$q]; + my $shlf = sprintf("%02x", $shelf); + my $card = sprintf("%02x", $lf1); + my $mac_index = getNextMac(); + my $mac_addr = "00:$shlf:$card:$mac_index"; + doCmd("add_mvlan $shelf $lf1 $pnum1 $mac_addr $i"); + + $pnum2 = $lf2_ports[$q]; + if ($pnum2 ne "") { + $card = sprintf("%02x", $lf2); + $mac_index = getNextMac(); + $mac_addr = "00:$shlf:$card:$mac_index"; + doCmd("add_mvlan $shelf $lf2 $pnum2 $mac_addr $i"); + } + if ($DEBUG > 1) { sleep($D_PAUSE); } + + # Throttle ourself so we don't over-run the poor LANforge system. + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + if ($pnum2 ne "") { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + } + $since_throttle = 0; + } + } + } + + doCmd("probe_ports"); + + # Wait until we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($pnum2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = $start_mvlan; $i<($num_mvlans + $start_mvlan); $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($pnum2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($pnum2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } +}#addMacVlans + +# Wait until the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2orig ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + if ($lf2orig ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + if ($tmp ne "0") { + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + if ($lf2orig ne "") { + if ($tmp2 ne "0") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + } + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i; + +# TODO: This loop needs to only loop for the number of "real"/necessary ports +# because lf1_ports is faked by num_cxs when num_mvlans is 0 and slows the +# script down. + for ($i = 0; $i<@lf1_ports; $i++) { +# for ($i = 0; $i<(@lf1orig_ports + @lf2orig_ports); $i++) { + +# if ($ip_lsb[$i] > 250) { +# $ip_c[$i]++; +# $ip_lsb[$i] = 2; +# } + +# TODO: The whole assignment of IPs to physical ports need to be encapsulated in +# a loop, much like the MACVLANs... + + my $ptmp = $lf1_ports[$i]; + my $ptmp2 = $lf2_ports[$i]; + my $cmd = ""; + if (!$ignore_phys_ports) { +# $cmd = "set_port $shelf $lf1 $ptmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"; + +# $cmd = "set_port $shelf $lf1 $ptmp " . +# "$ip_base[$i].$ip_c[$i].$ip_lsb[$i] $msk[$i] " . +# "$ip_gw[$i] NA NA NA"; + + if ($ptmp ne "") { +# doCmd($cmd); + } + if ($ptmp2 ne "") { +# $cmd = "set_port $shelf $lf2 $ptmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"; + +# $cmd = "set_port $shelf $lf2 $ptmp2 " . +# "$ip_base[$i+1].$ip_c[$i+1].$ip_lsb[$i+1] $msk[$i+1] " . +# "$ip_gw[$i+1] NA NA NA"; +# doCmd($cmd); + } + } + +# END TODO + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $ptmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = $script_speed; + my $since_throttle = 0; + + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + $cmd = "set_port $shelf $lf1 $pname\#$q " . + "$ip_base[$i].$ip_c[$i].$ip_lsb[$i] $msk[$i] " . + "$ip_gw[$i] NA NA NA"; + doCmd($cmd); + $ip_lsb[$i]++; + + if ($ip_lsb[$i] > 250) { + $ip_c[$i]++; + $ip_lsb[$i] = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$q"); + $since_throttle = 0; + } + } + + if ($ptmp2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $ptmp2); + $pname = $p1->{dev}; + + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + if (@ip_base == 1) { + $cmd = "set_port $shelf $lf2 $pname\#$q " . + "$ip_base[$i].$ip_c[$i].$ip_lsb[$i] $msk[$i] " . + "$ip_gw[$i] NA NA NA"; + doCmd($cmd); + $ip_lsb[$i]++; + + if ($ip_lsb[$i] > 250) { + $ip_c[$i]++; + $ip_lsb[$i] = 2; + } + } + else { + $cmd = "set_port $shelf $lf2 $pname\#$q " . + "$ip_base[$i+1].$ip_c[$i+1].$ip_lsb[$i+1] $msk[$i+1] " . + "$ip_gw[$i+1] NA NA NA"; + doCmd($cmd); + $ip_lsb[$i+1]++; + + if ($ip_lsb[$i+1] > 250) { + $ip_c[$i+1]++; + $ip_lsb[$i+1] = 2; + } + } + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pname\#$q"); + $since_throttle = 0; + } + } # for $q + } # if we have an lf2_ports defined + } +} + +sub doCmd { + my $cmd = shift; + + if ($cmd) { + print ">>> $cmd\n"; + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + } else { + print "\n***** doCmd (): NULL COMMAND !!! *****"; + print "\n$cmd\n\n"; + exit (1); + } + #sleep(1); +} + +sub getDate { + my $date = `date`; + chomp($date); + return $date +} + +sub printArgs { + print + "\n$script_name" + . "\nModified arguments:" + . "\ninit: $INIT" + . "\nmanager: $lfmgr_host\n" + . "\nlf1: $lf1\nlf2: $lf2\n" + . "\nlf1_ports: " . join(" ", @lf1_ports) + . "\nlf2_ports: " . join(" ", @lf2_ports) . "\n" + . "\nstart_macvlans: $start_mvlan" + . "\nnum_mvlans: $num_mvlans" + . "\nmvl: $mvl\n" + . "\nmin_rates: " . join(" ", @min_rate) + . "\nmax_rates: " . join(" ", @max_rate) + . "\nmin_pkt_sizes: " . join(" ", @min_pkt_szs) + . "\nmax_pkt_sizes: " . join(" ", @max_pkt_szs) . "\n" + . "\ncx_types: " . join(" ", @cx_types) + . "\none_cx_per_port: $one_cx_per_port\n\n" + . "\n" + . "Available CX types: " . join(", ", @cxts) . "\n" + . "Total of each CX type: " . join(", ", @t_cxts) . "\n" + . "Total number of ports: $t_ports\n" + . "Total number of urls: " . scalar(@l4_urls) . "\n" + . "Total number of endpoints and CXs: $t_num\n" + . "\n\n"; +} + +sub printMark { + print + "\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*" + ."\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n*\n"; +} + + +sub printHelp { + print + "\n$script_name\n" + . "USAGE: mgr=[ip-of-mgr] speed=[25|n] wait=[0|n] DEBUG=[0|1|2|...] D_PAUSE=[3|n]\n" + . " config_once=[0|1] init=[0|1] init_net=[1|0] init_tests=[1|0]\n" + . " test_mgr=\"ben_tm\" first_run=[1|0]\n" + . " first_name_id=[0|n] id_len=[0|n]\n" + . " create_only=[0|1] one_cx_per_port=[0|1] ignore_phy_ports=[1|0]\n" + . " lf1=[1|n] lf2=[none|n=!lf1]\n" + . " lf1_ports=[\"1 2 3\"|\"eth2 eth3\"] lf2_ports=[\"4 5 6\"|\"eth4 eth5\"]\n" + . " start_mvl=[0|n] num_mvl=[9|0]\n" + . " if (num_mvl=0) num_cxs_per_port=[10|n]\n" + . " mac3=0xf0 mac2=0xbe mac1=0xef\n" + . " ip_base= \"192.168 172.16\"\n" + . " ip_c = \"2 1\"\n" + . " ip_lsb = \"2 2\"\n" + . " ip_msk =\"255.255.0.0 255.255.0.0\"\n" + . " ip_gw =\"192.168.2.1 172.16.1.1\"\n" + . " cx_types=\"lf lf_udp lf_tcp custom_udp custom_tcp l4 fileIONFS fileIOCIFS\"\n" + . " min_rates=\"9600 56000 128000\" max_rates=\"56000 128000 25600\"\n" + . " min_pkt_sizes=\"500 500 500\" max_pkt_sizes=\"1000 1000 1000\"\n" + . " url_rate=100 l4_wait=10000\n" + . " urls=\"http://www.candelatech.com/file ftp://www.candelatech.com/file https://www.candelatech.com/file\"\n" + . " fsrw=[read|write] fio_targ_dir=tmp/ fio_base=/mnt/fio_base\n" + . "\n"; + +} + +sub handleCmdLineArg { + my $arg = $_[0]; + my $val = $_[1]; + + if ($arg eq "help" || $arg eq "--help" || $arg eq "-h" || $arg eq "-help" || $arg eq "-h" ) { + printHelp(); + exit(0); + } + elsif ($arg eq "debug" || $arg eq "DEBUG") { + $DEBUG = $val; + } + elsif ($arg eq "d_pause" || $arg eq "D_PAUSE") { + $D_PAUSE = $val; + } + elsif ($arg eq "mgr") { + $lfmgr_host = $val; + } + elsif ($arg eq "test_mgr") { + $test_mgr = $val; + } + elsif ($arg eq "init") { + $INIT = $val; + } + elsif ($arg eq "config_once") { + $init_once = $val; + } + elsif ($arg eq "init_net") { + $init_net = $val; + } + elsif ($arg eq "init_tests") { + $init_tests = $val; + } + elsif ($arg eq "first_run") { + $first_run = $val; + } + elsif ($arg eq "first_name_id") { + $name_id = $val; + } + elsif ($arg eq "id_len") { + $name_id_len = $val; + if (length($name_id) > $name_id_len) { + print "\nWARNING: id_len specifies a string length less that first_name_id.\n"; + } + } + elsif ($arg eq "speed") { + $script_speed = $val; + } + elsif ($arg eq "wait") { + $pause = $val; + } + elsif ($arg eq "lf1") { + $lf1 = $val; + } + elsif ($arg eq "lf2") { + $lf2 = $val; + if ($lf1 == $lf2) { + die("\nINVALID: First and second resource are the same !!!\n\n"); + } + } + elsif ($arg eq "mac3") { + $mac3 = $val; + } + elsif ($arg eq "mac2") { + $mac2 = $val; + } + elsif ($arg eq "mac1") { + $mac1 = $val; + } + elsif ($arg eq "ip_base") { + @ip_base = undef; + @ip_base = split(/ /, $val); + } + elsif ($arg eq "ip_c") { + @ip_c = undef; + @ip_c = split(/ /, $val); + } + elsif ($arg eq "ip_lsb") { + @ip_lsb = undef; + @ip_lsb = split(/ /, $val); + } + elsif ($arg eq "ip_msk") { + @msk = undef; + @msk = split(/ /, $val); + } + elsif ($arg eq "ip_gw") { + @ip_gw = undef; + @ip_gw = split(/ /, $val); + } + elsif ($arg eq "lf1_ports") { + @lf1_ports = split(/ /, $val); + } + elsif ($arg eq "lf2_ports") { + if ($lf2 == "" || $lf1 == $lf2) { + die("\nINVALID: Either second resource is not defined\nor first and second resource are the same !!!\n\n"); + } + else { + @lf2_ports = split(/ /, $val); + } + } + elsif ($arg eq "cx_types") { + @cx_types = split(/ /, $val); + } + elsif ($arg eq "min_pkt_sizes") { + @min_pkt_szs = split(/ /, $val); + } + elsif ($arg eq "max_pkt_sizes") { + @max_pkt_szs = split(/ /, $val); + } + elsif ($arg eq "start_mvl") { + $start_mvlan = $val; + } + elsif ($arg eq "num_mvl") { + $num_mvlans = $val; + } + elsif ($arg eq "num_cxs_per_port") { + $num_cxs = $val; + } + elsif ($arg eq "min_rates") { + @min_rate = split(/ /, $val); + } + elsif ($arg eq "max_rates") { + @max_rate = split(/ /, $val); + } + elsif ($arg eq "fsrw") { + $fsrw = $val; + } + elsif ($arg eq "fio_base") { + $fio_base = $val; + } + elsif ($arg eq "fio_targ_dir") { + $fio_targ_dir = $val; + } + elsif ($arg eq "urls") { + @l4_urls = split(/ /, $val); + } + elsif ($arg eq "url_rate") { + $urls_10m = $val; + } + elsif ($arg eq "l4_wait") { + $l4_timeout = $val; + } + elsif ($arg eq "one_cx_per_port") { + $one_cx_per_port = $val; + } + elsif ($arg eq "ignore_phy_ports") { + $ignore_phys_ports = $val; + } + elsif ($arg eq "create_only") { + $create_only = $val; + } + else { + print "\n\nCould not parse one or more of the arguments !!!\n" + . "First rejected argument: $arg\n"; + printHelp(); + exit(1); + } +} diff --git a/lanforge/lanforge-scripts/lf_macvlan2.pl b/lanforge/lanforge-scripts/lf_macvlan2.pl new file mode 100755 index 000000000..64f62e611 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_macvlan2.pl @@ -0,0 +1,654 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, and custom_tcp +# across 1 real port and manny macvlan ports on 2 machines. +# It then continously starts and stops the connections. + +# Un-buffer output +$| = 1; + +use strict; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; + +#my $lfmgr_host = "localhost"; +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf = 1; + +# This sets up connections between 2 LANforge machines +my $lf1 = 15; + +my $lf2 = 15; # We also have a second machine to create mac-vlans on. +#my $lf2 = ""; # Set to "" if we have no second machine, can only do l4 + # endpoints in this case. + +# Port pairs. These are the ports that should be talking to each other. +# Ie, the third column in lf1_ports talks to the third column in lf2_ports. +my @lf1_ports = (1); #, 2, 3); +my @lf2_ports = (2); #, 2, 3); + +my $ip_base = "172.2"; +my $ip_lsb = 2; +my $ip_c = 2; +my $msk = "255.255.0.0"; + +my $num_macvlans = 500; + +# If zero, will have one of EACH of the cx types on each port. +#my $one_cx_per_port = 1; +my $one_cx_per_port = 1; + +#my @cx_types = ("lf", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4"); +#my @min_pkt_szs = (64, 1, 1, 1, 1, 0); +#my @max_pkt_szs = (1514, 12000, 13000, 2048, 2048, 0); + +my @cx_types = ("lf_tcp", "lf_tcp", "lf_tcp", "lf_tcp", "lf_tcp", + "lf_tcp", "lf_tcp", "lf_tcp", "lf_tcp", "lf_tcp", + "lf_udp", "lf_tcp", "lf_udp", "lf_udp", "lf_tcp", + "lf_tcp", "lf_tcp"); +my @min_pkt_szs = (10000, 10000, 10000, 10000, 6000, 6000, + 10000, 10000, 10000, 10000, 6000, 6000, + 1472, 1472, 1472, 1472, 8000, + 400, 800); +my @max_pkt_szs = (16000, 16000, 16000, 16000, 6600, 6600, + 15555, 16000, 16000, 16000, 6000, 6600, + 1472, 1472, 1472, 1472, 27000, + 4000, 8071); + +# Layer-4 only +#my @cx_types = ("l4", "l4"); +#my @min_pkt_szs = (0, 0); +#my @max_pkt_szs = (0, 0); + +# URL will be acted on from machine $lf1 +my $l4_url = "http://172.1.5.75"; + +my $min_rate = 64000; +#my $max_rate = 512000; +my $max_rate = 64000; + +my $test_mgr = "ben_tm"; + +my $loop_max = 100; +my $start_stop_iterations = 100; +my $run_for_time = 120; # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Run for XX seconds..then will be stopped again +my $report_timer = 8000; # 8 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + +my $timeout = 60; + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => $timeout); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + initToDefaults(); + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + addMacVlans(); + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + my $rl = 0; + for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + + }# For some amount of start_stop iterations... +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub addMacVlans { + my $i; + my $q; + + my $v; + my $lsb = 10; + my $lsb2 = 10; + + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $pnum1 = $lf1_ports[$q]; + my $pnum2 = $lf2_ports[$q]; + for ($i = 0; $i<$num_macvlans; $i++) { + + $lsb++; + if ($lsb > 99) { + $lsb2++; + $lsb = 2; + } + + my $s2 = $shelf+10; + my $c2 = $lf1+10; + my $p2 = $pnum1+10; + my $mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + doCmd("add_mvlan $shelf $lf1 $pnum1 $mc"); + + if ($lf2 ne "") { + $c2 = $lf2+10; + $p2 = $pnum2+10; + $mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + doCmd("add_mvlan $shelf $lf2 $pnum2 $mc"); + + # Throttle ourself so we don't over-run the poor LANforge system. + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + $since_throttle = 0; + } + } + } + } + + doCmd("probe_ports"); + + # Wait untill we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = 0; $i<$num_macvlans; $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($lf2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } + + +}#addMacVlans + + +# Wait untill the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + my $wait_for_phantom = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + if ($ports[$i]->isPhantom()) { + # Wait a bit..hopefully it will go away. + if ($wait_for_phantom++ < 20) { + print "Sleeping a bit, found a phantom port."; + sleep(5); + doCmd("probe_ports"); + $found_one = 1; + } + } + else { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + } + } + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = "set_port $shelf $lf1 $tmp $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($lf2 ne "") { + $cmd = "set_port $shelf $lf2 $tmp2 $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + } + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $tmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf1 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$q"); + $since_throttle = 0; + } + + } + + $ip_lsb++; + + if ($lf2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $tmp2); + $pname = $p1->{dev}; + + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf2 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pname\#$q"); + $since_throttle = 0; + } + } + }# If we have an LF-2 defined. + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + + + my @all_ports1 = @lf1_ports; + my $j; + my $pname; + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + + my @all_ports2 = @lf2_ports; + if ($lf2 ne "") { + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + } + } + + print "About to start endpoints, all_ports1:\n" . join(" ", @all_ports1) . + "\nall_ports2: " . join(" ", @all_ports2) . "\n\n"; + + if ($one_cx_per_port) { + my $j = 0; + my $cxcnt = 0; + for ($j ; $j<@all_ports1; $j++) { + my $i = $cxcnt % @cx_types; + $cxcnt++; + + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "D_endp-${ep}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . + "dl $l4_url /tmp/$ep1' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = sprintf "cx-%04d", $cx; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = sprintf "cx-%04d", $cx; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for all ports + }#one_cx_per_port + else { + my $j = 0; + for ($j ; $j<@all_ports1; $j++) { + for ($i = 0; $i<@cx_types; $i++) { + my $cxt = $cx_types[$i]; + + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "D_endp-${ep}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . + "dl $l4_url /tmp/$ep1' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = sprintf "cx-%04d", $cx; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = sprintf "cx-%04d", $cx; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for cx types + }#for each port + }# each cx per port + +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor(Match => '/ \>\>RSLT:(.*)/', + Timeout => $timeout); + + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_macvlan3.pl b/lanforge/lanforge-scripts/lf_macvlan3.pl new file mode 100755 index 000000000..d53b9ef00 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_macvlan3.pl @@ -0,0 +1,630 @@ +#!/usr/bin/perl + +# This program is used to test the max TCP connections allowed through a firewall, +# and may be used as an example for others who wish to automate LANforge tests. + +# This script sets up 1 UDP connection and as many TCP connections as specified +# by $num_macvlans. Each connection is started and verified that it is passing +# traffic before starting the next connection. As each TCP connection is started +# the UDP connection is checked for any dropped packets. As soon as dropped packets +# are detected on the UDP connection, the number of TCP connections is recorded +# and the entire test is repeated for $loop_max times. An average number of TCP +# connections is calculated and reported at the conclusion of all the test runs. + +# Un-buffer output +$| = 1; + +use strict; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; +use LANforge::Endpoint; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf = 1; + +my $script_speed = 25; # Increase to issue commands faster. + +# The LANforge resources +my $lf1 = 1; # Minor Resource EID. +my $lf2 = ""; # Set to "" if we have no second machine. Or set to second Resource + # minor EID to create mac-vlans on it. + +# Port pairs. These are the ports that should be talking to each other. +# i.e. the third column in lf1_ports talks to the third column in lf2_ports. +# EIDs or aliases can be used. +# Port pairs must match on each shelf - will enhance to allow any pair on each shelf. +#my @lf1_ports = (1); #, 2, 3); +#my @lf2_ports = (2); #, 2, 3); +my @lf1_ports = ("eth0"); #, "eth2"); +my @lf2_ports = ("eth1"); #, "eth3"); + +my $mac1 = 0x00; # Starting MAC address 00:m5:m4:m3:m2:m1 where: +my $mac2 = 0x00; # m5 is shelf EID, m4 is card EID, m3 is $mac3, +my $mac3 = 0x00; # m2 is $mac2 and m1 is $mac1. + +my $ip_base1 = "192.168"; # +my $ip_c1 = 2; # +my $ip_lsb1 = 2; # +my $msk1 = "255.255.255.0"; # +my $ip_gw1 = "192.168.2.1"; # + +my $ip_base2 = "172.1"; # +my $ip_c2 = 1; # +my $ip_lsb2 = 2; # +my $msk2 = "255.255.255.0"; # +my $ip_gw2 = "172.1.1.1"; # + + +my $num_macvlans = 200; # Number of mac vlans per port, or the number of connections +my $pause_min = 3; # Depends on $num_macvlans and how well your LANforge system runs + +my $one_cx_per_port = 1;# If zero, will have one of EACH of the cx types on each port. + +#my @cx_types = ("lf", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4"); +#my @min_pkt_szs = (64, 1, 1, 1, 1, 0); +#my @max_pkt_szs = (1514, 12000, 13000, 2048, 2048, 0); + +my @cx_types = ("lf_tcp"); +my @min_pkt_szs = (1472); +my @max_pkt_szs = (1472); + +my $min_rate = 9600; +my $max_rate = 9600; + +my $test_mgr = "mac_tm"; + +my $mac_init = 0; # Set to 1 to initialize IPs when running looped test. +my $ip_init = 0; # Set to 1 to initialize IPs when running looped test. +my $loop_max = 3; # Number of times the test will be run before calculating average TCP connections +my $report_timer = 1000; # 1 second, must be set higher when using > 500 mac vlans +my $cxcnt = 0; +my $avg_cxcnt = 0; + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Timeout => 15, + Prompt => '/default\@btbits\>\>/'); + +my $timeout = 60; + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => $timeout); + +$t->waitfor("/btbits\>\>/"); +$t->max_buffer_length(1024 * 1024 * 10); # 10M buffer + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + +if ($lf2 == "") { $lf2 = $lf1; } +my $start_mvlan = 0; +my $num_mvlans = $num_macvlans; + + +my $i_c1 = $ip_c1; +my $i_lsb1 = $ip_lsb1; +my $i_c2 = $ip_c2; +my $i_lsb2 = $ip_lsb2; + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + @endpoint_names = (); + @cx_names = (); + $cxcnt = 0; + + initToDefaults(); + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + addMacVlans(); + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + print "Done adding CXs.\n"; + print "Pause $pause_min minutes for ports to update.\n"; + for (my $n=1; $n<=$pause_min; $n++) { + print "$n of $pause_min\n"; + sleep(60); + } + + # Start Cross-Connects + my $p = 0; + for (my $q=0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + $p = $q+$q; + + # check that the CX is passing packets + my $endp = new LANforge::Endpoint(); + $utils->updateEndpoint($endp, $endpoint_names[$p]); + my $en = $endp->rx_pkts(); + + my $slp=0; + while ($en == 0) { + # sleep to allow CX to connect + sleep(1); + $slp++; + $utils->updateEndpoint($endp, $endpoint_names[$p]); + $en = $endp->rx_pkts(); + if ($slp > 14) { + # too long + print "WARNING: Waited too long on endp $q\n"; + last; + } + } + + # check UDP CX for dropped packets + my $endp1 = new LANforge::Endpoint(); + $utils->updateEndpoint($endp1, $endpoint_names[0]); + my $en1 = $endp1->rx_dropped_pkts(); + + my $endp2 = new LANforge::Endpoint(); + $utils->updateEndpoint($endp2, $endpoint_names[1]); + my $en2 = $endp2->rx_dropped_pkts(); + + if ($en1 != 0 || $en2 != 0) { # If there are ANY dropped packets on UDP CX + $avg_cxcnt = $avg_cxcnt + $cxcnt; # Average calculated later + last; + } + elsif ($q > 0) { + # Successfully added TCP CX, count it + $cxcnt++; + } + } #for $q +} #for $loop_max + +if ($avg_cxcnt == 0) { + print "$cxcnt TCP connections were made.\n"; + print "No dropped packets were detected on the UDP connection.\n"; + print "Try increasing the number of connections.\n"; +} +else { + $avg_cxcnt = int($avg_cxcnt / $loop_max); + print "$loop_max test loops completed.\n"; + print "Average number of simultaneous TCP connections: $avg_cxcnt\n"; +} + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); + +}#initToDefaults + +my $lsb1 = sprintf("%d", $mac1); +my $lsb2 = sprintf("%d", $mac2); +my $lsb3 = sprintf("%d", $mac3); + +# Return a unique MAC address using last 3 octets +sub getNextMac { + $lsb1++; + if ($lsb1 > 255) { + $lsb2++; + $lsb1 = 0; + if ($lsb2 > 255) { + $lsb3++; + $lsb2 = 0; + if ($lsb3 > 255) { + print "*** WARNING, MAC address rolling over XX:YY:ZZ:ff:ff:ff ***\n"; + $lsb3 = 0; + } + } + } + $mac1 = sprintf("%02x", $lsb1); + $mac2 = sprintf("%02x", $lsb2); + $mac3 = sprintf("%02x", $lsb3); + return "$mac3:$mac2:$mac1"; +} # getNextMac + +sub addMacVlans { + if ($mac_init == 1 ) { + $lsb1 = sprintf("%d", $mac1); + $lsb2 = sprintf("%d", $mac2); + $lsb3 = sprintf("%d", $mac3); + } + my $i; + my $q; + my $v; + my $throttle = $script_speed; + my $since_throttle = 0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $pnum1 = $lf1_ports[$q]; + my $pnum2 = $lf2_ports[$q]; + for ($i = $start_mvlan; $i<($num_mvlans + $start_mvlan); $i++) { + + my $shlf = sprintf("%02x", $shelf); + my $card = sprintf("%02x", $lf1); + my $mac_index = getNextMac(); + my $mac_addr = "00:$shlf:$card:$mac_index"; + doCmd("add_mvlan $shelf $lf1 $pnum1 $mac_addr $i"); + + if ($lf2 ne "") { + $card = sprintf("%02x", $lf2); + $mac_index = getNextMac(); + $mac_addr = "00:$shlf:$card:$mac_index"; + doCmd("add_mvlan $shelf $lf2 $pnum2 $mac_addr $i"); + } + + # Throttle ourself so we don't over-run the poor LANforge system. + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + if ($lf2 ne "") { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + } + $since_throttle = 0; + } + } + } + + doCmd("probe_ports"); + + # Wait untill we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = 0; $i<$num_macvlans; $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($lf2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } + + +}#addMacVlans + + +# Wait untill the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for card: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + doCmd($ports[$i]->getDeleteCmd()); + } #fi isMacVlan + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + # init base IP or keep rolling since rolling might stress caching in DUT/NUT. + if ($ip_init = 1) { + $i_c1 = $ip_c1; + $i_lsb1 = $ip_lsb1; + $i_c2 = $ip_c2; + $i_lsb2 = $ip_lsb2; + } + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = ""; + $cmd = "set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"; + doCmd($cmd); + + if ($lf2 ne "") { + $cmd = "set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"; + doCmd($cmd); + } + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $tmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = $script_speed; + my $since_throttle = 0; + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf1 $pname\#$q $ip_base1.$i_c1.$i_lsb1 $msk1 " . + "$ip_gw1 NA NA NA"; + doCmd($cmd); + $i_lsb1++; + + if ($i_lsb1 > 250) { + $i_c1++; + $i_lsb1 = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$q"); + $since_throttle = 0; + } + } + + if ($lf2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $tmp2); + $pname = $p1->{dev}; + + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf2 $pname\#$q $ip_base2.$i_c2.$i_lsb2 $msk2 " . + "$ip_gw2 NA NA NA"; + doCmd($cmd); + $i_lsb2++; + + if ($i_lsb2 > 250) { + $i_c2++; + $i_lsb2 = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pname\#$q"); + $since_throttle = 0; + } + } + }# If we have an LF-2 defined. + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 1; + my $i = 0; + + + my @all_ports1 = @lf1_ports; + my $j; + my $pname; + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + + my @all_ports2 = @lf2_ports; + if ($lf2 ne "") { + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + } + } + + print "About to start endpoints, all_ports1:\n" . join(" ", @all_ports1) . + "\nall_ports2: " . join(" ", @all_ports2) . "\n\n"; + + if ($one_cx_per_port) { + my $j = 1; + my $cxs = 0; + for ($j ; $j<@all_ports1; $j++) { + my $i = $cxs % @cx_types; + $cxs++; + if ($j == 1) { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + # Create UDP endpoints + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the UDP endpoints + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " lf_udp " . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + if ($lf2 == "") { + die("Must have lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " lf_udp " . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = sprintf "cx-%04d", $cx; + $cmd = "add_cx $cx_name $test_mgr $ep2 $ep1"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = sprintf "cx-%04d", $cx; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for all ports + }#one_cx_per_port +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor(Match => '/ \>\>RSLT:(.*)/', + Timeout => $timeout); + + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_macvlan_l4.pl b/lanforge/lanforge-scripts/lf_macvlan_l4.pl new file mode 100755 index 000000000..59c16a5b6 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_macvlan_l4.pl @@ -0,0 +1,813 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, and custom_tcp +# across 1 real port and manny macvlan ports on 2 machines. +# It then continously starts and stops the connections. + +# Un-buffer output +$| = 1; + +use strict; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf = 1; + +# Set up connections between 2 LANforge machines +# ============================================== +my $INIT = 1; # If true, removes all previous tests!!! + +my $lf1 = 1; +#my $lf2 = 4; # We also have a second machine to create mac-vlans on. +my $lf2 = ""; # Set to "" if we have no second machine, can only do l4 + # endpoints in this case. + +# Port pairs. These are the ports that should be talking to each other. +# Ie, the third column in lf1_ports talks to the third column in lf2_ports. +my @lf1_ports = (0); #, 2, 3); +my @lf2_ports = (0); #, 2, 3); + +my $ip_base = "172.29"; +my $ip_lsb = 110; +my $ip_c = 3; +my $msk = "255.255.0.0"; + +my $use_mac_vlans = 1; # set to 1 for MAC-VLANS. Will use 8021q otherwise. +my $num_vlans = 100; +my $starting_vid = 1000; + +# If zero, will have one of EACH of the cx types on each port. +#my $one_cx_per_port = 1; +my $one_cx_per_port = 0; + +#my @cx_types = ("lf", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4"); +#my @min_pkt_szs = (64, 1, 1, 1, 1, 0); +#my @max_pkt_szs = (1514, 12000, 13000, 2048, 2048, 0); + +#my @cx_types = ("lf_tcp"); +#my @min_pkt_szs = (1); +#my @max_pkt_szs = (13000); + +# Layer-4 only +#my @cx_types = ("l4", "l4", "l4"); #, "l4", "l4"); +#my @min_pkt_szs = (0, 0, 0); #, 0, 0); +#my @max_pkt_szs = (0, 0, 0); #, 0, 0); +my @cx_types = ("l4"); #, "l4", "l4"); +my @min_pkt_szs = (0); #, 0, 0); +my @max_pkt_szs = (0); #, 0, 0); + +# URL will be acted on from machine $lf1 +#my $l4_url = "http://192.168.3.148/index.html"; +my $l4_url = "http://www.candelatech.com/oss/pktgen.c"; +my $save_to_dev_null = 1; # Set to zero if you want to save http files to /tmp/$ep1 + +my $min_rate = 64000; +#my $max_rate = 512000; +my $max_rate = 64000; +my $url_per_10m = 600; # 600 is 1 request per second +my $test_mgr = "ben_tm"; + +my $loop_max = 100000; +my $start_stop_iterations = 1; +my $run_for_time = 2000; # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Run for XX seconds..then will be stopped again +my $report_timer = 8000; # 8 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## +my @num = (); #make sorting by name easier :P +my $total_mvlans = (($num_macvlans+1)*2); +my $num_len = length ($total_mvlans); +use Switch; +my $i = 0; + + switch ($num_len) { + case 1 { + for ($i=0;$i<$total_mvlans;$i++) { + $num[$i] = sprintf("%01d", $i); + } + } + case 2 { + for ($i=0;$i<$total_mvlans;$i++) { + $num[$i] = sprintf("%02d", $i); + } + } + case 3 { + for ($i=0;$i<$total_mvlans;$i++) { + $num[$i] = sprintf("%03d", $i); + } + } + case 4 { + for ($i=0;$i<$total_mvlans;$i++) { + $num[$i] = sprintf("%04d", $i); + } + } + else { print '***** Error Invalid Number of MAC VLANS i.e. >10,000 !!!!'; } + } +#my $junk=0; +# for ($junk=0;$junk<$total_mvlans;$junk++) { +# printf "$num[$junk],"; +# } +#printf "\n"; + +#exit(0); + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Timeout => 45, + Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 45); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + @endpoint_names = (); + @cx_names = (); + + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + if ($INIT) { + initToDefaults(); + } + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + if ($use_mac_vlans) { + addMacVlans(); + } + else { + add8021qVlans(); + } + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + my $rl = 0; + #for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + for ($rl = 0; $rl<$loop; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Drop the ports.. (Testing kernel bug fix. ) + if ($loop % 2 == 0) { + clearVlanPorts($shelf, $lf1); + } + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + + }# For some amount of start_stop iterations... +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub addMacVlans { + my $i; + my $q; + + my $v; + my $lsb = 10; + my $lsb2 = 10; + + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $pnum1 = $lf1_ports[$q]; + my $pnum2 = $lf2_ports[$q]; + for ($i = 0; $i<$num_vlans; $i++) { + + $lsb++; + if ($lsb > 99) { + $lsb2++; + $lsb = 2; + } + + my $s2 = $shelf+10; + my $c2 = $lf1+10; + my $p2 = $pnum1+10; + my $mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + doCmd("add_mvlan $shelf $lf1 $pnum1 $mc"); + + if ($lf2 ne "") { + $c2 = $lf2+10; + $p2 = $pnum2+10; + $mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + doCmd("add_mvlan $shelf $lf2 $pnum2 $mc"); + + # Throttle ourself so we don't over-run the poor LANforge system. + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + $since_throttle = 0; + } + } + } + } + + doCmd("probe_ports"); + + # Wait untill we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = 0; $i<$num_vlans; $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($lf2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } + + +}#addMacVlans + + +sub add8021qVlans { + my $i; + my $q; + + my $v; + my $lsb = 10; + my $lsb2 = 10; + + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $pnum1 = $lf1_ports[$q]; + my $pnum2 = $lf2_ports[$q]; + for ($i = 0; $i<$num_vlans; $i++) { + + $lsb++; + if ($lsb > 99) { + $lsb2++; + $lsb = 2; + } + my $vid = $starting_vid + $i; + doCmd("add_vlan $shelf $lf1 $pnum1 $vid"); + + if ($lf2 ne "") { + $vid = $starting_vid + $num_vlans + $i; + doCmd("add_vlan $shelf $lf2 $pnum2 $vid"); + + # Throttle ourself so we don't over-run the poor LANforge system. + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + $since_throttle = 0; + } + } + } + } + + doCmd("probe_ports"); + + # Wait untill we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = 0; $i<$num_vlans; $i++) { + while (1) { + my $vid = $starting_vid + $i; + $utils->updatePort($p1, $shelf, $lf1, "$pname\.$vid"); + if ($lf2 ne "") { + $vid = $starting_vid + $num_vlans + $i; + $utils->updatePort($p2, $shelf, $lf2, "$pname2\.$vid"); + } + if ($p1->isPhantom() || (($lf2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } +}#add8021qVlans + + +# Wait untill the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } +} + +sub clearVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + my $wait_for_phantom = 0; + for ($i = 0; $i<$mx; $i++) { + my $tst = $ports[$i]->is8021qVlan(); + if ($use_mac_vlans) { + $tst = $ports[$i]->isMacVlan(); + } + if ($tst) { + if ($ports[$i]->isPhantom()) { + # Wait a bit..hopefully it will go away. + if ($wait_for_phantom++ < 20) { + print "Sleeping a bit, found a phantom port."; + sleep(5); + doCmd("probe_ports"); + $found_one = 1; + } + } + else { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + } + } + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = "set_port $shelf $lf1 $tmp $ip_base.$ip_c.$ip_lsb $msk " . + "172.29.0.5 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($lf2 ne "") { + $cmd = "set_port $shelf $lf2 $tmp2 $ip_base.$ip_c.$ip_lsb $msk " . + "172.29.0.5 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + } + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $tmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<$num_vlans; $q++) { + my $vid = $starting_vid + $q; + my $pnm = "$pname\.$vid"; + if ($use_mac_vlans) { + $pnm = "$pname\#$q"; + } + $cmd = "set_port $shelf $lf1 $pnm $ip_base.$ip_c.$ip_lsb $msk " . + "172.29.0.5 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pnm"); + $since_throttle = 0; + } + + } + + $ip_lsb++; + + if ($lf2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $tmp2); + $pname = $p1->{dev}; + + for ($q = 0; $q<$num_vlans; $q++) { + my $vid = $starting_vid + $num_vlans + $q; + my $pnm = "$pname\.$vid"; + if ($use_mac_vlans) { + $pnm = "$pname\#$q"; + } + $cmd = "set_port $shelf $lf2 $pnm $ip_base.$ip_c.$ip_lsb $msk " . + "172.29.0.5 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pnm"); + $since_throttle = 0; + } + } + }# If we have an LF-2 defined. + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + + + my @all_ports1 = @lf1_ports; + my $j; + my $pname; + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_vlans; $q++) { + if ($use_mac_vlans) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + else { + my $vid = $starting_vid + $q; + @all_ports1 = (@all_ports1, "$pname\.$vid"); + } + } + } + + my @all_ports2 = @lf2_ports; + if ($lf2 ne "") { + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_vlans; $q++) { + if ($use_mac_vlans) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + else { + my $vid = $starting_vid + $num_vlans + $q; + @all_ports2 = (@all_ports2, "$pname\.$vid"); + } + } + } + } + + print "About to start endpoints, all_ports1:\n" . join(" ", @all_ports1) . + "\nall_ports2: " . join(" ", @all_ports2) . "\n\n"; + + if ($one_cx_per_port) { + my $j = 0; + my $cxcnt = 0; + for ($j ; $j<@all_ports1; $j++) { + my $i = $cxcnt % @cx_types; + $cxcnt++; + + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "l4-${num[$ep]}-TX"; + $ep++; + my $ep2 = "D_l4-${num[$ep]}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + my $save_file = "/tmp/$ep1"; + if ($save_to_dev_null) { + $save_file = "/dev/null"; + } + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 $url_per_10m '" . + "dl $l4_url $save_file' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "endp-${num[$ep]}-TX"; + $ep++; + my $ep2 = "endp-${num[$ep]}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for all ports + }#one_cx_per_port + else { + my $j = 0; + for ($j ; $j<@all_ports1; $j++) { + for ($i = 0; $i<@cx_types; $i++) { + my $cxt = $cx_types[$i]; + + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "l4-${num[$ep]}-TX"; + $ep++; + my $ep2 = "D_l4-${num[$ep]}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + my $save_file = "/tmp/$ep1"; + if ($save_to_dev_null) { + $save_file = "/dev/null"; + } + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 $url_per_10m '" . + "dl $l4_url $save_file' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "endp-${num[$ep]}-TX"; + $ep++; + my $ep2 = "endp-${num[$ep]}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for cx types + }#for each port + }# each cx per port + +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_macvlan_streams.pl b/lanforge/lanforge-scripts/lf_macvlan_streams.pl new file mode 100755 index 000000000..4033b15eb --- /dev/null +++ b/lanforge/lanforge-scripts/lf_macvlan_streams.pl @@ -0,0 +1,723 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, and custom_tcp +# across 1 real port and manny macvlan ports on 2 machines. +# It then continously starts and stops the connections. + +# Un-buffer output +$| = 1; + +use strict; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; +use LANforge::Endpoint; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf = 1; + +# This sets up connections between 2 LANforge machines +my $lf1 = 1; + +my $lf2 = 2; # We also have a second machine to create mac-vlans on. +#my $lf2 = ""; # Set to "" if we have no second machine, can only do l4 +# # endpoints in this case. + +# Port pairs. These are the ports that should be talking to each other. +# Ie, the third column in lf1_ports talks to the third column in lf2_ports. +my @lf1_ports = (5); #, 2, 3); +my @lf2_ports = (5); #, 2, 3); + +# These are for the IP port, the initial value.... +my $port_nums = 5000; + +my $ip_base = "172.1"; +my $ip_lsb = 2; +my $ip_c = 2; +my $msk = "255.255.0.0"; + +my $num_macvlans = 50; + +# If zero, will have one of EACH of the cx types on each port. +#my $one_cx_per_port = 1; +my $one_cx_per_port = 0; + +my $mn_sz = 1000; +my $mx_sz = 1000; + +# 10 of each, on each port/macvlan With 100 mac-vlans, yields 1000 sessions. +my @cx_types = ("lf_udp", "lf_tcp", "lf_udp", "lf_tcp", "lf_udp", + "lf_tcp", "lf_udp", "lf_tcp", "lf_udp", "lf_tcp" ); +my @min_pkt_szs = ($mn_sz, $mn_sz, $mn_sz, $mn_sz, $mn_sz, + $mn_sz, $mn_sz, $mn_sz, $mn_sz, $mn_sz); +my @max_pkt_szs = ($mx_sz, $mx_sz, $mx_sz, $mx_sz, $mx_sz, + $mx_sz, $mx_sz, $mx_sz, $mx_sz, $mx_sz); + +# Layer-4 only +#my @cx_types = ("l4", "l4"); +#my @min_pkt_szs = (0, 0); +#my @max_pkt_szs = (0, 0); + +# URL will be acted on from machine $lf1 +#my $l4_url = "http://172.1.5.75"; +my $l4_url = "http://172.1.2.3"; + +my $min_rate = 24000; +my $max_rate = 24000; + +my $test_mgr = "ben_tm"; + +my $loop_max = 100; +my $start_stop_iterations = 10000; +my $run_for_time = 0; # Run for XX seconds..then will be stopped again +my $stop_for_time = 1; # Run for XX seconds..then will be stopped again +my $report_timer = 5000; # 5 seconds +my $stop_at_connections = 100000; + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +my $tot_cx_started = 0; + + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 45); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + initToDefaults(); + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + addMacVlans(); + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + my $begin_time = time(); + + my $was_rcv_silent = $utils->cli_rcv_silent(); + my $was_send_silent = $utils->cli_send_silent(); + + $utils->cli_rcv_silent(1); + + my $rl = 0; + for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + + my $stime = time(); + my $slp = 0; + + $utils->cli_send_silent($was_send_silent); + doCmd("set_cx_state $test_mgr all RUNNING", 0, 1); + $utils->cli_send_silent(1); + + #sleep(1); # Give the servers a chance to get started... + + if ($run_for_time == 0) { + # Stop test as soon as all have received a packet. + my $i; + my $endp1 = new LANforge::Endpoint(); + for ($i = 0; $i<@endpoint_names; $i++) { + my $en = $endpoint_names[$i]; + $utils->updateEndpoint($endp1, $en, 1); + while ($endp1->rx_pkts() <= 0) { + if (time() > $stime + 15) { + # Things are not working right, it should never take this long + print "WARNING: Endpoint $en is not receiving packets after $slp seconds.\n"; + exit 0; + } + $utils->updateEndpoint($endp1, $en, 1); + } + } + + # Stop cxs. + $utils->cli_send_silent($was_send_silent); + doCmd("set_cx_state $test_mgr all STOPPED", 0, 1); + $utils->cli_send_silent(1); + + my $elapsed = time() - $stime; + my $tot_elapsed = time() - $begin_time; + + $i = @cx_names; + $tot_cx_started += $i; + print "\nStarted and stopped $i connections this round in $elapsed seconds.\n"; + print "Started and stopped a total of $tot_cx_started in $tot_elapsed seconds.\n "; + print $tot_cx_started / $tot_elapsed . " connections per second...\n\n"; + + if ($tot_cx_started >= $stop_at_connections) { + exit 0; + } + + # Now, lets change the port numbers around. + for ($i = 0; $i<@endpoint_names; $i++) { + my $en = $endpoint_names[$i]; + + my $cmd = "add_endp $en NA NA NA NA $port_nums NA NA NA NA NA NA NA NA"; + $port_nums = nextPortNum($port_nums); + doCmd($cmd, 1, 1); + }#for + + } + else { + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + } + + }# For some amount of start_stop iterations... +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub addMacVlans { + my $i; + my $q; + + my $v; + my $lsb = 10; + my $lsb2 = 10; + + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $pnum1 = $lf1_ports[$q]; + my $pnum2 = $lf2_ports[$q]; + for ($i = 0; $i<$num_macvlans; $i++) { + + $lsb++; + if ($lsb > 99) { + $lsb2++; + $lsb = 2; + } + + my $s2 = $shelf+10; + my $c2 = $lf1+10; + my $p2 = $pnum1+10; + my $mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + doCmd("add_mvlan $shelf $lf1 $pnum1 $mc"); + + if ($lf2 ne "") { + $c2 = $lf2+10; + $p2 = $pnum2+10; + $mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + doCmd("add_mvlan $shelf $lf2 $pnum2 $mc"); + + # Throttle ourself so we don't over-run the poor LANforge system. + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + $since_throttle = 0; + } + } + } + } + + doCmd("probe_ports"); + + # Wait untill we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = 0; $i<$num_macvlans; $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($lf2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } + + +}#addMacVlans + + +# Wait untill the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + my $wait_for_phantom = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + if ($ports[$i]->isPhantom()) { + # Wait a bit..hopefully it will go away. + if ($wait_for_phantom++ < 20) { + print "Sleeping a bit, found a phantom port."; + sleep(5); + doCmd("probe_ports"); + $found_one = 1; + } + } + else { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + } + } + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = "set_port $shelf $lf1 $tmp $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($lf2 ne "") { + $cmd = "set_port $shelf $lf2 $tmp2 $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + } + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $tmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf1 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$q"); + $since_throttle = 0; + } + + } + + $ip_lsb++; + + if ($lf2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $tmp2); + $pname = $p1->{dev}; + + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf2 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pname\#$q"); + $since_throttle = 0; + } + } + }# If we have an LF-2 defined. + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + + + my @all_ports1 = @lf1_ports; + my $j; + my $pname; + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + + my @all_ports2 = @lf2_ports; + if ($lf2 ne "") { + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + } + } + + print "About to start endpoints, all_ports1:\n" . join(" ", @all_ports1) . + "\nall_ports2: " . join(" ", @all_ports2) . "\n\n"; + + if ($one_cx_per_port) { + my $j = 0; + my $cxcnt = 0; + for ($j ; $j<@all_ports1; $j++) { + my $i = $cxcnt % @cx_types; + $cxcnt++; + + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "D_endp-${ep}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . + "dl $l4_url /tmp/$ep1' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " $port_nums $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + $port_nums = nextPortNum($port_nums); + doCmd($cmd); + + + if ($lf2 == "") { + die("Must have lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " $port_nums $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + $port_nums = nextPortNum($port_nums); + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for all ports + }#one_cx_per_port + else { + my $j = 0; + for ($j ; $j<@all_ports1; $j++) { + for ($i = 0; $i<@cx_types; $i++) { + my $cxt = $cx_types[$i]; + + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "D_endp-${ep}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . + "dl $l4_url /tmp/$ep1' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " $port_nums $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + $port_nums = nextPortNum($port_nums); + doCmd($cmd); + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " $port_nums $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + $port_nums = nextPortNum($port_nums); + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for cx types + }#for each port + }# each cx per port + +}#addCrossConnects + + +sub nextPortNum { + my $cur = shift; + if ($cur > 65033) { + return int(rand(1000) + 5000); + } + $cur++; + return $cur; +} + +sub doCmd { + my $cmd = shift; + my $send_silent = shift; + my $rcv_silent = shift; + + if (! $send_silent) { + print ">>> $cmd\n"; + } + + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + if (! $rcv_silent) { + print "**************\n @rslt ................\n\n"; + } + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_mail.py b/lanforge/lanforge-scripts/lf_mail.py new file mode 100755 index 000000000..7e81516a6 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_mail.py @@ -0,0 +1,100 @@ +#!/usr/bin/python3 +import smtplib +import argparse +import logging +import sys + +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' + +EPILOG = '''\ +Text message via email: + +T-Mobile – number@tmomail.net +Virgin Mobile – number@vmobl.com +AT&T – number@txt.att.net +Sprint – number@messaging.sprintpcs.com +Verizon – number@vtext.com +Tracfone – number@mmst5.tracfone.com +Ting – number@message.ting.com +Boost Mobile – number@myboostmobile.com +U.S. Cellular – number@email.uscc.net +Metro PCS – number@mymetropcs.com +''' + +def usage(): + print("-u | --user: email account address --user @gmail.com required = True") + print("-pw | --passwd email password --passwd required = True") + print("-t | --to email send to --to @gmail.com required = True") + print("-su | --subject email subject --subject default Lanforge Report default = Lanforge Report") + print("-b | --body email body --body <body text> required = True") + print("-s | --smtp smtp server --smtp <smtp server> default smtp.gmail.com default=smtp.gmail.com") + print("-p | --port smtp port --port <port> default 465 (SSL) default=465") + +# see https://stackoverflow.com/a/13306095/11014343 +class FileAdapter(object): + def __init__(self, logger): + self.logger = logger + def write(self, data): + # NOTE: data can be a partial line, multiple lines + data = data.strip() # ignore leading/trailing whitespace + if data: # non-blank + self.logger.info(data) + def flush(self): + pass # leave it to logging to flush properly + +def main(): + + parser = argparse.ArgumentParser(description="lanforge email",epilog=EPILOG, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument("-u", "--user", type=str, help="email account --user <sender>@gmail.com", required = True) + parser.add_argument("-pw", "--passwd", type=str, help="email password --passwd <password for email account>", required = True) + parser.add_argument("-t", "--to", type=str, help="email send to --to <reciever>@gmail.com", required = True) + parser.add_argument("-su", "--subject", type=str, help="email subject --subject <title> default Lanforge Report", default="Lanforge Report") + parser.add_argument("-b", "--body", type=str, help="email body --body <body text>", required = True) + parser.add_argument("-s,", "--smtp", type=str, help="smtp server --smtp <smtp server> default smtp.gmail.com ", default="smtp.gmail.com") + parser.add_argument("-p,", "--port", type=str, help="smtp port --port <port> default 465 (SSL)", default="465") + parser.add_argument("-l", "--log", type=str, help="logfile for messages, stdout means output to console",default="stdout") + + + args = None + try: + args = parser.parse_args() + logfile = args.log + except Exception as e: + print(e) + usage() + exit(2) + + console_handler = logging.StreamHandler() + formatter = logging.Formatter(FORMAT) + logg = logging.getLogger(__name__) + logg.setLevel(logging.DEBUG) + file_handler = None + if (logfile is not None): + if (logfile != "stdout"): + file_handler = logging.FileHandler(logfile, "w") + + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + logg.addHandler(file_handler) + logg.addHandler(logging.StreamHandler(sys.stdout)) # allows to logging to file and stderr + else: + # stdout logging + logging.basicConfig(format=FORMAT, handlers=[console_handler]) + try: + email_text = 'Subject: {}\n\n{}'.format(args.subject, args.body ) + server = smtplib.SMTP_SSL(args.smtp, int(args.port)) + server.ehlo() + server.login(args.user,args.passwd) + server.sendmail(args.user, args.to, email_text) + server.close() + + logg.info('email Sent! smtp server: {} port: {}'.format(args.smtp, args.port)) + except: + logg.info('email failed') + logg.info("Is access for less secure apps setting has been turned on for the email account?") + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if __name__ == '__main__': + main() + print("Lanforge send email via smtp server") diff --git a/lanforge/lanforge-scripts/lf_many_conn.pl b/lanforge/lanforge-scripts/lf_many_conn.pl new file mode 100755 index 000000000..3dd9c54a1 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_many_conn.pl @@ -0,0 +1,466 @@ +#!/usr/bin/perl -w + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# The purpose of this script is to create as many TCP (or UDP) connections +# as possible during a given amount of time. If you tell later scripts not +# to initialize things to defaults, then you can run multiple copies of this +# script at once by changing the starting CX number. This script not only +# starts and stops connections, but also verifys that both ends of the connection +# have received data before tearing the connection down. (Errors will be printed +# to the console if the connection does not start in 15 seconds.) + +# Written by Candela Technologies Inc. +# Udated by: +# +# + +use strict; +use warnings; +use diagnostics; + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# Specify 'card' numbers for this configuration. +my $lanf1 = 7; +my $lanf2 = 5; + +# Script assumes that we are using one port on each machine for data transmission...specifically +# port 1. + +my $test_mgr = "conn-mgr"; + + +# Run for XX seconds before tearing down and bringing up the next set.. +my $run_for_time = 1000; +my $report_timer = 20000; # XX/1000 seconds + +# Default values for ye ole cmd-line args. +my $proto = "tcp"; # tcp, udp, or both +my $to_do_at_a_time = 3000; # Do XX cross-connects at a time. Don't make this too big... +my $quiet = "yes"; +my $start_cx_num = 0; +my $init_to_dflts = "yes"; +# Port pairs. These are the ports that should be talking to each other. +# Ie, the first item lf1_ports talks to the third column in lf2_ports. +# Syntax is: port_num ip_addr ip_mask ip_gateway(dlft_router) +my $lf1_port = "2 172.16.1.200 255.255.255.0 172.16.1.1"; +my $lf2_port = "2 172.16.1.220 255.255.255.0 172.16.1.1"; + + +my $min_rate_a = 1000; +my $max_rate_a = 1000; +my $min_rate_b = 128000; +my $max_rate_b = 3000000; +my $wsize_min_a = 4000; # Write size +my $wsize_max_a = 4000; # Write size +my $wsize_min_b = 24000; # Write size +my $wsize_max_b = 24000; # Write size +my $rcvb_a = 64000; +my $rcvb_b = 16000; +my $txb_a = 16000; +my $txb_b = 64000; + +my $do_bulk_removes = 0; +my $start_all_cx_at_once = 1; +my $do_cx_too = 1; # Should probably be 1 most of the time... +my $do_run_cxs = 1; #Should usually be 1 +my $fail_msg = ""; +my $manual_check = 0; + +#my $cmd_log_name = "lf_conn_cmds.txt"; +#open(CMD_LOG, ">$cmd_log_name") or die("Can't open $cmd_log_name for writing...\n"); +#print "History of all commands can be found in $cmd_log_name\n"; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $usage = "$0 [--lf1_port {\"port_num ip mask gateway\"}] + [--lf2_port {\"port_num ip mask gateway\"}] + [--protocol {tcp | udp}] + [--start_cx_num {num}] + [--quiet {yes | no}] + [--num_cxs {num}] + [--init_to_dflts {yes | no}] + +Example: + $0 --lf1_port \"1 172.22.22.2 255.255.255.0 172.22.22.1\" --lf2_port \"1 172.22.22.3 255.255.255.0 172.22.22.1\" --init_to_dflts yes\n"; + +GetOptions +( + 'protocol|p=s' => \$proto, + 'start_cx_num|s=i' => \$start_cx_num, + 'quiet|q=s' => \$quiet, + 'num_cxs|n=i' => \$to_do_at_a_time, + 'init_ports|i=s' => \$init_to_dflts, + 'lf1_port|a=s' => \$lf1_port, + 'lf2_port|b=s' => \$lf2_port, + 'init_to_dflts|d=s' => \$init_to_dflts, +) || die("$usage"); + + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(1); # Repress output from CLI ?? + +if ($init_to_dflts eq "yes") { + initToDefaults(); + + # Now, add back the test manager we will be using + $utils->doCmd("add_tm $test_mgr"); + $utils->doCmd("tm_register $test_mgr default"); #Add default user + $utils->doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + setUpPorts(); +} + +# $utils->doCmd("log_level 63"); + + +# Create the connections we will be manipulating. +my $i = 0; +my $ep = $start_cx_num * 2; + +my $cmd = ""; +my $cx = $start_cx_num; + + +my $burst_a = "NO"; +if ($min_rate_a != $max_rate_a) { + $burst_a = "YES"; +} +my $burst_b = "NO"; +if ($min_rate_b != $max_rate_b) { + $burst_b = "YES"; +} + +my $szrnd_a = "NO"; +if ($wsize_min_a != $wsize_max_a) { + $szrnd_a = "YES"; +} + +my $szrnd_b = "NO"; +if ($wsize_min_b != $wsize_max_b) { + $szrnd_b = "YES"; +} + + +for ($i = 0; $i<$to_do_at_a_time; $i++) { + my $pattern = "INCREASING"; + my $epnum = $i; + my $ep1 = "l3e-${ep}-TX"; + + $ep++; + my $ep2 = "l3e-${ep}-RX"; + $ep++; + + my ($pn, $ip, $msk, $gw) = split(/\s+/, $lf1_port); + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + $cmd = "add_endp $ep1 $shelf_num $lanf1 $pn lf_$proto -1 $burst_a $min_rate_a $max_rate_a $szrnd_a $wsize_min_a $wsize_max_a $pattern NO"; + $utils->doCmd($cmd); + + $cmd = "set_endp_details $ep1 $rcvb_a $txb_a"; + $utils->doCmd($cmd); + + + # Don't verify these, for speed reasons (and they should always work unless something + # is mis-configured. + #my $endp1 = new LANforge::Endpoint(); + #$utils->updateEndpoint($endp1, $ep1); + #verifyEndpointAttributes($endp1, $ep1, $shelf_num, $lf1, $lf1_ports[$j], $cx_types[$i], -1, $burst, + # $min_rate, $max_rate, $szrnd, $min_pkt_szs[$i], $max_pkt_szs[$i], $pattern, + # "NO"); # last is use_checksum + + ($pn, $ip, $msk, $gw) = split(/\s+/, $lf2_port); + $cmd = "add_endp $ep2 $shelf_num $lanf2 $pn lf_$proto -1 $burst_b $min_rate_b $max_rate_b $szrnd_b $wsize_min_b $wsize_max_b $pattern NO"; + + $utils->doCmd($cmd); + + $cmd = "set_endp_details $ep2 $rcvb_b $txb_b"; + $utils->doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + $utils->doCmd($cmd); + $utils->doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); +}#addCrossConnects + + +# Now, bring up and down connections + +my $tot_cx_started = 0; +my $begin_time = time(); + +while (1) { + my $stime = time(); + + if ($start_all_cx_at_once) { + my $nm = $cx_names[$i]; + $cmd = "set_cx_state $test_mgr ALL RUNNING"; + $utils->doCmd($cmd); + } + else { + for ($i = 0; $i<@cx_names; $i++) { + my $nm = $cx_names[$i]; + $cmd = "set_cx_state $test_mgr $nm RUNNING"; + $utils->doCmd($cmd); + } +} + + # Make sure they all started, and wait untill both sides have received + # a packet or two. + my $slp = 0; + for ($i = 0; $i<@endpoint_names; $i++) { + my $endp1 = new LANforge::Endpoint(); + my $en = $endpoint_names[$i]; + $utils->updateEndpoint($endp1, $en); + while ($endp1->rx_pkts() <= 0) { + if ($slp > 14) { + # Things are not working right, it should never take this long + print "WARNING: Endpoint $en is not receiving packets after $slp seconds.\n"; + last; + } + $slp++; + sleep(1); + $utils->updateEndpoint($endp1, $en); + } + } + + # Stop cxs. + for ($i = 0; $i<@cx_names; $i++) { + my $nm = $cx_names[$i]; + $cmd = "set_cx_state $test_mgr $nm STOPPED"; + $utils->doCmd($cmd); + } + + my $elapsed = time() - $stime; + my $tot_elapsed = time() - $begin_time; + + $i = @cx_names; + $tot_cx_started += $i; + print "\nStarted and stopped $i connections this round in $elapsed seconds.\n"; + print "Started and stopped a total of $tot_cx_started in $tot_elapsed seconds.\n\n"; + +} + +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + $utils->doCmd("rm_cx $test_mgr all"); + $utils->doCmd("rm_endp YES_ALL"); + $utils->doCmd("rm_test_mgr $test_mgr"); + + # initPortsToDefault(); +}#initToDefaults + + +sub initPortsToDefault { + # Set all ports we are messing with to known state. + my $i = 0; + my ($pn, $ip, $msk, $gw) = split(/\s+/, $lf1_port); + $utils->doCmd("set_port $shelf_num $lanf1 $pn 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + + ($pn, $ip, $msk, $gw) = split(/\s+/, $lf2_port); + $utils->doCmd("set_port $shelf_num $lanf2 $pn 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); +} + +sub testFailed { + my $msg = shift; + my $should_fail = shift; + + if (defined($should_fail) && ($should_fail eq "YES")) { + print "\nGOOD: SUB-TEST FAILED correctly: $msg\n"; + $fail_msg .= "GOOD (should fail): $msg"; + } + else { + print "\nSUB-TEST FAILED: $msg\n"; + $fail_msg .= $msg; + + if ($manual_check) { + #$utils->doCmd("log_level 7"); + print "Press enter to continue with test: "; + <STDIN>; + } + else { + die("FATAL ERROR: $fail_msg\n"); + } + } +}#testFailed + +sub setUpPorts { + # Set all ports we are messing with to known state. + my $i = 0; + + my ($pn, $ip, $msk, $gw) = split(/\s+/, $lf1_port); + my $cmd = "set_port $shelf_num $lanf1 $pn $ip $msk $gw NA NA NA"; + $utils->doCmd($cmd); + my $p1 = new LANforge::Port(); + # Tell the port what it is so it decodes the right one.. + $utils->updatePort($p1, $shelf_num, $lanf1, $pn); + # Make sure the values we attempted to set actually worked. + verifyPortAttributes($p1, $shelf_num, $lanf1, $pn, $ip, $msk, $gw); + + + ($pn, $ip, $msk, $gw) = split(/\s+/, $lf2_port); + $cmd = "set_port $shelf_num $lanf2 $pn $ip $msk $gw NA NA NA"; + $utils->doCmd($cmd); + my $p2 = new LANforge::Port(); + ($pn, $ip, $msk, $gw) = split(/\s+/, $lf2_port); + # Tell the port what it is so it decodes the right one.. + $utils->updatePort($p2, $shelf_num, $lanf2, $pn); + + verifyPortAttributes($p2, $shelf_num, $lanf2, $pn, $ip, $msk, $gw); + +}#setUpPorts + + +sub verifyPortAttributes { + my $port = shift; + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $ip = shift; + my $msk = shift; + my $gw = shift; + + my $_sn = $port->shelf_id(); + my $_cn = $port->card_id(); + my $_pn = $port->port_id(); + my $_ipa = $port->ip_addr(); + + my $p = $port->toStringBrief(); + + $_sn eq $sn or testFailed("$p: Shelf id: $_sn does not match: $sn\n"); + $_cn eq $cn or testFailed("$p: Card id: $_cn does not match: $cn\n"); + $_pn eq $pn or testFailed("$p: Port id: $_pn does not match: $pn\n"); + $_ipa eq $ip or testFailed("$p: IP Address: $_ipa does not match: $ip\n"); + $port->ip_mask() eq $msk or testFailed("$p: IP Mask: " . $port->ip_mask() . " does not match: $msk\n"); + $port->ip_gw() eq $gw or testFailed("$p: IP Gateway: " . $port->ip_gw() . " does not match: $gw\n"); + + print "$p verified as correct!\n"; +}#verifyPortAttributes + + +sub verifyEndpointAttributes { + my $endp = shift; + my $name = shift; + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $type = shift; + my $ip_port = shift; + my $bursty = shift; + my $min_rate = shift; + my $max_rate = shift; + my $szrnd = shift; + my $min_pkt_sz = shift; + my $max_pkt_sz = shift; + my $pattern = shift; + my $using_csum = shift; + my $should_fail = shift; + + my $_sn = $endp->shelf_id(); + my $_cn = $endp->card_id(); + my $_pn = $endp->port_id(); + + my $p = $endp->toStringBrief(); + + $_sn eq $sn or testFailed("$p: Shelf id: $_sn does not match: $sn\n", $should_fail); + $_cn eq $cn or testFailed("$p: Card id: $_cn does not match: $cn\n", $should_fail); + $_pn eq $pn or testFailed("$p: Port id: $_pn does not match: $pn\n", $should_fail); + $endp->isOfType($type) or testFailed("$p: Type: " . $endp->ep_type() . " does not match: $type\n", $should_fail); + if ($ip_port ne -1) { + $endp->ip_port() eq $ip_port or testFailed("$p: IP-Port: " . $endp->ip_port() . + " does not match: $ip_port\n", $should_fail); + } + $endp->getBursty() eq $bursty or testFailed("$p: Bursty: " . $endp->getBursty() . + " does not match: $bursty\n", $should_fail); + + $endp->min_tx_rate() eq $min_rate or testFailed("$p: Min-Tx-Rate: " . $endp->min_tx_rate() . + " does not match: $min_rate\n", $should_fail); + $endp->max_tx_rate() eq $max_rate or testFailed("$p: Max-Tx-Rate: " . $endp->max_tx_rate() . + " does not match: $max_rate\n", $should_fail); + + if ($endp->isCustom()) { + ($endp->size_random() eq "NO") or testFailed("$p: Size-Random: " . $endp->size_random() . + " but we are CUSTOM!!\n", $should_fail); + } + else { + $endp->size_random() eq $szrnd or testFailed("$p: Size-Random: " . $endp->size_random() . + " does not match: $szrnd\n", $should_fail); + } + + if (! $endp->isCustom()) { + $endp->min_pkt_size() eq $min_pkt_sz or testFailed("$p: Min-Packet-Size: " . $endp->min_pkt_size() . + " does not match: $min_pkt_sz\n", $should_fail); + $endp->max_pkt_size() eq $max_pkt_sz or testFailed("$p: Max-Packet-Size: " . $endp->max_pkt_size() . + " does not match: $max_pkt_sz\n", $should_fail); + } + $endp->pattern() eq $pattern or testFailed("$p: Pattern: " . $endp->pattern() . + " does not match: $pattern\n", $should_fail); + $endp->checksum() eq $using_csum or testFailed("$p: Using-Checksum: " . $endp->checksum() . + " does not match: $using_csum\n", $should_fail); + +}#verifyEndpointAttributes + + +sub genRandomHex { + my $bytes = shift; + + my @tbl = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"); + my $i; + my $pld = ""; + for ($i = 0; $i<$bytes; $i++) { + $pld .= $tbl[(rand() * 1000.0) % 16] . $tbl[(rand() * 1000.0) % 16]; #Generate some hex the hard way! + if ($i != ($bytes - 1)) { + $pld .= " "; + } + } + + return $pld; +}#genRandomHex diff --git a/lanforge/lanforge-scripts/lf_many_conn2.pl b/lanforge/lanforge-scripts/lf_many_conn2.pl new file mode 100755 index 000000000..468c593ff --- /dev/null +++ b/lanforge/lanforge-scripts/lf_many_conn2.pl @@ -0,0 +1,423 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# The purpose of this script is to create many connections +# This script not only starts and stops connections, but +# also verifys that both ends of the connection +# have received data before tearing the connection down. +# (Errors will be printed to the console if the connection +# does not start in 15 seconds.) + +# Written by Candela Technologies Inc. +# Udated by: +# +# + +use strict; + +# Un-buffer output +$| = 1; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; + +use Net::Telnet(); + +use Getopt::Long; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# Specify 'card' numbers for this configuration. +my $lanf1 = 4; +my $lanf2 = 4; + +# Script assumes that we are using one port on each machine for data transmission...specifically +# port 1. + +my $test_mgr = "conn-mgr"; + + +my $report_timer = 8000; # XX/1000 seconds + +my $between_start_stop = 120; # run for 120 seconds between start/stop + +# Default values for ye ole cmd-line args. +my $proto = "tcp"; # tcp, udp, or both +my $cx_to_create = 800; # How many we will try to create. +my $quiet = "yes"; +my $start_cx_num = 0; +my $init_to_dflts = "yes"; +my $speed = 200000; +my $payloadsize = 1400; + +# Port pairs. These are the ports that should be talking to each other. +# Ie, the first item lf1_ports talks to the third column in lf2_ports. +# Syntax is: port_num ip_addr ip_mask ip_gateway(dlft_router) +#my $lf1_port = "1 172.16.1.200 255.255.255.0 172.16.1.1"; +#my $lf2_port = "1 172.16.1.220 255.255.255.0 172.16.1.1"; +my $lf1_port = "1 172.17.1.200 255.255.255.0 172.17.1.1"; +my $lf2_port = "2 172.17.1.220 255.255.255.0 172.17.1.1"; + + + +my $do_bulk_removes = 1; +my $do_cx_too = 1; # Should probably be 1 most of the time... +my $do_run_cxs = 1; #Should usually be 1 +my $fail_msg = ""; +my $manual_check = 0; + +my $cmd_log_name = "lf_conn_cmds.txt"; +open(CMD_LOG, ">$cmd_log_name") or die("Can't open $cmd_log_name for writing...\n"); +print "History of all commands can be found in $cmd_log_name\n"; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $usage = "$0 [--lf1_port {\"port_num ip mask gateway\"}] + [--lf2_port {\"port_num ip mask gateway\"}] + [--protocol {tcp | udp}] + [--start_cx_num {num}] + [--quiet {yes | no}] + [--num_cxs {num}] + [--init_to_dflts {yes | no}] + +Example: + $0 --lf1_port \"1 172.22.22.2 255.255.255.0 172.22.22.1\" --lf2_port \"1 172.22.22.3 255.255.255.0 172.22.22.1\" --init_to_dflts yes\n"; + +my $i = 0; + +GetOptions +( + 'protocol|p=s' => \$proto, + 'start_cx_num|s=i' => \$start_cx_num, + 'quiet|q=s' => \$quiet, + 'num_cxs|n=i' => \$cx_to_create, + 'init_ports|i=s' => \$init_to_dflts, + 'lf1_port|l=s' => \$lf1_port, + 'lf2_port|L=s' => \$lf2_port, + 'init_to_dflts|d=s' => \$init_to_dflts, +) || die("$usage"); + + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +my $dt = ""; + + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(1); # Repress output from CLI ?? + + +my $dt = ""; + +if ($init_to_dflts eq "yes") { + initToDefaults(); + + # Now, add back the test manager we will be using + $utils->doCmd("add_tm $test_mgr"); + $utils->doCmd("tm_register $test_mgr default"); #Add default user + $utils->doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + setUpPorts(); +} + +# $utils->doCmd("log_level 63"); + + +# Create the connections we will be manipulating. +my $i = 0; +my $ep = $start_cx_num * 2; + +my $cmd = ""; +my $cx = $start_cx_num; + +for ($i = 0; $i<$cx_to_create; $i++) { + my $burst = "NO"; + my $szrnd = "NO"; + my $pattern = "INCREASING"; + my $epnum = $i; + my $ep1 = "endp-${ep}-TX"; + my $min_rate = $speed; + my $max_rate = $speed; + my $pktsz = $payloadsize; + + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + my ($pn, $ip, $msk, $gw) = split(/\s+/, $lf1_port); + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + $cmd = "add_endp $ep1 $shelf_num $lanf1 $pn lf_$proto -1 $burst $min_rate $max_rate $szrnd $pktsz $pktsz $pattern NO"; + $utils->doCmd($cmd); + + # Don't verify these, for speed reasons (and they should always work unless something + # is mis-configured. + #my $endp1 = new LANforge::Endpoint(); + #$utils->updateEndpoint($endp1, $ep1); + #verifyEndpointAttributes($endp1, $ep1, $shelf_num, $lf1, $lf1_ports[$j], $cx_types[$i], -1, $burst, + # $min_rate, $max_rate, $szrnd, $min_pkt_szs[$i], $max_pkt_szs[$i], $pattern, + # "NO"); # last is use_checksum + + ($pn, $ip, $msk, $gw) = split(/\s+/, $lf2_port); + $cmd = "add_endp $ep2 $shelf_num $lanf2 $pn lf_$proto -1 $burst $min_rate $max_rate $szrnd $pktsz $pktsz $pattern NO"; + + $utils->doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + $utils->doCmd($cmd); + $utils->doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); +}#addCrossConnects + + +# Now, bring up and down connections + +my $tot_cx_started = 0; +my $begin_time = time(); + +while (1) { + my $stime = time(); + + for ($i = 0; $i<@cx_names; $i++) { + my $nm = $cx_names[$i]; + $cmd = "set_cx_state $test_mgr $nm RUNNING"; + $utils->doCmd($cmd); + } + + # Make sure they all started, and wait untill both sides have received + # a packet or two. + my $slp = 0; + for ($i = 0; $i<@endpoint_names; $i++) { + my $endp1 = new LANforge::Endpoint(); + my $en = $endpoint_names[$i]; + $utils->updateEndpoint($endp1, $en); + while ($endp1->rx_pkts() <= 0) { + if ($slp > 20) { + # Things are not working right, it should never take this long + print "WARNING: Endpoint $en is not receiving packets after $slp seconds.\n"; + last; + } + $slp++; + sleep(1); + $utils->updateEndpoint($endp1, $en); + } + } + + sleep($between_start_stop); + + # Stop cxs. + for ($i = 0; $i<@cx_names; $i++) { + my $nm = $cx_names[$i]; + $cmd = "set_cx_state $test_mgr $nm STOPPED"; + $utils->doCmd($cmd); + } +}#while true + +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + $utils->doCmd("rm_cx $test_mgr all"); + $utils->doCmd("rm_endp YES_ALL"); + $utils->doCmd("rm_test_mgr $test_mgr"); + + # initPortsToDefault(); +}#initToDefaults + + +sub initPortsToDefault { + # Set all ports we are messing with to known state. + my $i = 0; + my ($pn, $ip, $msk, $gw) = split(/\s+/, $lf1_port); + $utils->doCmd("set_port $shelf_num $lanf1 $pn 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + + ($pn, $ip, $msk, $gw) = split(/\s+/, $lf2_port); + $utils->doCmd("set_port $shelf_num $lanf2 $pn 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); +} + +sub testFailed { + my $msg = shift; + my $should_fail = shift; + + if (defined($should_fail) && ($should_fail eq "YES")) { + print "\nGOOD: SUB-TEST FAILED correctly: $msg\n"; + $fail_msg .= "GOOD (should fail): $msg"; + } + else { + print "\nSUB-TEST FAILED: $msg\n"; + $fail_msg .= $msg; + + if ($manual_check) { + #$utils->doCmd("log_level 7"); + print "Press enter to continue with test: "; + <STDIN>; + } + else { + die("FATAL ERROR: $fail_msg\n"); + } + } +}#testFailed + +sub setUpPorts { + # Set all ports we are messing with to known state. + my $i = 0; + + my ($pn, $ip, $msk, $gw) = split(/\s+/, $lf1_port); + my $cmd = "set_port $shelf_num $lanf1 $pn $ip $msk $gw NA NA NA"; + $utils->doCmd($cmd); + my $p1 = new LANforge::Port(); + # Tell the port what it is so it decodes the right one.. + $utils->updatePort($p1, $shelf_num, $lanf1, $pn); + # Make sure the values we attempted to set actually worked. + verifyPortAttributes($p1, $shelf_num, $lanf1, $pn, $ip, $msk, $gw); + + + ($pn, $ip, $msk, $gw) = split(/\s+/, $lf2_port); + $cmd = "set_port $shelf_num $lanf2 $pn $ip $msk $gw NA NA NA"; + $utils->doCmd($cmd); + my $p2 = new LANforge::Port(); + ($pn, $ip, $msk, $gw) = split(/\s+/, $lf2_port); + # Tell the port what it is so it decodes the right one.. + $utils->updatePort($p2, $shelf_num, $lanf2, $pn); + + verifyPortAttributes($p2, $shelf_num, $lanf2, $pn, $ip, $msk, $gw); + +}#setUpPorts + + +sub verifyPortAttributes { + my $port = shift; + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $ip = shift; + my $msk = shift; + my $gw = shift; + + my $_sn = $port->shelf_id(); + my $_cn = $port->card_id(); + my $_pn = $port->port_id(); + my $_ipa = $port->ip_addr(); + + my $p = $port->toStringBrief(); + + $_sn eq $sn or testFailed("$p: Shelf id: $_sn does not match: $sn\n"); + $_cn eq $cn or testFailed("$p: Card id: $_cn does not match: $cn\n"); + $_pn eq $pn or testFailed("$p: Port id: $_pn does not match: $pn\n"); + $_ipa eq $ip or testFailed("$p: IP Address: $_ipa does not match: $ip\n"); + $port->ip_mask() eq $msk or testFailed("$p: IP Mask: " . $port->ip_mask() . " does not match: $msk\n"); + $port->ip_gw() eq $gw or testFailed("$p: IP Gateway: " . $port->ip_gw() . " does not match: $gw\n"); + + print "$p verified as correct!\n"; +}#verifyPortAttributes + + +sub verifyEndpointAttributes { + my $endp = shift; + my $name = shift; + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $type = shift; + my $ip_port = shift; + my $bursty = shift; + my $min_rate = shift; + my $max_rate = shift; + my $szrnd = shift; + my $min_pkt_sz = shift; + my $max_pkt_sz = shift; + my $pattern = shift; + my $using_csum = shift; + my $should_fail = shift; + + my $_sn = $endp->shelf_id(); + my $_cn = $endp->card_id(); + my $_pn = $endp->port_id(); + + my $p = $endp->toStringBrief(); + + $_sn eq $sn or testFailed("$p: Shelf id: $_sn does not match: $sn\n", $should_fail); + $_cn eq $cn or testFailed("$p: Card id: $_cn does not match: $cn\n", $should_fail); + $_pn eq $pn or testFailed("$p: Port id: $_pn does not match: $pn\n", $should_fail); + $endp->isOfType($type) or testFailed("$p: Type: " . $endp->ep_type() . " does not match: $type\n", $should_fail); + if ($ip_port ne -1) { + $endp->ip_port() eq $ip_port or testFailed("$p: IP-Port: " . $endp->ip_port() . + " does not match: $ip_port\n", $should_fail); + } + $endp->getBursty() eq $bursty or testFailed("$p: Bursty: " . $endp->getBursty() . + " does not match: $bursty\n", $should_fail); + + $endp->min_tx_rate() eq $min_rate or testFailed("$p: Min-Tx-Rate: " . $endp->min_tx_rate() . + " does not match: $min_rate\n", $should_fail); + $endp->max_tx_rate() eq $max_rate or testFailed("$p: Max-Tx-Rate: " . $endp->max_tx_rate() . + " does not match: $max_rate\n", $should_fail); + + if ($endp->isCustom()) { + ($endp->size_random() eq "NO") or testFailed("$p: Size-Random: " . $endp->size_random() . + " but we are CUSTOM!!\n", $should_fail); + } + else { + $endp->size_random() eq $szrnd or testFailed("$p: Size-Random: " . $endp->size_random() . + " does not match: $szrnd\n", $should_fail); + } + + if (! $endp->isCustom()) { + $endp->min_pkt_size() eq $min_pkt_sz or testFailed("$p: Min-Packet-Size: " . $endp->min_pkt_size() . + " does not match: $min_pkt_sz\n", $should_fail); + $endp->max_pkt_size() eq $max_pkt_sz or testFailed("$p: Max-Packet-Size: " . $endp->max_pkt_size() . + " does not match: $max_pkt_sz\n", $should_fail); + } + $endp->pattern() eq $pattern or testFailed("$p: Pattern: " . $endp->pattern() . + " does not match: $pattern\n", $should_fail); + $endp->checksum() eq $using_csum or testFailed("$p: Using-Checksum: " . $endp->checksum() . + " does not match: $using_csum\n", $should_fail); + +}#verifyEndpointAttributes + + +sub genRandomHex { + my $bytes = shift; + + my @tbl = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"); + my $i; + my $pld = ""; + for ($i = 0; $i<$bytes; $i++) { + $pld .= $tbl[(rand() * 1000.0) % 16] . $tbl[(rand() * 1000.0) % 16]; #Generate some hex the hard way! + if ($i != ($bytes - 1)) { + $pld .= " "; + } + } + + return $pld; +}#genRandomHex diff --git a/lanforge/lanforge-scripts/lf_many_vphy.pl b/lanforge/lanforge-scripts/lf_many_vphy.pl new file mode 100755 index 000000000..47db39216 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_many_vphy.pl @@ -0,0 +1,32 @@ +#!/usr/bin/perl -w +# Create lots of virtual radios with stations. +# Note that lf_associate_ap.pl has many more options that +# are not currently used here. + +use strict; +use Getopt::Long; + +my $usage = "$0 + [--num_radios { number } ] + [--ssid {ssid}] +"; + +my $num_radios = 1; +my $ssid = "ssid"; + + + + +GetOptions ( + 'num_radios|r=i' => \$num_radios, + 'ssid|s=s' => \$ssid, + ) || (print($usage) && exit(1)); + +my $i; +for ($i = 0; $i < $num_radios; $i++) { + my $idx = $i + 1; + my $sta = 600 + $idx; + my $cmd = "./lf_associate_ap.pl --resource 1 --radio vphy$idx --vrad_chan 1 --num_stations 1 --first_sta sta$sta --action add --first_ip DHCP --ssid $ssid"; + print "$cmd\n"; + system($cmd); +} diff --git a/lanforge/lanforge-scripts/lf_max_cxs_v1_3000.pl b/lanforge/lanforge-scripts/lf_max_cxs_v1_3000.pl new file mode 100755 index 000000000..ab30de0ac --- /dev/null +++ b/lanforge/lanforge-scripts/lf_max_cxs_v1_3000.pl @@ -0,0 +1,1749 @@ +#!/usr/bin/perl + +# This program is used to test the max TCP connections allowed through a firewall, +# and may be used as an example for others who wish to automate LANforge tests. + +# This script sets up 1 UDP connection and as many TCP connections as specified +# by $num_macvlans. Each connection is started and verified that it is passing +# traffic before starting the next connection. As each TCP connection is started +# the UDP connection is checked for any dropped packets. As soon as dropped packets +# are detected on the UDP connection, the number of TCP connections is recorded +# and the entire test is repeated for $loop_max times. An average number of TCP +# connections is calculated and reported at the conclusion of all the test runs. + + +# Un-buffer output +$| = 1; + +use strict; +use Switch; + +use Net::Telnet (); +use Time::HiRes qw (usleep); +use LANforge::Port; +use LANforge::Utils; +use LANforge::Endpoint; + +my $init_stop_all = 1; # Stop all tests before running script test. +my $script_speed = 25; # Increase to issue commands faster. +my $quiet_cli_cmds = 1; # Quiesce CLI response output to commands sent. +my $quiet_cli_output = 1; # Quiesce unsolicited CLI output. +my $cli_cmd_delay = 0; # Increase to slow command rate sent to cli. +my $report_timer = 9000; # Set report timer for all tests created in ms, i.e. 8 seconds + +my $INIT = 1; # If true, removes all previous tests and ports!!! +my $create_only = 0; # If true, only create tests, i.e. do not automatically run them. + +my $mac_init = 0; # Set to 1 to start MAC address from zero when running looped test. +my $ip_init = 0; # Set to 1 to start IP addresses from zero when running looped test. +my $init_once = 1; # Set to 1 to only initialize test creation once. +my $init_net = 1; # Set to 0 to disable reconfiguring MAC and IP addresses. +my $init_tests = 1; # Set to 0 to disable reconfiguring tests. +my $first_run = 1; # Set to 0 to disable initial configurations. +my $name_id = 0; # First index of name of endpoints and CXs. +my $name_id_len = 0; # Override for length of $name_id. +my $loop_max = 3; +my $start_stop_loops = 2; +my $run_for_time = 120; # Run for XX seconds..then will be stopped again. +my $stop_for_time = 5; # Run for XX seconds..then will be stopped again. +my $keep_running = 1; # If ture, will keep last test loop running. +my $ignore_phys_ports = 1; # If true, just muck with MACVLANs. + +my $one_cx_per_port = 0; # If zero, will have one of EACH of the cx types on each port. + +my $cx_types_from_file = 0; # If true, will rotate through the @cx_types_files + # when creating tests instead of using @cx_types array. + +my @cx_types = ( +#"lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp", +"lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp", +"lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp", +"lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp","lf_tcp"); + +my $test_mgr = "max_cxs_tm"; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf = 1; + +# This sets up connections. +my $lf1 = 1; # Minor Resource EID of first LANforge resource. +my $lf2 = ""; # Set to "" if we have no second machine. Or set to second Resource + # minor EID to create mac-vlans on it. + + +# Port pairs. These are the ports that should be talking to each other. +# i.e. the third column in lf1_ports talks to the third column in lf2_ports. +# EIDs or aliases can be used. +# Port pairs must match on each shelf - will enhance to allow any pair on each shelf. +#my @lf1_ports = (1); #, 2, 3); +#my @lf2_ports = (2); #, 2, 3); +my @lf1_ports = ( "eth0", "eth1"); +my @lf2_ports = (""); +my @ip_base = ( "192.168", "172.1"); +my @ip_c = ( 2 , 1 ); +my @ip_lsb = ( 2 , 2 ); +my @msk = ("255.255.255.0","255.255.255.0"); +my @ip_gw = ( "192.168.2.1", "172.1.1.1"); + +my $mac1 = 0x00; # Starting MAC address 00:m5:m4:m3:m2:m1 where: +my $mac2 = 0x00; # m5 is shelf EID, m4 is card EID, m3 is $mac3, +my $mac3 = 0x00; # m2 is $mac2 and m1 is $mac1. + + +my $start_mvlan = 0; +my $num_mvlans = 30; +my $num_cxs = 0; + +my @min_rate = (19200);# bps +my @max_rate = (19200);# bps +my @min_pkt_szs = (948); # bytes +my @max_pkt_szs = (948); # bytes + +########################## +# lf_max_cxs.pl specific # +########################## + +my $max_delay = 100; # Maximum endpoint delay threshold in milliseconds. +my $percent_ep_delay = 3.0;# Percentage of endpoints allowed to exceed the + # $max_delay. Exceeding percentage will cause curren + # test loop to exit. + +my $settle_time = 1; # Number of seconds to allow an endpoint to receive data +my $ep_rx_strikes = 3; # Number of strikes before declaring it failed. + +my $sample_time_dly = 500; # Milliseconds between endpoint delay samples. +my $samples = 3; + +my $use_udp_probe = 1; +my $use_udp_loss = 1; +my $end_udp_drop = 0; + +#my $percentile = 97; +#my $filename = "delay_data.txt"; + + +################ +# Layer-4 only # +################ + +my $url_dl = 1; # If true, test will download from URL. False will upload to URL. +#my $l4_dl_path = "/tmp"; # Path to save downloaded file. +#my $l4_dl_path = "NUL"; # Windows equivalent of *nix /dev/null. +my $l4_dl_path = "/dev/null"; # Improve performance by saving downloaded file to /dev/null. + +my @l4_urls = ( + "http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +,"http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +,"http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +,"http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +,"http://192.168.100.3/index.html", "ftp://192.168.100.3/file", "http://192.168.100.3/index.html", "ftp://192.168.100.3/file" +); +#my @l4_urls = ("ftp://192.168.100.3/file"); + +my $urls_10m = 100; # How many URLs to process every 10 minutes. +my $l4_timeout = 10000; # How long to wait for a connection, in milliseconds. + + +########### +# File-IO # +########### + +my $fio_base = "/mnt/fio_base"; +my $fio_targ_dir = ""; +my $fsrw = "write"; + + +######### +# Debug # +######### + +my $DEBUG = 0; +my $D_PAUSE = 3; +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## +my $script_name = $0; +$sample_time_dly = $sample_time_dly * 1000; + +# Parse cmd-line args +my $i; +my $j; +for ($i = 0; $i<@ARGV; $i++) { + my $var = $ARGV[$i]; + if ($var =~ m/(\S+)=(.*)/) { + my $arg = $1; + my $val = $2; + handleCmdLineArg($arg, $val); + } + else { + handleCmdLineArg($var); + } +} + +my $ss_wait + = 0.003 * $report_timer; # Increase delay (seconds) if experiencing problems on slow systems. + + +if ($lfmgr_host eq undef) { + print "\nYou must define a LANforge Manager!!!\n\n" + . "For example:\n" + . "./$script_name mgr=localhost\n" + . "OR\n" + . "./$script_name mgr=192.168.1.101\n\n"; + printHelp(); + exit (1); +} + +my $foundL4 = 0; +for ($i = 0; $i<@cx_types; $i++) { + if ($cx_types[$i] eq "l4") { + $foundL4 = 1; + last; + } +} +if ($lf2 == "" && @lf1_ports < 2 && !$foundL4) { + die ("Must have more than one port with only one resource."); +} + +#if (!$numvlan && !$num_cxs) { +# die ("Must have either number of MACVLANs (num_mvl) or cross-connects (num_cxs) > 0."); +#} + +print + "\nStarting script with the following arguments:" + . "\ninit: $INIT" + . "\nmanager: $lfmgr_host\n" + . "\nlf1: $lf1\nlf2: $lf2\n" + . "\nlf1_ports: " . join(" ", @lf1_ports) + . "\nlf2_ports: " . join(" ", @lf2_ports) . "\n" + . "\nstart_macvlans: $start_mvlan" + . "\nnum_mvlans: $num_mvlans\n" + . "\nmin_rates: " . join(" ", @min_rate) + . "\nmax_rates: " . join(" ", @max_rate) + . "\nmin_pkt_sizes: " . join(" ", @min_pkt_szs) + . "\nmax_pkt_sizes: " . join(" ", @max_pkt_szs) . "\n" + . "\ncx_types: " . join(" ", @cx_types) + . "\none_cx_per_port: $one_cx_per_port\n\n"; + +if ($DEBUG) { sleep ($D_PAUSE); } + + +# Determine total port and endpoint counts and make sorting by name easier in the GUI :P + +my @num = (); # Formatted index number for name sorting in GUI. +my $t_num = 0; +my $t_ports = 0; +my $ni=0; +my $nj=0; + +my $lf2orig = $lf2; + +if ($lf2 == "") { + $lf2 = $lf1; + if ($foundL4) { + @lf2_ports = undef; + } + else { + # put every other port into @lf2_ports to fake out lf2 info which makes the + # script work later. + my @lf1_ports_tmp = @lf1_ports; + @lf1_ports = undef; + @lf2_ports = undef; + $i=0; + for ($ni=0; $ni<@lf1_ports_tmp; $ni++) { + $lf1_ports[$i] = $lf1_ports_tmp[$ni]; + $lf2_ports[$i] = $lf1_ports_tmp[++$ni]; + $i++; + } + } +} + +# Check that ip_base address pairs aren't the same. +for ($ni = 0; $ni<@ip_base; $ni++) { + if ($ip_base[$ni] == $ip_base[$ni+1]) { + die ("ERROR: Base IP addresses cannot be the same."); + } + $ni++; +} + +my @cxts = ("lf", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4", + "fileIONFS", "fileIOCIFS"); +my @t_cxts = (); +for ($ni=0; $ni<@cxts; $ni++) { + @t_cxts[$ni] = 0; +} + +if ($lf2orig ne "") { + if ($ignore_phys_ports) { + $t_ports = $num_mvlans; + } + else { + $t_ports = @lf1_ports + @lf2_ports + ($num_mvlans); + } +} +elsif ($num_mvlans) { + if ($ignore_phys_ports) { + $t_ports = $num_mvlans; + } + else { + $t_ports = @lf1_ports + ($num_mvlans); + } +} +else { + $t_ports = @lf1_ports + @lf2_ports; +} + +my $t_cxtypes = @cx_types; +my $t_urls = @l4_urls; + +if (@min_rate != @max_rate ) { + die("Number of elements in min_rate does not match number of elements in max_rate."); +} +else { + my $t_rate = @min_rate + @max_rate; +} +if (@min_pkt_szs != @max_pkt_szs ) { + die("Number of elements in min_pkt_szs does not match number of elements in max_pkt_szs."); +} +else { + my $t_pkt_szs = @min_pkt_szs + @max_pkt_szs; +} + +for ($ni=0; $ni<@cx_types; $ni++) { + for ($nj=0; $nj<@cxts; $nj++) { + if ( $cx_types[$ni] eq $cxts[$nj] ) { + $t_cxts[$nj]++; + } + } +} + +for ($nj=0; $nj<@cxts; $nj++) { + if ( $cxts[$nj] eq "l4") { + $t_num += ($t_ports * (2 * ($t_cxts[$nj] * $t_urls))); + } + else { + $t_num += ($t_ports * (2 * $t_cxts[$nj])); + } +} +$t_num += $name_id; + +my $num_len; +if ($name_id_len) { + if (length($name_id) > $name_id_len || length($t_num) > $name_id_len) { + print "\nWARNING: id_len specifies a string length less that first_name_id or less that total number of endpoints\n"; + } + $num_len = $name_id_len; +} +else { + $num_len = length ($t_num); +} +$t_num -= $name_id; +$i = 0; +switch ($num_len) { + case 1 { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%01d", $name_id + $i); + } + } + case 2 { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%02d", $name_id + $i); + } + } + case 3 { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%03d", $name_id + $i); + } + } + case 4 { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%04d", $name_id + $i); + } + } + case 5 { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%05d", $name_id + $i); + } + } + case 6 { + for ($i ; $i<$t_num; $i++) { + $num[$i] = sprintf("%06d", $name_id + $i); + } + } + else { + for ($i ; $i<$t_num; $i++) { + $num[$i] = $name_id + $i; + } + } +} +if ($DEBUG > 99) { + $i = 0; + print "name_id: $name_id, t_num: $t_num, num_len: $num_len :-\n"; + for ($i ; $i<$t_num; $i++) { + print $num[$i] . " "; + } + print "\n"; + sleep ($D_PAUSE); +} +if ($DEBUG) { printArgs(); sleep ($D_PAUSE); } + +# Open connection to the LANforge server. +my $t = new Net::Telnet(Timeout => 15, + Prompt => '/default\@btbits\>\>/'); + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 60); + +$t->waitfor("/btbits\>\>/"); +$t->max_buffer_length(1024 * 1024 * 10); # 10M buffer + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent($quiet_cli_cmds); # Do not show input to CLI +$utils->cli_rcv_silent($quiet_cli_output); # Repress output from CLI ?? + +my $dt = getDate(); +my $dt_start = $dt; +my $cmd; + +my @t_cx_run_loop = (); +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); +my @ep_delay = (); +my $cx_run = 0; +my $avg_cx_run = 0; +my $t_cx_run = 0; +my $t_prcnt_ep_dly = 0; +my $avg_prcnt_ep_dly = 0; +my $eia = 0; +my $eib = 0; +my $ci = 0; +my $ep_dly_cnt = 0; +my $t_ep_dly_cnt = 0; +my $avg_ep_dly_cnt = 0; +my $prcnt_ep_dly = 0; +my $epa_rx = 0; +my $epb_rx = 0; +my $ep_delay = 0.0; +#my $epa_delay = 0; +#my $epb_delay = 0; +my $epa_drop = 0; +my $epb_drop = 0; +my $t_ep_run = 0; +my $prcnt_ep_dlyd = 0; + + +if ($init_stop_all) { doCmd("set_cx_state ALL ALL STOPPED"); } + +$SIG{'INT'} = 'CLEANUP'; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = getDate(); + print "\n\n***** Starting $script_name at: $dt. Test Loop: ". ($loop+1) . " *****\n\n"; + + if (!$init_once) { + if ($INIT) { initToDefaults(); } + + if ($init_net) { + addMacVlans(); # Add MACVLANs. + initIpAddresses(); # Add some IP addresses to the ports. + } + if ($init_tests) { + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); # Add default user + doCmd("tm_register $test_mgr default_gui"); # Add default GUI user + addCrossConnects(); # Add our endpoints. + print "Done adding CXs.\n"; + } + } + elsif ($first_run) { + if ($INIT) { initToDefaults(); } + + if ($init_net) { + addMacVlans(); + initIpAddresses(); + } + if ($first_run && $init_tests) { + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); # Add default user + doCmd("tm_register $test_mgr default_gui"); # Add default GUI user + addCrossConnects(); # Add our endpoints. + print "Done adding CXs.\n"; + } + $first_run = 0; + } + + if ($DEBUG) { printArgs(); } + $dt = getDate(); + print "\n\n*** Started $script_name script at : $dt_start ***\n" + . "*** Finished $script_name configuration at: $dt ***\n\n"; + sleep($D_PAUSE); + + if ($create_only == 1) { exit(0); } + + print "Wait $ss_wait seconds for ports to update.\n"; + sleep($ss_wait); + + ####################### + # START lf_max_cxs.pl # + ####################### + # Start Cross-Connects + my $endp = new LANforge::Endpoint(); + for ($ci=0; $ci<@cx_names; $ci++) { + $cmd = "set_cx_state $test_mgr " . $cx_names[$ci] . " RUNNING"; + doCmd($cmd); + $eia = 2 * $ci; + $eib = $eia + 1; + + $ep_delay[$eia] = $ep_delay[$eib] = 0.0; + + # check that the CX is passing packets + $utils->updateEndpoint($endp, $endpoint_names[$eia]); + $epa_rx = $endp->rx_pkts(); + + $utils->updateEndpoint($endp, $endpoint_names[$eib]); + $epb_rx = $endp->rx_pkts(); + + my $slp = 0; + $ep_delay = 0.0; +# $epa_delay = $epb_delay = 0; + while ($epa_rx == 0 || $epb_rx == 0) { + sleep($settle_time); # sleep to allow CX to connect + $slp++; + + $utils->updateEndpoint($endp, $endpoint_names[$eia]); + $epa_rx = $endp->rx_pkts(); + + $utils->updateEndpoint($endp, $endpoint_names[$eib]); + $epb_rx = $endp->rx_pkts(); + + if ($slp > $ep_rx_strikes) { + # too long + print "WARNING: Waited too long on endpoint $ci to receive packet\n"; + if ($epa_rx == 0) { + $ep_delay[$eia] = 999999; + } + if ($epb_rx == 0) { + $ep_delay[$eib] = 999999; + } + last; # for $ci + } + } # while + + $cx_run++; + if ($DEBUG > 99) { + print "\n[DEBUG] cx_run: $cx_run\n"; + } + print "Test Loop: " . ($loop+1) . "\n Processing data for " . ($eib+1) . " endpoints"; + $ep_dly_cnt = 0; + for ($i=0; $i<=$eib; $i++) { + print "."; + # MIGHT MOVE UDP CHECK into loop so that tcp delay and udp loss or delay can be used. + # if UDP check CX for dropped packets + if ($use_udp_probe) { + if ($use_udp_loss) { + $utils->updateEndpoint($endp, $endpoint_names[0]); + $epa_drop = $endp->rx_dropped_pkts(); + + $utils->updateEndpoint($endp, $endpoint_names[1]); + $epb_drop = $endp->rx_dropped_pkts(); + + if (($epa_drop || $epb_drop) && $i == 0) { # If there are ANY dropped packets on UDP CX. + print "DROP ON PROBE ENDPOINT DETECTED"; + if ($end_udp_drop) { + print "\nSTOP FURTHER PROCESSING !!!\n"; + # Probably should refine this to have a drop threshold. +# $t_cx_run += $cx_run; # Average calculated later. +# $t_ep_run = $t_cx_run * 2; # Probably need more or different results now. + # Might add processing for all UDP CXs.... + #save ep delays + last; # not sure but NOT for $i - need the next one to break out of for $i + } + } + #elsif ($ci > 0) { + # # Successfully added TCP CX, count it + # $cx_run++; + #} + } + # if UDP delay? Nothing special about delay wrt UDP - just loss is special + } + if ($end_udp_drop && ($epa_drop || $epb_drop)) { + last; # for $i + } + if ($endpoint_names[$eia] eq $endpoint_names[$i] || $endpoint_names[$eib] eq $endpoint_names[$i] ) { + for ($j=0; $j<$samples; $j++) { + $utils->updateEndpoint($endp, $endpoint_names[$i]); + $ep_delay += $endp->avg_latency(); +# $epa_delay += $endp->avg_latency(); +# $utils->updateEndpoint($endp, $endpoint_names[($i+1)]); +# $epb_delay += $endp->avg_latency(); + if ($DEBUG > 1) { + print "\n[DEBUG] Sample#" . ($j+1) . ": ". $endpoint_names[$i] . " - ep_delay +=: $ep_delay ms\n" +# print "\n[DEBUG] Sample#" . ($j+1) . ": ". $endpoint_names[$i] . " - epa_delay: $epa_delay\n" +# . "[DEBUG] Sample#" . ($j+1) . ": ". $endpoint_names[($i+1)] . " - epb_delay: $epb_delay\n"; + } + usleep ($sample_time_dly); + } + $ep_delay[$i] = $ep_delay / $samples; + } else { + $utils->updateEndpoint($endp, $endpoint_names[$i]); + $ep_delay = $endp->avg_latency(); + $ep_delay[$i] = $ep_delay; + if ($DEBUG > 1 ) { + print "\n[DEBUG] Single sample ". $endpoint_names[$i] . " - ep_delay: $ep_delay ms\n"; + } +# $epa_delay = $endp->avg_latency(); +# $utils->updateEndpoint($endp, $endpoint_names[($i+1)]); +# $epb_delay = $endp->avg_latency(); + } +# $ep_delay[($i+1)] = $epb_delay / $samples; +# $epa_delay = $epb_delay = 0; + $ep_delay = 0.0; +# if ($ep_delay[$i] > $max_delay || $ep_delay[($i+1)] > $max_delay) { + if ($ep_delay[$i] > $max_delay) { + $ep_dly_cnt++; + } # if $ep_delay > $max_delay + } # for $i Processing endpoint delay data + + if ($end_udp_drop && ($epa_drop || $epb_drop)) { + print "\nSTOP FURTHER PROCESSING !!!\n"; + last; # for $ci + } + + $prcnt_ep_dly = ($ep_dly_cnt / ($eib+1)) * 100.0; + if ($prcnt_ep_dly > $percent_ep_delay) { + $t_cx_run += $cx_run; + $t_ep_run = $t_cx_run * 2.0; + $t_ep_dly_cnt += $ep_dly_cnt; + $t_prcnt_ep_dly += $prcnt_ep_dly; + print "\n\n PERCENT DELAY EXCEEDED!!!\n"; + #if ($DEBUG > 99) { + print "\n"; + for ($i=0; $i<=$eib; $i++) { + print " Delay Exceeded, Endpoint: " . $endpoint_names[$i] . ", Delay: ". $ep_delay[$i] . " ms\n"; + } + print "\n ep_dly_cnt: $ep_dly_cnt, prcnt_ep_dly: $prcnt_ep_dly%" + . "\n loop: $loop" + . "\n t_cx_run: $t_cx_run, t_ep_dly_cnt: $t_ep_dly_cnt, t_prcnt_ep_dly: $t_prcnt_ep_dly" + . "\n"; + sleep ($D_PAUSE); + #} + #$avg_ep_dly_cnt + #do something like write out the ep delay data to file + #wonder if there is a way to use internal perl sort on the delay and still keep the endpoint + #name correctly indexed. + + last; # for $ci + } + # $ep_dly_cnt = 0; might need to transfer to average ep_dly_cnt for loops. + #perhaps, do array sort on delays don't see why if were checking for a certain percentage of delayed CXs + #sort would be slooow and painful + } #for $ci + $t_cx_run_loop[$loop] = $cx_run; + $cx_run = 0; + if ($keep_running) { + if ($loop < ($loop_max - 1)) { + doCmd("set_cx_state $test_mgr ALL STOPPED"); + } else { + last; # for $loop + } + } else { + doCmd("set_cx_state $test_mgr ALL STOPPED"); + } + + # SHOULD probably get throughput data for each pass + # need to save off each loops results. + $epa_drop = $epb_drop = 0; + doCmd("clear_cx_counters ALL"); +} #for $loop_max + +#save endpoints delays to file +print "\n\n*** RESULTS ****\n\n"; +$loop++; +if ($t_cx_run == 0 && $use_udp_probe) { + print "$cx_run connections were made.\n"; + print "No dropped packets were detected on the UDP connection.\n"; + print "Try increasing the number of connections.\n"; +} +#elsif ($use_udp_probe) { +# $avg_cx_run = int($t_cx_run / $loop); +# print "\n$loop test loops completed.\n" +# . "Average number of simultaneous connections: $avg_cx_run\n"; +#} +elsif ($t_cx_run == 0) { +# if ($DEBUG) { + for ($i=0; $i<$loop_max; $i++) { + print "Loop " . ($i+1) . ": " . $t_cx_run_loop[$i] . " simultaneous connections.\n"; + } +# } + print "$cx_run connections were made.\n" + . "Less than $percent_ep_delay% of endpoints exceeded $max_delay ms of delay.\n" + . "Actual percentage of endpoints that exceeded $max_delay ms of delay is $prcnt_ep_dly%.\n" + . "Try increasing the number of connections.\n"; +} +else { + $prcnt_ep_dlyd = ($t_ep_dly_cnt / $t_ep_run) * 100.0; + $avg_cx_run = ($t_cx_run / $loop); + $avg_ep_dly_cnt = ($t_ep_dly_cnt / $loop); + $avg_prcnt_ep_dly = ($t_prcnt_ep_dly / $loop); + my $mean_ep_dly = 0; + #for ($i=0; $i<=$eib; $i++) { + #my $t_ep_dly += $_ foreach @ep_delay; + #my $avg_ep_dly = $t_ep_dly / + + #} + if ($DEBUG > 1) { + print "\n"; + for ($i=0; $i<=$eib; $i++) { + print "[DEBUG] Endpoint: " . $endpoint_names[$i] . " - Delay: ". $ep_delay[$i] . " ms\n"; + } + } + for ($i=0; $i<$loop_max; $i++) { + print "Loop " . ($i+1) . ": " . $t_cx_run_loop[$i] . " simultaneous connections.\n"; + } + + print "\n" + . "$loop test loops completed.\n\n" + . "Over $percent_ep_delay% of endpoints exceeded $max_delay ms of delay.\n\n" + . "Total number of endpoints that exceeded $max_delay ms is $t_ep_dly_cnt\n" + . "Total number of endpoints run $t_ep_run\n" + . "Total percentage of delayed endpoints $prcnt_ep_dlyd%\n\n" + . "Average number of simultaneous connections per test loop is $avg_cx_run\n" + . "Average percentage of endpoints that exceeded $max_delay ms of delay per test loop is $avg_prcnt_ep_dly%\n" + . "Average number of endpoints exceeding $max_delay ms per test loop is $avg_ep_dly_cnt" + . "\n"; +} + + +if ($DEBUG) { printArgs(); } + +$dt = getDate(); +print "\nStarted $script_name script at : $dt_start\n"; +print "Completed $script_name script at: $dt\n\n"; +exit(0); +##################### +# END lf_macvlan.pl # +##################### +sub CLEANUP { +print "\n\n*** RESULTS ****\n\n"; +#save endpoints delays to file +$loop++; +if ($t_cx_run == 0 && $use_udp_probe) { + print "$cx_run connections were made.\n"; + print "No dropped packets were detected on the UDP connection.\n"; + print "Try increasing the number of connections.\n"; +} +#elsif ($use_udp_probe) { +# $avg_cx_run = int($t_cx_run / $loop); +# print "\n$loop test loops completed.\n" +# . "Average number of simultaneous connections: $avg_cx_run\n"; +#} +elsif ($t_cx_run == 0) { +# if ($DEBUG) { + for ($i=0; $i<$loop_max; $i++) { + print "Loop " . ($i+1) . ": " . $t_cx_run_loop[$i] . " simultaneous connections.\n"; + } +# } + print "$cx_run connections were made.\n" + . "Less than $percent_ep_delay% of endpoints exceeded $max_delay ms of delay.\n" + . "Actual percentage of endpoints that exceeded $max_delay ms of delay is $prcnt_ep_dly%.\n" + . "Try increasing the number of connections.\n"; +} +else { + $prcnt_ep_dlyd = ($t_ep_dly_cnt / $t_ep_run) * 100.0; + $avg_cx_run = ($t_cx_run / $loop); + $avg_ep_dly_cnt = ($t_ep_dly_cnt / $loop); + $avg_prcnt_ep_dly = ($t_prcnt_ep_dly / $loop); + my $mean_ep_dly = 0; + #for ($i=0; $i<=$eib; $i++) { + #my $t_ep_dly += $_ foreach @ep_delay; + #my $avg_ep_dly = $t_ep_dly / + + #} + if ($DEBUG > 1) { + print "\n"; + for ($i=0; $i<=$eib; $i++) { + print "[DEBUG] Endpoint: " . $endpoint_names[$i] . " - Delay: ". $ep_delay[$i] . " ms\n"; + } + } + for ($i=0; $i<$loop_max; $i++) { + print "Loop " . ($i+1) . ": " . $t_cx_run_loop[$i] . " simultaneous connections.\n"; + } + + print "\n" + . "$loop test loops completed.\n\n" + . "Over $percent_ep_delay% of endpoints exceeded $max_delay ms of delay.\n\n" + . "Total number of endpoints that exceeded $max_delay ms is $t_ep_dly_cnt\n" + . "Total number of endpoints run $t_ep_run\n" + . "Total percentage of delayed endpoints $prcnt_ep_dlyd%\n\n" + . "Average number of simultaneous connections per test loop is $avg_cx_run\n" + . "Average percentage of endpoints that exceeded $max_delay ms of delay per test loop is $avg_prcnt_ep_dly%\n" + . "Average number of endpoints exceeding $max_delay ms per test loop is $avg_ep_dly_cnt" + . "\n"; +} + + +if ($DEBUG) { printArgs(); } + +$dt = getDate(); +print "\nStarted $script_name script at : $dt_start\n"; +print "Exited $script_name script at: $dt\n\n"; + +exit (0); +} # CLEANUP + + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + my $szs = 0; + my $r = 0; + my @all_ports1 = @lf1_ports; + my @all_ports2 = (""); + my $j; + my $pname; + + if ($foundL4) { + my $p1 = new LANforge::Port(); + my $q; + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + for ($j = 0; $j<@lf1_ports; $j++) { + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + if ($ignore_phys_ports) { + for ($j = 0; $j<@lf1_ports; $j++) { + shift(@all_ports1); + } + } + } + else { + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + my $q; + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + + @all_ports2 = @lf2_ports; + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + my $q; + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + } + if ($ignore_phys_ports) { + for ($j = 0; $j<@lf1_ports; $j++) { + shift(@all_ports1); + } + for ($j = 0; $j<@lf2_ports; $j++) { + shift(@all_ports2); + } + } + } + + print "\nCreating endpoints on " . @all_ports1 . " ports:\nall_ports1: " . join(" ", @all_ports1); + +# if ($lf2orig ne "") { + print "\nCreating endpoints on " . @all_ports2 . " ports:\nall_ports2: " . join(" ", @all_ports2) . "\n\n"; +# } + +if ($DEBUG) { sleep($D_PAUSE); } + + if ($one_cx_per_port) { + my $j = 0; + my $cxcnt = 0; + my $fecnt = 0; + for ($j ; $j<@all_ports1; $j++) { + my $i = $cxcnt % @cx_types; + $cxcnt++; + + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "L4-${num[$ep]}"; +# $ep++; + my $ep2 = "D_L4-${num[$ep]}"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + if ($l4_dl_path = "/dev/null") { + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 $l4_timeout $urls_10m 'dl ${l4_urls[0]} $l4_dl_path' ' '"; + } + else { + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 $l4_timeout $urls_10m 'dl ${l4_urls[0]} $l4_dl_path/$ep1' ' '"; + } + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "L4-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + }# if L4 + elsif (($cxt eq "fileIONFS") || ($cxt eq "fileIOCIFS")) { + # Create File-IO endpoint + + my $FST = "nfs"; + if ($cxt eq "fileIOCIFS") { + $FST = "cifs"; + } + + my $ep1 = "fe-${num[$fecnt]}"; + my $ep2 = "D_$ep1"; + $fecnt++; + $ep++; +# $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_file_endp $ep2 $shelf $lf1 " . $all_ports1[$j] + . " fe_generic $min_rate[$r] $max_rate[$r] $min_rate[$r] $max_rate[$r]" + . " increasing $fio_base/$ep2 $ep2"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_file_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " fe_generic $min_rate[$r] $max_rate[$r] $min_rate[$r] $max_rate[$r]" + . " increasing \'$fio_base/$FST" + . "_$all_ports1[$j]" . $fio_targ_dir . "\' $ep1"; + doCmd($cmd); + + $cmd = "set_fe_info $ep1 16384 16384 10 1000000 1000000 \'$fio_base/$FST" . "_$all_ports1[$j]" + . $fio_targ_dir . "\' $ep1 $fsrw"; + doCmd($cmd); + + if ($r < (@min_rate - 1)) { + $r++; + } + else { + $r = 0; + } + + # Now, add the cross-connects + my $cx_name = "L4-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + }# elsif FIO + else { + # Create L3 endpoint + + my $burst = "NO"; + if ($min_rate[$r] != $max_rate[$r]) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$szs] != $max_pkt_szs[$szs]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "L3e-${num[$ep]}tx"; + $ep++; + my $ep2 = "L3e-${num[$ep]}rx"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . $max_pkt_szs[$szs] . + " $pattern NO"; + doCmd($cmd); + + if ($lf2 ne "") { +# die("Must have lf2 defined if using non-l4 endpoints."); + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . + $max_pkt_szs[$szs] . " $pattern NO"; + } + else { + $cmd = "add_endp $ep2 $shelf $lf1 " . $all_ports1[($j)] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . $max_pkt_szs[$szs] . + " $pattern NO"; + } + doCmd($cmd); + + if ($szs < (@min_pkt_szs - 1)) { $szs++; } + else { $szs = 0; } + if ($r < (@min_rate - 1)) { $r++; } + else { $r = 0; } + + # Now, add the cross-connects + my $cx_name = "L3-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + + if ($use_udp_probe && $first_run) { + $first_run = 0; + # Delete first CX and related Endpoints. + doCmd("rm_cx $test_mgr $cx_names[0]"); + doCmd("rm_endp $endpoint_names[0]"); + doCmd("rm_endp $endpoint_names[1]"); + + # Add UDP CX as first CX. + + $cmd = "add_endp $endpoint_names[0] $shelf $lf1 " . $all_ports1[0] . " lf_udp " + . " -1 $burst $min_rate[0] $max_rate[0] $szrnd " . $min_pkt_szs[0] . " " + . $max_pkt_szs[0] . " $pattern NO"; + doCmd($cmd); + + $cmd = "add_endp $endpoint_names[1] $shelf $lf2 " . $all_ports2[0] . " lf_udp " + . " -1 $burst $min_rate[0] $max_rate[0] $szrnd " . $min_pkt_szs[0] . " " + . $max_pkt_szs[0] . " $pattern NO"; + doCmd($cmd); + doCmd("add_cx $cx_names[0] $test_mgr $endpoint_names[0] $endpoint_names[1]"); + doCmd("set_cx_report_timer $test_mgr $cx_names[0] 1000"); + + if ($DEBUG > 99) { sleep ($D_PAUSE); } + } + }# else L3 + }#for all ports + }#one_cx_per_port = 1 + else { + my $j = 0; + my $n = 0; + my $fecnt = 0; + for ($j; $j<@all_ports1; $j++) { + for ($i = 0; $i<@cx_types; $i++) { + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + for ($n = 0; $n<@l4_urls; $n++) { + my $ep1 = "L4-${num[$ep]}"; +# $ep++; + my $ep2 = "D_L4-${num[$ep]}"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + if ($l4_dl_path = "/dev/null") { + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 $l4_timeout $urls_10m 'dl ${l4_urls[$n]} $l4_dl_path' ' '"; + } + else { + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " l4_generic 0 $l4_timeout $urls_10m 'dl ${l4_urls[$n]} $l4_dl_path/$ep1' ' '"; + } + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "L4-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } #for url_list + } + elsif (($cxt eq "fileIONFS") || ($cxt eq "fileIOCIFS")) { + # Create File-IO endpoint + my $FST = "nfs"; + if ($cxt eq "fileIOCIFS") { + $FST = "cifs"; + } + + my $ep1 = "fe-${num[$fecnt]}"; + my $ep2 = "D_$ep1"; + $fecnt++; + $ep++; +# $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_file_endp $ep2 $shelf $lf1 " . $all_ports1[$j] + . " fe_generic $min_rate[$r] $max_rate[$r] $min_rate[$r] $max_rate[$r]" + . " increasing $fio_base/$ep2 $ep2"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_file_endp $ep1 $shelf $lf1 " . $all_ports1[$j] + . " fe_generic $min_rate[$r] $max_rate[$r] $min_rate[$r] $max_rate[$r]" + . " increasing $fio_base/$FST" . "_$all_ports1[$j]" . $fio_targ_dir . " $ep1"; + doCmd($cmd); + + $cmd = "set_fe_info $ep1 16384 16384 10 1000000 1000000 $fio_base/$FST" + . "_$all_ports1[$j]" . $fio_targ_dir . " $ep1 $fsrw"; + doCmd($cmd); + + if ($r < (@min_rate - 1)) { $r++; } + else { $r = 0; } + + # Now, add the cross-connects + my $cx_name = "L4-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + # Create L3 endpoint + + my $burst = "NO"; + if ($min_rate[$r] != $max_rate[$r]) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$szs] != $max_pkt_szs[$szs]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "L3e-${num[$ep]}tx"; + $ep++; + my $ep2 = "L3e-${num[$ep]}rx"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . $max_pkt_szs[$szs] . + " $pattern NO"; + doCmd($cmd); + + + if ($lf2 ne "") { +# die("Must have lf2 defined if using non-l4 endpoints."); + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . + $max_pkt_szs[$szs] . " $pattern NO"; + } + else { + $cmd = "add_endp $ep2 $shelf $lf1 " . $all_ports1[$j+1] . " " . @cx_types[$i] . + " -1 $burst $min_rate[$r] $max_rate[$r] $szrnd " . $min_pkt_szs[$szs] . " " . $max_pkt_szs[$szs] . + " $pattern NO"; + } + doCmd($cmd); + + if ($szs < (@min_pkt_szs - 1)) { $szs++; } + else { $szs = 0; } + if ($r < (@min_rate - 1)) { $r++; } + else { $r = 0; } + + # Now, add the cross-connects + my $cx_name = "L3-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + + if ($use_udp_probe && $first_run) { + $first_run = 0; + # Delete first CX and related Endpoints. + doCmd("rm_cx $test_mgr $cx_names[0]"); + doCmd("rm_endp $endpoint_names[0]"); + doCmd("rm_endp $endpoint_names[1]"); + + # Add UDP CX as first CX. + + $cmd = "add_endp $endpoint_names[0] $shelf $lf1 " . $all_ports1[0] . " lf_udp " + . " -1 $burst $min_rate[0] $max_rate[0] $szrnd " . $min_pkt_szs[0] . " " + . $max_pkt_szs[0] . " $pattern NO"; + doCmd($cmd); + + $cmd = "add_endp $endpoint_names[1] $shelf $lf2 " . $all_ports2[0] . " lf_udp " + . " -1 $burst $min_rate[0] $max_rate[0] $szrnd " . $min_pkt_szs[0] . " " + . $max_pkt_szs[0] . " $pattern NO"; + doCmd($cmd); + doCmd("add_cx $cx_names[0] $test_mgr $endpoint_names[0] $endpoint_names[1]"); + doCmd("set_cx_report_timer $test_mgr $cx_names[0] 1000"); + + if ($DEBUG > 99) { sleep ($D_PAUSE); } + } + } + }#for cx types + }#for each port + }#each cx per port +}#addCrossConnects +sub initToDefaults { + # Clean up database if stuff exists + if ($DEBUG) { + print "\nsub initToDefaults\n"; + } + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + +my $lsb1 = sprintf("%d", $mac1); +my $lsb2 = sprintf("%d", $mac2); +my $lsb3 = sprintf("%d", $mac3); + +# Return a unique MAC address using last 3 octets +sub getNextMac { + $lsb1++; + if ($lsb1 > 255) { + $lsb2++; + $lsb1 = 0; + if ($lsb2 > 255) { + $lsb3++; + $lsb2 = 0; + if ($lsb3 > 255) { + print "*** WARNING, MAC address rolling over XX:YY:ZZ:ff:ff:ff ***\n"; + $lsb3 = 0; + } + } + } + $mac1 = sprintf("%02x", $lsb1); + $mac2 = sprintf("%02x", $lsb2); + $mac3 = sprintf("%02x", $lsb3); + return "$mac3:$mac2:$mac1"; +} # getNextMac + +sub addMacVlans { + if ($DEBUG) { + print "\nsub addMacVlans\n"; + } + if ($mac_init == 1 ) { + $lsb1 = sprintf("%d", $mac1); + $lsb2 = sprintf("%d", $mac2); + $lsb3 = sprintf("%d", $mac3); + } + my $i; + my $q; + my $pnum1; + my $pnum2; + my $throttle = $script_speed; + my $since_throttle = 0; + for ($i = $start_mvlan; $i<($num_mvlans + $start_mvlan); $i++) { + for ($q = 0; $q<@lf1_ports; $q++) { + + $pnum1 = $lf1_ports[$q]; + my $shlf = sprintf("%02x", $shelf); + my $card = sprintf("%02x", $lf1); + my $mac_index = getNextMac(); + my $mac_addr = "00:$shlf:$card:$mac_index"; + doCmd("add_mvlan $shelf $lf1 $pnum1 $mac_addr $i"); + + $pnum2 = $lf2_ports[$q]; + if ($pnum2 ne "") { + $card = sprintf("%02x", $lf2); + $mac_index = getNextMac(); + $mac_addr = "00:$shlf:$card:$mac_index"; + doCmd("add_mvlan $shelf $lf2 $pnum2 $mac_addr $i"); + } + if ($DEBUG > 1) { sleep($D_PAUSE); } + + # Throttle ourself so we don't over-run the poor LANforge system. + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + if ($pnum2 ne "") { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + } + $since_throttle = 0; + } + } + } + + doCmd("probe_ports"); + + # Wait until we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($pnum2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = $start_mvlan; $i<($num_mvlans + $start_mvlan); $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($pnum2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($pnum2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } +}#addMacVlans + +# Wait until the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2orig ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + if ($lf2orig ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + if ($tmp ne "0") { + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + if ($lf2orig ne "") { + if ($tmp2 ne "0") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + } + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i; + for ($i = 0; $i<@lf1_ports; $i++) { + +# if ($ip_lsb > 250) { +# $ip_c++; +# $ip_lsb = 2; +# } + + my $ptmp = $lf1_ports[$i]; + my $ptmp2 = $lf2_ports[$i]; +# my $cmd = ""; + if (!$ignore_phys_ports) { +# $cmd = "set_port $shelf $lf1 $ptmp $ip_base1.$ip_c1.$ip_lsb1 $msk1 " . +# "$ip_gw1 NA NA NA"; + $cmd = "set_port $shelf $lf1 $ptmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"; + if ($ptmp ne "") { + doCmd($cmd); + } +# $ip_lsb++; + if ($ptmp2 ne "") { +# $cmd = "set_port $shelf $lf2 $tmp2 $ip_base1.$ip_c1.$ip_lsb1 $msk1 " . +# "$ip_gw1 NA NA NA"; + $cmd = "set_port $shelf $lf2 $ptmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"; + doCmd($cmd); +# $ip_lsb++; + } + } + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $ptmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = $script_speed; + my $since_throttle = 0; + + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + $cmd = "set_port $shelf $lf1 $pname\#$q " . + "$ip_base[$i].$ip_c[$i].$ip_lsb[$i] $msk[$i] " . + "$ip_gw[$i] NA NA NA"; + doCmd($cmd); + $ip_lsb[$i]++; + + if ($ip_lsb[$i] > 250) { + $ip_c[$i]++; + $ip_lsb[$i] = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$q"); + $since_throttle = 0; + } + } + +# $ip_lsb++; + + if ($ptmp2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $ptmp2); + $pname = $p1->{dev}; + + for ($q = $start_mvlan; $q<($num_mvlans + $start_mvlan); $q++) { + if (@ip_base == 1) { + $cmd = "set_port $shelf $lf2 $pname\#$q " . + "$ip_base[$i].$ip_c[$i].$ip_lsb[$i] $msk[$i] " . + "$ip_gw[$i] NA NA NA"; + doCmd($cmd); + $ip_lsb[$i]++; + + if ($ip_lsb[$i] > 250) { + $ip_c[$i]++; + $ip_lsb[$i] = 2; + } + } + else { + $cmd = "set_port $shelf $lf2 $pname\#$q " . + "$ip_base[$i+1].$ip_c[$i+1].$ip_lsb[$i+1] $msk[$i+1] " . + "$ip_gw[$i+1] NA NA NA"; + doCmd($cmd); + $ip_lsb[$i+1]++; + + if ($ip_lsb[$i+1] > 250) { + $ip_c[$i+1]++; + $ip_lsb[$i+1] = 2; + } + } + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pname\#$q"); + $since_throttle = 0; + } + } # for $q + } # if we have an lf2_ports defined + } +} + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + sleep($cli_cmd_delay); +} + +sub getDate { + my $date = `date`; + chomp($date); + return $date +} + +sub printArgs { + print + "\n$script_name" + . "\nModified arguments:" + . "\ninit: $INIT" + . "\nmanager: $lfmgr_host\n" + . "\nlf1: $lf1\nlf2: $lf2\n" + . "\nlf1_ports: " . join(" ", @lf1_ports) + . "\nlf2_ports: " . join(" ", @lf2_ports) . "\n" + . "\nstart_macvlans: $start_mvlan" + . "\nnum_mvlans: $num_mvlans\n" + . "\nmin_rates: " . join(" ", @min_rate) + . "\nmax_rates: " . join(" ", @max_rate) + . "\nmin_pkt_sizes: " . join(" ", @min_pkt_szs) + . "\nmax_pkt_sizes: " . join(" ", @max_pkt_szs) . "\n" + . "\ncx_types: " . join(" ", @cx_types) + . "\none_cx_per_port: $one_cx_per_port\n\n" + . "\n" + . "Available CX types: " . join(", ", @cxts) . "\n" + . "Total of each CX type: " . join(", ", @t_cxts) . "\n" + . "Total number of ports: $t_ports\n" + . "Total number of urls: " . @l4_urls . "\n" + . "Total number of endpoints and CXs: $t_num\n" + . "\n\n"; +} + +sub printHelp { + print + "\n$script_name\n" + . "USAGE: mgr=[ip-of-mgr] speed=[25|n] slowsys_wait=[0|n] DEBUG=[0|1|2|...] D_PAUSE=[3|n]\n" + . " config_once=[0|1] init=[0|1] init_net=[1|0] init_tests=[1|0] exit_running=[0|1]\n" + . " test_loops=[3|n] cli_cmd_dly=[0|1] quiet_cli_cmds=[0|1] quiet_cli_output=[0|1]\n" + . " test_mgr=\"ben_tm\" first_run=[1|0] rpt_timer=[9000|n]\n" + . " first_name_id=[0|n] id_len=[0|n]\n" + . " create_only=[0|1] one_cx_per_port=[0|1] ignore_phys_ports=[1|0]\n" + . " lf1=X lf2=Y\n" + . " lf1_ports=[\"1 2 3\"|\"eth2 eth3\"] lf2_ports=[\"4 5 6\"|\"eth4 eth5\"]\n" + . " start_mvl=X num_mvl=X\n" + . " mac3=0xf0 mac2=0xbe mac1=0xef\n" + . " ip_base= \"192.168 172.16\"\n" + . " ip_c = \"2 1\"\n" + . " ip_lsb = \"2 2\"\n" + . " ip_msk =\"255.255.0.0 255.255.0.0\"\n" + . " ip_gw =\"192.168.2.1 172.16.1.1\"\n" + . " cx_types=\"lf lf_udp lf_tcp custom_udp custom_tcp l4 fileIONFS fileIOCIFS\"\n" + . " min_rates=\"9600 56000 128000\" max_rates=\"56000 128000 25600\"\n" + . " min_pkt_sizes=\"500 500 500\" max_pkt_sizes=\"1000 1000 1000\"\n" + . " url_rate=100 l4_wait=10000\n" + . " urls=\"http://www.candelatech.com/file ftp://www.candelatech.com/file https://www.candelatech.com/file\"\n" + . " fsrw=[read|write] fio_targ_dir=tmp/ fio_base=/mnt/fio_base\n" + . "\n"; + +} + +sub handleCmdLineArg { + my $arg = $_[0]; + my $val = $_[1]; + + if ($arg eq "help" || $arg eq "--help" || $arg eq "-h" || $arg eq "-help" || $arg eq "-h" ) { + printHelp(); + exit(0); + } + elsif ($arg eq "debug" || $arg eq "DEBUG") { + $DEBUG = $val; + } + elsif ($arg eq "d_pause" || $arg eq "D_PAUSE") { + $D_PAUSE = $val; + } + elsif ($arg eq "mgr") { + $lfmgr_host = $val; + } + elsif ($arg eq "test_mgr") { + $test_mgr = $val; + } + elsif ($arg eq "init") { + $INIT = $val; + } + elsif ($arg eq "config_once") { + $init_once = $val; + } + elsif ($arg eq "init_net") { + $init_net = $val; + } + elsif ($arg eq "init_tests") { + $init_tests = $val; + } + elsif ($arg eq "exit_running") { + $keep_running = $val; + } + elsif ($arg eq "test_loops") { + $loop_max = $val; + } + elsif ($arg eq "cli_cmd_dly") { + $cli_cmd_delay = $val; + } + elsif ($arg eq "quiet_cli_cmds") { + $quiet_cli_cmds = $val; + } + elsif ($arg eq "quiet_cli_output") { + $quiet_cli_output = $val; + } + elsif ($arg eq "first_run") { + $first_run = $val; + } + elsif ($arg eq "rpt_timer") { + $report_timer = $val; + } + elsif ($arg eq "first_name_id") { + $name_id = $val; + } + elsif ($arg eq "id_len") { + $name_id_len = $val; + if (length($name_id) > $name_id_len) { + print "\nWARNING: id_len specifies a string length less that first_name_id.\n"; + } + } + elsif ($arg eq "speed") { + $script_speed = $val; + } + elsif ($arg eq "slowsys_wait") { + $ss_wait = $val; + } + elsif ($arg eq "lf1") { + $lf1 = $val; + } + elsif ($arg eq "lf2") { + $lf2 = $val; + if ($lf1 == $lf2) { + die("\nINVALID: First and second resource are the same !!!\n\n"); + } + } + elsif ($arg eq "mac3") { + $mac3 = $val; + } + elsif ($arg eq "mac2") { + $mac2 = $val; + } + elsif ($arg eq "mac1") { + $mac1 = $val; + } + elsif ($arg eq "ip_base") { + @ip_base = split(/ /, $val); + } + elsif ($arg eq "ip_c") { + @ip_c = split(/ /, $val); + } + elsif ($arg eq "ip_lsb") { + @ip_lsb = split(/ /, $val); + } + elsif ($arg eq "ip_msk") { + @msk = split(/ /, $val); + } + elsif ($arg eq "ip_gw") { + @ip_gw = split(/ /, $val); + } + elsif ($arg eq "lf1_ports") { + @lf1_ports = split(/ /, $val); + } + elsif ($arg eq "lf2_ports") { + if ($lf2 == "" || $lf1 == $lf2) { + die("\nINVALID: Either second resource is not defined\nor first and second resource are the same !!!\n\n"); + } + else { + @lf2_ports = split(/ /, $val); + } + } + elsif ($arg eq "cx_types") { + @cx_types = split(/ /, $val); + } + elsif ($arg eq "min_pkt_sizes") { + @min_pkt_szs = split(/ /, $val); + } + elsif ($arg eq "max_pkt_sizes") { + @max_pkt_szs = split(/ /, $val); + } + elsif ($arg eq "start_mvl") { + $start_mvlan = $val; + } + elsif ($arg eq "num_mvl") { + $num_mvlans = $val; + } + elsif ($arg eq "min_rates") { + @min_rate = split(/ /, $val); + } + elsif ($arg eq "max_rates") { + @max_rate = split(/ /, $val); + } + elsif ($arg eq "fsrw") { + $fsrw = $val; + } + elsif ($arg eq "fio_base") { + $fio_base = $val; + } + elsif ($arg eq "fio_targ_dir") { + $fio_targ_dir = $val; + } + elsif ($arg eq "urls") { + @l4_urls = split(/ /, $val); + } + elsif ($arg eq "url_rate") { + $urls_10m = $val; + } + elsif ($arg eq "l4_wait") { + $l4_timeout = $val; + } + elsif ($arg eq "one_cx_per_port") { + $one_cx_per_port = $val; + } + elsif ($arg eq "ignore_phys_ports") { + $ignore_phys_ports = $val; + } + elsif ($arg eq "create_only") { + $create_only = $val; + } + else { + print "\n\nCould not parse one or more of the arguments !!!\n" + . "First rejected argument: $arg\n"; + printHelp(); + exit(1); + } +} diff --git a/lanforge/lanforge-scripts/lf_mcast.bash b/lanforge/lanforge-scripts/lf_mcast.bash new file mode 100755 index 000000000..8fb170211 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_mcast.bash @@ -0,0 +1,39 @@ +#!/bin/bash + +# Example script that creates and starts some multicast endpoints using +# the lf_firemod.pl script. Lots of hard-coded variables in this +# file that could become command-line switches, or could be re-implemented +# in perl or some other favorite scripting language. + +xmit_count=200 +rcv_count=100 # Could create more of these and only start a subset + +lf_mgr=192.168.100.212 +resource=3 +quiet=no +report_timer=1000 + +# Create and start transmitters +for ((i=0; i<$xmit_count; i+=1)) +do + port_num=$((10000 + i)) + # Creat transmitter endpoint + ./lf_firemod.pl --action create_endp --endp_name mcast_xmit_$i --speed 154000 --endp_type mc_udp --mcast_addr 224.9.9.$i --mcast_port $port_num --rcv_mcast NO --port_name eth1 --min_pkt_sz 1472 --max_pkt_sz 1472 --use_csums NO --ttl 32 --mgr $lf_mgr --resource $resource --quiet $quiet --report_timer $report_timer + + # Start transmitter + ./lf_firemod.pl --endp_name mcast_xmit_$i --action start_endp --mgr $lf_mgr +done + +# Create and start receivers. +for ((i=0; i<$rcv_count; i+=1)) +do + port_num=$((10000 + i)) + ./lf_firemod.pl --action create_endp --endp_name mcast_rcv_$i --speed 0 --endp_type mc_udp --mcast_addr 224.9.9.$i --mcast_port $port_num --rcv_mcast YES --port_name sta2 --use_csums NO --mgr $lf_mgr --resource $resource --quiet $quiet --report_timer $report_timer + + # Start receiver + ./lf_firemod.pl --endp_name mcast_rcv_$i --action start_endp --mgr $lf_mgr +done + + +# Script could then randomly start and stop the receivers +# to cause multicast join and leave messages. diff --git a/lanforge/lanforge-scripts/lf_monitor.pl b/lanforge/lanforge-scripts/lf_monitor.pl new file mode 100755 index 000000000..4fe2ed9e4 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_monitor.pl @@ -0,0 +1,259 @@ +#!/usr/bin/perl -w +# This program is used to monitor and manage Layer4 connections +# +# Written by Candela Technologies Inc. + +use strict; +use warnings; +use Carp; +# Un-buffer output +$| = 1; +use lib '/home/lanforge/scripts'; +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +use constant NA => "NA"; +use constant NL => "\n"; +our $shelf_num = 1; +our $utils; +# Default values for ye ole cmd-line args. +our $resource = 1; +our $quiet = "yes"; +our $cx_name = ""; +our $do_cmd = NA; +our $action = "show_port"; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; +our $cx_vals = undef; +our $stop_at = ""; +our $fail_msg = ""; +our $interval = 10; +our $reqs_sufx = qq<reqs*|requests*|urls*>; +our $bytes_sufx = qq<bytes*>; +our $secs_sufx = qq<secs*|seconds*>; +our $known_suffixes = qq<$reqs_sufx|$bytes_sufx|$secs_sufx>; + +our $rx_bytes = 0; +our $url_count = 0; +our $runtime = 0; +our $is_running = 0; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## +# nice but not requested +# show_endp output can be narrowed with key-value arguments +#[--cx_vals {key,key,key,key}] +# Examples: +# --action show_cx --cx_vals MinTxRate,DestMAC,Avg-Jitter + +my $usage = "$0 --action { show_cx | watch_cx | list_cx } ] + [--mgr {host-name | IP}] + [--mgr_port {ip port}] + [--cx_name {name}] + [--resource {number}] + [--interval {number of seconds}] + [--stop_at {[seconds]sec | [requests]req | [transferred]bytes} + req can also be: requests reqs url urls + [--quiet { yes | no }] + +Example: + $0 --mgr jedtest --action watch_cx --cx_name gl4g00 --interval 2 --stop_at 3urls +"; + +my $i = 0; + +GetOptions +( + 'action|a=s' => \$action, + 'cx_name|e=s' => \$cx_name, + 'cx_vals|o=s' => \$cx_vals, + 'mgr|m=s' => \$lfmgr_host, + 'mgr_port|p=i' => \$lfmgr_port, + 'resource|r=i' => \$resource, + 'quiet|q=s' => \$quiet, + 'stop_at|s=s' => \$stop_at, + 'interval|i=i' => \$interval, +) || do_err_exit("$usage"); + +if ($do_cmd ne "NA") { + $action = "do_cmd"; +} + +if (!(($action eq "show_cx") || + ($action eq "watch_cx") || + ($action eq "list_cx") || + ($action eq "list_ports"))) { + do_err_exit("Invalid action: $action\n$usage\n"); +} + +do_err_exit("mgr should not be empty; $usage") if ("$lfmgr_host" eq "" ); +do_err_exit("mgr_port should not be empty; $usage") if ("$lfmgr_port" eq "" ); +do_err_exit("resource should not be empty; $usage") if ("$resource" eq "" ); + +if ($action eq "show_cx") { + do_err_exit("cx_name should not be empty; $usage") if ("$cx_name" eq "" ); +} +elsif( $action eq "watch_cx") { + do_err_exit("stop_at should be greater than zero; $usage") if ("$stop_at" eq ""); + do_err_exit("interval should be greater than zero; $usage") if ($interval < 1 ); + do_err_exit("cx_name should not be empty; $usage") if ("$cx_name" eq "" ); + + if ($stop_at !~ /^\d+($known_suffixes)$/) { + do_err_exit("stop_at should not have spaces and should end with $known_suffixes; $usage"); + } +} + +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +sub do_err_exit { + my $errmsg = shift; + print $errmsg.NL; + exit(1); +} + +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# Open connection to the LANforge server. +# Wait up to 20 seconds when requesting info from LANforge. +sub init { + my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); + + $t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + + $t->waitfor("/btbits\>\>/"); + + $::utils = new LANforge::Utils(); + $::utils->connect($lfmgr_host, $lfmgr_port); +} +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +sub stop_cx { + my $_name = $::cx_name; + $_name = "CX_".$::cx_name if ( $::cx_name !~ /^CX_/); + my $result = $utils->doAsyncCmd("set_cx_state default_tm $_name STOPPED"); + print $result.NL; +} +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +sub summarize_cx { + my $name = $::cx_name; + do_err_exit("please call summarize_cx() with endpoint name") if (!defined $name || "$name" eq ""); + + $name = "CX_".$::cx_name if ( $::cx_name !~ /^CX_/); + my @lines = split(NL, $::utils->doAsyncCmd("show_cxe default_tm $name")); + + for my $line (@lines) { + chomp $line; + if ( $line =~ /^L4Endp /) { + ($line =~ /^L4Endp .*? \((\w+)\)/); + $::is_running = ("$1" eq "RUNNING") ? 1 : 0; + } + if ( $line =~ / RunningFor: /) { + ($::runtime) = ($line =~ / RunningFor: (\d+s) /); + } + if ( $line =~ / URLs Processed: / ) { + ($::url_count) = ($line =~ / Total: (\d+) /); + } + if ( $line =~ / Bytes Read: / ) { + ($::rx_bytes) = ($line =~ / Total: (\d+) /); + } + } +} +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + + + +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +## +## M A I N +## +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +# begin our connection. +init(); + +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +if( $action eq "list_cx") { + my @lines = split(NL, $utils->doAsyncCmd("show_endpoints")); + my $msg = ""; + my $l4_flag = 0; + my $print_flag = 0; + for my $line (@lines) { + chomp $line; + + $l4_flag = 1 if ( $line =~ /^L4Endp /); + next if (! $l4_flag); + + if ( $line =~ /^L4Endp /) { + ($msg) = ($line =~ /^L4Endp (.*)$/); + } + if ( $line =~ /^\s+URL: /) { + (my $u) = ($line =~ /^\s+URL: \S+ (\S+) /); + $msg .= " $u"; + $print_flag = 1; + } + if ( $print_flag ) { + print $msg . NL; + $l4_flag = 0; + $print_flag = 0; + $msg = ''; + } + } + exit 0; +} + +## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +if ($action eq "show_cx") { + my $_name = $::cx_name; + $_name = "CX_".$::cx_name if ( $::cx_name !~ /^CX_/); + print $utils->doAsyncCmd("show_cxe default_tm $_name") . NL; + exit 0; +} + +if( $action eq "watch_cx") { + my $thresh; + ($thresh) = ( $stop_at =~ /^(\d+)\w+$/); + do_err_exit("stop_at should be greater than zero; $usage") if ("$stop_at" eq ""); + do_err_exit("stop_at should be greater than zero; $usage") if ($thresh < 1); + do_err_exit("interval should be greater than zero; $usage") if ($interval < 1 ); + do_err_exit("cx_name should not be empty; $usage") if ("$cx_name" eq "" ); + + summarize_cx( $cx_name ); + my $continue = 1; + while ($continue) { + sleep $interval; + summarize_cx( $cx_name ); + print "$cx_name: " .($is_running ? "active":"inactive"); + print " $::runtime, $::url_count urls, $::rx_bytes bytes\n"; + + # now check for bailout + #print "Thresh $thresh | $stop_at | runtime $::runtime urls $::url_count rx $::rx_bytes\n"; + if ( $stop_at =~ /^\d+$secs_sufx$/ ) { + my ($rtime) = ($::runtime =~ /^(\d+)s/); + if ($rtime >= $thresh) { + $continue = 0; + } + } + elsif ( $stop_at =~ /^\d+($reqs_sufx)$/) { + if ($::url_count >= $thresh) { + $continue = 0; + } + } + elsif ( $stop_at =~ /^\d+$bytes_sufx*$/ ) { + if ($::rx_bytes >= $thresh) { + $continue = 0; + } + } + } + stop_cx(); + print "connection $cx_name stopped.\n"; +} + +#eof diff --git a/lanforge/lanforge-scripts/lf_netoptics.pl b/lanforge/lanforge-scripts/lf_netoptics.pl new file mode 100755 index 000000000..c73937f73 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_netoptics.pl @@ -0,0 +1,762 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections to load-test pairs of ports. +# The user does not need to give many details..the script attempts +# to configure connections with optimal values for maximum throughput. + +# Un-buffer output +$| = 1; + +# This breaks Net::Telnet...gah! +#use bigint; + +use strict; +#use Switch; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; + +my @cx_types = (); + +my $test_mgr = "netoptics_tm"; +my $report_timer = 1000; # Set report timer for all tests created in ms, i.e. 8 seconds + +my $lfmgr_host = "127.0.0.1"; +my $lfmgr_port = 4001; + +my $shelf = 1; + +# This sets up connections. +my $lf1 = 1; # Minor Resource EID of first LANforge resource. + +my @lf1_ports = (); + +my $num_vlans = 3; # .1q vlans per physical port +my $vid = "RANDOM"; +my $vlan_mac = "RANDOM"; +my $num_mvlans = 3; # mac-vlans per .1q vlan +my $mvlan_mac = "RANDOM"; +my $num_cxs = 5; # CXs per MVL pair (or endpoints per MVL) +my $ipaddr = "DHCP"; +my $mask = "255.255.0.0"; +my $subnet_per_vl = 1; + +my $multicon = "AUTO"; +my $duration = 10 * 60 * 1000; # 10 minutes by default +my $max_rate = 10000000000; +my $max_pkt_sz = "AUTO"; +my $dbname = "netoptics-scr"; +my $clear_port_on_start = 1; +my $group_prefix = "L3"; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $ports_rpt = "Interface VID MAC IP\n"; + +# Parse cmd-line args +my $i; +for ($i = 0; $i<@ARGV; $i++) { + my $var = $ARGV[$i]; + if ($var =~ m/(\S+)=(.*)/) { + my $arg = $1; + my $val = $2; + handleCmdLineArg($arg, $val); + } + else { + handleCmdLineArg($var); + } +} + +if (@cx_types == 0) { + @cx_types = ("lf_tcp"); +} + +if (@lf1_ports < 2) { + print("ERROR: Must specify two base ports, ie: --portA=eth1 --portB=eth2\n"); + exit(1); +} + +if ($lfmgr_host eq undef) { + print "\nYou must define a LANforge Manager!!!\n\n" + . "For example:\n" + . "./lf_netoptics.pl --mgr=localhost\n" + . "OR\n" + . "./lf_netoptics.pl --mgr=192.168.1.101\n\n"; + printHelp(); + exit (1); +} + +print + "\nStarting script with the following arguments:" + . "\nmanager: $lfmgr_host:$lfmgr_port" + . "\nlf1: $lf1" + . "\nlf1_ports: " . join(" ", @lf1_ports) + . "\nipaddr: $ipaddr" + . "\nsubnet-per-vlan: $subnet_per_vl" + . "\nnum_mvlans: $num_mvlans" + . "\nmax_rate: $max_rate" + . "\nmax_pkt_size: $max_pkt_sz" + . "\ncx_types: " . join(" ", @cx_types) + . "\nnum_cxs: $num_cxs\n\n"; + +# Run some logic tests. +if (1) { + my $tst_ip = "99.99.99.2"; + my $tsti = toIpString($tst_ip); + my $tips = toStringIp($tsti); + if ($tst_ip ne $tips) { + print ("tst-ip: $tst_ip as-integer: $tsti as-string-again: $tips\n"); + die("bug"); + } +} + +# Open connection to the LANforge server. +my $t = new Net::Telnet(Timeout => 15, + #Dump_Log => "lf_netoptics.log", + Prompt => '/default\@btbits\>\>/'); + +$t->telnetmode(0); # Not true telnet protocol +$t->max_buffer_length(1024 * 1024 * 10); # 10M buffer + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 45); + +$t->waitfor('/.*btbits\>\>.*/'); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + +my $dt = getDate(); +my $dt_start = $dt; + + +initToDefaults(); + +doCmd("add_tm $test_mgr"); +doCmd("tm_register $test_mgr default"); # Add default user +doCmd("tm_register $test_mgr default_gui"); # Add default GUI user +doCmd("tm_register $test_mgr Admin"); + +my $i; +my $p; +my $q; +my $m; +my $ip; + +# For each port, add .1q vlans. +# For each .1q vlan, add mac-vlans + +# Create list of IP addresses, one for each mac-vlan. +if ($ipaddr eq "RANDOM") { + # basically, just randomize the middle two octets + $ip = (10 << 24) + int(rand(1<<23)); + $ip &= 0xffffff00; + $ip |= 2; +} +else { + if ($ipaddr eq "DHCP") { + $ip = 0; + } + else { + $ip = toIpString($ipaddr); + print "IP-addr: $ipaddr (as int: $ip)\n"; + } +} +my @vl_ips = (); +for ($q = 0; $q<$num_vlans; $q++) { + @vl_ips = (@vl_ips, $ip); + if ($subnet_per_vl) { + if ($ipaddr eq "RANDOM") { + # basically, just randomize the middle two octets + $ip = (10 << 24) + int(rand(1<<23)); + $ip &= 0xffffff00; + $ip |= 2; + } + else { + if ($ipaddr eq "DHCP") { + $ip = 0; + } + else { + my $maski = toIpString($mask); + print "maski: $maski ip: $ip\n"; + $ip += ~$maski; + $ip &= $maski; + $ip |= 2; + print "after: ip: $ip\n"; + } + } + } + else { + $ip++; + } +} + +my @ips = (); +for ($p = 0; $p<@lf1_ports; $p++) { + for ($q = 0; $q<$num_vlans; $q++) { + for ($m = 0; $m<$num_mvlans; $m++) { + if ($subnet_per_vl) { + my $ip = $vl_ips[$q]; + @ips = (@ips, $ip); + $ip++; + $vl_ips[$q] = $ip; + } + else { + @ips = (@ips, $ip); + $ip++; + } + } + } +} + +my $total_mvlans = @lf1_ports * $num_vlans * $num_mvlans; + +# Build list of VIDs, we want same VID on each different +# physical/base port. +my $myvid; +if ($vid eq "RANDOM") { + $myvid = int(rand(4094)); + if ($myvid <= 0) { + $myvid = 1; + } +} +else { + $myvid = $vid; +} + +my @vids = ($myvid); +for ($q = 0; $q < ($num_vlans - 1); $q++) { + my $myvid; + if ($vid eq "RANDOM") { + $myvid = int(rand(4094)); + if ($myvid <= 0) { + $myvid = 1; + } + } + else { + $vid++; + $myvid = $vid; + } + @vids = (@vids, $myvid); +} + + +my $do_simple_names = (@lf1_ports == 2); + +my $ip_idx = 0; +for ($p = 0; $p<@lf1_ports; $p++) { + for ($q = 0; $q<$num_vlans; $q++) { + # Create .1q vlan + my $myvid = $vids[$q]; + my $vname = $lf1_ports[$p] . ".$myvid"; + doCmd("add_vlan $shelf $lf1 $lf1_ports[$p] $myvid $vname 8000"); + + if ($vlan_mac ne "PARENT") { + my $mac_addr; + if ($vlan_mac eq "RANDOM") { + $mac_addr = getNextMac($vlan_mac); + } + else { + $mac_addr = $vlan_mac; + } + doCmd("set_port $shelf $lf1 $vname NA NA NA NA NA $mac_addr"); + if ($vlan_mac ne "RANDOM") { + $vlan_mac = getNextMac($vlan_mac); + } + } + + # Create mac-vlans + for ($m = 0; $m<$num_mvlans; $m++) { + + my $mac_addr; + if ($mvlan_mac eq "RANDOM") { + $mac_addr = getNextMac($mvlan_mac); + } + else { + $mac_addr = $mvlan_mac; + } + + my $mvname = "$vname#$m"; + doCmd("add_mvlan $shelf $lf1 $vname $mac_addr $m $mvname"); + + my $ips = toStringIp($ips[$ip_idx]); + $ip_idx++; + my $masks = $mask; + my $interest_flags = 0x4000 | 0x4 | 0x8 ; # dhcp, IP, Mask + my $cur_flags = 0; + if ($ipaddr eq "DHCP") { + $masks = "0.0.0.0"; + $cur_flags = 0x80000000; # use-dhcp + } + + # Set up IP addressing on the mac-vlan + doCmd("set_port $shelf $lf1 $mvname $ips $masks NA NA $cur_flags NA NA NA NA $interest_flags"); + + $ports_rpt .= "$mvname $myvid $mac_addr $ips\n"; + + # Now, create endpoints on this port. + my $e; + for ($e = 0; $e < $num_cxs; $e++) { + my $burst = "NO"; + my $szrnd = "NO"; + my $pattern = "increasing"; + my $ep1 = "$group_prefix-$p.$q#$m-$e"; + my $etype = $cx_types[$e % @cx_types]; + my $rate = int($max_rate / $num_cxs); + my $pdu_sz = getPduSize($etype, $max_rate); + my $mcon = $multicon; + if ($mcon eq "AUTO") { + if ($max_rate > 1000000000) { + $mcon = 1; + } + else { + $mcon = 0; + } + } + my $cmd = "add_endp $ep1 $shelf $lf1 $mvname $etype -1 $burst $rate $rate $szrnd $pdu_sz $pdu_sz $pattern NO NA NA $mcon"; + doCmd($cmd); + if ($clear_port_on_start) { + doCmd("set_endp_flag $ep1 ClearPortOnStart 1"); + } + } + + if ($mvlan_mac ne "RANDOM") { + $mvlan_mac = getNextMac($mvlan_mac); + } + } + } +}#for all ports + +my $pdu_sz = getPduSize($cx_types[0], $max_rate); +my $flags = 4; # symmetric +my $script_body = "my-script $flags Script2544 '$duration 5000 bps,$max_rate $pdu_sz 50000,100000,500000,100000,0 bps,$max_rate $pdu_sz 0 NONE' ALL 0"; + +# Add cross-connects between the endpoints on port-pairs. +for ($p = 0; $p<@lf1_ports; $p += 2) { + # Add test-group for this port-pair + my $pgname = "$group_prefix-$p"; + if ($do_simple_names) { + $pgname = "$group_prefix-all"; + } + doCmd("add_group $pgname 4 4"); + doCmd("set_script $pgname $script_body"); + + for ($q = 0; $q<$num_vlans; $q++) { + my $myvid = $vids[$q]; + + # Add test-group for this vlan-pair + my $vgname = "$group_prefix-$p.v$myvid"; + if ($do_simple_names) { + $vgname = "$group_prefix-all-v$myvid"; + } + + doCmd("add_group $vgname 4 4"); + doCmd("set_script $vgname $script_body"); + + for ($m = 0; $m<$num_mvlans; $m++) { + + # Add test-group for this mvlan pair + my $gname = "$group_prefix-$p.$q#$m"; + if ($do_simple_names) { + $gname = "$group_prefix-v$myvid#$m"; + } + doCmd("add_group $gname 4 4"); + doCmd("set_script $gname $script_body"); + + my $e; + for ($e = 0; $e < $num_cxs; $e++) { + # Now, add the cross-connects + my $pp = int($p / 2); + my $p2 = $p+1; + my $ep1 = "$group_prefix-$p.$q#$m-$e"; + my $ep2 = "$group_prefix-$p2.$q#$m-$e"; + my $cx_name = "$group_prefix-$pp.$q.$m-$e"; + if ($do_simple_names) { + $cx_name = "$group_prefix-$myvid#$m-$e"; + } + + my $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + # Add to groups + doCmd("add_tgcx $gname $cx_name"); + doCmd("add_tgcx $vgname $cx_name"); + doCmd("add_tgcx $pgname $cx_name"); + + # TODO: Add 2544 scripts to test-groups + } + } + } +}; + +# Save this in a database for later retrieval. +doCmd("save $dbname"); + +# Print some reporting on what was configured. +print "<PORTS_CREATED>\n$ports_rpt</PORTS_CREATED>\n"; + + +$dt = getDate(); +print "Started lf_netoptics.pl script at : $dt_start\n"; +print "Completed lf_netoptics.pl script at: $dt\n\n"; +exit(0); +##################### +# END lf_macvlan.pl # +##################### + + + +sub initToDefaults { + # Clean up database if stuff exists + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + my $rslt = doCmd("show_group"); + my @rslts = split(/\n/, $rslt); + my $i; + my $pat = ".*TestGroup name: (${group_prefix}-\\S+)\\s+"; + #print "pattern -:$pat:-\n"; + for ($i = 0; $i<@rslts; $i++) { + my $ln = $rslts[$i]; + chomp($ln); + #print "test-group-rslt-line -:$ln:-\n"; + if ($ln =~ /$pat/) { + doCmd("rm_group $1"); + } + } + + initPortsToDefault(); +}#initToDefaults + +sub getNextMac { + my $last = shift; + if ($last eq "RANDOM") { + my $msb = int(rand(255)) & 0xfe; # make sure odd bit (mcast) isn't set. + return sprintf("%02x:%02x:%02x:%02x:%02x:%02x", $msb, int(rand(255)), int(rand(255)), int(rand(255)), + int(rand(255)), int(rand(255))); + } + else { + # Parse last, and increment. + if ($last =~ /(\S+):(\S+):(\S+):(\S+):(\S+):(\S+)/) { + my $dl = hex($6); + $dl |= (hex($5) << 8); + $dl |= (hex($4) << 16); + $dl |= (hex($3) << 24); + + my $dh |= hex($2); + $dh |= (hex($1) << 8); + + $dl++; # Increment mac by one. + if ($dl == 0) { + # Wrapped, how unlucky. + $dh++; + } + return sprintf("%02x:%02x:%02x:%02x:%02x:%02x", + ($dh & 0xff00) >> 8, + ($dh & 0xff), + ($dl & 0xff000000) >> 24, + ($dl & 0xff0000) >> 16, + ($dl & 0xff00) >> 8, + ($dl & 0xff)); + } + } +} # getNextMac + + +sub toIpString { + my $ips = shift; + if ($ips =~ /(\S+)\.(\S+)\.(\S+)\.(\S+)/) { + my $d = int($4); + $d += ((int($3) << 8) & 0xff00); + $d += ((int($2) << 16) & 0xff0000); + $d += ((int($1) << 24) & 0xff000000); + return $d; + } + return 0; +} + +sub toStringIp { + my $ip = shift; + return sprintf("%d.%d.%d.%d", + ($ip >> 24), + ($ip & 0xff0000) >> 16, + ($ip & 0xff00) >> 8, + ($ip & 0xff)); +} + +# Wait until the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearVlanPorts($shelf, $lf1); + + throttleCard($shelf, $lf1); + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } +} + +sub clearVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + for ($i = 0; $i<$mx; $i++) { + if (($ports[$i]->isMacVlan()) || ($ports[$i]->is8021qVlan())) { + # See if it belongs to any of our interfaces + my $par = $ports[$i]->parent(); + if ($par ne "") { + my $base; + if ($par =~ /(\S+)\#.*/) { + $base = $1; # mac-vlan + } + elsif ($par =~ /(\S+)\..*/) { + $base = $1; # .1q vlan + } + else { + $base = $par; + } + + my $p; + for ($p = 0; $p < @lf1_ports; $p++) { + if ($lf1_ports[$p] eq $base) { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + last; + } + }# for all physical/base ports + }# if found port has parent device + }# Found a vlan device + }# for all found ports + }# while we found something to delete +}#clearVlanPorts + +# Returns string, might want to split it to get line-by-line option +sub doCmd { + my $cmd = shift; + + if ($cmd) { + print ">>> $cmd\n"; + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + return join("\n", @rslt); + } else { + print "\n***** doCmd (): NULL COMMAND !!! *****"; + print "\n$cmd\n\n"; + exit (1); + } +} + +sub getDate { + my $date = `date`; + chomp($date); + return $date +} + +sub printArgs { + print + . "\nModified arguments:" + . "\nmanager: $lfmgr_host\n" + . "\nlf1: $lf1\n" + . "\nlf1_ports: " . join(" ", @lf1_ports) + . "\nnum_mvlans: $num_mvlans" + . "\nmax_rate: $max_rate" + . "\ncx_types: " . join(" ", @cx_types) + . "\n\n"; +} + +sub printHelp { + print + . "USAGE: --mgr=[ip-of-mgr]\n" + . " --testMgrName=\"ben_tm\"\n" + . " --resourceId=[1|n]\n" + . " --protocolFlags=[n]: tcp4:1, udp4:2, tcp6:4 udp6:8\n" + . " --portA=\"eth1\"\n" + . " --portB=\"eth2\"\n" + . " --vlanAmt=[3|n]\n" + . " --macVlanAmt=[3|n]\n" + . " --clearPortOnStart=[0|1]\n" + . " --cxPerMacVlanAmt=[5|n]\n" + . " --vlanID=[RANDOM|n]\n" + . " --ip=\"DHCP|RANDOM|192.168.7.2\"\n" + . " --mask=\"255.255.0.0\"\n" + . " --subnetPerMacVlan=[0|1]\n" + . " --dbName=\"my_db_name\"\n" + . " --desiredTotalTxRate=[n] (in bits-per-second)\n" + . " --pduSize=[AUTO|n] (in bytes, payload size)\n" + . " --duration=[n] (duration of script run, in miliseconds)\n" + . " --multicon=[AUTO|0|1|n] (Enable multi-conn feature, or not)\n" + . "\n"; + +} + +sub getPduSize { + my $etype = shift; + my $rate = shift; + + if ($max_pkt_sz ne "AUTO") { + return $max_pkt_sz; + } + + my $rv; + if ($rate > 1000000000) { + # Use big pkts for > 1Gbps + if ($etype =~ /.*udp.*/i) { + return 64000; + } + else { + return 200000; + } + } + else { + # Attempt to fit into 1500 byte MTU pkt + if ($etype eq "lf_udp") { + return 1472; + } + elsif ($etype eq "lf_tcp") { + return 1460; + } + elsif ($etype eq "lf_udp6") { + return 1452; + } + elsif ($etype eq "lf_tcp6") { + return 1440; + } + else { + print "Unknown cx type: $etype in PDU auto-cal method, returning 4000\n"; + return 4000; + } + } +} + +sub handleCmdLineArg { + my $arg = $_[0]; + my $val = $_[1]; + + if ($arg eq "help" || $arg eq "--help" || $arg eq "-h" || $arg eq "-help" || $arg eq "-h" ) { + printHelp(); + exit(0); + } + elsif ($arg eq "--mgr") { + $lfmgr_host = $val; + } + elsif ($arg eq "--testMgrName") { + $test_mgr = $val; + } + elsif ($arg eq "--resourceId") { + $lf1 = $val; + } + elsif ($arg eq "--protocolFlags") { + my $vi = int($val); + if ($vi & 0x1) { + @cx_types = (@cx_types, "lf_tcp"); + } + if ($vi & 0x2) { + @cx_types = (@cx_types, "lf_udp"); + } + if ($vi & 0x4) { + @cx_types = (@cx_types, "lf_tcp6"); + } + if ($vi & 0x8) { + @cx_types = (@cx_types, "lf_udp6"); + } + } + elsif ($arg eq "--portA") { + @lf1_ports = (@lf1_ports, $val); + } + elsif ($arg eq "--portB") { + @lf1_ports = (@lf1_ports, $val); + } + elsif ($arg eq "--vlanAmt") { + $num_vlans = $val; + } + elsif ($arg eq "--macVlanAmt") { + $num_mvlans = $val; + } + elsif ($arg eq "--vlanID") { + $vid = $val; + } + elsif ($arg eq "--vlanMAC") { + $vlan_mac = $val; + } + elsif ($arg eq "--macVlanMAC") { + $mvlan_mac = $val; + } + elsif ($arg eq "--ip") { + $ipaddr = $val; + } + elsif ($arg eq "--mask") { + $mask = $val; + } + elsif ($arg eq "--subnetPerMacVlan") { + $subnet_per_vl = $val; + } + elsif ($arg eq "--dbName") { + $dbname = $val; + } + elsif ($arg eq "--cxPerMacVlanAmt") { + $num_cxs = $val; + } + elsif ($arg eq "--clearPortOnStart") { + $clear_port_on_start = int($val); + } + elsif ($arg eq "--desiredTotalTxRate") { + $max_rate = $val; + } + elsif ($arg eq "--pduSize") { + $max_pkt_sz = $val; + } + elsif ($arg eq "--duration") { + $duration = int($val); + } + elsif ($arg eq "--multicon") { + $multicon = $val; + } + else { + print "\n\nCould not parse one or more of the arguments !!!\n" + . "First rejected argument: $arg\n"; + printHelp(); + exit(1); + } +} diff --git a/lanforge/lanforge-scripts/lf_nfs_io.pl b/lanforge/lanforge-scripts/lf_nfs_io.pl new file mode 100755 index 000000000..d672572fc --- /dev/null +++ b/lanforge/lanforge-scripts/lf_nfs_io.pl @@ -0,0 +1,1064 @@ +#!/usr/bin/perl -w +# +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, custom_tcp, l4 (http, https, ftp and fileIO) +# across real ports and MACVLAN ports on one or more machines. +# It then continously starts and stops the connections. +package main; +$| = 1; # Un-buffer output + +use strict; +use warnings; +use Carp; +use Net::Telnet (); +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +use Scalar::Util; #::looks_like_number; +use lib '/home/lanforge/scripts'; # this is pedantic necessity for the following use statements +use LANforge::Port; +use LANforge::Utils; +use LANforge::Endpoint; +use Net::Telnet (); +use Net::Ping; +use Getopt::Long; +use Time::HiRes ('sleep'); +use Socket; + +use POSIX; +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## package variables below +use constant NL => "\n"; +use constant NA => "NA"; +use constant AUTO => "AUTO"; +use constant READ => "read"; +use constant WRITE => "write"; + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +our $report_timer = 8000; # Set report timer for all tests created in ms, i.e. 8 seconds +our $lfmgr_host = undef; +our $lfmgr_port = 4001; +our $resource = 1; +our $quiet = 1; +our $group = undef; +our $tmp_group = undef; +our $tmp_group_min = 0; +our $tmp_group_max = 0; +our $action = undef; +our $nfs_mnt = undef; +our $nfs_list = undef; +our $local_mnt = AUTO; +our $first_mvlan_ip = undef; #"10.26.1.10"; +our $netmask = "255.255.255.0"; +our $parent_port = undef; +our $utils = undef; +our $shelf_num = 1; +our $DEBUG = 0; +our $sleep_after_wo = 15; # Second to sleep after starting writers (before starting readers) +our $D_PAUSE = 3; + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# File-IO configuration constants +our $quiesce_after_files = 0; +our $min_rw_size = "512"; +our $max_rw_size = "65536"; +our $use_crc = "yes"; +our $min_read_bps = "1000000"; # 1Mbps +our $max_read_bps = "2000000"; # 2Mbps +our $num_files = 2; +our $min_file_size = 1024 * 1024; +our $max_file_size = 1024 * 1024 * 2; +our $min_write_bps = "3000000"; # 3Mbps +our $max_write_bps = "4000000"; # 4Mbps +our $mount_options = "NONE"; +our $skip_writers = 0; +our $skip_readers = 0; + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# below are sorted names and associated endpoints +# the third argument can be used as the default parent +# port for the mac-vlans for that group +our %group_names = ( + #"group1" => [ 1, 20, 'eth1'], + #"group2" => [ 21, 40, 'eth1'], + #"group3" => [ 41, 60, 'eth1'], + #"group4" => [ 1, 1, 'eth1'], + #"group5" => [ 2, 2, 'eth1'] +); +our $fast_forward_ep = 0; # set this to 1 to leave exiting file endpoints alone +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +our %mnt_map = (); +our %file_endpoints = (); +our %cross_connects = (); +our %test_groups = (); +our %all_file_ep = (); +our %mac_vlans = (); +our %vlan_ips = (); + +# These are set based on group chosen. +our $qty_mac_vlans = 0; +our $start = 0; # First idx, inclusive +our $stop = 0; # Last idx + +sub ipSummary { + my $first; + if((!defined $::first_mvlan_ip ) || ("$::first_mvlan_ip" eq "")) { + $first = "0.0.0.0"; + } + else { + $first = $::first_mvlan_ip; + } + my $linestart = " # "; + my $summary = NL; + for my $name (sort keys %::group_names) { + $summary .= $linestart.$name.": "; + my $a = addrtoint( $first ) + $::group_names{ $name }->[0] -1; + my $b = addrtoint( $first ) + $::group_names{ $name }->[1] -1; + $summary .= inttoaddr( $a )." - ".inttoaddr( $b ).NL; + } + return $summary; +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## Usage == +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +my $first = ($::first_mvlan_ip) ? $::first_mvlan_ip : "0.0.0.0"; + +my $usage = "$0 [--mgr {host-name | IP}] + [--mgr_port {ip port ($lfmgr_port)}] + [--resource {resource ($resource)}] + [--nfs_mnt {[IP|host-name]:/path}] + # 192.168.1.1:/foo + # filehost:/home/fileio + [--nfs_list {local file}]\t# list of nfs mountpoints + # Will be modulo distributed among groups mac vlans + # Obviates --nfs_mnt; examples: + # --nfs_list ./my-list-of-mounts + # --nfs_list /tmp/mnt-list + # File format: + # 10.20.30.40:/a/b/c + # filehost:/z/y + [--parent_port {parent eth port}]\t# parent of mac vlans + [--first-mvlan-ip {ip ($first)}]\t# mvlan ips: ".ipSummary()." + [--netmask {mask ($netmask)}]\t#mac-vlan netmask + [--action {list_groups|run_group|stop_group|del_group}] + # list_groups: list test groups + # run_group: assemble and start writer then reader group + # stop_group: quiece writer then reader group + # del_group: delete reader then writer file endpoints + [--group {name}] # test group base name, creates: + # <group>_wo for writers and + # <group>_ro for readers + [--min_rw_size ($min_rw_size)]\t# in bytes + [--max_rw_size ($max_rw_size)]\t# in bytes + [--use_crc {yes|no}] + [--min_read_bps ($min_read_bps)]\t# in bps, 2000000 = 1Mbps + [--max_read_bps ($max_read_bps)]\t# in bps, 2000000 = 2Mbps + [--num_files ($num_files)]\t# files per writer + [--quiesce_after_files ($quiesce_after_files)]\t# files to read/write before stopping test. + O == infinite (default) + + [--skip_readers ($skip_readers)]\t# Should we not create reader connections: 0 | 1 + [--skip_writers ($skip_readers)]\t# Should we not create writer connections: 0 | 1 + [--min_file_size ($min_file_size)]\t# in bytes + [--max_file_size ($max_file_size)]\t# in bytes + [--min_write_bps ($min_write_bps)]\t# in bps, 3000000 = 3Mbps + [--max_write_bps ($max_write_bps)]\t# in bps, 4000000 = 4Mbps + [--mount_options ($mount_options)]\t# as per nfs(1) + [--tmp_group {name}]\t# for specifying ad-hoc group, you will see group <name> + [--min 1-n]\t first macvlan in tmp group + [--max 1-n]\t last macvlan in tmp group + +Examples: + $0 --mgr 10.0.0.1 --resource 1 --action list_groups + + $0 --mgr 10.0.0.1 --resource 1 --action run_group --group group1 \\ + --parent_port eth2 --netmask 255.255.0.0 \\ + --nfs_mnt 192.168.99.99:/fire + + $0 --mgr 10.0.0.1 --resource 1 \\ + --action stop_group --group group1 + + $0 --mgr 10.0.0.1 --resource 1 \\ + --action del_group --group group1 + + $0 --mgr 10.0.0.1 --resource 1 \\ + --action run_group --group group2 --parent_port eth9 \\ + --first_mvlan_ip 172.168.90.1 --netmask 255.255.0.0 \\ + --nfs_list ./nfsexports.txt \\ + --num_files 20 --min_file_size 4096 --max_file_size 524288 \\ + --min_write_bps 1000000 --max_write 900000000 + +$0 --mgr 10.0.0.1 --resource 1 \\ + --action run_group --group group1 \\ + --nfs_mnt 192.168.99.99:/fire \\ + --mount_options sync,hard,timeo=120,retrans=4 + +$0 --mgr 10.0.0.1 --resource 1 --action run_group \\ + --tmp_group lag1 --min 1 --max 2 --parent_port eth1 \\ + --first_mvlan_ip 10.30.0.10 \\ + --nfs_mnt 10.30.0.1:/tmp + +$0 --mgr 10.0.0.1 --resource 1 \\ + --action stop_group --tmp_group lag1 --min 1 --max 2 --parent_port eth1 +"; + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## ip_to_a/a_to_ip +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub addrtoint { + return( unpack( "N", pack( "C4", split( /[.]/,$_[0]) ) ) ); +}; +sub inttoaddr { + return( join( ".", unpack( "C4", pack( "N", $_[0] ) ) ) ); +}; + + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## Open connection to the LANforge server, configure our utils. +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub init { + my $conn = new Net::Telnet(Timeout => 20, + Prompt => '/default\@btbits\>\>/'); + $conn->open(Host => $::lfmgr_host, + Port => $::lfmgr_port, + Timeout => 10); + + $conn->waitfor("/btbits\>\>/"); + $conn->max_buffer_length(1024 * 1024 * 10); # 10M buffer + + $::utils = new LANforge::Utils(); + $::utils->connect($lfmgr_host, $lfmgr_port); + + if ($::group && $::group ne "") { + my $ra_bounds = $::group_names{ $::group }; + $::start = @$ra_bounds[0]; + $::stop = @$ra_bounds[1]; + + $::qty_mac_vlans = ($::stop - $::start) + 1; + print "group [$group] vlans: $::stop - $::start = $::qty_mac_vlans\n" if($::DEBUG); + } +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## set a port or mvlan up or down +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub fmt_port_up_down { + my ($resource, $port_id, $state) = @_; + + my $cur_flags = 0; + if ($state eq "down") { + $cur_flags |= 0x1; # port down + } + + # Specify the interest flags so LANforge knows which flag bits to pay attention to. + my $ist_flags = 0; + $ist_flags |= 0x2; # check current flags + $ist_flags |= 0x800000; # port down + + my $cmd = $::utils->fmt_cmd("set_port", 1, $resource, $port_id, NA, + NA, NA, NA, "$cur_flags", + NA, NA, NA, NA, "$ist_flags"); + return $cmd; +} # ~port up/down + + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## Create mac_vlans if they do not exist. +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub preparePorts { + do_err_exit("First mac vlan IP is not set. Please set --first_mvlan_ip.") + unless ((defined $::first_mvlan_ip) && ("$::first_mvlan_ip" ne "")); + + do_err_exit( "preparePorts: Unnamed parent port. Please set \$parent_port") + unless((defined $::parent_port) && ("$::parent_port" ne "")); + + my $sleep_after = 0; + my %all_ports = (); + # build list of MAC VLANS + print "shelf_num $::shelf_num, resource $::resource\n" if ($::DEBUG); + my @ports = $::utils->getPortListing( 1, $::resource +0); + + for my $rh_port (@ports) { + print "added port $rh_port->{'dev'}".NL if($::DEBUG); + $all_ports{ $rh_port->{'dev'} } = $rh_port; + next unless ( $rh_port->{'dev'} eq $::parent_port ); + } + do_err_exit("preparePorts: Failed to populate ports list, please debug.") + unless (keys %all_ports > 0); + + my $i; + my %new_items = (); + my $ra_bounds = $::group_names{ $group }; + my $start_str = $::first_mvlan_ip; + my $start_int = addrtoint($start_str); + my $next_ip; + + if (($::qty_mac_vlans + $::start) < 1) { + do_err_exit("preparePorts: expects a positive, non-zero number of mvlans to create. Cannot continue."); + } + + for ($i = 0 + $::start; $i < $::qty_mac_vlans + $::start; $i++) { + my $devname = $::parent_port."#".$i; + print "start_str[$start_str] start_int[$start_int] i[$i]\n" if ($::DEBUG); + my $next_ip = inttoaddr( $start_int + $i -1); + print "next_ip[$next_ip]\n" if ($::DEBUG); + $::vlan_ips{ $devname } = $next_ip; + $::mac_vlans{ $devname } = 0; + if ( defined $all_ports{ $devname }) { + $::mac_vlans{ $devname } = 1; + } + else { # create mac_vlan + my $mac_addr = mac(); + my $cmd = $::utils->fmt_cmd( "add_mvlan", $::shelf_num, + $::resource, $::parent_port, $mac_addr, $i); + print " + ".$cmd.NL if ($::DEBUG); + $::utils->doCmd($cmd); + sleep(0.1); + } + $new_items{ $devname } = 1; + $sleep_after++; + } #~for + + if ( keys %new_items > 0 ) { + print "Creating ".(keys %new_items)." new ports:..."; + + # set the port IP + for my $new_item (keys(%new_items)) { + my $ip = $::vlan_ips{ $new_item }; + my $cmd_flags = 0x0; + my $current = 0x0; + my $interesting = 0x0 | 0x4 | 0x8; + + my $cmd = $::utils->fmt_cmd("set_port", + $::shelf_num, $::resource, $new_item, $ip, + $::netmask, NA, $cmd_flags, $current, NA, + NA, NA, NA, $interesting, + NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, + NA, NA, NA, NA, NA, NA, NA); + + if ($::DEBUG) { + print NL." $new_item $ip: $cmd "; + } + else { + print "."; + } + $::utils->doCmd($cmd); + sleep(0.1); + $sleep_after++; + + my @ports = $::utils->getPortListing($::shelf_num, $::resource); + for my $rh_port (@ports) { + $::all_ports{ $rh_port->{'dev'} } = $rh_port; #reset ports + } + } + print NL."Created ".(keys %new_items)." new mac vlans".NL; + } + if ($sleep_after > 10) { + $sleep_after = $sleep_after / 2; + } + + if ($sleep_after) { + print "\nSleeping: $sleep_after seconds to allow ports to be created and configured.\n"; + for $i (1..$sleep_after) { sleep(1); print "."; } + print NL; + } + # check that the port is up by trying to run a ping from it + if ( defined $::nfs_mnt && "$::nfs_mnt" ne "") { + my ($nfs_name) = split(/:/, $::nfs_mnt ); + print "Emitting one ping from each mvlan reduces failed mounts:"; + + foreach my $name (reverse sort keys %new_items) { + my $rh_p = $::all_ports{ $name }; + my $ip = $rh_p->{'ip_addr'}; + #my $ping = "ping -n -c1 -w1 -W1 -I $ip $nfs_name"; + print "ping $ip".NL if ($::DEBUG); + my $ping = Net::Ping->new('tcp', 1); + $ping->bind($ip); + $ping->port_number(scalar(getservbyname("nfs", "tcp"))); + my $counter = 5; + while( $counter > 0 ) { + if ($ping->ping($nfs_name)) { + print "."; + $counter = 0; + } + else { + print "$nfs_name did not ack nfs packet from $ip".NL; + $counter --; + sleep(0.2); + } + } + $ping->close(); + undef($ping); + #$ping->close(); + } + } + print NL; + +} # ~preparePorts + +sub notBlank { + my ($name, $value) = @_; + if((!defined $name) || ("$name" eq "")) { + print "Name itself is blank, value[$value]".NL; + } + if((!defined $value) || ("$value" eq "")) { + print "Value of $name is blank".NL; + } + #die if($::DEBUG); +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## prepareFileEndp - creates requested file endpoints if they do not exist, +## creates implied FIO endpoints if they dont exist and adds endpoints +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub prepareFileEndpoints { + do_err_exit("prepareFileEndpoints: Unnamed parent port. Please set \$parent_port") + unless((defined $::parent_port) && ("$::parent_port" ne "")); + + do_err_exit("prepareFileEndpoints: Blank all_ports array. Please run preparePorts() first.") + unless( keys %::all_ports > 1 ); + + do_err_exit("prepareFileEndpoints: Blank mac_vlans array. Please run preparePorts() first.") + unless( keys %::mac_vlans > 0 ); + + if((defined $::nfs_list) && ("$::nfs_list" ne "")) { + if((keys %::mnt_map) < 1) { + do_err_exit("prepareFileEndpoints: empty --nfs_list. Provide mountpoints in [$::nfs_list]"); + } + } + elsif((! defined $::nfs_mnt) || ("$::nfs_mnt" eq "")) { + do_err_exit("prepareFileEndpoints: undefined --nfs_mnt. Please specify --nfs_mnt first"); + } + + my %all_file_endpoints = (); + my %all_cross_connects = (); + my %endpoints_mvlans = (); + my @new_file_endpoints = (); + my @new_cross_connects = (); + + my $ep_name = undef; + for my $grp (sort(keys %::group_names)) { + my $ra_bounds = $::group_names{ $grp }; + print "skip_writers[$::skip_writers] skip_readers[$::skip_readers]" if ($::DEBUG); + sleep(3) if($::DEBUG); + + for my $i (@$ra_bounds[0]..@$ra_bounds[1]) { + if (! $::skip_writers) { + $ep_name = $grp ."_wo_".sprintf( "%03d", $i); + $all_file_endpoints{ $ep_name } = 0; + $endpoints_mvlans{ $ep_name } = $::parent_port."#".$i; + $all_cross_connects{ $ep_name } = 0; + } + + if (! $::skip_readers) { + $ep_name = $grp ."_ro_".sprintf( "%03d", $i); + $all_file_endpoints{ $ep_name } = 0; + $endpoints_mvlans{ $ep_name } = $::parent_port."#".$i; + $all_cross_connects{ $ep_name } = 0; + } + } + } + + print NL."Reading endpoints..."; + my $endpoint_str = $::utils->doCmd("nc_show_endpoints all all"); + my @endpoint_lines = split(/\n/, $endpoint_str); + + if ($::fast_forward_ep) { + for my $line (@endpoint_lines) { + $line =~ m/Endpoint \[(.*?)\]/; # proves this ep exists + next if(! $1); + $all_file_endpoints{ $1 } = 1; + } + } + for my $ep_name (sort(keys %all_file_endpoints)) { + print " $ep_name [".$all_file_endpoints{$ep_name}."] " if ($::DEBUG); + if ($all_file_endpoints{$ep_name} == 0 ){ + my $begins = $::group."_"; + if( $ep_name =~ /^$begins/ ){ + push( @new_file_endpoints, $ep_name ); + } + } + } + # assert we have sufficient remote_mnt entries + for my $ep_name (@new_file_endpoints) { + my $mvlan = $endpoints_mvlans{ $ep_name }; + my $remote_mnt = $::mnt_map{ $mvlan }; + notBlank( $mvlan, $remote_mnt ); + } + + if (@new_file_endpoints > 0) { + print "Creating ".@new_file_endpoints." new file endpoints..."; + my $endpoint_type = "lf"; + my $ip_port = "-1"; + my $bursty = "no"; + my $rand_pkt_sz = "no"; + my $remote_mnt = undef; + + for my $ep_name (@new_file_endpoints) { + my $mvlan = $endpoints_mvlans{ $ep_name }; + $remote_mnt = $::mnt_map{ $mvlan }; + + my $read_write = ($ep_name =~ /_ro_/)? READ : WRITE; + my $rw_prefix = $ep_name; + $rw_prefix =~ s/_ro_/_wo_/; + my $prefix = ($read_write eq WRITE) ? AUTO : $rw_prefix; + my $directory = ($read_write eq WRITE) ? AUTO : '/mnt/lf/'.$rw_prefix; + my $min_write_rt = ($read_write eq READ ) ? "0" : $::min_write_bps; + my $max_write_rt = ($read_write eq READ ) ? "0" : $::max_write_bps; + my $min_read_rt = ($read_write eq WRITE ) ? "0" : $::min_read_bps; + my $max_read_rt = ($read_write eq WRITE ) ? "0" : $::max_read_bps; + my $pattern = "increasing"; #($read_write eq "read" ) ? NA : "increasing"; + my $mount_retry_nap = 3500; + my $mount_dir = "NA"; + + # we do not want anything 'blank' in this + my @names=qw(ep_name ::shelf_num ::resource mvlan min_read_rt max_read_rt min_write_rt max_write_rt pattern directory prefix remote_mnt mount_options mount_dir mount_retry_nap); + + my @values=($ep_name, $::shelf_num, $::resource, $mvlan, $min_read_rt, $max_read_rt, $min_write_rt, $max_write_rt, $pattern, $directory, $prefix, $remote_mnt, $mount_options, $mount_dir, $mount_retry_nap); + for (my $i=0; $i<@names; $i++) { + notBlank($names[$i], $values[$i]); + } + + my $cmd = $::utils->fmt_cmd( + "add_file_endp", $ep_name, $::shelf_num, $::resource, $mvlan, + 'fe_nfs', $min_read_rt , $max_read_rt, $min_write_rt, $max_write_rt, + $pattern, $directory, $prefix, $remote_mnt, $mount_options, + "7", $mount_dir, NA, $mount_retry_nap); + print " + " . $cmd.NL if ($::DEBUG); + $::utils->doCmd($cmd); + sleep(0.1); # if ($::DEBUG); + print "."; + $cmd = $::utils->fmt_cmd( "set_fe_info", $ep_name, + $min_rw_size, $max_rw_size, $num_files, $min_file_size, $max_file_size, + $directory, $prefix, $read_write, $quiesce_after_files); + print " + " . $cmd.NL if ($::DEBUG); + + $::utils->doCmd($cmd); + sleep(0.1); # if ($::DEBUG); + + print "\bo"; + $::utils->doCmd($::utils->fmt_cmd( "set_endp_quiesce", $ep_name, 5)); + sleep(0.1); + print "\bO"; + $::utils->doCmd($::utils->fmt_cmd( "set_endp_report_timer", $ep_name, 1000)); + sleep(0.1); + print "\b*"; + $::utils->doCmd($::utils->fmt_cmd( "set_endp_flag", $ep_name, "ClearPortOnStart", 0)); + sleep(0.1); + print "\b|"; + my $cx_name = "CX_".$ep_name; + my $tg_name = $::group.(($read_write eq READ) ? "_ro" : "_wo"); + my $tm_name = "default_tm"; #= $::group.(($read_write eq READ) ? "_ro" : "_wo"); + my $tx_endp = $ep_name; + my $rx_endp = NA; + + my $add_cx = $::utils->fmt_cmd( "add_cx", $cx_name, $tm_name, $tx_endp, $rx_endp ); + my $set_cx = $::utils->fmt_cmd( "set_cx_report_timer", $tm_name, $cx_name, "1000", "cxonly"); + my $add_tgcx = $::utils->fmt_cmd( "add_tgcx", $tg_name, $cx_name ); + print $add_tgcx.NL if ($::DEBUG); + print $add_cx.NL if ($::DEBUG); + print $set_cx.NL if ($::DEBUG); + + $::utils->doCmd($add_cx); + sleep(0.1); + print "\bi"; + $::utils->doCmd($set_cx); + sleep(0.1); + print "\b:"; + $::utils->doCmd($add_tgcx); + sleep(0.1); + print "\b."; + } #~for + print "\nAdded ".@new_file_endpoints." endpoints to group $::group\n"; + sleep(3); + } #~if need to create file endpoints + +} #~prepareFileEndpoints + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## prepareExportList - assemble the list of nfs exports from either +## the nfs_list resource or the nfs_mnt option +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub prepareExportList { + + if ((defined $::nfs_list) && ("$::nfs_list" ne "")) { + open(my $fh, "<", $::nfs_list) or do_err_exit("Unable to open [$::nfs_list]. Cannot continue."); + my @lines = (); + while(<$fh>) { + chomp; + next if (/^\s*[#;]/); + next if (/^\s*$/); + s/^\s+//; + s/\s+$//; + if (/[0-9a-zA-Z.-]+:\/.*$/) { + push( @lines, $_ ); + } + } + close($fh); + if (@lines < 1) { + do_err_exit("Unable to find lines that look like nfs mount points in [$::nfs_list]. Cannot continue."); + } + my $i = 0; + for my $mvlan (sort keys %::mac_vlans) { + $::mnt_map{ $mvlan } = $lines[ $i ]; + print " mapping $mvlan => ".$lines[ $i ].NL if($::DEBUG); + $i = ++$i % @lines; + } + } + elsif ((defined $::nfs_mnt) && ("$::nfs_mnt" ne "")) { + for my $mvlan (sort keys %::mac_vlans) { + $::mnt_map{ $mvlan } = $::nfs_mnt; + } + } + else { + do_err_exit("Niether --nfs_list or --nfs_mnt are specified. Cannot continue."); + } + + if ((keys %::mnt_map) < 1) { + do_err_exit("prepareExportList: unable to build map of mount entries. Cannot continue."); + } +} +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## prepareGroups - creates requested test group if it does not exist, +## file-endpoints will be created expecting these refs +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub prepareTestGroups { + my @new_items = (); + my $test_group_str = $::utils->doCmd("show_group all"); + my @test_group_lines = split(/\n/, $test_group_str ); + my %test_groups = (); + + for my $group_name (sort(keys %::group_names)) { + $test_groups{ $group_name } = 0; + } + for my $line ( @test_group_lines) { + if ($line =~ /TestGroup name: ([^\[]+)\s*\[.*$/) { + $test_groups{ $1 } = 1; + } + } + for my $group_name (keys %test_groups) { + if ($test_groups{$group_name} == 0 ) { + push(@new_items, $group_name); + } + } + if (@new_items > 0 ) { + sleep(5); + for my $group_name (@new_items) { + + $::utils->doCmd($::utils->fmt_cmd("add_group", $group_name."_wo", NA, NA)) if (!$::skip_writers); + $::utils->doCmd($::utils->fmt_cmd("add_group", $group_name."_ro", NA, NA)) if (!$::skip_readers); + + #print " + tg $group_name "; + } + } +} # ~prepareTestGroups + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## generates random mac address +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub mac { + my $rv = "00:"; + for (my $i=0; $i<5; $i++) { + $rv.=sprintf("%02X",int(rand(255))).(($i<4)?':':''); + } + return $rv; +} + + + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## list ports +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub showGroups { + my @ports = $::utils->getPortListing($::shelf_num, $::resource); + print "in show groups, found $#ports ports\n" if ($::DEBUG); + for my $group_name ( sort(keys(%::group_names))) { + print "show groups, $group_name _ro\n" if ($::DEBUG); + my $cmd = $::utils->fmt_cmd("show_group", $group_name."_ro"); + print $::utils->doCmd($cmd); + + print "show groups, $group_name _wo\n" if ($::DEBUG); + $cmd = $::utils->fmt_cmd("show_group", $group_name."_wo"); + print $::utils->doCmd($cmd); + } + print "\n"; +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## start the writers then start the readers +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub runGroup { + my $cmd; + if (!$::skip_writers) { + $cmd = "start_group ".$::group."_wo"; + $::utils->doCmd($cmd); + print "Starting ".$::group."_wo..."; + for my $i ( 1..$sleep_after_wo ) { sleep(1); print "."; } + print "...started".NL; + } + if (!$::skip_readers) { + $cmd = "start_group ".$::group."_ro"; + $::utils->doCmd($cmd); + print "Starting ".$::group."_ro ..."; + for my $i ( 1..3 ) { sleep(1); print "."; } + print "...started".NL; + } +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## stop writers then stop readers +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub stopGroup { + my $cmd; + if (!($::skip_writers || $skip_readers)) { + print NL."Quiesceing all connections..."; + $cmd = "quiesce_group all"; + $::utils->doCmd($cmd); + } + if (!$::skip_writers) { + print NL."Quiesceing writers..."; + $cmd = "quiesce_group ".$::group."_wo"; + $::utils->doCmd($cmd); + } + if (!$::skip_readers) { + print NL."Quiesceing readers..."; + $cmd = "quiesce_group ".$::group."_ro"; + $::utils->doCmd($cmd); + } +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## poll the set of connections to see if they are 'stopped' +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub pollCxForStop { + my $cmd; + my $lines; + my $num_lines = 0; + my $num_counted = 0; + my $num_stopped = 0; + my $upper_limit = 60; + my $attempts = 0; + while( $num_counted == 0 || ($num_counted != $num_stopped)) { + return 0 if ($attempts > $upper_limit); + sleep(2); + $attempts++; + $cmd = $::utils->fmt_cmd("show_cx", "all", "all"); + $lines = $::utils->doCmd($cmd); + $num_counted = 0; + $num_stopped = 0; + for my $line (split(NL, $lines)) { + next unless ($line =~ /CX_.*?_[wr]o_\d+/); + $num_counted ++; + my @h = split(/ +/, $line); + + $num_stopped += ($h[10] eq "STOPPED") ? 1 : 0; + print "${h[2]} target:${h[8]} reported:${h[10]} counted:$num_counted stopped:$num_stopped attempts: $attempts".NL if ($::DEBUG); + } + print " counted:$num_counted stopped:$num_stopped attempts: $attempts".NL if ($::DEBUG); + } + return 1; +} + + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## stop the group, +## delete the file endpoints from the group, +## then delete the group +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + +sub deleteGroup { + if ((!defined $::group) || ("$::group" eq "")) { + do_err_exit("Cannot delete group if --group unspecified."); + } + my $group_name = undef; + if( @_ < 1 ) { + if ( $::skip_readers || $::skip_writers) { + if (!$::skip_readers) { + print "send Delete Group $group_name to $::group _ro \n" if ($::DEBUG); + deleteGroup( $::group."_ro") ; + } + if (!$::skip_writers) { + print "send Delete Group $group_name to $::group _wo \n" if ($::DEBUG); + deleteGroup( $::group."_wo"); + } + return; + } + $group_name = $::group; + } + else { + ($group_name) = @_; + } + print "Delete Group $group_name\n" if ($::DEBUG); + + my %cx_names = (); + my %endp_names = (); + my %portlist = (); + my $cx_prefix = "CX_".$group_name."_"; + my $should_stop_group = 0; + my $cmd; + my $resp; + my $endp_name; + my $cx_name; + + if ("$group_name" eq "$::group") { + $cmd = $::utils->fmt_cmd("show_group", $group_name."_wo"); + $resp = $::utils->doCmd($cmd); + $cmd = $::utils->fmt_cmd("show_group", $group_name."_ro"); + $resp .= $resp.NL.$::utils->doCmd($cmd); + } + else { + $cmd = $::utils->fmt_cmd("show_group", $group_name); + $resp = $::utils->doCmd($cmd); + } + + # collect cross connect names + for my $line (split(NL, $resp)) { + chomp $line; + $should_stop_group = 1 if ($line =~ /TestGroup name:.*?\[RUNNING/); + if( $line =~ / $cx_prefix/ ) { + for my $tg_name_hunk (sort split(/\s+/, $line )) { + next if ( $tg_name_hunk =~ /^ *$/); + print "$tg_name_hunk " if ($::DEBUG); + $cx_names{$tg_name_hunk}++ if ($tg_name_hunk =~ /^CX_/); + } + } + } + + if ($should_stop_group) { + stopGroup(); + if (! pollCxForStop()) { + stopGroup(); + print "Not all connections have stopped. Continuing.".NL; + } + } + + # I'm not getting output here for many seconds after polling for stop + foreach my $x (1..10) { sleep(1); print "."; } + print NL; + + # build endpoint names and find ports + for $cx_name (reverse sort keys %cx_names) { + next if( $cx_name !~ /^CX_/); + my $endp_name = $cx_name; + $endp_name =~ s/CX_//; + print "."; sleep(0.2); + my $lines = $::utils->doCmd("nc_show_endp $endp_name"); + for my $line (split(NL, $lines)) { + if ($line =~ /Shelf: /) { + $line =~ s/,//g; + my @hunks = split(/\s+/, $line); + my $port = $hunks[2]." ".$hunks[4]." ".$hunks[6]; + #print "PORT: $port\n"; + $portlist{$port}++; + } + } + } + print NL; + die "Error making portlist" if (length(keys %portlist) < 1); + + # remove cross connects and endpoints immediately after each + print "Removing Cross Connects and endpoints: "; + for $cx_name (reverse sort keys %cx_names) { + next if( $cx_name !~ /^CX_/); + $cmd = $::utils->fmt_cmd("rm_cx", "all", $cx_name); + print "** $cmd **".NL if ($::DEBUG); + + $::utils->doCmd($cmd); + print "0"; #$cx_name "; + sleep(0.5); + my $endp_name = $cx_name; + $endp_name =~ s/CX_//; + $cmd = $::utils->fmt_cmd("rm_endp", $endp_name); + $::utils->doCmd($cmd); + print "o"; #$endp_name "; + sleep(0.1); + } + print NL; + sleep(1); + + # remove group + if ( "$group_name" eq "$::group" ) { + $cmd = $::utils->fmt_cmd("rm_group", $group_name."_wo"); + print " + $cmd".NL if ($::DEBUG); + $::utils->doCmd($cmd); + print "Removed ".$group_name."_wo".NL; + sleep(0.2); + $cmd = $::utils->fmt_cmd("rm_group", $group_name."_ro"); + print " + $cmd".NL if ($::DEBUG); + $::utils->doCmd($cmd); + print "Removed ".$group_name."_ro".NL; + sleep(0.2); + } + else { + $cmd = $::utils->fmt_cmd("rm_group", $group_name); + print " + $cmd".NL if ($::DEBUG); + $::utils->doCmd($cmd); + print "Removed ".$group_name.NL; + } + sleep(2); + + # set those vlans admin down + print "Setting mvlans down: "; + for my $port (reverse sort keys %portlist) { + my @h = split(/\s+/, $port); + $cmd = fmt_port_up_down($h[1], $h[2], "down"); + print "$cmd".NL if ($::DEBUG); + $::utils->doCmd($cmd); + print "o"; # $port "; + sleep(0.4); + } + print NL; + sleep(0.2 * length(keys %portlist)); + # remove macvlans + print "Removing mvlans ports: "; + for my $port (reverse sort keys %portlist) { + $cmd = "rm_vlan $port"; + print "Remove VLAN: $cmd".NL if ($::DEBUG); + $::utils->doCmd($cmd); + print "*"; #$port "; + sleep(0.2); + } + print "...done.".NL; +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +## use this method to find the user prefix for each ip last octet +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +sub get_prefix_for_octet { + my($octet) = @_; + die "get_prefix_for_octet called without octet as argument" if ((!defined $octet) || ("$octet" eq "")); + for my $name (sort( keys %::group_names)) { + my $ra_bounds = $::group_names{ $name }; + if (($octet >= @$ra_bounds[0]) && ($octet <= @$ra_bounds[1])) { + return $name; + } + } + die "get_prefix_for_octet: no prefix found for octet [$octet]"; +} + +sub do_err_exit { + my $msg = shift; + print $msg.NL; + exit(1); +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- == +## == +## M A I N == +## == +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- == + +GetOptions +( + 'mgr|m=s' => \$lfmgr_host, + 'mgr_port|mp=i' => \$lfmgr_port, + 'resource|r=i' => \$resource, + 'quiet|q=s' => \$quiet, + 'action|a=s' => \$action, + 'nfs_mnt|h=s' => \$nfs_mnt, + 'nfs_list|e=s' => \$nfs_list, + 'first_mvlan_ip|n=s' => \$first_mvlan_ip, + 'group|g=s' => \$group, + 'debug' => \$DEBUG, + 'dbg_nap|dp=i' => \$D_PAUSE, + 'parent_port|pp=s' => \$parent_port, + 'min_rw_size|nws=i' => \$min_rw_size, + 'max_rw_size|xws=i' => \$max_rw_size, + 'use_crc|crc=s' => \$use_crc, + 'min_read_bps|nrbps=i' => \$min_read_bps, + 'max_read_bps|xrbps=i' => \$max_read_bps, + 'num_files|nf=i' => \$num_files, + 'quiesce_after_files|qaf=i' => \$quiesce_after_files, + 'skip_readers|sr=i' => \$skip_readers, + 'skip_writers|sw=i' => \$skip_writers, + 'min_file_size|nsz=i' => \$min_file_size, + 'max_file_size|xsz=i' => \$max_file_size, + 'min_write_bps|nwbps=i' => \$min_write_bps, + 'max_write_bps|xwbps=i' => \$max_write_bps, + 'mount_options|mo=s' => \$mount_options, + 'netmask|nm=s' => \$netmask, + 'tmp_group|tmp=s' => \$tmp_group, + 'min|ga=i' => \$tmp_group_min, + 'max|gb=i' => \$tmp_group_max, +) || do_err_exit("$usage"); + + +if ((!defined $action) || ($action eq "")) { + do_err_exit("Please specify an action to perform.\n$usage"); +} + +if ( ! ( $action eq "list_groups" + || $action eq "run_group" + || $action eq "stop_group" + || $action eq "del_group" )) { + do_err_exit("Unknown action $action:\n$usage"); +} + +if ((defined $tmp_group) && ($tmp_group ne "")) { + print "Using temporary group [$tmp_group]\n"; + do_err_exit("Please set --min when using tmp_group") if ($tmp_group_min <= 0); + do_err_exit("Please set --max when using tmp_group") if ($tmp_group_max <= 0); + $group = $tmp_group; +} +elsif ( !(defined $group) || ("$group" eq "")) { + do_err_exit("Blank or unknown group. Cannot continue."); +} + +if ( $group eq $tmp_group ) { + if ( !defined $parent_port || "$parent_port" eq "" ) { + do_err_exit("Undefined --parent_port value. Cannot continue."); + } + $group_names{ $tmp_group } = [ $tmp_group_min, $tmp_group_max, $parent_port ]; + print "assigned values to group name [$group]\n" if ($::DEBUG); + my $ra = $group_names{ $tmp_group }; + print "values: [".join(':', @$ra)."]\n" if ($::DEBUG); +} + +if ( !defined $parent_port || "$parent_port" eq "" ) { + $parent_port = $group_names{ $group }[2]; + if ( !defined $parent_port || "$parent_port" eq "" ) { + do_err_exit("Undefined --parent_port value. Cannot continue."); + } +} + +init(); + +if ( $action eq "list_groups" ) { + showGroups(); + exit(0); +} + +print "checking group name [$group]\n" if ($::DEBUG); +if ( !defined $group || $group eq "" || ! $group_names{ $group }) { + $group = "" if ( !defined $group ); + do_err_exit("Blank or unknown group [$group]" + .NL."Known groups: ".join(', ', sort(keys(%group_names)))); +} +print "Using group [$group]\n"; + + +if ( $action eq "stop_group" ) { + stopGroup(); +} +elsif ( $action eq "run_group" ) { + prepareTestGroups(); + preparePorts(); + prepareExportList(); + prepareFileEndpoints(); + runGroup(); +} +elsif ( $action eq "del_group" ) { + if ( $::skip_readers || $::skip_writers ) { + deleteGroup(); + } + else { + deleteGroup( $::group ); + } +} +else { + die "Unknown action $action:\n$usage"; +} + +#eof diff --git a/lanforge/lanforge-scripts/lf_parse_tshark_log.pl b/lanforge/lanforge-scripts/lf_parse_tshark_log.pl new file mode 100755 index 000000000..b86998eba --- /dev/null +++ b/lanforge/lanforge-scripts/lf_parse_tshark_log.pl @@ -0,0 +1,87 @@ +#!/usr/bin/perl + +use strict; + +$| = 1; # Don't buffer things... + +my $last_seq = -1; +my $last_pkt = -1; +my $last_ts = -1; + +my $last_seq_ooo = -1; +my $last_pkt_ooo = -1; +my $last_ts_ooo = -1; + +# Reads in input like: +#23930 18.005150 192.168.1.102 -> 192.168.1.101 LANforge Seq: 66653 +#23931 18.005265 192.168.1.102 -> 192.168.1.101 LANforge Seq: 66654 +#23932 18.005391 192.168.1.102 -> 192.168.1.101 LANforge Seq: 66655 + +while(<>) { + my $ln = $_; + chomp($ln); + if ($ln =~ /^\s*(\d+)\s+(\S+)\s+(.*)\s+LANforge Seq:\s+(\d+)/) { + my $pkt = $1; + my $ts = $2; + my $stream = $3; + my $seq = $4; + + #print "pkt is LANforge protocol: $ln\n"; + + my $gap = $seq - $last_seq; + my $skip_update = 0; + # TODO: Deal with different streams, have to take IP ports into account too probably. + if ($gap != 1) { + if ($gap > 1) { + print "DROP: pkt-gap, seq: $last_seq\/$seq pkt-cnt: $last_pkt\/$pkt timestamp: $last_ts\/$ts gap: $gap\n"; + $last_seq_ooo = -1; + } + elsif ($gap == 0) { + print "DUP: pkt-gap, seq: $last_seq\/$seq pkt-cnt: $last_pkt\/$pkt timestamp: $last_ts\/$ts gap: $gap\n"; + $last_seq_ooo = -1; + } + else { + # New seq is smaller than old. Either an OOO pkt, or perhaps a seq-number wrap? + if ($seq <= 10) { + # Assume wrap + print "WRAP: pkt-gap, seq: $last_seq\/$seq pkt-cnt: $last_pkt\/$pkt timestamp: $last_ts\/$ts gap: $gap\n"; + $last_seq_ooo = -1; + } + else { + my $ooo_gap = $seq - $last_seq_ooo; + my $skip_update_ooo = 0; + if ($last_seq_ooo == -1) { + print "OOO: pkt-gap, seq: $last_seq\/$seq pkt-cnt: $last_pkt\/$pkt timestamp: $last_ts\/$ts gap: $gap\n"; + } + elsif ($ooo_gap > 1) { + print "OOO-DROP: pkt-gap, seq: $last_seq_ooo\/$seq pkt-cnt: $last_pkt_ooo\/$pkt timestamp: $last_ts_ooo\/$ts gap: $ooo_gap\n"; + } + elsif ($ooo_gap == 0) { + print "OOO-DUP: pkt-gap, seq: $last_seq_ooo\/$seq pkt-cnt: $last_pkt_ooo\/$pkt timestamp: $last_ts_ooo\/$ts gap: $ooo_gap\n"; + } + elsif ($ooo_gap < 0) { + # Fun, out of order flow in already out of order flow! + print "OOO-OOO: pkt-gap, seq: $last_seq_ooo\/$seq pkt-cnt: $last_pkt_ooo\/$pkt timestamp: $last_ts_ooo\/$ts gap: $ooo_gap\n"; + $skip_update_ooo = 1; + } + + if (! $skip_update_ooo) { + # Start of OOO pkt sequence + $last_seq_ooo = $seq; + $last_pkt_ooo = $pkt; + $last_ts_ooo = $ts; + } + + # Don't update main pkt counters for OOO pkts. + $skip_update = 1; + } + } + } + + if (! $skip_update) { + $last_seq = $seq; + $last_pkt = $pkt; + $last_ts = $ts; + } + } +} diff --git a/lanforge/lanforge-scripts/lf_port_walk.pl b/lanforge/lanforge-scripts/lf_port_walk.pl new file mode 100755 index 000000000..c41397047 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_port_walk.pl @@ -0,0 +1,279 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# The purpose of this script is to create 10 (or more) TCP and/or UDP connections on +# specified ports. The connections will run for a short period of time, and +# then 10 more will be created on a new set of ports (the next 10). It +# writes it's cmds to a log file so you can get an idea of what it's doing. +# +# This script should be useful for people who are testing firewalls and other +# types of systems that care about what ports the data is transmitted on... +# +# Written by Candela Technologies Inc. +# Udated by: +# +# + +# Un-buffer output +$| = 1; + +use Net::Telnet (); +use Getopt::Long; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# Specify 'card' numbers for this configuration. +my $lanf1 = 1; +my $lanf2 = 2; + +# Script assumes that we are using one port on each machine for data transmission...specifically +# port 1. + +my $test_mgr = "port-walker"; + + +my $run_for_time = 20; # Run for XX seconds before tearing down and bringing up the next set.. +my $report_timer = 8000; # XX/1000 seconds + +# Default values for ye ole cmd-line args. +my $proto = "both"; # tcp, udp, or both +my $start_port = 1; # Port to start with... +my $end_port = 65535; # port to end with +my $to_do_at_a_time = 20; # Do XX cross-connects at a time. Don't make this too big, + # especially now...there is a buglet w/the GUI, especially... +my $do_bulk_removes = 1; +my $do_cx_too = 1; # Should probably be 1 most of the time... +my $do_run_cxs = 1; #Should usually be 1 + +my $cmd_log_name = "lf_port_walk_cmds.txt"; +open(CMD_LOG, ">$cmd_log_name") or die("Can't open $cmd_log_name for writing...\n"); +print "History of all commands can be found in $cmd_log_name\n"; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $usage = "$0 [--protocol={tcp | udp | both}] [--start_port={port}] [--end_port={port}]\n"; + +my $i = 0; + +GetOptions +( + 'protocol|p=s' => \$proto, + 'start_port|s=i' => \$start_port, + 'end_port|e=i' => \$end_port, +) || die("$usage"); + + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +my $dt = ""; + +# Lets create udp and tcp connections on all ports. Some of these +# won't work, so we'll ignore them. + +# get these numbers by doing something like: +# netstat -an | grep LISTEN +# There may be more or less on your machine...it would be best to check with the +# above cmd. +# +my @tcp_ignore_array = ( +6010, # X +3999, 4002, 4001, # LANforge +1024, # varies, rpc.statd often +111, # portmapper for NFS +22, #ssh +25, #smtp (email) +); + +# Set up a hash for fast existence checking... +my %ignore_ports = (); +for ($i = 0; $i<@tcp_ignore_array; $i++) { + my $prt = $tcp_ignore_array[$i]; + $ignore_ports->{$prt} = "$prt"; +} + +$dt = `date`; +chomp($dt); +print "\n\n***** Starting loop at: $dt *****\n\n"; + +# Remove any existing configuration information +initToDefaults(); + +print " ***Sleeping 3 seconds for ports to initialize to defaults...\n"; +sleep(3); + +#exit(0); + +# Now, add back the test manager we will be using +doCmd("add_tm $test_mgr"); +doCmd("tm_register $test_mgr default"); #Add default user +doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + +# Add some IP addresses to the ports +initIpAddresses(); + +print " ***Sleeping 3 seconds for ports to initialize to current values...\n"; +sleep(3); + + +# Now, go build lots of endpoints, one for every tcp/udp port known to man and beast! +for ($i = $start_port; $i<$end_port; $i++) { + + # Do XX at once. + my $j = 0; + for ($j = 0; $j<$to_do_at_a_time; $j++) { + + my $ht = $ignore_ports->{$i}; + if ((defined($ht)) && (length($ht) > 0)) { + # continue...it's in our ignore list + # TODO: We could probably still do UDP, so we should really have separate + # ingore lists for the different protocols... + print " *** Skipping port: $i\n"; + $i++; + next; + } + + # Syntax for adding an endpoint is: + # add_endp [alias] [shelf] [card] [port] [type] [IP-port] [bursty] [min_rate] [max_rate] + # [pkt_sz_random] [min_pkt] [max_pkt] [pattern] [use_checksum] + + if (($proto eq "both") || ($proto eq "udp")) { + # Set up 128Kbps full duplex UDP link, 1200 byte UDP payloads, on port $i + print " *** Creating UDP endpoint on port $i\n"; + doCmd("add_endp udp-$i-TX $shelf_num $lanf1 1 lf_udp $i NO 512000 512000 NO 1200 1200 increasing NO"); + doCmd("add_endp udp-$i-RX $shelf_num $lanf2 1 lf_udp $i NO 512000 512000 NO 1200 1200 increasing NO"); + if ($do_cx_too) { + doCmd("add_cx udp-$i $test_mgr udp-${i}-TX udp-${i}-RX"); + @cx_names = (@cx_names, "udp-$i"); + } + + @endpoint_names = (@endpoint_names, "udp-${i}-TX", "udp-${i}-RX"); + } + + if (($proto eq "both") || ($proto eq "tcp")) { + # Set up 128Kbps full duplex TCP link, 1200 byte TCP payloads, on port $i + print " *** Creating TCP endpoint on port $i\n"; + doCmd("add_endp tcp-$i-TX $shelf_num $lanf1 1 lf_tcp $i NO 512000 512000 NO 1200 1200 increasing NO"); + doCmd("add_endp tcp-$i-RX $shelf_num $lanf2 1 lf_tcp $i NO 512000 512000 NO 1200 1200 increasing NO"); + if ($do_cx_too) { + doCmd("add_cx tcp-$i $test_mgr tcp-${i}-TX tcp-${i}-RX"); + @cx_names = (@cx_names, "tcp-$i"); + } + + @endpoint_names = (@endpoint_names, "tcp-${i}-TX", "tcp-${i}-RX"); + } + + $i++; + if ($i >= $end_port) { + last; + } + } + + # So, our CXs and endpoints are created...lets start them running. + if ($do_run_cxs) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + + # SLeep for a bit, because it takes connections, especially TCP a bit to get started + # properly...and we want to give the user time to see if the expected behaviour is + # really happening.... + + print " ***Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + if ($do_run_cxs) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + + my $q = 0; + if (! $do_bulk_removes) { + for ($q = 0; $q<@cx_names; $q++) { + # Delete the endpoints and cross-connects related to this test manager. + doCmd("rm_cx $test_mgr $cx_names[$q]"); + } + + for ($q = 0; $q<@endpoint_names; $q++) { + # Delete the endpoints and cross-connects related to this test manager. + doCmd("rm_endp $endpoint_names[$q]"); + } + } + else { + doCmd("rm_cx $test_mgr ALL"); + doCmd("rm_endp YES_ALL"); # Won't delete those attached to cross-connects still... + } + + @endpoint_names = (); + @cx_names = (); + +}# for all ports + + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub initPortsToDefault { + # Set all ports we are messing with to known state. + my $i = 0; + my $num_ports = 1; + for ($i = 1; $i<=$num_ports; $i++) { + doCmd("set_port $shelf_num $lanf1 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + doCmd("set_port $shelf_num $lanf2 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + + # Syntax for setting port info is: + # set_port [shelf] [card] [port] [ip] [mask] [gateway] [cmd-flags] [cur-flags] [MAC] + # NOTE: Just use NA for the flags for now...not tested otherwise. + + doCmd("set_port $shelf_num $lanf1 1 172.25.7.2 255.255.255.0 172.25.7.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf2 1 172.25.7.3 255.255.255.0 172.25.7.1 NA NA NA"); +} + +sub doCmd { + my $cmd = shift; + + print CMD_LOG "$cmd\n"; + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_portmod.pl b/lanforge/lanforge-scripts/lf_portmod.pl new file mode 100755 index 000000000..64547fb86 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_portmod.pl @@ -0,0 +1,700 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# If Net::Telnet is not found, try: yum install "perl(Net::Telnet)" + +# If the LANforge libraries are not found, make sure you are running +# from the /home/lanforge directory (or where-ever you installed LANforge) + +# Contact: support@candelatech.com if you have any questions or suggestions +# for improvement. + +# Written by Candela Technologies Inc. +# Updated by: greearb@candelatech.com +# +# + +package main; +use strict; +use warnings; +use diagnostics; +use Data::Dumper; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; + +use Cwd qw(getcwd); +my $cwd = getcwd(); + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; +my $shelf_num = 1; +# Specify 'card' numbers for this configuration. +my $card = 1; + +# Default values for ye ole cmd-line args. +my $list_port_names; +my $list_ports; +my $filter_ports; +my $port_name = ""; +my $cmd = ""; +our $quiet = 1; +my $load = ""; +my $amt_resets = 1; +my $max_port_name = 0; +my $min_sleep = 60; +my $max_sleep = 120; +my $if_state = "unset"; +my $fail_msg = ""; +my $manual_check = 0; +my $amt_resets_sofar = 0; +my $show_port = "NA"; +my @port_stats = (); +my $cmd_log_name = ""; #= "lf_portmod.txt"; +my $set_speed = "NA"; +my $set_channel = "NA"; +my $set_nss = "NA"; +my $wifi_mode = "NA"; +my $passwd = "NA"; +my $ssid = "NA"; +my $ap = "NA"; +my $eap_identity = "NA"; +my $eap_passwd = "NA"; +my $cli_cmd = ""; +my $log_file = ""; +my $NOT_FOUND = "-not found-"; +my $stats_from_file = ""; +my $ip = "NA"; # DHCP or IP address +my $netmask = "NA"; # Netmask, only changed when 'IP' is set. +my $gw = "NA"; # Gateway, only changed when 'IP' is set. + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my $usage = << "EOF" + $0 --port_name {name | number} +--cmd { reset | delete } +[--manager|--mgr|-m { network address of LANforge manager} ] +[--manager_port|mgr_port|lf_port|mp { manager port number } ] +[--cli_cmd { lf-cli-command text } ] +[--amt_resets { number (0 means forever) } ] +[--max_port_name { number } ] +[--min_sleep { number (seconds) } ] +[--max_sleep { number (seconds) } ] +[--load { db-name } ] +[--card|--resource|-r { resource-id } ] +[--quiet { 0|no|1|yes } ] +[--set_ifstate { up|down } ] +[--list_port_names # prints port names for resource ] +[--list_ports # shows all ports and port details for resource] +[--filter_ports { keyword } # prints port name and matching value for all ports for resource ] +[--show_port [key,key,key] ] + # show all port stats or just those matching /key:value/ +[--stats_from_file [file-name] + # Read 'show-port' ouput from a file instead of direct query from LANforge. + # This can save a lot of time if we already have the show-port output available. +[--set_speed { wifi port speed, see GUI port-modify drop-down for possible values. Common + examples: 'OS Defaults', '6 Mbps a/g', '1 Stream /n', '2 Streams /n', MCS-0 (x1 15 M), MCS-10 (x2 90 M), + 'v-MCS-0 (x1 32.5 M)', 'v-1 Stream /AC', 'v-2 Streams /AC', ... } +[--wifi_mode { wifi mode: 0: AUTO, 1: 802.11a, 2: b, 3: g, 4: abg, 5: abgn, + 6: bgn 7: bg, 8: abgnAC, 9 anAC, 10 an} + # wifi-mode option is applied when --set_speed is used. +[--set_channel { for radios, wifi channel (36, 100, etc) } +[--set_nss { for radios, set spatial streams (1, 2, 3, 4) } +[--passwd { WiFi WPA/WPA2/ password} +[--ssid { WiFi SSID} +[--ap { BSSID of AP, or 'DEFAULT' for any.} +[--eap_identity { value | [BLANK]}] +[--eap_passwd { value | [BLANK]}] +[--log_file { value }] # disabled by default +[--ip { DHCP | IPv4 Address }] +[--netmask { network mask, only modified if IP is specified as well.] +[--gw { network gateway, only modified if IP is specified as well.] +[--help|-h ] # show help + +Examples: +./lf_portmod.pl --manager 192.168.1.101 --resource 1 --list_ports +./lf_portmod.pl --mgr 192.168.1.101 --card 1 --port_name eth2 --show_port +./lf_portmod.pl -m 192.168.1.101 -r 1 --port_name sta1 --show_port AP,ESSID,bps_rx,bps_tx +./lf_portmod.pl -m 192.168.1.101 --card 1 --port_name sta1 --stats_from_file /tmp/ports.txt --show_port AP,ESSID,bps_rx,bps_tx +./lf_portmod.pl -m 192.168.1.101 --cli_cmd "scan 1 1 sta0" +./lf_portmod.pl - 192.168.1.101 --card 1 --port_name eth2 --cmd reset +./lf_portmod.pl -m 192.168.1.101 --card 1 --port_name eth2 --set_ifstate down +./lf_portmod.pl -m 192.168.1.101 --card 1 --port_name eth2 --ip DHCP +./lf_portmod.pl -m 192.168.1.101 --card 1 --port_name eth2 --ip 10.1.1.1 --netmask 255.255.0.0 --gw 10.1.1.254 +./lf_portmod.pl -m 192.168.1.101 --card 1 --port_name sta0 --wifi_mode 2 --set_speed "1 Mbps /b" \\ + --ssid fast-ap --passwd "secret passwd" --ap DEFAULT +./lf_portmod.pl -m 192.168.1.101 --card 1 --port_name wiphy0 --set_channel "36" --set_nss 2 +./lf_portmod.pl --load my_db +./lf_portmod.pl -m 192.168.100.138 --cmd reset --port_name 2 --amt_resets 5 --max_port_name 8 --card 1 --min_sleep 10 --max_sleep 20 +./lf_portmod.pl -m 192.168.1.101 --card 1 --port_name sta11 --cmd set_wifi_extra --eap_identity 'adams' --eap_passwd 'family' + +# Set wlan0 to /a/b/g mode, 1Mbps encoding rate +./lf_portmod.pl --manager localhost --card 1 --port_name wlan0 --wifi_mode 4 --set_speed "1 Mbps /b" + +# Set wlan0 to /a/b/g mode, default encoding rates +./lf_portmod.pl --manager localhost --card 1 --port_name wlan0 --wifi_mode 4 --set_speed "DEFAULT" + +# Set wlan0 to /a/b/g/n mode, default encoding rates for 1 antenna stations (1x1) +./lf_portmod.pl --manager localhost --card 1 --port_name wlan0 --wifi_mode 5 --set_speed "1 Stream /n" + +# Set wlan0 to /a/b/g/n mode, default encoding rates for 2 antenna stations (2x2) +./lf_portmod.pl --manager localhost --card 1 --port_name wlan0 --wifi_mode 5 --set_speed "2 Streams /n" + +# Set wlan0 to /a/b/g/n mode, default encoding rates for 3 antenna stations (3x3) +./lf_portmod.pl --manager localhost --card 1 --port_name wlan0 --wifi_mode 5 --set_speed "DEFAULT" + +# Set wlan0 to /a/b/g/n/AC mode, default encoding rates for 1 antenna stations (1x1) +./lf_portmod.pl --manager localhost --card 1 --port_name wlan0 --wifi_mode 8 --set_speed "v-1 Stream /AC" + +# Set wlan0 to /a/b/g/n/AC mode, default encoding rates for 2 antenna stations (2x2) +./lf_portmod.pl --manager localhost --card 1 --port_name wlan0 --wifi_mode 8 --set_speed "v-2 Streams /AC" + +# Set wlan0 to /a/b/g/n/AC mode, default encoding rates for 3 antenna stations (3x3) +./lf_portmod.pl --manager localhost --card 1 --port_name wlan0 --wifi_mode 8 --set_speed "DEFAULT" +EOF +; + +my $i = 0; +my $log_cli = 'unset'; +my $show_help = 0; + +if (@ARGV < 2) { + print $usage; + exit 0; +} +GetOptions +( + 'help|h' => \$show_help, + 'ap=s' => \$ap, + 'port_name|e=s' => \$port_name, + 'cmd=s' => \$cmd, + 'cli_cmd=s' => \$cli_cmd, + 'manager|mgr|m=s' => \$lfmgr_host, + 'manager_port|mgr_port|lf_port|mp:i' => \$lfmgr_port, + 'load_db|load=s' => \$load, + 'quiet|q=s' => \$::quiet, + 'resource|card|res|r=i' => \$card, + 'amt_resets=i' => \$amt_resets, + 'max_port_name=i' => \$max_port_name, + 'min_sleep=i' => \$min_sleep, + 'max_sleep=i' => \$max_sleep, + 'passwd=s' => \$passwd, + 'set_ifstate|s=s' => \$if_state, + 'set_speed=s' => \$set_speed, + 'set_channel=s' => \$set_channel, + 'set_nss=s' => \$set_nss, + 'ssid=s' => \$ssid, + 'list_port_names!' => \$list_port_names, + 'list_ports!' => \$list_ports, + 'filter_ports|filter|f=s' => \$filter_ports, + 'show_port:s' => \$show_port, + 'stats_from_file=s' => \$stats_from_file, + 'port_stats=s{1,}' => \@port_stats, + 'eap_identity|eapid=s' => \$eap_identity, + 'eap_passwd|p=s' => \$eap_passwd, + 'ip=s' => \$ip, + 'netmask=s' => \$netmask, + 'gw=s' => \$gw, + 'log_file|l=s' => \$log_file, + 'log_cli=s{0,1}' => \$log_cli, + 'wifi_mode=i' => \$wifi_mode, + ) || (print($usage) && exit(1)); + + if ($::quiet eq "0") { + $::quiet = "no"; + } + elsif ($::quiet eq "1") { + $::quiet = "yes"; + } + +if ($show_help) { + print $usage; + exit 0 +} + +if ($set_nss ne "NA") { + # Convert to what LANforge CLI wants. + if ($set_nss eq "2") { + $set_nss = "4"; + } + elsif ($set_nss eq "3") { + $set_nss = "7"; + } + elsif ($set_nss eq "4") { + $set_nss = "8"; + } +} + +our $utils = undef; +my $t = undef; + +if ($stats_from_file eq "") { + # Open connection to the LANforge server. + if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } + } + + # Open connection to the LANforge server. + my $dt = ""; + + # Configure our utils. + $::utils = new LANforge::Utils(); + my $t = $::utils->connect($lfmgr_host, $lfmgr_port); + $::utils->sleep_ms(10); + if ($::utils->isQuiet()) { + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + $::utils->cli_send_silent(0); + } + else { + $::utils->cli_send_silent(1); # Do not show input to telnet + } + $::utils->cli_rcv_silent(1); # Repress output from telnet + } + else { + $::utils->cli_send_silent(0); # Show input to telnet + $::utils->cli_rcv_silent(0); # Show output from telnet + } + $::utils->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); +} # ~if stats_from_file + + +if (defined $log_file && ($log_file ne "")) { + open(CMD_LOG, ">$log_file") or die("Can't open $log_file for writing...\n"); + $cmd_log_name = $log_file; + if (!$::utils->isQuiet()) { + print "History of all commands can be found in $log_file\n"; + } +} + + +sub fmt_port_up_down { + my ($resource, $port_id, $state) = @_; + + my $cur_flags = 0; + if ($state eq "down") { + $cur_flags |= 0x1; # port down + } + + # Specify the interest flags so LANforge knows which flag bits to pay attention to. + my $ist_flags = 0; + $ist_flags |= 0x2; # check current flags + $ist_flags |= 0x800000; # port down + + my $cmd = $::utils->fmt_cmd("set_port", 1, $resource, $port_id, "NA", + "NA", "NA", "NA", "$cur_flags", + "NA", "NA", "NA", "NA", "$ist_flags"); + return $cmd; +} + +sub fmt_port_ip { + my ($resource, $port_id, $ip, $mask, $gw) = @_; + + my $set_ip = "NA"; + my $set_mask = "NA"; + my $set_gw = "NA"; + + my $ist_flags = 0; + $ist_flags |= 0x4000; # interested in dhcp + + # Specify the interest flags so LANforge knows which flag bits to pay attention to. + my $cur_flags = 0; + if ($ip eq "DHCP") { + $cur_flags |= 0x80000000; + } + else { + $set_ip = $ip; + $set_mask = $mask; + $set_gw = $gw; + + if ($set_ip ne "NA") { + $ist_flags |= (1<<2); # interested in IP + } + if ($set_mask ne "NA") { + $ist_flags |= (1<<3); # interested in netmask + } + if ($set_gw ne "NA") { + $ist_flags |= (1<<4); # interested in gateway + } + } + + my $cmd = $::utils->fmt_cmd("set_port", 1, $resource, $port_id, "$set_ip", + "$set_mask", "$set_gw", "NA", "$cur_flags", + "NA", "NA", "NA", "NA", "$ist_flags"); + return $cmd; +} + +sub fmt_wifi_extra { + my ($resource, $port_id, $eap_id, $eap_passwd) = @_; + my $cmd = $::utils->fmt_cmd("set_wifi_extra", 1, $resource, $port_id, + "NA", # key_mgmt Key management: WPA-PSK, WPA-EAP, IEEE8021X, NONE, WPA-PSK-SHA256, WPA-EAP-SHA256 or combo. + "NA", # pairwise Pairwise ciphers: CCMP, TKIP, NONE, or combination. + "NA", # group Group cyphers: CCMP, TKIP, WEP104, WEP40, or combination. + "NA", # psk WPA pre-shared key. + "NA", # key WEP key0. Should enter this in ascii-hex. + "NA", # ca_cert CA-CERT file name. + "NA", # eap EAP method: MD5, MSCHAPV2, OTP, GTC, TLS, PEAP, TTLS. + "$eap_id", # identity EAP Identity string. + "NA", # anonymous_identity Anonymous identity string for EAP. + "NA", # phase1 Outer-authentication, ie TLS tunnel parameters. + "NA", # phase2 Inner authentication with TLS tunnel. + "$eap_passwd", # password EAP Password string. + "NA", # pin EAP-SIM pin string. (For AP, this field is HS20 Operating Class) + "NA", # pac_file EAP-FAST PAC-File name. (For AP, this field is the RADIUS secret password) + "NA", # private_key EAP private key certificate file name. (For AP, this field is HS20 WAN Metrics) + "NA", # pk_passwd EAP private key password. (For AP, this field is HS20 connection capability) + "NA", # hessid 802.11u HESSID (MAC address format). + "NA", # realm 802.11u realm: mytelco.com + "NA", # client_cert 802.11u Client cert file /etc/wpa_supplicant/ca.pem + "NA", # imsi 802.11u IMSI: 310026-000000000 + "NA", # milenage 802.11u milenage: 90dca4eda45b53cf0f12d7c9c3bc6a89:cb9cccc4b9258e6dca4760379fb82 + "NA", # domain 802.11u domain: mytelco.com + "NA", # roaming_consortium 802.11u roaming consortium: 223344 (15 characters max) + "NA", # venue_group 802.11u Venue Group, integer. VAP only. + "NA", # venue_type 802.11u Venue Type, integer. VAP only. + "NA", # network_type 802.11u network type, integer, VAP only.* + "NA", # ipaddr_type_avail 802.11u network type available, integer, VAP only. + "NA", # network_auth_type 802.11u network authentication type, VAP only. + "NA" # anqp_3gpp_cell_net 802.11u 3GCPP Cellular Network Info, VAP only. + ); + return $cmd; +} + +# $::utils->doCmd("log_level 63"); + +if ($cli_cmd ne "") { + print $::utils->doAsyncCmd($cli_cmd) ."\n"; + close(CMD_LOG); + exit(0); +} + +if ($load ne "") { + $cli_cmd = "load $load overwrite"; + my @rslt = $::utils->doAsyncCmd($cli_cmd); + # $t->waitfor("/LOAD-DB: Load attempt has been completed./"); + if (!$::utils->isQuiet()) { + print @rslt; + print "\n"; + } + close(CMD_LOG); + exit(0); +} + + +if ((defined $list_port_names) && ($list_port_names ne "")) { + my @lines =split("\n", $::utils->doAsyncCmd("nc_show_ports 1 $card all")); + my $note = ""; + my $eid = ""; + my $ip = ""; + my @out = (); + for my $line (@lines) { + if ($line =~ /^Shelf: 1, /) { + my ($r, $n) = ($line =~ /Card: (\d+), Port: (\d+) /); + $eid .="1.${r}.${n} "; + } + if ($line =~ / IP: /) { + my ($i) = ($line =~ / IP: ([^ ]+) /); + $ip .=" $i"; + } + if ($line =~ / MAC: .* DEV: /) { + my ($mac, $dev) = ($line =~ / MAC: ([^ ]+)\s*DEV: ([^ ]+) /); + push(@out, "$eid\t$dev\t$mac\t$ip"); + $eid = ""; + $ip = ""; + } + } + + if (@out > 0) { + print("EID\tDEV\tMAC \tIP\n"); + print (join("\n", sort(@out))."\n"); + } + exit(0); +} + +if ((defined $filter_ports) && ($filter_ports ne "")) { + my @lines =split("\n", $::utils->doAsyncCmd("nc_show_ports 1 $card all")); + my @keys = split(/,/, $filter_ports); + my $note = ""; + my $eid = ""; + my $next_eid = ""; + my $ip = ""; + my @out = (); + my $dev = ""; + my %matches = (); + for(my $i=0; $i <= $#keys; $i++) { + $keys[$i] =~ tr/ |-/./; + } + my $pattern =join("|", @keys); + my $pattern_la = "(?=".join("|", @keys).": )"; + my $pattern_lb = "(?< |, |$)"; + + for my $line (@lines) { + my $saw_shelf = 0; + if ($line =~ /^Shelf: 1, /) { + $saw_shelf = 1; + my ($r, $p) = ($line =~ /Card: (\d+), Port: (\d+) /); + $next_eid ="1.${r}.${p}"; + } + print "Looking for $pattern in $line\n"; + if ($line =~ / (${pattern}): /i) { + for my $k (@keys) { + if (my ($m) = ($line =~ / (?:${k}): ([^ ]+)/i)) { + $matches{$k} = $m; + } + } + } + if ($line =~ / MAC: .* DEV: /) { + ($dev) = ($line =~ / DEV: ([^ ]+) /); + } + if ($saw_shelf == 1) { + if (keys(%matches) > 0) { + for my $k (sort keys(%matches)) { + $note .="; " if ($note ne ""); + $note .= "$k: $matches{$k}"; + } + push(@out, "$eid\t$dev\t$note"); + %matches = (); + } + $eid = $next_eid; + $dev = ""; + $note = ""; + $saw_shelf = 0; + } + } + + if (@out > 0) { + print("EID\tDEV\t".join("\t", @keys)."\n"); + print (join("\n", sort(@out))."\n"); + } + exit(0); +} + +if ((defined $list_ports) && ($list_ports ne "")) { + print $::utils->doAsyncCmd("nc_show_ports 1 $card all"); + exit(0); +} + +if (length($port_name) == 0) { + print "ERROR: Must specify port name.\n"; + die("$usage"); +} + +# this is the --show_port options ("") +if (($show_port ne "NA") && (($show_port eq "1") || ($show_port eq ""))) { + #$::quiet = 0; + #$::utils->cli_rcv_silent(0); + print $::utils->doAsyncCmd("nc_show_port 1 $card $port_name") . "\n"; + exit(0); +} +# this is the --show_port "ssss" options (key,key,key) +elsif(($show_port ne "NA") && ($show_port ne "")) { + my %option_map = (); + my $option = ''; + for $option (split(',', $show_port)) { + #print "preprare option_map.$option to ''\n"; + $option="DNS-Servers" if ($option eq "DNS Servers"); + $option="TX-Queue-Len" if ($option eq "TX Queue Len"); + $option="Missed-Beacons" if ($option eq "Missed Beacons"); + $option_map{ $option } = ''; + } + my $i; + my @lines = (); + if ($stats_from_file ne "") { + @lines = split("\n", get_stats_from_file($stats_from_file, 1, $card, $port_name)); + } + else { + @lines = split("\n", $::utils->doAsyncCmd("nc_show_port 1 $card $port_name")); + } + + my $rh_value_map = $::utils->show_as_hash(\@lines, 1); + + for my $option (keys %option_map) { + my $val = '-'; + + if (defined $rh_value_map->{$option}) { + $val = $rh_value_map->{$option}; + + $option_map{"$option"} = $val; + + if (defined $rh_value_map->{"Cfg-$option"}) { + $val = $rh_value_map->{"Cfg-$option"}; + $option_map{"Cfg-$option"} = $val; + } + if (defined $rh_value_map->{"Probed-$option"}) { + $val = $rh_value_map->{"Probed-$option"}; + $option_map{"Probed-$option"} = $val; + } + } + } + + + for $option ( sort keys %option_map ) { + #print("Checking option: $option\n"); + print $option.": ".$option_map{ $option }."\n"; + } + + exit(0); +} + +if ($if_state ne "unset") { + if ($if_state eq "up" || $if_state eq "down") { + $cli_cmd = fmt_port_up_down($card, $port_name, $if_state); + $::utils->doCmd($cli_cmd); + } + else { + print "ERROR: ifstate must be 'up' or 'down', value was: $if_state.\n"; + exit (1); + } +} + +if ($set_speed ne "NA" || $ssid ne "NA" || $passwd ne "NA" || $ap ne "NA") { + $cli_cmd = "add_vsta 1 $card NA $port_name NA '$ssid' NA '$passwd' '$ap' NA NA $wifi_mode '$set_speed'"; + $::utils->doCmd($cli_cmd); +} + +if (($set_channel ne "NA") || ($set_nss ne "NA")) { + $cli_cmd = "set_wifi_radio 1 $card $port_name NA $set_channel NA NA NA NA NA NA NA $set_nss"; + $::utils->doCmd($cli_cmd); +} + +if ($eap_identity ne "NA" || $eap_passwd ne "NA") { + my $cli_cmd = fmt_wifi_extra( $card, $port_name, "$eap_identity", "$eap_passwd"); + $::utils->doCmd($cli_cmd); +} + +if ($ip ne "NA") { + my $cli_cmd = fmt_port_ip( $card, $port_name, $ip, $netmask, $gw); + $::utils->doCmd($cli_cmd); +} + +if ($cmd eq "reset") { + my $pn_int = -1; + if ($port_name =~ /^\d+$/ ) { + $pn_int = int($port_name); + } + while (1) { + my $pname = $port_name; + if (($pn_int > 0) && ($pn_int < $max_port_name)) { + $pname = $pn_int + int(rand($max_port_name - $pn_int)); + } + print("Resetting port: ${shelf_num}.${card}.${pname}\n"); + $cli_cmd = "reset_port $shelf_num $card $pname"; + $::utils->doCmd($cli_cmd); + $amt_resets_sofar++; + if ($amt_resets != 0) { + if ($amt_resets_sofar >= $amt_resets) { + print("Completed: $amt_resets_sofar resets, exiting.\n"); + close(CMD_LOG); + exit(0); + } + } + my $sleep_time = $min_sleep; + if ($min_sleep < $max_sleep) { + $sleep_time += int(rand($max_sleep - $min_sleep)); + } + if ($sleep_time > 0) { + print("Sleeping for: $sleep_time seconds before next reset.\n"); + sleep($sleep_time); + } + }#while +} + +if ($cmd eq "delete") { + print("Deleting port: ${shelf_num}.${card}.${port_name}\n"); + $cli_cmd = "rm_vlan $shelf_num $card $port_name"; + $::utils->doCmd($cli_cmd); +} + +close(CMD_LOG); +exit(0); + + +sub get_stats_from_file { + my $fname = shift; + my $shelf = shift; + my $resource = shift; + my $port_name = shift; + + open(F, "<$fname") or die("Can't open $fname for reading: $!\n"); + + my $port_text = ""; + my $s = -1; + my $c = -1; + my $p = -1; + + my @lines = (); + while ( my $line = <F>) { + @lines = (@lines, $line); + } + # Append dummy line to make it easier to terminate the parse logic. + @lines = (@lines, "Shelf: 9999, Card: 9999, Port: 9999 Type: STA Alias: \n"); + + my $i; + for ($i = 0; $i<@lines;$i++) { + my $line = $lines[$i]; + chomp($line); + if ($line =~ /Shelf:\s+(\d+).*Card:\s+(\d+).*Port:\s+(\d+)/) { + my $m1 = $1; + my $m2 = $2; + my $m3 = $3; + + if ($port_text ne "") { + # See if existing port entry matches? + if ($s == $shelf && $c == $resource) { + my $pname = ""; + my $palias = ""; + if ($port_text =~ /\s+DEV:\s+(\S+)/) { + $pname = $1; + } + if ($port_text =~ /\s+Alias:\s+(\S+)/) { + $palias = $1; + } + #print("search for port_name: $port_name p: $p palias: $palias pname: $pname\n"); + if (("$p" eq $port_name) || + ($palias eq $port_name) || + ($pname eq $port_name)) { + return $port_text; + } + } + } + + $port_text = "$line\n"; + $s = $m1; + $c = $m2; + $p = $m3; + } + else { + if ($port_text ne "") { + $port_text .= "$line\n"; + } + } + } + return ""; +} diff --git a/lanforge/lanforge-scripts/lf_show_events.pl b/lanforge/lanforge-scripts/lf_show_events.pl new file mode 100755 index 000000000..613b5e12f --- /dev/null +++ b/lanforge/lanforge-scripts/lf_show_events.pl @@ -0,0 +1,83 @@ +#!/usr/bin/perl -w +# This program is used to create a hunt-script +# # used for matrix load emulation on LANforge +# # (C) Candela Technologies 2015 + +use strict; +use warnings; +#use Carp; +#$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; +use lib '/home/lanforge/scripts'; +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + + +# Default values for ye ole cmd-line args. +#our $resource = 1; +our $quiet = "yes"; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; +our $do_clear = 0; +our $do_alerts = 0; +# ######################################################################## +# # Nothing to configure below here, most likely. +# ######################################################################## +our $usage = qq($0 ... + [--mgr {host-name | IP}] + [--mgr_port {ip port}] + [--resource {number}] + [--quiet { yes | no }] + [--clear] # or -c; clear events. Alerts cannot be cleared. + [--alerts] # or -a; show alerts instead of events +); +my $i = 0; +my $cmd; +die($::usage) if (@ARGV < 2); + +GetOptions +( + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p=i' => \$::lfmgr_port, + 'quiet|q=s' => \$::quiet, + 'alerts|a' => \$::do_alerts, + 'clear|c' => \$::do_clear, +) || die("$::usage"); + +my $utils = new LANforge::Utils(); +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); +$t->open( Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); +$t->waitfor("/btbits\>\>/"); + +$utils->telnet($t); +if ($quiet eq "yes") { + $utils->cli_send_silent(1); + $utils->cli_rcv_silent(1); +} +else { + $utils->cli_send_silent(0); + $utils->cli_rcv_silent(0); +} + +if ($do_alerts) { + print $utils->doAsyncCmd("show_alerts"); +} +else { + print $utils->doAsyncCmd("show_events"); +} +print "\n"; + +if ($do_clear) { + $utils->doAsyncCmd("rm_event all"); +} + +exit(0); +# diff --git a/lanforge/lanforge-scripts/lf_sniff.py b/lanforge/lanforge-scripts/lf_sniff.py new file mode 100755 index 000000000..e2ec44a36 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_sniff.py @@ -0,0 +1,287 @@ +#!/usr/bin/python3 +''' + +Sniff stations on one set of radios using secondary radios. + +make sure pexpect is installed: +$ sudo yum install python3-pexpect +$ sudo yum install python3-xlsxwriter + +You might need to install pexpect-serial using pip: +$ pip3 install pexpect-serial +$ pip3 install XlsxWriter + +The user is responsible for setting up the stations oustide of this script, however. + +When specifying ports, if the port starts with [Number]., like 1.eth1, then the 1 specifies +the resource ID. + +./lf_sniff.py --lfmgr 192.168.100.178 \ + --station "1.wlan0 1.wlan1" --sniffer_radios "2.wiphy0 2.wiphy1" \ + --duration 5 + +''' + +# TODO: Maybe HTML output too? +# TODO: Allow selecting tabs or commas for output files + +import sys +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +import re +import logging +import time +from time import sleep +import pprint +import argparse +import subprocess +import xlsxwriter +from subprocess import PIPE + +NL = "\n" +CR = "\r\n" +Q = '"' +A = "'" +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' + +lfmgr = "127.0.0.1" +lfstation = "1.wlan0" +sniffer_radios = "2.wiphy0" +upstream = "" +dur = 5 * 60 +moni_flags = "0x100000000" # 160Mhz mode enabled + +# rssi_adjust = (current_nf - nf_at_calibration) + +def usage(): + print("$0 ") + print("--station: LANforge station names (1.wlan0 1.wlan1 ...)") + print("--sniffer_radios: LANforge radios to use as sniffers (2.wiphy0 2.wiphy1 ...)") + print("--lfmgr: LANforge manager IP address") + print("--duration: Duration to run traffic, in minutes") + print("--moni_flags: Monitor flags (see LANforge CLI help for set_wifi_monitor command) Default enables 160Mhz") + print("--upstreams: Upstream ports to sniff (1.eth1 ...)") + print("-h|--help") + +def main(): + global lfmgr + global lfstation + global upstream + global sniffer_radios + global dur + global moni_flags + + parser = argparse.ArgumentParser(description="Sniffer control Script") + parser.add_argument("--sniffer_radios", type=str, help="LANforge sniffer radios to use (2.wiphy0 2.wiphy1 ...)") + parser.add_argument("--station", type=str, help="LANforge stations to use (1.wlan0 1.wlan1 etc)") + parser.add_argument("--lfmgr", type=str, help="LANforge Manager IP address") + parser.add_argument("--duration", type=float, help="Duration to sniff, in minutes") + parser.add_argument("--moni_flags", type=str, help="Monitor port flags, see LANforge CLI help for set_wifi_monitor. Default enables 160Mhz") + parser.add_argument("--upstreams", type=str, help="Upstream ports to sniff (1.eth1 ...)") + parser.add_argument("--moni_idx", type=str, help="Optional monitor number", default=None) + + args = None + try: + args = parser.parse_args() + if (args.station != None): + lfstation = args.station + if (args.upstreams != None): + upstream = args.upstreams + if (args.sniffer_radios != None): + sniffer_radios = args.sniffer_radios + if (args.lfmgr != None): + lfmgr = args.lfmgr + if (args.duration != None): + dur = args.duration * 60 + if (args.moni_flags != None): + moni_flags = args.moni_flags + filehandler = None + except Exception as e: + logging.exception(e); + usage() + exit(2); + + # Use subprocess.check_output("Cmd") to utilize existing LF scripts. + + upstreams = upstream.split() + lfstations = lfstation.split() + radios = sniffer_radios.split() + monis_n = [] # monitor device names + monis_r = [] # monitor device resources + + idx = 0 + for sta in lfstations: + sta_resource = "1" + sta_name = sta; + if sta[0].isdigit(): + tmpa = sta.split(".", 1); + sta_resource = tmpa[0]; + sta_name = tmpa[1]; + + # Assume station is up and/or something else is bringing it up + + channel = 36 + bsssid = "00:00:00:00:00:00" + + i = 0 + wait_ip_print = False; + wait_assoc_print = False; + # Wait until LANforge station connects + while True: + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", sta_resource, "--port_name", sta_name, + "--show_port", "AP,IP,Mode,NSS,Bandwidth,Probed-Channel,Signal,Noise,Status,RX-Rate"], stdout=PIPE, stderr=PIPE); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + _status = None + _ip = None + + for line in pss.splitlines(): + #print("line: %s\n"%line) + m = re.search('AP:\s+(.*)', line) + if (m != None): + bssid = m.group(1) + m = re.search('Status:\s+(.*)', line) + if (m != None): + _status = m.group(1) + m = re.search('Probed-Channel:\s+(.*)', line) + if (m != None): + channel = m.group(1) + m = re.search('IP:\s+(.*)', line) + if (m != None): + _ip = m.group(1) + + #print("IP %s Status %s"%(_ip, _status)) + + if (_status == "Authorized"): + if ((_ip != None) and (_ip != "0.0.0.0")): + print("Station is associated with IP address.") + break + else: + if (not wait_ip_print): + print("Waiting for station %s.%s to get IP Address."%(sta_resource, sta_name)) + wait_ip_print = True + else: + if (not wait_assoc_print): + print("Waiting up to 180s for station %s.%s to associate."%(sta_resource, sta_name)) + wait_assoc_print = True + + i = i + 1 + # We wait a fairly long time since AP will take a long time to start on a CAC channel. + if (i > 180): + err = "ERROR: Station did not connect within 180 seconds." + print(err) + e_tot += err + e_tot += " " + if (args.wait_forever): + print("Will continue waiting, you may wish to debug the system...") + i = 0 + else: + break + + time.sleep(1) + + # Get station AID and other info + + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, + "--cli_cmd", "probe_port 1 %s %s"%(sta_resource, sta_name)], stdout=PIPE, stderr=PIPE); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + aid = 0 + for line in pss.splitlines(): + m = re.search('Local-AID:\s+(.*)', line) + if (m != None): + aid = m.group(1) + break + + # Create monitor on radio X + rad = radios[idx] + rad_resource = "1" + rad_name = rad; + #print("idx: %i moni: %s\n"%(idx, moni)) + if rad[0].isdigit(): + tmpa = rad.split(".", 1) + rad_resource = tmpa[0] + rad_name = tmpa[1] + + # Set channel on the radio + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", rad_resource, "--port_name", rad_name, + "--set_channel", "%s"%channel]); + + # Get radio index so we can name the monitor similar to how the system would auto-create them + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", rad_resource, "--port_name", rad_name, + "--show_port", "Port"], stdout=PIPE, stderr=PIPE); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + + moni_idx = args.moni_idx + if args.moni_idx is None: + for line in pss.splitlines(): + m = re.search('Port:\s+(.*)', line) + if (m != None): + moni_idx = m.group(1) + + # Create monitor interface + mname = "moni%sa"%(moni_idx); + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, + "--cli_cmd", "add_monitor 1 %s %s %s %s 0xFFFFFFFFFFFF %s %s"%(rad_resource, rad_name, mname, moni_flags, aid, bssid)]) + + print("Created monitor interface: %s on resource %s\n"%(mname, rad_resource)) + monis_n.append(mname) + monis_r.append(rad_resource) + + idx = idx + 1 + + # Start sniffing on all monitor ports + idx = 0 + sflags = "0x02" # dumpcap, no terminal + for m in monis_n: + r = monis_r[idx] + + # Wait for monitor to be non-phantom + isph = True + while isph: + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", r, "--port_name", m, + "--show_port", "Current"], stdout=PIPE, stderr=PIPE); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + for line in pss.splitlines(): + #print("line: %s\n"%line) + ma = re.search('Current:\s+(.*)', line) + if (ma != None): + cf = ma.group(1); + isph = False + for f in cf.split(): + if (f == "PHANTOM"): + isph = True + break + if isph: + print("Waiting for monitor port %s.%s to become non-phantom\n"%(r, m)); + sleep(1) + + + print("Starting sniffer on port %s.%s for %s seconds, saving to file %s.pcap on resource %s\n"%(r, m, dur, m, r)) + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, + "--cli_cmd", "sniff_port 1 %s %s NA %s %s.pcap %i"%(r, m, sflags, m, float(dur))]); + idx = idx + 1 + + # Start sniffing on all upstream ports + for u in upstreams: + u_resource = "1" + u_name = u; + if u[0].isdigit(): + tmpa = u.split(".", 1); + u_resource = tmpa[0]; + u_name = tmpa[1]; + + print("Starting sniffer on upstream port %s.%s for %s seconds, saving to file %s.pcap on resource %s\n"%(u_resource, u_name, dur, u_name, u_resource)) + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, + "--cli_cmd", "sniff_port 1 %s %s NA %s %s.pcap %i"%(u_resource, u_name, sflags, u_name, float(dur))]); + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if __name__ == '__main__': + main() + +#### +#### +#### diff --git a/lanforge/lanforge-scripts/lf_sta_name.pl b/lanforge/lanforge-scripts/lf_sta_name.pl new file mode 100755 index 000000000..653b23c1c --- /dev/null +++ b/lanforge/lanforge-scripts/lf_sta_name.pl @@ -0,0 +1,190 @@ +#!/usr/bin/perl -w +# +# This program is used to modify the LANforge virtual station aliases +# +# (C) 2016 Candela Technologies Inc. +# + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; + +use lib '/home/lanforge/scripts'; +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; +our $shelf_num = 1; +our $resource = 1; +our $quiet = "yes"; +our $do_cmd = "NA"; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## +our $usage = qq< +$0 --action { set_alias | reset_alias } ] + [--mgr {host-name | IP} default: $::lfmgr_host] + [--resource {lanforge resource id}] + [--mgr_port {ip port}] + + [--first_dev {actual device name with suffix number}] + [--last_dev {actual device name with suffix number}] + [--new_prefix {phrase to replace 'sta' with}] + [--old_prefix {old prefix}] + [--quiet { yes | no }] + # spaces and punctuation are prohibitied in aliases! + +Examples: + # alias sta100-sta149 as truck100-truck149 + $0 --mgr 192.168.100.138 --action set_alias --first_dev sta100 --last_dev sta149 --new_prefix truck + + # reset truck* stations to original sta* names + $0 --mgr 192.168.100.138 --action reset_alias --old_prefix truck + + # reset a series of station aliases to original names + $0 --mgr 192.168.100.138 --action reset_alias --first_sta truck100 --last_sta truck110 +>; + +GetOptions +( + 'action|a=s' => \$::action, + 'cmd|c=s' => \$::do_cmd, + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p=i' => \$::lfmgr_port, + 'resource|r=i' => \$::resource, + 'quiet|q=s' => \$::quiet, + 'new_prefix=s' => \$::new_prefix, + 'old_prefix=s' => \$::old_prefix, + 'first_dev=s' => \$::first_dev, + 'last_dev=s' => \$::last_dev, +) || (print($usage) && exit(1)); + +die ("Please specify manager address. $::usage") + if (!defined $::lfmgr_host || "$::lfmgr_host" eq "" ); + +die ("Please specify resource id. $::usage") + if (!defined $::resource || "$::resource" eq "" ); + +die ("Please tell me what to do with --action. " ) + if (!defined $::action || "$::action" eq ""); + +if ($::action eq "set_alias" ) { + die( "Please specify the first station device. $::usage") + if (!defined $::first_dev || "$::first_dev" eq "" ); + die( "Please specify the last station device. $::usage") + if (!defined $::last_dev || "$::last_dev" eq "" ); + die( "Please specify the new prefix. $::usage") + if (!defined $::new_prefix || "$::new_prefix" eq "" ); +} +elsif ($::action eq "reset_alias" && !defined $::first_dev) { + die( "Please specify the old prefix. $::usage") + if (!defined $::old_prefix || "$::old_prefix" eq ""); +} + + +# Open connection to the LANforge server. + +our $telnet = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); + +$::telnet->open( Host => $::lfmgr_host, + Port => $::lfmgr_port, + Timeout => 10); + +$::telnet->waitfor("/btbits\>\>/"); + + +# Configure our utils. +our $utils = new LANforge::Utils(); +$::utils->telnet($::telnet); # Set our telnet object. +if ($::quiet eq "yes") { + $::utils->cli_send_silent(1); # Do show input to CLI + $::utils->cli_rcv_silent(1); # Repress output from CLI ?? +} +else { + $utils->cli_send_silent(0); # Do show input to CLI + $utils->cli_rcv_silent(0); # Repress output from CLI ?? +} + +my $in_bounds = 0; +my @port_names = (); +my @sorted_names; +my @matching_devices = (); +my %port_map = (); +my $port_name; +my $port; +my $cmd; +my $alias; +my @ports; +if ($::action eq "set_alias" || $::action eq "reset_alias") { + @ports = $::utils->getPortListing($::shelf_num, $::resource); +} +else { + die("Actions are set_alias and reset_alias."); +} + +for (my $i = 0; $i<@ports; $i++) { + $port_name = $ports[$i]->dev(); + push(@port_names, $port_name); + $port_map{ $port_name } = $i; +} +@sorted_names = sort { lc($a) cmp lc($b) } @port_names; +for $port_name (@sorted_names) { + my $i = $port_map{ $port_name }; + $port = $ports[ $i ]; + $alias = $port->alias(); + if (defined $::first_dev && defined $::last_dev) { + if ($port_name eq $::first_dev || $alias eq $::first_dev) { + $in_bounds = 1; + } + if ($in_bounds) { + push(@matching_devices, $port); + } + if ($port_name eq $::last_dev || $alias eq $::last_dev) { + $in_bounds = 0; + } + } + if (defined $::old_prefix && "$::old_prefix" ne "") { + print "\nchecking $port_name ($alias)" if ($quiet eq "no"); + if ($alias =~ /^$::old_prefix\d+/) { + print "* " if ($quiet eq "no"); + push(@matching_devices, $port); + } + } +} + +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +## Note that alias is for mvlans, nothing will be found # +## $cmd = $::utils->fmt_cmd("set_port_alias", $::shelf_num, # +## $::resource, $parname, $mac, $alias); # +## ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # + +for $port (@matching_devices) { + $port_name = $port->dev(); + my $portno = $port->port_id(); + my ($suffix) = $port_name =~/^.*?(\d+)$/; + + if ($::action eq "set_alias" ) { + $alias = "$::new_prefix$suffix"; + } + else { + $alias = $port->dev(); + } + # set_port shelf resource port ip_addr netmask gateway + # cmd_flags current_flags MAC MTU tx_queue_len alias interest + $cmd = $::utils->fmt_cmd("set_port", $::shelf_num, $::resource, $portno, + "NA", "NA", "NA", "NA", "NA", "NA", "NA", "NA", + $alias, 0x1000); + $::utils->doCmd($cmd); +} + +# diff --git a/lanforge/lanforge-scripts/lf_staggered_dl.sh b/lanforge/lanforge-scripts/lf_staggered_dl.sh new file mode 100755 index 000000000..ab9c8c0be --- /dev/null +++ b/lanforge/lanforge-scripts/lf_staggered_dl.sh @@ -0,0 +1,299 @@ +#!/bin/bash +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# This script starts a series of Layer-3 connections across a series of stations # +# each station will wait $nap seconds, download $quantity KB and then remove # +# its old CX. # +# # +# INSTALL # +# Copy this script to to /home/lanforge/scripts/lf_staggered_dl.sh # +# If you are copying this via DOS/Windows, follow these steps: # +# 1) copy using samba or pscp or winscp or whatever this script to # +# /home/lanforge/scripts/lf_staggered_dl.sh # +# 2) in a terminal on the LANforge, run dos2unix and # +# $ cd /home/lanforge/sripts # +# $ dos2unix lf_staggered_dl.sh # +# 3) make the script executable: # +# $ chmod a+x lf_staggered_dl.sh # +# # +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# . lanforge.profile +[ ! -f lf_firemod.pl ] && echo "Unable to find lf_firemod.pl." && exit 1 + +#set -e +q="'" +Q='"' +manager=localhost # :m +resource=1 # :m +first_sta= # :f +upstream=x # :u +last_sta=x # :l +num_sta=x # :n +naptime=x # :z +payload_kb=x # :s +tx_rate=x # :t +check_naptime=1.0 # seconds between lf_firemod check on endpoint stats +timer=1000 # report timer + + +function term_procs() { + echo -en "\nCleaning up background tasks: "; + for pid in "${childprocs[@]}"; do + echo -n "$pid, " + kill -9 $pid &>/dev/null || true + done + echo " done" +} +trap term_procs EXIT + +function usage() { + cat <<__EOF__ +${0}: starts a series of layer-3 connections and makes each start +downloading a fixed amount of data after a naptime. + -m # lanforge manager (defaults to localhost) + -r # lanforge resource (defaults to 1) + -f # first station/port + -n # number of stations/ports + -z # naptime before beginning download + -u # upstream port that will transmit + -t # transmit bps + -p # payload size in KB + +Example: # 20 stations (sta100-sta120) nap 3 seconds before downloading 200KB + ${0} -m 192.168.1.101 -r 1 -f sta100 -n 20 -z 3 -u eth1 -p 250 -t 1500000 + +__EOF__ +} + +while getopts ":f:m:n:p:r:t:u:z:" opt; do + case "${opt}" in + f) + first_sta="${OPTARG}" + ;; + m) + manager="${OPTARG}" + ;; + n) + num_sta="${OPTARG}" + ;; + p) + payload_kb="${OPTARG}" + ;; + r) + resource="${OPTARG}" + ;; + t) + tx_rate="${OPTARG}" + ;; + u) + upstream="${OPTARG}" + ;; + z) + naptime="${OPTARG}" + ;; + *) + usage + exit 1 + ;; + esac +done +shift $(( OPTIND - 1 )); + +[ -z "$manager" -o "$manager" == x ] \ + && echo "Please specify LANforge manager ip or hostname." && usage && exit 1 + +[ -z "$resource" -o "$resource" == x ] \ + && echo "Please specify LANforge resource for stations." && usage && exit 1 + +[ -z "$first_sta" -o "$first_sta" == x ] \ + && echo "Please specify first station or port in series to download " && usage && exit 1 + +[ -z "$num_sta" -o "$num_sta" == x ] \ + && echo "Please specify number of stations to put connections on." && usage && exit 1 + +[ -z "$naptime" -o "$naptime" == x ] \ + && echo "Please specify number of seconds to wait before transmitting." && usage && exit 1 + +[ -z "$payload_kb" -o "$payload_kb" == x ] \ + && echo "Please specify kilobytes to transfer per connection." && usage && exit 1 + +[ -z "$upstream" -o "$upstream" == x ] \ + && echo "Please specify upstream port to transmit from" && usage && exit 1 + +[ -z "$tx_rate" -o "$tx_rate" == x ] \ + && echo "Please specify transmit rate in bps" && usage && exit 1 + +declare -a childprocs +declare -a stations +declare -a cx_names +declare -a cx_create_endp +declare -a cx_create_cx +declare -a cx_mod_endp +declare -a cx_start_cx +declare -a cx_started +declare -a cx_finished +declare -a cx_destroy_cx +declare -A map_destroy_cx + +sta_pref=${first_sta//[0-9]/} +sta_start=${first_sta//[A-Za-z]/} +[ -z "$sta_pref" ] && echo "Unable to determine beginning station prefix" && exit 1 +[ -z "$sta_start" -o $sta_start -lt 0 ] && echo "Unable to determine beginning station number." && exit 1 +[ $num_sta -lt 1 ] && echo "Unable to deterine number of stations to create." && exit 1 + +packets=$(( 1 + $(( $payload_kb * 1000 / 1460 )) )) +[ -z "$packets" -o $packets -lt 2 ] && echo "Unable to calculate packets for transfer." && exit 1 +# 111 is a trick number that we'll truncate to three digits later + +expon=`echo "111 * 10^${#sta_start}" | bc -l` +counter=$(( expon + $sta_start )) +limit=$(( expon + $sta_start + $num_sta -1 )) +for i in `seq $counter $limit` ; do + stations+=("${sta_pref}${counter#111}") + cx_names+=("c-${upstream}-${sta_pref}${counter#111}"); + counter=$(( counter + 1 )) +done +_act="./lf_firemod.pl --mgr $manager --resource $resource --quiet yes --action" +_cmd="./lf_firemod.pl --mgr $manager --resource $resource --quiet yes --cmd" +counter=0 +for cx in "${cx_names[@]}"; do + cx_create_endp+=("$_act create_endp --endp_name ${cx}-A --speed $tx_rate --endp_type lf_tcp --port_name ${upstream} --report_timer $timer") + cx_create_endp+=("sleep 0.1") + cx_create_endp+=("$_act create_endp --endp_name ${cx}-B --speed 0 --endp_type lf_tcp --port_name ${stations[$counter]} --report_timer $timer") + cx_create_endp+=("sleep 0.1") + + cx_create_cx+=("$_act create_cx --cx_name ${cx} --cx_endps ${cx}-A,${cx}-B --report_timer $timer") + cx_create_cx+=("sleep 0.2") + + cx_mod_endp+=("${cx}-A NA NA NA ${packets}") + + nap=$(( $naptime * $counter )) + + cx_start_cx+=("sleep $nap; $_cmd ${Q}set_cx_state all ${cx} RUNNING${Q} &>/dev/null") + + cx_destroy_cx+=("$_act delete_cx --cx_name ${cx}") + cx_destroy_cx+=("sleep 0.1") + cx_destroy_cx+=("$_act delete_endp --endp_name ${cx}-A") + cx_destroy_cx+=("$_act delete_endp --endp_name ${cx}-B") + cx_destroy_cx+=("sleep 0.1") + + map_destroy_cx[${cx}]="$_act delete_cx --cx_name ${cx}; sleep 0.1; $_act delete_endp --endp_name ${cx}-A; $_act delete_endp --endp_name ${cx}-B"; + counter=$(( counter + 1 )) +done +echo -n "Removing previous connections..." +for command in "${cx_destroy_cx[@]}" ; do + $command +done +sleep 1 + +echo "done" + +echo -n "Creating new endpoints..." +for command in "${cx_create_endp[@]}" ; do + $command +done +echo "done" + +echo -n "Creating new cross connects..." +sleep $(( 2 + $(( $counter /2 )) )) +for command in "${cx_create_cx[@]}" ; do + $command +done +./lf_firemod.pl --mgr $manager --quiet yes --cmd "nc_show_endp all" > /tmp/ep_count.txt +echo "done" + +echo -n "Configuring payload sizes..." +outf="/tmp/cmd.$$.txt" +for command in "${cx_mod_endp[@]}" ; do + result=1 + rm -f $outf + while [ $result -ne 0 ]; do + ep=${command%% *} + #$_cmd "nc_show_endp $ep" + $_cmd "set_endp_details $command" > $outf + result=$(awk '/RSLT:/{print $2}' $outf) + if [ $result -ne 0 ]; then + cat $outf + sleep 5 + fi + done + #sleep 0.1 +done +echo "done" + +echo -n "Starting staggered transmissions..." +sleep $(( 2 + $(( $counter /2 )) )) +for command in "${cx_start_cx[@]}" ; do + bash -c "$command" & + childprocs+=($!) + sleep 0.1 +done +echo "done" + +echo "Monitoring staggered downloads for ports:" +echo -e "\t${cx_names[@]}" +echo "_R_unning _Q_uiesce _N_ot Running (Tx Packets/Requested Packets)" +echo "--------------------------------------------------------------" +counter=1 +while [ $counter -ne 0 ]; do + messages=() + #echo -en "" + for cx in "${cx_names[@]}"; do + while read L ; do + endp_report+=("$L") + done < <($_act show_endp --endp_name ${cx}-A) + + state="?" + for i in `seq 0 $((${#endp_report[@]}-1))`; do + ine="${endp_report[$i]}"; + h=($ine); + case "${ine}" in + "Endpoint "*) + state="${h[2]:1:-1}" + ;; + "Tx Pkts: "*) + txpkts="${h[3]}"; + ;; + esac + done + + if [[ " ${cx_started[@]} " =~ " ${cx} " ]]; then # can inspect for finishing + if [[ ! " ${cx_finished[@]} " =~ " ${cx} " ]]; then + if [ ${txpkts} -ge ${packets} -a ${state} = "NOT_RUNNING" ] ; then + cx_finished+=(${cx}) + messages+=("$cx: finished running") + cmd=${map_destroy_cx[${cx}]}; + #messages+=(" CMD[ $cmd ]"); + bash -c "$cmd" + cx_started=(${cx_started[@]/$cx}) + fi + fi + elif [[ " ${cx_finished[@]} " =~ " ${cx} " ]]; then + : + else + if [ ${txpkts} -gt 0 ]; then + messages+=("$cx: started running") + cx_started+=(${cx}) + fi + fi + case $state in + "RUNNING") st="R";; + "NOT_RUNNING") st="N";; + "QUIESCE") st="Q";; + *) + esac + echo -en "${cx}: ${st} ${txpkts}/${packets} " + done + echo "" + for m in "${messages[@]}"; do + echo -e "\t${m}" + done + + # compare the number of finished stations to total number of stations + [ ${#cx_finished[@]} -eq ${#cx_names[@]} ] && break; + + sleep $check_naptime +done +echo "Waiting for background jobs to finish..." +wait + +#eof diff --git a/lanforge/lanforge-scripts/lf_stress1.pl b/lanforge/lanforge-scripts/lf_stress1.pl new file mode 100755 index 000000000..a96410311 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_stress1.pl @@ -0,0 +1,257 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, and custom_tcp +# across 3 ports on 2 machines. +# It then continously starts and stops the connections. + +# Un-buffer output +$| = 1; + +use Net::Telnet (); + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# This sets up connections between 2 LANforge machines +my $lf1 = 1; +my $lf2 = 2; + +# Port pairs. These are the ports that should be talking to each other. +# Ie, the third column in lf1_ports talks to the third column in lf2_ports. +my @lf1_ports = ( 1, 2, 3 ); +my @lf2_ports = ( 1, 2, 3 ); + +my @lf1_port_ips = ( "172.1.1.2", "172.1.2.2", "172.1.2.200" ); +my @lf2_port_ips = ( "172.1.1.3", "172.1.2.3", "172.1.2.201" ); + +my @lf1_port_gws = ( "172.1.1.1", "172.1.2.1", "172.1.2.1" ); +my @lf2_port_gws = ( "172.1.1.1", "172.1.2.1", "172.1.2.1" ); + +# Set up one CX of each of these types on each port pair. +my @cx_types = + ( "lf", "lf_udp", "lf_tcp", "custom_ether", "custom_udp", "custom_tcp" ); +my @min_pkt_szs = ( 64, 1, 1, 64, 1, 1 ); +my @max_pkt_szs = ( 1514, 12000, 13000, 1514, 2048, 2048 ); + +my $min_rate = 512000; +my $max_rate = 1024000; + +my $test_mgr = "ben_tm"; + +my $loop_max = 100; +my $start_stop_iterations = 100; +my $run_for_time = 120; # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Run for XX seconds..then will be stopped again +my $report_timer = 3000; # 3 seconds + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet( Prompt => '/default\@btbits\>\>/' ); + +$t->open( + Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10 +); + +$t->waitfor("/btbits\>\>/"); + +my $dt = ""; + +my $loops = 0; +for ( $loop = 0 ; $loop < $loop_max ; $loop++ ) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + initToDefaults(); + + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + my $rl = 0; + for ( $rl = 0 ; $rl < $start_stop_iterations ; $rl++ ) { + if ( ( $rl % 2 ) == 0 ) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + + # Do one at a time + my $q = 0; + for ( $q = 0 ; $q < @cx_names ; $q++ ) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if ( ( $rl % 2 ) == 0 ) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + + # Do one at a time + my $q = 0; + for ( $q = 0 ; $q < @cx_names ; $q++ ) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + + } # For some amount of start_stop iterations... +} # for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + +sub initToDefaults { + + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +} #initToDefaults + +sub initPortsToDefault { + + # Set all ports we are messing with to known state. + my $i = 0; + for ( $i = 0 ; $i < @lf1_ports ; $i++ ) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf_num $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + doCmd("set_port $shelf_num $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } +} + +sub initIpAddresses { + + # Set all ports we are messing with to known state. + my $i = 0; + for ( $i = 0 ; $i < @lf1_ports ; $i++ ) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = + "set_port $shelf_num $lf1 $tmp " + . $lf1_port_ips[$i] + . " 255.255.255.0 " + . $lf1_port_gws[$i] + . " NA NA NA"; + doCmd($cmd); + $cmd = + "set_port $shelf_num $lf2 $tmp2 " + . $lf2_port_ips[$i] + . " 255.255.255.0 " + . $lf2_port_gws[$i] + . " NA NA NA"; + doCmd($cmd); + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + for ( $i = 0 ; $i < @cx_types ; $i++ ) { + my $j = 0; + for ( $j = 0 ; $j < @lf1_ports ; $j++ ) { + my $burst = "NO"; + if ( $min_rate != $max_rate ) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ( $min_pkt_szs[$i] != $max_pkt_szs[$i] ) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ( $cx_types[$i] =~ /custom/ ) { + $pattern = "custom"; + } + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = ( @endpoint_names, $ep1, $ep2 ); + + my $cmd = + "add_endp $ep1 $shelf_num $lf1 " + . $lf1_ports[$j] . " " + . @cx_types[$i] + . " -1 $burst $min_rate $max_rate $szrnd " + . $min_pkt_szs[$i] . " " + . $max_pkt_szs[$i] + . " $pattern NO"; + doCmd($cmd); + + $cmd = + "add_endp $ep2 $shelf_num $lf2 " + . $lf2_ports[$j] . " " + . @cx_types[$i] + . " -1 $burst $min_rate $max_rate $szrnd " + . $min_pkt_szs[$i] . " " + . $max_pkt_szs[$i] + . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = ( @cx_names, $cx_name ); + + } #for all ports + } #for all endpoint types +} #addCrossConnects + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_stress2.pl b/lanforge/lanforge-scripts/lf_stress2.pl new file mode 100755 index 000000000..9101b7de7 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_stress2.pl @@ -0,0 +1,234 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This creates a few fast connections between 3 ports on two machines. +# It then starts/stops them with a fairly lengthy run between them.. + +# Un-buffer output +$| = 1; + +use Net::Telnet (); + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# This sets up connections between 2 LANforge machines (card 1 and card 2) +my $lf1 = 1; +my $lf2 = 2; + +# Port pairs. These are the ports that should be talking to each other. +# Ie, the third column in lf1_ports talks to the third column in lf2_ports. +my @lf1_ports = (1, 2, 3); +my @lf2_ports = (1, 2, 3); + +my @lf1_port_ips = ("172.1.1.2", "172.1.2.2", "172.1.2.200"); +my @lf2_port_ips = ("172.1.1.3", "172.1.2.3", "172.1.2.201"); + +my @lf1_port_gws = ("172.1.1.1", "172.1.2.1", "172.1.2.1"); +my @lf2_port_gws = ("172.1.1.1", "172.1.2.1", "172.1.2.1"); + +# Set up one CX of each of these types on each port pair. +my @cx_types = ("lf_udp", "lf_tcp"); +my @min_pkt_szs = (8000, 8000); +my @max_pkt_szs = (12000, 12000); + +my $min_rate = 10000000; +my $max_rate = 10000000; + +my $test_mgr = "ben_tm"; + +my $loop_max = 100; +my $start_stop_iterations = 100; +my $run_for_time = 1200; # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Stop for XX seconds, before running again +my $report_timer = 5000; # XX/1000 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +my $dt = ""; + +my $loops = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + initToDefaults(); + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + my $rl = 0; + for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + + }# For some amount of start_stop iterations... +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + + initPortsToDefault(); +}#initToDefaults + + +sub initPortsToDefault { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf_num $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + doCmd("set_port $shelf_num $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = "set_port $shelf_num $lf1 $tmp " . $lf1_port_ips[$i] . " 255.255.255.0 " . + $lf1_port_gws[$i] . " NA NA NA"; + doCmd($cmd); + $cmd = "set_port $shelf_num $lf2 $tmp2 " . $lf2_port_ips[$i] . " 255.255.255.0 " . + $lf2_port_gws[$i] . " NA NA NA"; + doCmd($cmd); + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + for ($i = 0; $i<@cx_types; $i++) { + my $j = 0; + for ($j = 0; $j<@lf1_ports; $j++) { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf_num $lf1 " . $lf1_ports[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + $cmd = "add_endp $ep2 $shelf_num $lf2 " . $lf2_ports[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + + }#for all ports + }#for all endpoint types +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_stress3.pl b/lanforge/lanforge-scripts/lf_stress3.pl new file mode 100755 index 000000000..2460b219f --- /dev/null +++ b/lanforge/lanforge-scripts/lf_stress3.pl @@ -0,0 +1,297 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script is used to test 4 high-end machines. Two of them have +# GigE NICs in them, and will be configured to run back-to-back. Two +# other machines have a 4-port NIC and 2 single-port NICs. These ports +# will be configured to talk to each other.. + +# Un-buffer output +$| = 1; + +use Net::Telnet (); + +my $lfmgr_host = "lanf3"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# Specify 'card' numbers for this configuration. +my $lanf1 = 1; +my $lanf2 = 2; +my $lanf3 = 3; +my $lanf4 = 4; + +my $test_mgr = "whoi"; + +my $loop_max = 100; +my $start_stop_iterations = 100; +my $run_for_time = (60 * 60 * 24); # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Stop for XX seconds..then will be started again +my $report_timer = 3000; # 3 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +my $dt = ""; + +# Do some thing over and over again... +my $loops = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + # Remove any existing configuration information + initToDefaults(); + + print " ***Sleeping 8 seconds for ports to initialize to defaults...\n"; + sleep(8); + + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + # Add some IP addresses to the ports + initIpAddresses(); + + print " ***Sleeping 8 seconds for ports to initialize to current values...\n"; + sleep(8); + + # Add our endpoints + addCrossConnects(); + + my $rl = 0; + for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + + }# For some amount of start_stop iterations... +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub initPortsToDefault { + # Set all ports we are messing with to known state. + my $i = 0; + + # All have 3 ports + for ($i = 1; $i<=3; $i++) { + doCmd("set_port $shelf_num $lanf1 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + doCmd("set_port $shelf_num $lanf2 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + doCmd("set_port $shelf_num $lanf3 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + doCmd("set_port $shelf_num $lanf4 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + + # lanf1, lanf3 have 6 ports total... + for ($i = 4; $i<=6; $i++) { + doCmd("set_port $shelf_num $lanf1 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + doCmd("set_port $shelf_num $lanf3 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + + # Syntax for setting port info is: + # set_port [shelf] [card] [port] [ip] [mask] [gateway] [cmd-flags] [cur-flags] [MAC] + # NOTE: Just use NA for the flags for now...not tested otherwise. + + # Set up GigE ports, they will talk to each other for now... + doCmd("set_port $shelf_num $lanf2 3 172.25.3.2 255.255.255.0 172.25.3.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf4 3 172.25.3.4 255.255.255.0 172.25.3.1 NA NA NA"); + + # Set up the 2 10/100 ports on the GigE machines. They will be set up to talk to + # each other too. + doCmd("set_port $shelf_num $lanf2 1 172.25.7.2 255.255.255.0 172.25.7.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf2 2 172.25.8.2 255.255.255.0 172.25.8.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf4 1 172.25.7.4 255.255.255.0 172.25.7.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf4 2 172.25.8.4 255.255.255.0 172.25.8.1 NA NA NA"); + + + # Set up the ports NICs on lanf1. They should be connected to an ether-switch that also + # connects to the ports on lanf3. These will all be on the same subnet, but LANforge + # (Linux, really) magic will make them act as separate machines. + + doCmd("set_port $shelf_num $lanf1 1 172.25.5.2 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf1 2 172.25.5.3 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf1 3 172.25.5.4 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf1 4 172.25.5.5 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf1 5 172.25.5.6 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf1 6 172.25.5.7 255.255.255.0 172.25.5.1 NA NA NA"); + + + # Set up the ports on lanf3 + + doCmd("set_port $shelf_num $lanf3 1 172.25.5.102 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf3 2 172.25.5.103 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf3 3 172.25.5.104 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf3 4 172.25.5.105 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf3 5 172.25.5.106 255.255.255.0 172.25.5.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf3 6 172.25.5.107 255.255.255.0 172.25.5.1 NA NA NA"); + +} + +sub addCrossConnects { + # Syntax for adding an endpoint is: + # add_endp [alias] [shelf] [card] [port] [type] [IP-port] [bursty] [min_rate] [max_rate] + # [pkt_sz_random] [min_pkt] [max_pkt] [pattern] [use_checksum] + + # Set up first 50Mbps full duplex UDP link on the GigE ports. + doCmd("add_endp udp-gig1-TX $shelf_num $lanf4 3 lf_udp -1 NO 50000000 50000000 NO 12000 12000 increasing NO"); + doCmd("add_endp udp-gig1-RX $shelf_num $lanf2 3 lf_udp -1 NO 50000000 50000000 NO 12000 12000 increasing NO"); + doCmd("add_cx udp-gig1 $test_mgr udp-gig1-TX udp-gig1-RX"); + @endpoint_names = (@endpoint_names, "udp-gig1-TX", "udp-gig1-RX"); + @cx_names = (@cx_names, "udp-gig1"); + + # Set up first 50Mbps full duplex TCP link on the GigE ports. + doCmd("add_endp tcp-gig1-TX $shelf_num $lanf4 3 lf_tcp -1 NO 50000000 50000000 NO 12000 12000 increasing NO"); + doCmd("add_endp tcp-gig1-RX $shelf_num $lanf2 3 lf_tcp -1 NO 50000000 50000000 NO 12000 12000 increasing NO"); + doCmd("add_cx tcp-gig1 $test_mgr tcp-gig1-TX tcp-gig1-RX"); + @endpoint_names = (@endpoint_names, "tcp-gig1-TX", "tcp-gig1-RX"); + @cx_names = (@cx_names, "tcp-gig1"); + + + # Set up first 50Mbps - 1Mbps asymetric TCP link + doCmd("add_endp tcp-gig2-TX $shelf_num $lanf4 3 lf_tcp -1 NO 50000000 50000000 NO 12000 12000 increasing NO"); + doCmd("add_endp tcp-gig2-RX $shelf_num $lanf2 3 lf_tcp -1 NO 10000000 10000000 NO 12000 12000 increasing NO"); + doCmd("add_cx tcp-gig2 $test_mgr tcp-gig2-TX tcp-gig2-RX"); + @endpoint_names = (@endpoint_names, "tcp-gig2-TX", "tcp-gig2-RX"); + @cx_names = (@cx_names, "tcp-gig2"); + + # Set up second 50Mbps - 1Mbps asymetric TCP link + doCmd("add_endp tcp-gig3-TX $shelf_num $lanf4 3 lf_tcp -1 NO 50000000 50000000 NO 12000 12000 increasing NO"); + doCmd("add_endp tcp-gig3-RX $shelf_num $lanf2 3 lf_tcp -1 NO 10000000 10000000 NO 12000 12000 increasing NO"); + doCmd("add_cx tcp-gig3 $test_mgr tcp-gig3-TX tcp-gig3-RX"); + @endpoint_names = (@endpoint_names, "tcp-gig3-TX", "tcp-gig3-RX"); + @cx_names = (@cx_names, "tcp-gig3"); + + + # Set up 6 cross-connects between lanf1 and lanf3 + my $i = 1; + my $tp = "tcp"; + my $tp2 = "lf_tcp"; + my $rate = 6000000; # 6Mbps + + for ($i = 1; $i<=6; $i++) { + my $tx_nm = "${tp}-qp${i}-TX"; + my $rx_nm = "${tp}-qp${i}-RX"; + + doCmd("add_endp $tx_nm $shelf_num $lanf1 $i $tp2 -1 NO $rate $rate NO 4000 4000 random_fixed NO"); + + my $rt = $rate / 2; # Non-symetric cross-connect + + doCmd("add_endp $rx_nm $shelf_num $lanf3 $i $tp2 -1 NO $rt $rt NO 4000 4000 decreasing NO"); + + my $cx_nm = "${tp}-qp${i}"; + # Add cross-connect + doCmd("add_cx $cx_nm $test_mgr $tx_nm $rx_nm"); + + @endpoint_names = (@endpoint_names, $rx_nm, $tx_nm); + @cx_names = (@cx_names, $cx_nm); + } + + + # Set up 6 cross-connects between lanf1 and lanf3 + $i = 1; + $tp = "udp"; + $tp2 = "lf_udp"; + $rate = 9000000; # 9Mbps + + for ($i = 1; $i<=6; $i++) { + my $tx_nm = "${tp}-qp${i}-TX"; + my $rx_nm = "${tp}-qp${i}-RX"; + + doCmd("add_endp $tx_nm $shelf_num $lanf1 $i $tp2 -1 NO $rate $rate NO 4000 4000 random_fixed NO"); + + my $rt = $rate / 2; # Non-symetric cross-connect + + doCmd("add_endp $rx_nm $shelf_num $lanf3 $i $tp2 -1 NO $rt $rt NO 4000 4000 decreasing NO"); + + my $cx_nm = "${tp}-qp${i}"; + # Add cross-connect + doCmd("add_cx $cx_nm $test_mgr $tx_nm $rx_nm"); + + @endpoint_names = (@endpoint_names, $rx_nm, $tx_nm); + @cx_names = (@cx_names, $cx_nm); + } + +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_stress4.pl b/lanforge/lanforge-scripts/lf_stress4.pl new file mode 100755 index 000000000..b89bc7e6e --- /dev/null +++ b/lanforge/lanforge-scripts/lf_stress4.pl @@ -0,0 +1,230 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This specifically is designed for two machines with 3 data-generating ports each. + +# Un-buffer output +$| = 1; + +use Net::Telnet (); + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# Specify 'card' numbers for this configuration. +my $lanf1 = 1; +my $lanf2 = 2; + +my $test_mgr = "ben_tm"; + +my $loop_max = 100; +my $start_stop_iterations = 100; +my $run_for_time = (60 * 60 * 24); # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Stop for XX seconds..then will be started again +my $report_timer = 3000; # 3 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +my $dt = ""; + +# Do some thing over and over again... +my $loops = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + # Remove any existing configuration information + initToDefaults(); + + print " ***Sleeping 3 seconds for ports to initialize to defaults...\n"; + sleep(3); + + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + # Add some IP addresses to the ports + initIpAddresses(); + + print " ***Sleeping 3 seconds for ports to initialize to current values...\n"; + sleep(3); + + # Add our endpoints + addCrossConnects(); + + my $rl = 0; + for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + + sleep($stop_for_time); + + }# For some amount of start_stop iterations... +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub initPortsToDefault { + # Set all ports we are messing with to known state. + my $i = 0; + + # All have 3 ports + for ($i = 1; $i<=3; $i++) { + doCmd("set_port $shelf_num $lanf1 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + doCmd("set_port $shelf_num $lanf2 $i 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + + # Syntax for setting port info is: + # set_port [shelf] [card] [port] [ip] [mask] [gateway] [cmd-flags] [cur-flags] [MAC] + # NOTE: Just use NA for the flags for now...not tested otherwise. + + # Set up the 3 10/100 ports. They will be set up to talk to + # each other. + doCmd("set_port $shelf_num $lanf1 1 172.25.7.2 255.255.255.0 172.25.7.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf1 2 172.25.8.2 255.255.255.0 172.25.8.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf1 3 172.25.8.4 255.255.255.0 172.25.8.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf2 1 172.25.7.4 255.255.255.0 172.25.7.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf2 2 172.25.8.3 255.255.255.0 172.25.8.1 NA NA NA"); + doCmd("set_port $shelf_num $lanf2 3 172.25.8.5 255.255.255.0 172.25.8.1 NA NA NA"); + + +} + +sub addCrossConnects { + # Syntax for adding an endpoint is: + # add_endp [alias] [shelf] [card] [port] [type] [IP-port] [bursty] [min_rate] [max_rate] + # [pkt_sz_random] [min_pkt] [max_pkt] [pattern] [use_checksum] + + # Set up 3 TCP cross-connects between lanf1 and lanf2 + my $i = 1; + my $tp = "tcp"; + my $tp2 = "lf_tcp"; + my $rate = 6000000; # 6Mbps + + for ($i = 1; $i<=3; $i++) { + my $tx_nm = "${tp}-qp${i}-TX"; + my $rx_nm = "${tp}-qp${i}-RX"; + + doCmd("add_endp $tx_nm $shelf_num $lanf1 $i $tp2 -1 NO $rate $rate NO 4000 4000 random_fixed NO"); + + my $rt = $rate / 2; # Non-symetric cross-connect + + doCmd("add_endp $rx_nm $shelf_num $lanf2 $i $tp2 -1 NO $rt $rt NO 4000 4000 decreasing NO"); + + my $cx_nm = "${tp}-qp${i}"; + # Add cross-connect + doCmd("add_cx $cx_nm $test_mgr $tx_nm $rx_nm"); + + @endpoint_names = (@endpoint_names, $rx_nm, $tx_nm); + @cx_names = (@cx_names, $cx_nm); + } + + + # Set up 3 UDP cross-connects between lanf1 and lanf2 + my $i = 1; + my $tp = "udp"; + my $tp2 = "lf_udp"; + my $rate = 6000000; # 6Mbps + + for ($i = 1; $i<=3; $i++) { + my $tx_nm = "${tp}-qp${i}-TX"; + my $rx_nm = "${tp}-qp${i}-RX"; + + doCmd("add_endp $tx_nm $shelf_num $lanf2 $i $tp2 -1 NO $rate $rate NO 4000 4000 random_fixed NO"); + + my $rt = $rate / 2; # Non-symetric cross-connect + + doCmd("add_endp $rx_nm $shelf_num $lanf1 $i $tp2 -1 NO $rt $rt NO 4000 4000 decreasing NO"); + + my $cx_nm = "${tp}-qp${i}"; + # Add cross-connect + doCmd("add_cx $cx_nm $test_mgr $tx_nm $rx_nm"); + + @endpoint_names = (@endpoint_names, $rx_nm, $tx_nm); + @cx_names = (@cx_names, $cx_nm); + } + +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_testmod.pl b/lanforge/lanforge-scripts/lf_testmod.pl new file mode 100755 index 000000000..5d1879a70 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_testmod.pl @@ -0,0 +1,206 @@ +#!/usr/bin/perl -w + +# This program is used to load GUI tests (TR-398, Capacity, etc) from a file +# and configure it in the LANforge server. See gui/README.txt +# (C) 2020 Candela Technologies Inc. +# +# + +use strict; +use warnings; +use diagnostics; +use Carp; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "../"; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; + +our $NA = 'NA'; +our $NL = "\n"; + +# Default values for ye ole cmd-line args. +our $quiet = "yes"; +our $test_type = "Plugin-Settings"; +our $test_name = ""; +our $file_name = ""; +our $action = "show"; +our $lfmgr_host = "localhost"; +our $lfmgr_port = 4001; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +our $usage = <<"__EndOfUsage__"; +$0 [ --action { + show | set + } ] + [--file {data file name}] + [--test_name {test name or ALL}] + [--test_type {Plugin-Settings or other type or ALL}] + [--mgr {host-name | IP}] + [--mgr_port {ip port}] + [--quiet { yes | no }] + [--log_cli {1|filename}] + +Example: + $0 --action set --test_name AP-Auto-ap-auto-32-64-dual --file test_configs/AP-Auto-ap-auto-32-64-dual.txt +__EndOfUsage__ + +my $i = 0; +my $cmd; + +my $log_cli = "unset"; # use ENV{LOG_CLI} elsewhere +my $show_help = 0; + +if (@ARGV < 2) { + print $usage; + exit 0; +} + +our $debug = 0; + +GetOptions +( + 'action|a=s' => \$::action, + 'file=s' => \$::file_name, + 'test_name=s' => \$::test_name, + 'test_type=s' => \$::test_type, + 'debug|d' => \$::debug, + 'help|h' => \$show_help, + 'log_cli=s{0,1}' => \$log_cli, + 'manager|mgr|m=s' => \$::lfmgr_host, + 'lfmgr_port|mgr_port|port|p=i' => \$::lfmgr_port, + 'quiet|q=s' => \$::quiet, + +) || die("$::usage"); + +if ($show_help) { + print $usage; + exit 0; +} + +use Data::Dumper; + +if ($::debug) { + $ENV{DEBUG} = 1 if (!(defined $ENV{DEBUG})); +} + +if ($::quiet eq "0") { + $::quiet = "no"; +} +elsif ($::quiet eq "1") { + $::quiet = "yes"; +} + +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } +} + +our @valid_actions = qw(show set); + +if (! (grep {$_ eq $::action} @::valid_actions )) { + die("Invalid action: $::action\n$::usage\n"); +} +if ($::quiet eq "1" ) { + $::quiet = "yes"; +} + +# Open connection to the LANforge server. +our $utils = new LANforge::Utils(); +$::utils->connect($lfmgr_host, $lfmgr_port); + + +if ($::action eq "show") { + $cmd = "show_text_blob $test_type $test_name"; + my $txt = $::utils->doCmd($cmd); + my @r = split(/\n/, $txt); + my $first = $r[0]; + chomp($first); + if ($first =~ /.*::(.*)/) { + print "$1\n"; + } + my $i; + for ($i = 1; $i<@r; $i++) { + my $ln = $r[$i]; + chomp($ln); + if ($ln =~ /\s*>>RSLT.*/) { + # ignore + } + elsif ($ln =~ /\s*default\@btbits.*/) { + # ignore + } + else { + print "$ln\n"; + } + } +} +elsif ($::action eq "set") { + if ($file_name eq "") { + print("ERROR: Must specify file name when doing the 'set' action\n"); + exit(1); + } + + my @cmds = `cat $file_name`; + if (@cmds == 0) { + print("ERROR: Could not read any lines from the file: $file_name\n"); + exit(2); + } + + # First clean out any old text blob. + $cmd = "rm_text_blob $test_type $test_name"; + $::utils->doCmd($cmd); + + # And add the new blob + for ($i = 0; $i<@cmds; $i++) { + my $ln = $cmds[$i]; + chomp($ln); + + # Skip blank lines + if ($ln eq "") { + next; + } + + $cmd = "add_text_blob '$test_type' '$test_name' $ln"; + print("$cmd\n"); + if (($i % 25) != 0) { + $::utils->doCmd($cmd, 1); # send and do not wait for result + } + else { + $::utils->doCmd($cmd); # send and wait for result + } + } + + # Wait until we complete processing of all cmds. + $cmd = "gossip __gossip_test__"; + $::utils->doCmd($cmd, 0, "/__gossip_test__/"); +} +else { + die("Unknown action: $::action\n$::usage\n"); +} + +exit(0); + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- diff --git a/lanforge/lanforge-scripts/lf_tos_plus_test.py b/lanforge/lanforge-scripts/lf_tos_plus_test.py new file mode 100755 index 000000000..f110efd7e --- /dev/null +++ b/lanforge/lanforge-scripts/lf_tos_plus_test.py @@ -0,0 +1,836 @@ +#!/usr/bin/python3 +''' + +Create connections using stations with different a/b/g/n/AC/AX modes, +traffic with different QoS, packet size, requested rate, and tcp or udp protocol. +Report the latency and other throughput values. + +Optionally start packet-capture on secondary radio(s) and upstream port. + +When specifying ports, if the port starts with [Number]., like 1.eth1, then the 1 specifies +the resource ID. + +The --cx argument is defined as: station-radio, station-port, mode, upstream-port, protocol, pkt-size, speed_ul, speed_dl, QoS. +Pass this argument multiple times to create multiple connections. +The --radio argument is defined as: radio, spatial-streams, channel +Use NA if you do not want to change from current values. Use 0 for any channel. + +Supported modes are: a, b, g, abg, abgn, bgn, bg, abgnAC, anAC, an, bgnAC, abgnAX, bgnAX, anAX + +If sniffer_radios is specified, then the lf_sniff.py script will be started in the background once +the stations have become admin-up, and the sniff will run for the entire duration time specified +(there is not a good way to stop the capture early based on packets-sent since on-the-air frames +are very likely more than the PDU count) + +# Run connections on wlan0 and sta0 for 1 minute, set radio wiphy0 to use any frequency +# and 2 spatial streams. Use wiphy2 as a sniffer, stop traffic after 10,000 PDUs +# have been sent. Sniffer radio will automatically change to the correct settings +# to sniff the first station. --wait_sniffer 1 tells the script to pause until the +# sniffer process has completed. + +./lf_tos_plus_test.py --dur 1 --lfmgr 192.168.100.156 --ssid NETGEAR68-5G --passwd aquaticbug712 \ + --radio "1.wiphy0 2 0" --txpkts 9999 --wait_sniffer 1 \ + --cx "1.wiphy0 1.wlan0 an 1.eth1 udp 1024 10000 500000000 BK" \ + --cx "1.wiphy0 1.wlan0 an 1.eth1 udp MTU 10000 500000000 VI" \ + --cx "1.wiphy0 1.sta0 anAC 1.eth1 tcp 1472 56000 2000000 BK" \ + --sniffer_radios "1.wiphy2" + +# You can also create connections between non-station endpoints. +./lf_tos_plus_test.py --dur 1 --lfmgr 192.168.100.156 --txpkts 99 \ + --cx "NA 1.rddVR0 NA 1.rddVR1 tcp MTU 1000000 2000000 0" --wait_sniffer 1 + +make sure pexpect, pandas is installed: +$ sudo yum install python3-pandas +$ sudo yum install python3-pexpect +$ sudo yum install python3-xlsxwriter + +You might need to install pexpect-serial using pip: +$ pip3 install pexpect-serial +$ pip3 install XlsxWriter + +''' + +# TODO: Maybe HTML output too? +# TODO: Allow selecting tabs or commas for output files + +import sys +import os +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +import re +import logging +import time +from time import sleep +import pprint +import argparse +import subprocess +import xlsxwriter +import datetime +import pandas as pd +from subprocess import PIPE + +NL = "\n" +CR = "\r\n" +Q = '"' +A = "'" +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' + +lfmgr = "127.0.0.1" +cx_strs = [] # station-radio, station-port, mode, upstream-port, protocol, pkt-size, speed_ul, speed_dl, QoS. +outfile = "tos+_results.xlsx" +dur = 5 * 60 +passwd = "" +ssid = "Test-SSID" +security = "open" +radio_strs = [] # Radios to modify: radio nss channel +txpkts = "0" # 0 == Run forever +sniffer_radios = "" +wait_sniffer = False + +# rssi_adjust = (current_nf - nf_at_calibration) + +def usage(): + print("$0 ") + print("--outfile: Write results here.") + print("--cx: Connection tuple: station-radio station-port mode upstream-port protocol pkt-size speed_ul speed_dl QoS") + print("--radio: Radio tuple: radio nss channel"); + print("--lfmgr: LANforge manager IP address") + print("--duration: Duration to run traffic, in minutes") + print("--ssid: AP's SSID") + print("--passwd: Optional: password (do not add this option for OPEN)") + print("--security: Default is 'open', or if passwd is configured, default is wpa2. wpa3 is also valid option") + print("--txpkts: Optional: amount of packets to transmit (and then stop the data connections)") + print("--sniffer_radios: Optional: list of radios to sniff wifi traffic \"1.wiphy2 1.wiphy4\")") + print("--wait_sniffer: Optional: 1 means wait on sniffer to finish before existing script") + print("-h|--help") + +def main(): + global lfmgr + global cx_strs + global outfile + global dur + global passwd + global ssid + global security + global radio_strs + global txpkts + global sniffer_radios + global wait_sniffer + + do_sniff = False + + parser = argparse.ArgumentParser(description="ToS++ report Script") + parser.add_argument("--cx", type=str, action='append', help="Connection tuple: station-radio station-port mode upstream-port protocol pkt-size speed_ul speed_dl QoS") + parser.add_argument("--radio", type=str, action='append', help="Radio tuple: radio nss channel") + parser.add_argument("--lfmgr", type=str, help="LANforge Manager IP address") + parser.add_argument("--outfile", type=str, help="Output file for csv data") + parser.add_argument("--duration", type=float, help="Duration to run traffic, in minutes. If txpkts is specified, that may stop the test earlier.") + parser.add_argument("--ssid", type=str, help="AP's SSID") + parser.add_argument("--passwd", type=str, help="AP's password if using PSK authentication, skip this argement for OPEN") + parser.add_argument("--security", type=str, help="Default is 'open', or if passwd is configured, default is wpa2. wpa3 is also valid option") + parser.add_argument("--txpkts", type=str, help="Optional: Packets (PDUs) to send before stopping data connections Default (0) means infinite") + parser.add_argument("--sniffer_radios", type=str, help="Optional: list of radios to sniff wifi traffic \"1.wiphy2 1.wiphy4\"") + parser.add_argument("--wait_sniffer", type=str, help="Optional: 1 means wait on sniffer to finish before existing script.\"") + + args = None + try: + args = parser.parse_args() + cx_strs = args.cx.copy() + if (args.radio != None): + radio_strs = args.radio.copy() + if (args.lfmgr != None): + lfmgr = args.lfmgr + if (args.duration != None): + dur = args.duration * 60 + if (args.ssid != None): + ssid = args.ssid + if (args.passwd != None): + passwd = args.passwd + security = "wpa2" + if (args.security != None): + security = args.security + if (args.outfile != None): + outfile = args.outfile + if (args.txpkts != None): + txpkts = args.txpkts + if (args.sniffer_radios != None): + sniffer_radios = args.sniffer_radios + do_sniff = True + if (args.wait_sniffer != None): + do_sniff = True + wait_sniffer = args.wait_sniffer == "1" + filehandler = None + except Exception as e: + logging.exception(e); + usage() + exit(2); + + # XLSX file + workbook = xlsxwriter.Workbook(outfile) + worksheet = workbook.add_worksheet() + + bold = workbook.add_format({'bold': True, 'align': 'center'}) + dblue_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dblue_bold.set_bg_color("#b8cbe4") + dblue_bold.set_border(1) + dtan_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dtan_bold.set_bg_color("#dcd8c3") + dtan_bold.set_border(1) + dpeach_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dpeach_bold.set_bg_color("#ffd8bb") + dpeach_bold.set_border(1) + dpink_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dpink_bold.set_bg_color("#fcc8ca") + dpink_bold.set_border(1) + dyel_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dyel_bold.set_bg_color("#ffe699") + dyel_bold.set_border(1) + dgreen_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dgreen_bold.set_bg_color("#c6e0b4") + dgreen_bold.set_border(1) + dgreen_bold_left = workbook.add_format({'bold': True, 'align': 'left'}) + dgreen_bold_left.set_bg_color("#c6e0b4") + dgreen_bold_left.set_border(1) + center = workbook.add_format({'align': 'center'}) + center_blue = workbook.add_format({'align': 'center'}) + center_blue.set_bg_color("#dbe5f1") + center_blue.set_border(1) + center_tan = workbook.add_format({'align': 'center'}) + center_tan.set_bg_color("#edede1") + center_tan.set_border(1) + center_peach = workbook.add_format({'align': 'center'}) + center_peach.set_bg_color("#fce4d6") + center_peach.set_border(1) + center_yel = workbook.add_format({'align': 'center'}) + center_yel.set_bg_color("#fdf2cc") + center_yel.set_border(1) + center_yel_red = workbook.add_format({'align': 'center', 'color': 'red'}) + center_yel_red.set_bg_color("#fdf2cc") + center_yel_red.set_border(1) + center_pink = workbook.add_format({'align': 'center'}) + center_pink.set_bg_color("ffd2d3") + center_pink.set_border(1) + red = workbook.add_format({'color': 'red', 'align': 'center'}) + red.set_bg_color("#e0efda") + red.set_border(1) + red_left = workbook.add_format({'color': 'red', 'align': 'left'}) + red_left.set_bg_color("#e0efda") + red_left.set_border(1) + green = workbook.add_format({'color': 'green', 'align': 'center'}) + green.set_bg_color("#e0efda") + green.set_border(1) + green_left = workbook.add_format({'color': 'green', 'align': 'left'}) + green_left.set_bg_color("#e0efda") + green_left.set_border(1) + + worksheet.set_row(0, 45) # Set height + + bucket_hdrs = "0 1 2-3 4-7 8-15 16-31 32-63 64-127 128-255 256-511 512-1023 1024-2047 2048-4095 4096-8191 8192-16383 16384-32767 32768-65535".split() + col = 0 + row = 0 + + dwidth = 13 # Set width to 13 for all columns by default + + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'CX-Name', dblue_bold); col += 1 + worksheet.set_column(col, col, 15) + worksheet.write(row, col, 'Endp-Name', dblue_bold); col += 1 + worksheet.set_column(col, col, 12) + worksheet.write(row, col, 'Port', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Protocol', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'ToS', dblue_bold); col += 1 + worksheet.set_column(col, col, 20) + worksheet.write(row, col, 'AP BSSID', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Band\nwidth', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Mode', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Last MCS\nRx', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Combined\nRSSI', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Endpoint\nTX Pkt\nSize', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Endpoint\nOffered\nLoad', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Endpoint\nRx\nThroughput', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Cx\nOffered\nLoad', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Cx\nRx\nThroughput', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Avg\nLatency', dyel_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Min\nLatency', dyel_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Max\nLatency', dyel_bold); col += 1 + for i in range(17): + btitle = "Latency\nRange\n%s"%(bucket_hdrs[i]) + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, btitle, dpeach_bold); col += 1 + + worksheet.set_column(col, col, 50) + worksheet.write(row, col, 'Warnings and Errors', dgreen_bold_left); col += 1 + row += 1 + + # Use subprocess.check_output("Cmd") to utilize existing LF scripts. + + sta_to_mode = {} # map station name to mode + cxnames = [] # list of all cxnames + endpnames = [] + endp_to_port = {} + endp_to_pktsz = {} + endp_to_proto = {} + endp_to_tos = {} + + e_tot = "" + + opposite_speed = 56000 + count = 0 + + # Configure radios as requested + for rad in radio_strs: + ra = rad.split() + radio = ra[0] + nss = ra[1] + ch = ra[2] + + rad_resource = "1" + rad_name = radio; + if radio[0].isdigit(): + tmpa = radio.split(".", 1); + rad_resource = tmpa[0]; + rad_name = tmpa[1]; + + print("Setting radio %s.%s NSS %s Channel %s"%(rad_resource, rad_name, nss, ch)) + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", rad_resource, "--port_name", rad_name, + "--set_nss", nss, "--set_channel", ch]); + + upstreams = [] + stations = [] + for cx in cx_strs: + cxa = cx.split() + #station-radio, station-port, mode, upstream-port, protocol, pkt-size, speed_ul, speed_dl, QoS. + radio = cxa[0] + sta = cxa[1] + mode = cxa[2] + upstream_port = cxa[3] + p = cxa[4] + pkt_sz = cxa[5] + cx_speed_ul = cxa[6] + cx_speed_dl = cxa[7] + t = cxa[8] # qos + + u_name = upstream_port + u_resource = 1 + if (upstream_port[0].isdigit()): + tmpa = upstream_port.split(".", 1) + u_resource = tmpa[0] + u_name = tmpa[1] + + sta_resource = "1" + sta_name = sta; + if sta[0].isdigit(): + tmpa = sta.split(".", 1); + sta_resource = tmpa[0]; + sta_name = tmpa[1]; + + sta_key = "%s.%s"%(sta_resource, sta_name) + if sta_key in sta_to_mode: + old_mode = sta_to_mode[sta_key] + if old_mode != mode: + emsg = ("ERROR: Skipping connection: \"%s\", mode conflicts with previous mode: %s"%(cx, old_mode)) + e_tot.append(cxa) + print(emsg) + continue + else: + sta_to_mode[sta_key] = mode + stations.append(sta_key) + + ukey = "%s.%s"%(u_resource, u_name) + if not ukey in upstreams: + upstreams.append(ukey) + + if radio != "NA": + rad_resource = "1" + rad_name = radio; + if radio[0].isdigit(): + tmpa = radio.split(".", 1); + rad_resource = tmpa[0]; + rad_name = tmpa[1]; + + # Create or update station with requested mode + subprocess.run(["./lf_associate_ap.pl", "--mgr", lfmgr, "--resource", rad_resource, "--action", "add", + "--radio", rad_name, "--ssid", ssid, "--passphrase", passwd, "--security", security, + "--first_sta", sta_name, "--first_ip", "DHCP", "--wifi_mode", mode, "--num_stations", "1"]) + else: + # Assume it should be sniffed. + ukey = "%s.%s"%(sta_resource, sta_name) + if not ukey in upstreams: + upstreams.append(ukey) + + # Up station / A-side Port + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", sta_resource, "--port_name", sta_name, + "--set_ifstate", "up"]); + + i = 0 + wait_ip_print = False; + wait_assoc_print = False; + # Wait until LANforge station connects + while True: + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", sta_resource, "--port_name", sta_name, + "--show_port", "AP,IP,Mode,NSS,Bandwidth,Channel,Signal,Noise,Status,RX-Rate"], stdout=PIPE, stderr=PIPE); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + _status = None + _ip = None + + for line in pss.splitlines(): + m = re.search('Status:\s+(.*)', line) + if (m != None): + _status = m.group(1) + m = re.search('IP:\s+(.*)', line) + if (m != None): + _ip = m.group(1) + + #print("IP %s Status %s"%(_ip, _status)) + + if sta_name.startswith("wlan") or sta_name.startswith("sta"): + if (_status == "Authorized"): + if ((_ip != None) and (_ip != "0.0.0.0")): + print("Station is associated with IP address.") + break + else: + if (not wait_ip_print): + print("Waiting for station %s.%s to get IP Address."%(sta_resource, sta_name)) + wait_ip_print = True + else: + if (not wait_assoc_print): + print("Waiting up to 180s for station %s.%s to associate."%(sta_resource, sta_name)) + wait_assoc_print = True + else: + if ((_ip != None) and (_ip != "0.0.0.0")): + print("Port is associated with IP address.") + break + else: + if (not wait_ip_print): + print("Waiting for port %s.%s to get IP Address."%(sta_resource, sta_name)) + wait_ip_print = True + + i = i + 1 + # We wait a fairly long time since AP will take a long time to start on a CAC channel. + if (i > 180): + err = "ERROR: Station did not connect within 180 seconds." + print(err) + e_tot += err + e_tot += " " + if (args.wait_forever): + print("Will continue waiting, you may wish to debug the system...") + i = 0 + else: + break + + time.sleep(1) + + # Station is up, create connection + # Create connections. + # First, delete any old ones + cxn = "scr-tos-%i"%count + ena = "scr-tos-%i-A"%count + enb = "scr-tos-%i-B"%count + + cxnames.append(cxn) + endpnames.append(ena) + endpnames.append(enb) + + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", "rm_cx ALL %s"%cxn], stderr=PIPE, stdout=PIPE); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", "rm_endp %s"%ena], stderr=PIPE, stdout=PIPE); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", "rm_endp %s"%enb], stderr=PIPE, stdout=PIPE); + + cx_proto = p; + if (cx_proto == "udp"): + cx_proto = "lf_udp" + if (cx_proto == "tcp"): + cx_proto = "lf_tcp" + + endp_to_port[ena] = "%s.%s"%(sta_resource, sta_name); + endp_to_pktsz[ena] = pkt_sz + endp_to_proto[ena] = p; + endp_to_tos[ena] = t; + endp_to_port[enb] = "%s.%s"%(u_resource, u_name); + endp_to_pktsz[enb] = pkt_sz + endp_to_proto[enb] = p; + endp_to_tos[enb] = t; + + # Now, create the new connection + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", "%s"%sta_resource, "--action", "create_endp", "--port_name", sta_name, + "--endp_type", cx_proto, "--endp_name", ena, "--speed", "%s"%cx_speed_ul, "--report_timer", "1000", "--tos", t, + "--min_pkt_sz", pkt_sz, "--multicon", "1", "--pkts_to_send", txpkts])#, capture_output=True); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", "%s"%u_resource, "--action", "create_endp", "--port_name", u_name, + "--endp_type", cx_proto, "--endp_name", enb, "--speed", "%s"%cx_speed_dl, "--report_timer", "1000", "--tos", t, + "--min_pkt_sz", pkt_sz, "--multicon", "1", "--pkts_to_send", txpkts])# capture_output=True); + + # Enable Multi-Helper + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", "--cmd", + "set_endp_flag %s AutoHelper 1"%(ena)]) + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", "--cmd", + "set_endp_flag %s AutoHelper 1"%(enb)]) + + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", "--cmd", + "add_cx %s default_tm %s %s"%(cxn, ena, enb)])# capture_output=True); + count = count + 1 + + # Start sniffer? + if do_sniff: + lfstations = "" + lfupstreams = "" + if sniffer_radios != "": + radios = sniffer_radios.split() + ri = 0 + for r in radios: + lfstations = lfstations + " " + stations[ri] + ri = ri + 1 + + for u in upstreams: + lfupstreams = lfupstreams + " " + u + + # Add 15 seconds to capture length in case it takes a bit of time to start all + # of the connections. + cmd = ["./lf_sniff.py", "--lfmgr", lfmgr, "--duration", "%f"%((dur + 15) / 60)] + if lfstations != "": + cmd.extend(["--station", lfstations, "--sniffer_radios", sniffer_radios]) + if lfupstreams != "": + cmd.extend(["--upstreams", lfupstreams]) + + subprocess.run(cmd) + + sniff_done_at = time.time() + dur + 15; + + # All traffic connects are created, now start them all + for cxn in cxnames: + # Start traffic + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", "set_cx_state all %s RUNNING"%cxn]); + + # Traffic is started, wait requested amount of time + stop_at = time.time() + dur; + if txpkts == 0: + print("Waiting %s seconds to let traffic run for a bit"%(dur)) + time.sleep(dur) + else: + # Wait until connections are done transmitting and all are stopped + print("Waiting until all connections have finished transmitting %s PDUs and have stopped themselves."%txpkts); + done = False + while not done: + if time.time() > stop_at: + print("Duration expired, stop waiting for Endpoints to quiesce.") + break + + foundone = False + for ename in endpnames: + #print("Checking endpoint: %s"%ename) + + endp_stats = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--endp_name", ename, + "--endp_vals", "Endpoint-flags"], stderr=PIPE, stdout=PIPE); + ess = endp_stats.stdout.decode('utf-8', 'ignore'); + for line in ess.splitlines(): + #print("endp-stats line: %s"%line) + m = re.search('Endpoint-flags:\s+(.*)', line) + if (m != None): + flags = m.group(1) + if not "NOT_RUNNING" in flags: + foundone = True + #print("Flags, was running: %s"%flags) + break + #else: + #print("Flags, was not running: %s"%flags) + + if foundone: + break + if not foundone: + print("All endpoints stopped, continuing on.") + done = True + break + sleep(3) # wait 3 seconds, then poll again + + # Gather probe results and record data, verify NSS, BW, Channel + sta_stats = {} # Key is resource.station, holds array of values + + for sta in sta_to_mode: + sta_resource = "1" + sta_name = sta; + if sta[0].isdigit(): + tmpa = sta.split(".", 1); + sta_resource = tmpa[0]; + sta_name = tmpa[1]; + + sta_key = "%s.%s"%(sta_resource, sta_name) + print("Checking station stats, key: %s"%(sta_key)) + + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", sta_resource, "--port_name", sta_name, + "--show_port", "AP,Mode,Bandwidth,Signal,Status,RX-Rate"], stderr=PIPE, stdout=PIPE); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + _ap = None + _bw = None + _mode = None + _rxrate = None + _signal = None + + for line in pss.splitlines(): + m = re.search('AP:\s+(.*)', line) + if (m != None): + _ap = m.group(1) + m = re.search('Bandwidth:\s+(.*)Mhz', line) + if (m != None): + _bw = m.group(1) + m = re.search('Mode:\s+(.*)', line) + if (m != None): + _mode = m.group(1) + m = re.search('RX-Rate:\s+(.*)', line) + if (m != None): + _rxrate = m.group(1) + m = re.search('Signal:\s+(.*)', line) + if (m != None): + _signal = m.group(1) + + sta_stats[sta_key] = [_ap, _bw, _mode, _rxrate, _signal]; + print("sta-stats found: %s, mode: %s bw: %s"%(sta_key, _mode, _bw)); + + for cxn in cxnames: + # Results: tx_bytes, rx_bytes, tx_bps, rx_bps, tx_pkts, rx_pkts, Latency + resultsA = ["0"] * 7 + resultsB = ["0"] * 7 + e_tot2 = "" + + ena = "%s-A"%cxn; + enb = "%s-B"%cxn; + enames = [ena, enb] + for ename in enames: + results = [""] * 7 + + endp_stats = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--endp_vals", "RealTxRate,RealRxRate,Tx Bytes,Rx Bytes,Tx Pkts,Rx Pkts,Latency", + "--endp_name", ename], stderr=PIPE, stdout=PIPE); + pss = endp_stats.stdout.decode('utf-8', 'ignore'); + + for line in pss.splitlines(): + #print("probe-line, endp: %s: %s"%(ename, line)) + m = re.search('Rx Bytes:\s+(\d+)', line) + if (m != None): + results[1] = int(m.group(1)) + if (results[1] == 0): + err = "ERROR: No bytes received by data connection %s, test results may not be valid."%(ename) + e_tot2 += err + e_tot2 += " " + m = re.search('Tx Bytes:\s+(\d+)', line) + if (m != None): + results[0] = int(m.group(1)) + if (results[0] == 0): + err = "ERROR: No bytes transmitted by data connection %s, test results may not be valid."%(ename) + e_tot2 += err + e_tot2 += " " + + m = re.search('RealTxRate:\s+(.*)bps', line) + if (m != None): + results[2] = m.group(1) + + m = re.search('RealRxRate:\s+(.*)bps', line) + if (m != None): + results[3] = m.group(1) + + m = re.search('Tx Pkts:\s+(.*)', line) + if (m != None): + results[4] = m.group(1) + + m = re.search('Rx Pkts:\s+(.*)', line) + if (m != None): + results[5] = m.group(1) + + m = re.search('Latency:\s+(.*)', line) + if (m != None): + results[6] = m.group(1) + + if (ena == ename): + resultsA = results.copy() + else: + resultsB = results.copy() + + # Now that we know both latencies, we can normalize them. + endp_statsA = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "normalize_latency", + "--lat1", resultsA[6], "--lat2", resultsB[6]], stderr=PIPE, stdout=PIPE); + pssA = endp_statsA.stdout.decode('utf-8', 'ignore'); + endp_statsB = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "normalize_latency", + "--lat1", resultsB[6], "--lat2", resultsA[6]], stderr=PIPE, stdout=PIPE); + pssB = endp_statsB.stdout.decode('utf-8', 'ignore'); + + #print("%s: latA: %s"%(cxn, resultsA[6])) + #print("%s: latB: %s"%(cxn, resultsB[6])) + #print("%s: pssA: %s"%(cxn, pssA)) + #print("%s: pssB: %s"%(cxn, pssB)) + + for line in pssA.splitlines(): + m = re.search('Normalized-Latency:\s+(.*)', line) + if (m != None): + resultsA[6] = m.group(1); + + for line in pssB.splitlines(): + m = re.search('Normalized-Latency:\s+(.*)', line) + if (m != None): + resultsB[6] = m.group(1); + + for ename in enames: + col = 0 + + if (ena == ename): + results = resultsA.copy() + else: + results = resultsB.copy() + + lat_cols = results[6].split() # min, max, avg, columns.... + + worksheet.write(row, col, cxn, center_blue); col += 1 + worksheet.write(row, col, ename, center_blue); col += 1 + + en_port = endp_to_port[ename] + worksheet.write(row, col, en_port, center_blue); col += 1 + + worksheet.write(row, col, endp_to_proto[ename], center_blue); col += 1 + worksheet.write(row, col, endp_to_tos[ename], center_blue); col += 1 + + if ename == ena: + key = en_port + #print("endp, key: %s"%(key)); + sta_rpt = sta_stats[key] + worksheet.write(row, col, sta_rpt[0], center_blue); col += 1 + worksheet.write(row, col, sta_rpt[1], center_blue); col += 1 + worksheet.write(row, col, sta_rpt[2], center_blue); col += 1 + worksheet.write(row, col, sta_rpt[3], center_blue); col += 1 + worksheet.write(row, col, sta_rpt[4], center_blue); col += 1 + else: + # Upstream is likely wired, don't print station info + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + + #print("results[2]:%s 3: %s"%(results[2], results[3])) + + worksheet.write(row, col, endp_to_pktsz[ename], center_tan); col += 1 + worksheet.write(row, col, "%.2f"%(float(results[2]) / 1000000), center_tan); col += 1 + worksheet.write(row, col, "%.2f"%(float(results[3]) / 1000000), center_tan); col += 1 + worksheet.write(row, col, "%.2f"%((float(resultsA[2]) + float(resultsB[2])) / 1000000), center_tan); col += 1 + worksheet.write(row, col, "%.2f"%((float(resultsA[3]) + float(resultsB[3])) / 1000000), center_tan); col += 1 + worksheet.write(row, col, lat_cols[2], center_yel); col += 1 + worksheet.write(row, col, lat_cols[0], center_yel); col += 1 + worksheet.write(row, col, lat_cols[1], center_yel); col += 1 + for x in range(17): + worksheet.write(row, col, lat_cols[x + 3], center_peach); col += 1 + + if (e_tot2 == ""): + worksheet.write(row, col, e_tot2, green_left); col += 1 + else: + worksheet.write(row, col, e_tot2, red_left); col += 1 + row += 1 + + # Stop traffic + for cxn in cxnames: + cmd = "set_cx_state all %s STOPPED"%cxn + #print("Stopping CX: %s with command: %s"%(cxn, cmd)); + + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", cmd]); + + workbook.close() + + # Convert workbook to csv + + csvfname = "%s.csv"%(outfile) + csv = open(csvfname, "w") + df = pd.read_excel(outfile) + + list_of_columns = df.columns.values + + # Print header + for c in range(len(list_of_columns)): + cell = list_of_columns[c] + if cell != cell: + cell = "" + # convert newlines to spaces + if isinstance(cell, str): + cell = cell.replace('\n', ' ') + cell = cell.replace('\r', '') + # Remove commas + cell = cell.replace(',', '') + if c == 0: + csv.write(cell) + else: + csv.write(",%s"%(cell)) + csv.write("\n") + + for r in range (len(df)): + for c in range(len(list_of_columns)): + #print("list-of-columns[c]: %s"%(list_of_columns[c])) + cell = df[list_of_columns[c]][r] + #print("cell: %s c: %i r: %i"%(cell, c, r)) + # NAN check + if cell != cell: + cell = "" + if isinstance(cell, str): + # convert newlines to spaces + cell = cell.replace('\n', ' ') + cell = cell.replace('\r', '') + # Remove commas + cell = cell.replace(',', '') + if c == 0: + csv.write(cell) + else: + csv.write(",%s"%(cell)) + csv.write("\n"); + csv.close() + print("CSV report data saved to: %s"%(csvfname)) + + tstr = "" + if sniffer_radios != "": + now = time.time() + if now < sniff_done_at: + waitfor = int(sniff_done_at - now); + if wait_sniffer: + print("Waiting %i seconds until sniffer completes."%(waitfor)) + sleep(waitfor) + + # move capture files into a new directory + tstr = time.strftime("%Y-%m-%d-%H:%M:%S") + os.mkdir(tstr) + os.system("mv /home/lanforge/*.pcap %s"%(tstr)) + print("Captures are found in directory: %s"%tstr) + else: + print("Sniffer will complete in %f seconds."%(waitfor)) + + + # Create a file easily sourced by a shell script to communicate the directory + # name and such. + fname = "TOS_PLUS.sh" + sh = open(fname, "w") + sh.write("CAPTURE_DIR=%s\n"%(tstr)) + sh.write("CSV_FILE=%s\n"%(csvfname)) + sh.write("XLSX_FILE=%s\n"%(outfile)) + + sh.close() + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if __name__ == '__main__': + main() + print("Xlsx results stored in %s"%(outfile)) + +#### +#### +#### diff --git a/lanforge/lanforge-scripts/lf_tos_test.py b/lanforge/lanforge-scripts/lf_tos_test.py new file mode 100755 index 000000000..282352ba1 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_tos_test.py @@ -0,0 +1,549 @@ +#!/usr/bin/python3 +''' + +Create connections with different QoS, run them, and report the latency +and other throughput values. + +make sure pexpect is installed: +$ sudo yum install python3-pexpect +$ sudo yum install python3-xlsxwriter + +You might need to install pexpect-serial using pip: +$ pip3 install pexpect-serial +$ pip3 install XlsxWriter + +The user is responsible for setting up the stations oustide of this script, however. + +When specifying ports, if the port starts with [Number]., like 1.eth1, then the 1 specifies +the resource ID. + +./lf_tos_test.py --lfmgr 192.168.100.178 \ + --station "1.wlan0 1.wlan1" --tos "BK BE VI VO" --proto udp \ + --speed_mbps 1000 --upstream_port 1.eth2 --duration_min 5 + +''' + +# TODO: Maybe HTML output too? +# TODO: Allow selecting tabs or commas for output files + +import sys +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +import re +import logging +import time +from time import sleep +import pprint +import argparse +import subprocess +import xlsxwriter +from subprocess import PIPE + +NL = "\n" +CR = "\r\n" +Q = '"' +A = "'" +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' + +lfmgr = "127.0.0.1" +lfstation = "1.wlan0" +outfile = "tos_results.xlsx" +upstream_port = "1.eth1" +speed = 1000000000 +proto = "udp" +dur = 5 * 60 +tos = "BK"; + +# rssi_adjust = (current_nf - nf_at_calibration) + +def usage(): + print("$0 ") + print("--outfile: Write results here.") + print("--station: LANforge station names (1.wlan0 1.wlan1 ...)") + print("--upstream_port: LANforge upstream port name (1.eth1)") + print("--lfmgr: LANforge manager IP address") + print("--tos: IP Type of Service: BK BE VI VO") + print("--speed_mbps: Total requested transmit speed, in Mbps") + print("--duration: Duration to run traffic, in minutes") + print("--proto: List of protocols (udp, tcp)") + print("-h|--help") + +def main(): + global lfmgr + global lfstation + global outfile + global upstream_port + global speed + global proto; + global dur; + global tos; + + parser = argparse.ArgumentParser(description="ToS report Script") + parser.add_argument("--upstream_port", type=str, help="LANforge upsteram-port to use (1.eth1, etc)") + parser.add_argument("--station", type=str, help="LANforge stations to use (1.wlan0 1.wlan1 etc)") + parser.add_argument("--lfmgr", type=str, help="LANforge Manager IP address") + parser.add_argument("--outfile", type=str, help="Output file for csv data") + parser.add_argument("--tos", type=str, help="IP Type of Service: BK BE VI VO") + parser.add_argument("--speed_mbps", type=int, help="Total requested transmit speed, in Mbps") + parser.add_argument("--duration", type=float, help="Duration to run traffic, in minutes") + parser.add_argument("--proto", type=str, help="List of protocols (udp tcp)") + + args = None + try: + args = parser.parse_args() + if (args.station != None): + lfstation = args.station + if (args.upstream_port != None): + upstream_port = args.upstream_port + if (args.lfmgr != None): + lfmgr = args.lfmgr + if (args.tos != None): + tos = args.tos + if (args.proto != None): + proto = args.proto + if (args.speed_mbps != None): + speed = args.speed_mbps * 1000000 + if (args.duration != None): + dur = args.duration * 60 + if (args.outfile != None): + outfile = args.outfile + filehandler = None + except Exception as e: + logging.exception(e); + usage() + exit(2); + + # XLSX file + workbook = xlsxwriter.Workbook(outfile) + worksheet = workbook.add_worksheet() + + bold = workbook.add_format({'bold': True, 'align': 'center'}) + dblue_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dblue_bold.set_bg_color("#b8cbe4") + dblue_bold.set_border(1) + dtan_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dtan_bold.set_bg_color("#dcd8c3") + dtan_bold.set_border(1) + dpeach_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dpeach_bold.set_bg_color("#ffd8bb") + dpeach_bold.set_border(1) + dpink_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dpink_bold.set_bg_color("#fcc8ca") + dpink_bold.set_border(1) + dyel_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dyel_bold.set_bg_color("#ffe699") + dyel_bold.set_border(1) + dgreen_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dgreen_bold.set_bg_color("#c6e0b4") + dgreen_bold.set_border(1) + dgreen_bold_left = workbook.add_format({'bold': True, 'align': 'left'}) + dgreen_bold_left.set_bg_color("#c6e0b4") + dgreen_bold_left.set_border(1) + center = workbook.add_format({'align': 'center'}) + center_blue = workbook.add_format({'align': 'center'}) + center_blue.set_bg_color("#dbe5f1") + center_blue.set_border(1) + center_tan = workbook.add_format({'align': 'center'}) + center_tan.set_bg_color("#edede1") + center_tan.set_border(1) + center_peach = workbook.add_format({'align': 'center'}) + center_peach.set_bg_color("#fce4d6") + center_peach.set_border(1) + center_yel = workbook.add_format({'align': 'center'}) + center_yel.set_bg_color("#fdf2cc") + center_yel.set_border(1) + center_yel_red = workbook.add_format({'align': 'center', 'color': 'red'}) + center_yel_red.set_bg_color("#fdf2cc") + center_yel_red.set_border(1) + center_pink = workbook.add_format({'align': 'center'}) + center_pink.set_bg_color("ffd2d3") + center_pink.set_border(1) + red = workbook.add_format({'color': 'red', 'align': 'center'}) + red.set_bg_color("#e0efda") + red.set_border(1) + red_left = workbook.add_format({'color': 'red', 'align': 'left'}) + red_left.set_bg_color("#e0efda") + red_left.set_border(1) + green = workbook.add_format({'color': 'green', 'align': 'center'}) + green.set_bg_color("#e0efda") + green.set_border(1) + green_left = workbook.add_format({'color': 'green', 'align': 'left'}) + green_left.set_bg_color("#e0efda") + green_left.set_border(1) + + worksheet.set_row(0, 45) # Set height + + bucket_hdrs = "0 1 2-3 4-7 8-15 16-31 32-63 64-127 128-255 256-511 512-1023 1024-2047 2048-4095 4096-8191 8192-16383 16384-32767 32768-65535".split() + col = 0 + row = 0 + + dwidth = 13 # Set width to 13 for all columns by default + + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'CX-Name', dblue_bold); col += 1 + worksheet.set_column(col, col, 15) + worksheet.write(row, col, 'Endp-Name', dblue_bold); col += 1 + worksheet.set_column(col, col, 12) + worksheet.write(row, col, 'Port', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Protocol', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'ToS', dblue_bold); col += 1 + worksheet.set_column(col, col, 20) + worksheet.write(row, col, 'AP BSSID', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Band\nwidth', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Mode', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Last MCS\nRx', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Combined\nRSSI', dblue_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Endpoint\nOffered\nLoad', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Endpoint\nRx\nThroughput', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Cx\nOffered\nLoad', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Cx\nRx\nThroughput', dtan_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Avg\nLatency', dyel_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Min\nLatency', dyel_bold); col += 1 + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, 'Max\nLatency', dyel_bold); col += 1 + for i in range(17): + btitle = "Latency\nRange\n%s"%(bucket_hdrs[i]) + worksheet.set_column(col, col, dwidth) + worksheet.write(row, col, btitle, dpeach_bold); col += 1 + + worksheet.set_column(col, col, 50) + worksheet.write(row, col, 'Warnings and Errors', dgreen_bold_left); col += 1 + row += 1 + + # Use subprocess.check_output("Cmd") to utilize existing LF scripts. + + lfstations = lfstation.split() + toss = tos.split() + protos = proto.split() + + u_name = upstream_port + u_resource = 1 + if (upstream_port[0].isdigit()): + tmpa = upstream_port.split(".", 1) + u_resource = tmpa[0] + u_name = tmpa[1] + + mcount = len(lfstations) * len(toss) * len(protos); + cx_speed = int(speed / mcount); + opposite_speed = 56000 + count = 0 + cxnames = [] + for sta in lfstations: + e_tot = "" + sta_resource = "1" + sta_name = sta; + if sta[0].isdigit(): + tmpa = sta.split(".", 1); + sta_resource = tmpa[0]; + sta_name = tmpa[1]; + + # Up station + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", sta_resource, "--port_name", sta_name, + "--set_ifstate", "up"]); + + i = 0 + wait_ip_print = False; + wait_assoc_print = False; + # Wait untill LANforge station connects + while True: + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", sta_resource, "--port_name", sta_name, + "--show_port", "AP,IP,Mode,NSS,Bandwidth,Channel,Signal,Noise,Status,RX-Rate"], stdout=PIPE, stderr=PIPE); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + _status = None + _ip = None + + for line in pss.splitlines(): + m = re.search('Status:\s+(.*)', line) + if (m != None): + _status = m.group(1) + m = re.search('IP:\s+(.*)', line) + if (m != None): + _ip = m.group(1) + + #print("IP %s Status %s"%(_ip, _status)) + + if (_status == "Authorized"): + if ((_ip != None) and (_ip != "0.0.0.0")): + print("Station is associated with IP address.") + break + else: + if (not wait_ip_print): + print("Waiting for station %s.%s to get IP Address."%(sta_resource, sta_name)) + wait_ip_print = True + else: + if (not wait_assoc_print): + print("Waiting up to 180s for station %s.%s to associate."%(sta_resource, sta_name)) + wait_assoc_print = True + + i = i + 1 + # We wait a fairly long time since AP will take a long time to start on a CAC channel. + if (i > 180): + err = "ERROR: Station did not connect within 180 seconds." + print(err) + e_tot += err + e_tot += " " + if (args.wait_forever): + print("Will continue waiting, you may wish to debug the system...") + i = 0 + else: + break + + time.sleep(1) + + for p in protos: + for t in toss: + e_tot2 = e_tot + # Create connections. + # First, delete any old ones + cxn = "scr-tos-%i"%count + ena = "scr-tos-%i-A"%count + enb = "scr-tos-%i-B"%count + + cxnames.append(cxn) + + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", "rm_cx ALL %s"%cxn], stderr=PIPE, stdout=PIPE); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", "rm_endp %s"%ena], stderr=PIPE, stdout=PIPE); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", "rm_endp %s"%enb], stderr=PIPE, stdout=PIPE); + + cx_proto = p; + if (cx_proto == "udp"): + cx_proto = "lf_udp" + if (cx_proto == "tcp"): + cx_proto = "lf_tcp" + + # Now, create the new connection + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", "%s"%sta_resource, "--action", "create_endp", "--port_name", sta_name, + "--endp_type", cx_proto, "--endp_name", ena, "--speed", "%s"%opposite_speed, "--report_timer", "1000", "--tos", t, + "--multicon", "1"])#, capture_output=True); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", "%s"%u_resource, "--action", "create_endp", "--port_name", u_name, + "--endp_type", cx_proto, "--endp_name", enb, "--speed", "%s"%cx_speed, "--report_timer", "1000", "--tos", t, + "--multicon", "1"])# capture_output=True); + + # Enable Multi-Helper + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", "--cmd", + "set_endp_flag %s AutoHelper 1"%(ena)]) + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", "--cmd", + "set_endp_flag %s AutoHelper 1"%(enb)]) + + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", "--cmd", + "add_cx %s default_tm %s %s"%(cxn, ena, enb)])# capture_output=True); + + # Start traffic + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", "set_cx_state all %s RUNNING"%cxn]); + + count = count + 1 + + # Traffic is started, wait requested amount of time + print("Waiting %s seconds to let traffic run for a bit"%(dur)) + time.sleep(dur) + + # Gather probe results and record data, verify NSS, BW, Channel + count = 0 + for sta in lfstations: + sta_resource = "1" + sta_name = sta; + if sta[0].isdigit(): + tmpa = sta.split(".", 1); + sta_resource = tmpa[0]; + sta_name = tmpa[1]; + + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", sta_resource, "--port_name", sta_name, + "--show_port", "AP,Mode,Bandwidth,Signal,Status,RX-Rate"], stderr=PIPE, stdout=PIPE); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + _ap = None + _bw = None + _mode = None + _rxrate = None + _signal = None + + for line in pss.splitlines(): + m = re.search('AP:\s+(.*)', line) + if (m != None): + _ap = m.group(1) + m = re.search('Bandwidth:\s+(.*)Mhz', line) + if (m != None): + _bw = m.group(1) + m = re.search('Mode:\s+(.*)', line) + if (m != None): + _mode = m.group(1) + m = re.search('RX-Rate:\s+(.*)', line) + if (m != None): + _rxrate = m.group(1) + m = re.search('Signal:\s+(.*)', line) + if (m != None): + _signal = m.group(1) + + count = 0 + for p in protos: + for t in toss: + cxn = cxnames[count] + count = count + 1 + + # Results: tx_bytes, rx_bytes, tx_bps, rx_bps, tx_pkts, rx_pkts, Latency + resultsA = ["0"] * 7 + resultsB = ["0"] * 7 + + ena = "%s-A"%cxn; + enb = "%s-B"%cxn; + enames = [ena, enb] + for ename in enames: + results = [""] * 7 + + endp_stats = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--endp_vals", "tx_bps,rx_bps,Tx Bytes,Rx Bytes,Tx Pkts,Rx Pkts,Latency", + "--endp_name", ename], stderr=PIPE, stdout=PIPE); + pss = endp_stats.stdout.decode('utf-8', 'ignore'); + + for line in pss.splitlines(): + #print("probe-line, endp: %s: %s"%(ename, line)) + m = re.search('Rx Bytes:\s+(\d+)', line) + if (m != None): + results[1] = int(m.group(1)) + if (results[1] == 0): + err = "ERROR: No bytes received by data connection %s, test results may not be valid."%(ename) + e_tot2 += err + e_tot2 += " " + m = re.search('Tx Bytes:\s+(\d+)', line) + if (m != None): + results[0] = int(m.group(1)) + if (results[0] == 0): + err = "ERROR: No bytes transmitted by data connection %s, test results may not be valid."%(ename) + e_tot2 += err + e_tot2 += " " + + m = re.search('tx_bps:\s+(.*)', line) + if (m != None): + results[2] = m.group(1) + + m = re.search('rx_bps:\s+(.*)', line) + if (m != None): + results[3] = m.group(1) + + m = re.search('Tx Pkts:\s+(.*)', line) + if (m != None): + results[4] = m.group(1) + + m = re.search('Rx Pkts:\s+(.*)', line) + if (m != None): + results[5] = m.group(1) + + m = re.search('Latency:\s+(.*)', line) + if (m != None): + results[6] = m.group(1) + + if (ena == ename): + resultsA = results.copy() + else: + resultsB = results.copy() + + # Now that we know both latencies, we can normalize them. + endp_statsA = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "normalize_latency", + "--lat1", resultsA[6], "--lat2", resultsB[6]], stderr=PIPE, stdout=PIPE); + pssA = endp_statsA.stdout.decode('utf-8', 'ignore'); + endp_statsB = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "normalize_latency", + "--lat1", resultsB[6], "--lat2", resultsA[6]], stderr=PIPE, stdout=PIPE); + pssB = endp_statsB.stdout.decode('utf-8', 'ignore'); + + #print("%s: latA: %s"%(cxn, resultsA[6])) + #print("%s: latB: %s"%(cxn, resultsB[6])) + #print("%s: pssA: %s"%(cxn, pssA)) + #print("%s: pssB: %s"%(cxn, pssB)) + + for line in pssA.splitlines(): + m = re.search('Normalized-Latency:\s+(.*)', line) + if (m != None): + resultsA[6] = m.group(1); + + for line in pssB.splitlines(): + m = re.search('Normalized-Latency:\s+(.*)', line) + if (m != None): + resultsB[6] = m.group(1); + + for ename in enames: + col = 0 + + if (ena == ename): + results = resultsA.copy() + else: + results = resultsB.copy() + + lat_cols = results[6].split() # min, max, avg, columns.... + + worksheet.write(row, col, cxn, center_blue); col += 1 + worksheet.write(row, col, ename, center_blue); col += 1 + if ename == ena: + worksheet.write(row, col, "%s.%s"%(sta_resource, sta_name), center_blue); col += 1 + else: + worksheet.write(row, col, "%s.%s"%(u_resource, u_name), center_blue); col += 1 + worksheet.write(row, col, p, center_blue); col += 1 + worksheet.write(row, col, t, center_blue); col += 1 + if ename == ena: + worksheet.write(row, col, _ap, center_blue); col += 1 + worksheet.write(row, col, _bw, center_blue); col += 1 + worksheet.write(row, col, _mode, center_blue); col += 1 + worksheet.write(row, col, _rxrate, center_blue); col += 1 + worksheet.write(row, col, _signal, center_blue); col += 1 + else: + # Upstream is likely wired, don't print station info + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + + #print("results[2]:%s 3: %s"%(results[2], results[3])) + + worksheet.write(row, col, "%.2f"%(float(results[2]) / 1000000), center_tan); col += 1 + worksheet.write(row, col, "%.2f"%(float(results[3]) / 1000000), center_tan); col += 1 + worksheet.write(row, col, "%.2f"%((float(resultsA[2]) + float(resultsB[2])) / 1000000), center_tan); col += 1 + worksheet.write(row, col, "%.2f"%((float(resultsA[3]) + float(resultsB[3])) / 1000000), center_tan); col += 1 + worksheet.write(row, col, lat_cols[2], center_yel); col += 1 + worksheet.write(row, col, lat_cols[0], center_yel); col += 1 + worksheet.write(row, col, lat_cols[1], center_yel); col += 1 + for x in range(17): + worksheet.write(row, col, lat_cols[x + 3], center_peach); col += 1 + + if (e_tot2 == ""): + worksheet.write(row, col, e_tot2, green_left); col += 1 + else: + worksheet.write(row, col, e_tot2, red_left); col += 1 + row += 1 + + # Stop traffic + for cxn in cxnames: + cmd = "set_cx_state all %s STOPPED"%cxn + #print("Stopping CX: %s with command: %s"%(cxn, cmd)); + + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "do_cmd", + "--cmd", cmd]); + + workbook.close() + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if __name__ == '__main__': + main() + print("Xlsx results stored in %s"%(outfile)) + +#### +#### +#### diff --git a/lanforge/lanforge-scripts/lf_tx_power.py b/lanforge/lanforge-scripts/lf_tx_power.py new file mode 100755 index 000000000..e53a162bc --- /dev/null +++ b/lanforge/lanforge-scripts/lf_tx_power.py @@ -0,0 +1,2197 @@ +#!/usr/bin/python3 +''' +LANforge 192.168.100.178 +Controller at 192.168.100.112 admin/Cisco123 +Controller is 192.1.0.10 +AP is 192.1.0.2''' + +EPILOG = '''\ + +############################################################################################## +# Support History +############################################################################################## + +############################################################################## +Sample script to run create station, wlan and talk to ap 1/26/2021 run on 9800 +carriage returns specifically left out +############################################################################## +./lf_tx_power.py -d 172.19.27.55 -u admin -p Wnbulab@123 --port 2013 --scheme telnet --ap 9120_Candela --bandwidth "20" --channel "52 100 104" --nss 4 --txpower "1" --pathloss 56 --antenna_gain 6 --band a --upstream_port eth2 --series 9800 --radio wiphy5 --slot 1 --ssid open-wlan --prompt "katar_candela" --create_station sta0001 --ssidpw [BLANK] --security open --verbose --wlan open-wlan --wlanID 1 --wlanSSID open-wlan --ap_info "ap_scheme==telnet ap_prompt==9120_Candela ap_ip==172.19.27.55 ap_port==2008 ap_user==admin ap_pw==Wnbulab@123" + + + +############################################################# +Sample to test pf_ignore_offset switch 1/27/2021 run on 9800 +carriage returns specifically left out +############################################################# +./lf_tx_power.py -d 172.19.27.55 -u admin -p Wnbulab@123 --port 2013 --scheme telnet --ap 9120_Candela --bandwidth "20" --channel "36 52 100 104 161" --nss 4 --txpower "1" --pathloss 56 --antenna_gain 6 --band a --upstream_port eth2 --series 9800 --radio wiphy5 --slot 1 --ssid open-wlan --prompt "katar_candela" --create_station sta0001 --ssidpw [BLANK] --security open --verbose --wlan open-wlan --wlanID 1 --wlanSSID open-wlan --pf_ignore_offset "35" + + +############################################################################################## +############################################################################################## + +make sure pexpect is installed: +$ sudo yum install python3-pexpect +$ sudo yum install python3-xlsxwriter + +You might need to install pexpect-serial using pip: +$ pip3 install pexpect-serial +$ pip3 install XlsxWriter + +This script will automatically create and start a layer-3 UDP connection between the +configured upstream port and station. + +The user also has the option of setting up the station oustide of this script, however. + +# Examples: +# See cisco_power_results.txt when complete. +# See cisco_power_results.xlsx when complete. +NOTE: Telnet port 23 unless specified , ssh port 22 unless specified, scheme defaults to ssh + + +############################################################################################## +# read AP for powercfg values using : show controllers dot11Radio 1 powercfg | g T1' +############################################################################################## + +./lf_tx_power.py -d 172.19.27.55 -u admin -p Wnbulab@123 --port 2013 --scheme telnet \ + --ap 9120_Candela --bandwidth "20" --channel "149" --nss 4 --txpower "1" \ + --pathloss 56 --band a --upstream_port eth2 --series 9800 --radio wiphy5 --slot 1 --ssid open-wlan \ + --prompt "katar_candela" --create_station sta0001 --ssidpw [BLANK] --security open --verbose \ + --antenna_gain "6" --wlanID 1 --wlan open-wlan --wlanSSID open-wlan\ + --ap_info "ap_scheme==telnet ap_prompt==9120_Candela ap_ip==172.19.27.55 ap_port==2008 ap_user==admin ap_pw==Wnbulab@123" + + +############################################################################################## +# send email and or text on --exit_on_fail +############################################################################################## + +./lf_tx_power.py -d 192.168.100.112 -u admin -p Cisco123 -s ssh --port 22 -a APA453.0E7B.CF9C --lfmgr 192.168.100.178 \ + --bandwidth "80" --channel "144" --nss 4 --txpower "1" --pathloss 51 --antenna_gain 10 --lfmgr 192.168.100.178 --band a \ + --upstream_port eth3 --outfile cisco_power_results --create_station sta0001 --radio wiphy1 --ssid test_candela --ssidpw [BLANK] \ + --security open -l out_file2 -D 14 --exit_on_fail \ + --email "user==lanforgetest@gmail.com passwd==lanforge123 to==2082868321@vtext.com smtp==smtp.gmail.com port==465"\ + --email "user==lanforgetest@gmail.com passwd==lanforge123 to==lanforgetest@gmail.com smtp==smtp.gmail.com port==465"\ + --series "3504" --prompt "(Cisco Controler)" + +############################################################################################## +# Long duration test -- need to create the ---wlanID 1 --wlan open-wlan --wlanSSID open-wlan +############################################################################################## + +./lf_tx_power.py -d 172.19.36.168 -u admin -p Wnbulab@123 --port 23 --scheme telnet --ap "APA453.0E7B.CF60" \ + --bandwidth "20 40 80" --channel "36 40 44 48 52 56 60 64 100 104 108 112 116 120 124 128 132 136 140 144 149 153 157 161 165" \ + --nss 4 --txpower "1 2 3 4 5 6 7 8" --pathloss 54 --antenna_gain 6 --band a --upstream_port eth2 --series 9800 \ + --wlanID 1 --wlan open-wlan --wlanSSID open-wlan --create_station sta0001 --radio wiphy1 --ssid open-wlan --ssidpw [BLANK] --security open \ + --outfile cisco_power_results_60_chan_ALL --cleanup --slot 1 --verbose + + +############################################################################################## +# Per-channel path-loss example station present +############################################################################################## + +./lf_tx_power.py -d 192.168.100.112 -u admin -p Cisco123 -s ssh --port 22 -a VC --lfmgr 192.168.100.178 \ + --station sta00000 --bandwidth "20 40 80 160" --channel "36:64 149:60" --antenna_gain 5 --nss 4 --txpower "1 2 3 4 5 6 7 8" --pathloss 64 \ + --band a --upstream_port eth2 --lfresource2 2 --verbose + +############################################################################################## +# To create a station run test against station create open-wlan +############################################################################################## + +./lf_tx_power.py -d <router IP> -u admin -p Cisco123 -port 23 --scheme telnet --ap AP6C71.0DE6.45D0 \ +--station sta2222 --bandwidth "20" --channel "36" --nss 4 --txpower "1 2 3 4 5 6 7 8" --pathloss 54 --antenna_gain 6 --band a \ +--upstream_port eth2 --series 9800 --wlanID 1 --wlan open-wlan --wlanSSID open-wlan --create_station sta2222 --radio wiphy1 --ssid open-wlan \ +--ssidpw [BLANK] --security open --verbose + +############################################################################################## +# station already present +############################################################################################## + +./lf_tx_power.py -d <router IP> -u admin -p Cisco123 -port 23 --scheme telnet --ap AP6C71.0DE6.45D0 \ +--station sta0000 --bandwidth "20" --channel "36" --nss 4 --txpower "1 2 3 4 5 6 7 8" --pathloss 64 --antenna_gain 5 --band a \ +--upstream_port eth2 --series 9800 --wlanID 1 --wlan open-wlan --wlanSSID open-wlan --verbose + + +############################################################################################## +# to create a station +############################################################################################## + +./lf_associate_ap.pl --radio wiphy1 --ssid open-wlan --passphrase [BLANK] ssecurity open --upstream eth1 \ +--first_ip DHCP --first_sta sta0001 --duration 5 --cxtype udp + +Changing regulatory domain should happen outside of this script. + +############################################################################################## +# If wish to send Text after test completion follow the email format based on carrier +############################################################################################## +Text message via email: + +T-Mobile – number@tmomail.net +Virgin Mobile – number@vmobl.com +AT&T – number@txt.att.net +Sprint – number@messaging.sprintpcs.com +Verizon – number@vtext.com +Tracfone – number@mmst5.tracfone.com +Ting – number@message.ting.com +Boost Mobile – number@myboostmobile.com +U.S. Cellular – number@email.uscc.net +Metro PCS – number@mymetropcs.com + +############################################################################################## +# OUTPUT in XLSX file - Spread sheet how values determined +############################################################################################## + +Tx Power : Input from command line (1-8) +Allowed Per Path : Read from the Controller +Cabling Pathloss : Input from command line, best if verified prior to testing +Antenna Gain : Input from command line, if AP cannot detect antenna connection +Beacon RSSI (beacon_sig) : From Lanforge probe, command ./lf_portmod.pl with cli parameter probe_port 1 (~line 1183, ~line 1209) +Combined RSSI User (sig) : From Lanforge probe, command ./lf_portmod.pl with cli parameter probe_port 1 (~line 1183, ~line 1193) +RSSI 1, RSSI 2, RSSI 3, RSSI 4 : (~line 1160) + ants[q] (antX) read from Lanforge probe, command ./lf_portmod.pl with cli parameter probe_port 1 + +Ant 1, Ant 2, Ant 3, Ant 4 : () + Starting Value for antX read from lanforge probe, using command ./lf_portmod.pl with cli parameter porbe_port 1 + + _noise_bear (_noise_i) = from Lanforge returning NOISE from command (!line 1070) lf_portmod.pl reading --show_port + "AP, IP, Mode, NSS, Bandwith, Channel, Signal, NOISE, Status, RX-Rate + + rssi_adj (only used if --adjust_nf and _noise_bare != None) (~line 1263) _noise_i(_noise_bear) - nf_at_calibration (fixed value of -105) + + Thus calc_antX = int(antX read from Lanforge) + pi (path loss from command line) + rssi_adj + ag (antenna gain from command line) + + calc_antX is put on the spread sheet under Ant X + +Offset 1, Offset 2, Offset 3, Offset 4: which in the code is diff_aX = calc_antX - allowed_per_path (adjusted based on number of streams) + +Pass/Fail : (~line 1286) If the diff / offset is greater than the pfrange determins the pass or fail + +''' + +import sys +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +import re +import logging +import time +from time import sleep +import argparse +import subprocess +import xlsxwriter +import math + +NL = "\n" +CR = "\r\n" +Q = '"' +A = "'" +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' + +lfmgr = "127.0.0.1" +lfstation = "sta00000" +lfresource = "1" +lfresource2 = "1" +outfile = "cisco_power_results.txt" +full_outfile = "cisco_power_results_full.txt" +outfile_xlsx = "cisco_power_results.xlsx" +upstream_port = "eth1" +pf_dbm = 6 + +# Allow one chain to have a lower signal, since customer's DUT has +# lower tx-power on one chain when doing high MCS at 4x4. +pf_ignore_offset = 0 + +# Threshold for allowing a pass +failed_low_threshold = 0 + +# This below is only used when --adjust_nf is used. +# Noise floor on ch 36 where we calibrated -54 path loss (based on hard-coded -95 noise-floor in driver) +nf_at_calibration = -105 +# older ath10k driver hard-codes noise-floor to -95 when calculating RSSI +# RSSI = NF + reported_power +# Shift RSSI by difference in actual vs calibrated noise-floor since driver hard-codes +# the noise floor. + +# rssi_adjust = (current_nf - nf_at_calibration) + +def usage(): + print("############ USAGE ############") + print("-d|--dest: destination host, address of the controller") + print("-o|--port: destination port, default = 23") + print("-u|--user: login name or username") + print("-p|--passwd: password") + print("-s|--scheme (serial|telnet|ssh): connect via serial, ssh or telnet") + print("-t|--tty tty serial device") + print("-l|--log <store true> log has same namelog messages here ,stdout means output to console, default stdout") + print("-a|--ap select AP") + print("-b|--bandwidth: List of bandwidths to test: 20 40 80 160, NA means no change, 160 can only do 2x2 spatial streams due to radio limitations") + print("-c|--channel: List of channels, with optional path-loss to test: 36:64 100:60 , NA means no change") + print("-n|--nss: List of spatial streams to test: 1x1 2x2 3x3 4x4, NA means no change") + print("-T|--txpower: List of TX power values to test: 1 2 3 4 5 6 7 8, NA means no change, 1 is highest power, the power is halved for each subsquent setting") + print("-k|--keep_state <store true> keep the state, no configuration change at the end of the test, store true flage present ") + print("--station: LANforge station name for test(sta0000), use if station present and --create_station not used") + print("--upstream_port: LANforge upstream port name (eth1)") + print("--lfmgr: LANforge manager IP address") + print("--lfresource: LANforge resource ID for station") + print("--lfresource2: LANforge resource ID for upstream port") + print("--outfile: Output file for txt and xlsx data, default cisco_power_results") + print("--pathloss: Calculated path-loss between LANforge station and AP") + print("--antenna_gain: Antenna gain for AP, if no Antenna attached then antenna gain needs to be taken into account, default 0") + print("--band: Select band (a | b | abgn), a means 5Ghz, b means 2.4, abgn means 2.4 on dual-band AP, default a") + print("--pf_dbm: Pass/Fail range, default is 6") + print("--pf_ignore_offset: Allow one chain to use lower tx-power and still pass when doing 4x4, default 100. so disabled") + print("--wait_forever: <store true> Wait forever for station to associate, may aid debugging if STA cannot associate properly") + print("--adjust_nf: <store true> Adjust RSSI based on noise-floor. ath10k without the use-real-noise-floor fix needs this option") + print("--wlan: for 9800, wlan identifier ") + print("--wlanID: wlanID for 9800 , defaults to 1") + print("--wlanSSID: wlanSSID for 9800") + print("--series: controller series 9800 , defaults to 3504") + print("--slot: 9800 AP slot defaults to 1") + print("--create_station", "create LANforge station at the beginning of the test") + print("--radio" ,"radio to create LANforge station on at the beginning of the test") + print("--ssid", "ssid default open-wlan") + print("--ssidpw", "ssidpw default [BLANK]") + print("--security", "security default open") + print("--cleanup", "<store true> Clean up all stations after test completes, only need switch for True, Defaults False") + print("--vht160", "<store true> Enables VHT160 in lanforge, only need switch for True, Defaults False") + print("--verbose","<store ture> switch the cisco controller output will be captured") + print("--exit_on_fail","--exit_on_fail, exit on test failure") + print("--exit_on_error","--exit_on_error, exit on test error, test mechanics failed") + print('-e','--email', "--email user==<from email> passwd==<email password> to==<to email> smtp==<smtp server> port==<smtp port> 465 (SSL)") + print('-ccp','--prompt', "--prompt controller prompt default WLC") + print('--beacon_dbm_diff', "--beacon_dbm_diff <value> is the delta that is allowed between the controller tx and the beacon measured") + print('--show_lf_portmod',"<store_true> show the output of lf_portmod after traffic to verify RSSI values measured by lanforge") + print('-api','--ap_info', "--ap_info ap_scheme==<telnet,ssh or serial> ap_prompt==<ap_prompt> ap_ip==<ap ip> ap_port==<ap port number> ap_user==<ap user> ap_pw==<ap password>") + + + + print("-h|--help") + +# see https://stackoverflow.com/a/13306095/11014343 +class FileAdapter(object): + def __init__(self, logger): + self.logger = logger + def write(self, data): + # NOTE: data can be a partial line, multiple lines + data = data.strip() # ignore leading/trailing whitespace + if data: # non-blank + self.logger.info(data) + def flush(self): + pass # leave it to logging to flush properly + +def exit_test(workbook): + workbook.close() + sleep(0.5) + exit(1) + + +def main(): + global lfmgr + global lfstation + global lfresource + global lfresource2 + global outfile + global outfile_xlsx + global full_outfile + global upstream_port + global pf_dbm + global pf_ignore_offset + global failed_low_threshold + + scheme = "ssh" + + parser = argparse.ArgumentParser(description="Cisco TX Power report Script",epilog=EPILOG, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument("-d", "--dest", type=str, help="address of the cisco controller",required=True) + parser.add_argument("-o", "--port", type=str, help="control port on the controller",required=True) + parser.add_argument("-u", "--user", type=str, help="credential login/username",required=True) + parser.add_argument("-p", "--passwd", type=str, help="credential password",required=True) + parser.add_argument("-s", "--scheme", type=str, choices=["serial", "ssh", "telnet"], help="Connect via serial, ssh or telnet") + parser.add_argument("-t", "--tty", type=str, help="tty serial device") + parser.add_argument("-l", "--log", action='store_true', help="create logfile for messages, default stdout") + parser.add_argument("-a", "--ap", type=str, help="select AP") + parser.add_argument("-b", "--bandwidth", type=str, help="List of bandwidths to test. NA means no change") + parser.add_argument("-c", "--channel", type=str, help="List of channels to test, with optional path-loss, 36:64 149:60. NA means no change") + parser.add_argument("-n", "--nss", type=str, help="List of spatial streams to test. NA means no change") + parser.add_argument("-T", "--txpower", type=str, help="List of txpowers to test. NA means no change") + parser.add_argument("-k","--keep_state", action="store_true",help="keep the state, no configuration change at the end of the test") + parser.add_argument('-D','--duration', type=str, help='--traffic <how long to run in seconds> example -t 20 (seconds) default: 20 ',default='20') + parser.add_argument("--station", type=str, help="LANforge station to use (sta0000, etc) use if station present and --create_station not used") + parser.add_argument("--upstream_port", type=str, help="LANforge upsteram-port to use (eth1, etc)") + parser.add_argument("--lfmgr", type=str, help="LANforge Manager IP address") + parser.add_argument("--lfresource", type=str, help="LANforge resource ID for the station") + parser.add_argument("--lfresource2", type=str, help="LANforge resource ID for the upstream port system") + parser.add_argument("--outfile", type=str, help="Output file for csv data",default="cisco_power_results") + parser.add_argument("--pathloss", type=str, help="Calculated pathloss between LANforge Station and AP") + parser.add_argument("--antenna_gain", type=str, help="Antenna gain, take into account the gain due to the antenna",default="0") + parser.add_argument("--band", type=str, help="Select band (a | b), a means 5Ghz, b means 2.4Ghz. Default is a", + choices=["a", "b", "abgn"]) + parser.add_argument("--pf_dbm", type=str, help="Pass/Fail threshold. Default is 6",default="6" ) + parser.add_argument("--pf_ignore_offset", type=str, help="Allow a chain to have lower tx-power and still pass. default 0 so disabled",default="0") + parser.add_argument("--wait_forever", action='store_true', help="Wait forever for station to associate, may aid debugging if STA cannot associate properly") + parser.add_argument("--adjust_nf", action='store_true', help="Adjust RSSI based on noise-floor. ath10k without the use-real-noise-floor fix needs this option") + parser.add_argument("--wlan", type=str, help="--wlan 9800, wlan identifier",required=True) + parser.add_argument("--wlanID", type=str, help="--wlanID 9800 , defaults to 1",default="1",required=True) + parser.add_argument("--wlanSSID", type=str, help="--wlan 9800, wlan SSID, this must match the -ssid , ssid for station",required=True) + parser.add_argument("--series", type=str, help="--series 9800 or 3504, defaults to 9800",default="9800") + parser.add_argument("--slot", type=str, help="--slot 1 , 9800 AP slot defaults to 1",default="1") + parser.add_argument("--create_station", type=str, help="create LANforge station at the beginning of the test") + parser.add_argument("--radio", type=str, help="radio to create LANforge station on at the beginning of the test") + parser.add_argument("--ssid", type=str, help="ssid, this must patch the wlan",required=True) + parser.add_argument("--ssidpw", type=str, help="ssidpw",required=True) + parser.add_argument("--security", type=str, help="security",required=True) + parser.add_argument("--cleanup", action='store_true',help="--cleanup , Clean up stations after test completes ") + parser.add_argument("--vht160", action='store_true',help="--vht160 , Enable VHT160 in lanforge ") + parser.add_argument('--verbose', action='store_true',help='--verbose , switch the cisco controller output will be captured') + parser.add_argument("--exit_on_fail", action='store_true',help="--exit_on_fail, exit on test failure") + parser.add_argument("--exit_on_error", action='store_true',help="--exit_on_error, exit on test error, test mechanics failed") + parser.add_argument('-e','--email', action='append', nargs=1, type=str, help="--email user==<from email> passwd==<email password> to==<to email> smtp==<smtp server> port==<smtp port> 465 (SSL)") + parser.add_argument('-ccp','--prompt', type=str,help="controller prompt",required=True) + parser.add_argument('--beacon_dbm_diff', type=str,help="--beacon_dbm_diff <value> is the delta that is allowed between the controller tx and the beacon measured",default="7") + parser.add_argument('--show_lf_portmod', action='store_true',help="--show_lf_portmod, show the output of lf_portmod after traffic to verify RSSI values measured by lanforge") + parser.add_argument('-api','--ap_info', action='append', nargs=1, type=str, help="--ap_info ap_scheme==<telnet,ssh or serial> ap_prompt==<ap_prompt> ap_ip==<ap ip> ap_port==<ap port number> ap_user==<ap user> ap_pw==<ap password>") + + + #current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "{:.3f}".format(time.time() - (math.floor(time.time())))[1:] + #print(current_time) + #usage() + args = None + try: + # Parcing the input parameters and assignment + args = parser.parse_args() + if (args.scheme != None): + scheme = args.scheme + #logfile = args.log + if (args.station != None): + lfstation = args.station + if (args.create_station != None): + lfstation = args.create_station + if (args.station != None): + print("NOTE: both station: {} and create_station: {} on command line, test will use create_station {} ".format(args.station, args.create_station, args.create_station)) + if (args.upstream_port != None): + upstream_port = args.upstream_port + if (args.lfmgr != None): + lfmgr = args.lfmgr + if (args.lfresource != None): + lfresource = args.lfresource + if (args.lfresource2 != None): + lfresource2 = args.lfresource2 + if (args.outfile != None): + outfile = args.outfile + full_outfile = "full-%s"%(outfile) + outfile_xlsx = "%s.xlsx"%(outfile) + if (args.band != None): + band = args.band + else: + band = "a" + if (args.pf_dbm != None): + pf_dbm = int(args.pf_dbm) + if (args.pf_ignore_offset != None): + pf_ignore_offset = int(args.pf_ignore_offset) + if (args.verbose): + # capture the controller output , thus won't go to stdout some output always present + cap_ctl_out = False + else: + cap_ctl_out = True + if (args.wlanSSID != args.ssid): + print("####### ERROR ################################") + print("wlanSSID: {} must equial the station ssid: {}".format(args.wlanSSID,args.ssid)) + print("####### ERROR ################################") + exit(1) + # note: there would always be an args.outfile due to the default + current_time = time.strftime("%m_%d_%Y_%H_%M_%S", time.localtime()) + outfile = "{}_{}.txt".format(args.outfile,current_time) + full_outfile = "{}_full_{}.txt".format(args.outfile,current_time) + outfile_xlsx = "{}_{}.xlsx".format(args.outfile,current_time) + print("output file: {}".format(outfile)) + print("output file full: {}".format(full_outfile)) + print("output file xlsx: {}".format(outfile_xlsx)) + if args.log: + outfile_log = "{}_{}_output_log.log".format(args.outfile,current_time) + print("output file log: {}".format(outfile_log)) + + ap_dict = [] + if args.ap_info: + ap_info = args.ap_info + for _ap_info in ap_info: + print("ap_info {}".format(_ap_info)) + ap_keys = ['ap_scheme','ap_prompt','ap_ip','ap_port','ap_user','ap_pw'] + ap_dict = dict(map(lambda x: x.split('=='), str(_ap_info).replace('[','').replace(']','').replace("'","").split())) + for key in ap_keys: + if key not in ap_dict: + print("missing ap config, for the {}, all these need to be set {} ".format(key,ap_keys)) + exit(1) + print("ap_dict: {}".format(ap_dict)) + + email_dicts = [] + if args.email: + emails = args.email + for _email in emails: + #print("email {}".format(_email)) + email_keys = ['user','passwd','to','smtp','port'] + _email_dict = dict(map(lambda x: x.split('=='), str(_email).replace('[','').replace(']','').replace("'","").split())) + #print("email_dict {}".format(_email_dict)) + for key in email_keys: + if key not in _email_dict: + print("missing config, for the {}, all of the following need to be present {} ".format(key,email_keys)) + exit(1) + email_dicts.append(_email_dict) + print("email_dicts: {}".format(email_dicts)) + + except Exception as e: + logging.exception(e) + usage() + exit(2) + + + console_handler = logging.StreamHandler() + formatter = logging.Formatter(FORMAT) + logg = logging.getLogger(__name__) + logg.setLevel(logging.DEBUG) + + file_handler = None + # Setting up log file if specified + if args.log: + file_handler = logging.FileHandler(outfile_log, "w") + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + logg.addHandler(file_handler) + logg.addHandler(logging.StreamHandler(sys.stdout)) # allows to logging to file and stderr + #logging.basicConfig(format=FORMAT, handlers=[console_handler]) + + else: + #pass + # stdout logging + logging.basicConfig(format=FORMAT, handlers=[console_handler]) + #logg.addHandler(logging.StreamHandler()) # allows to logging to file and stderr + + if bool(ap_dict): + logg.info("ap_dict {}".format(ap_dict)) + + if bool(email_dicts): + logg.info("email_dicts {}".format(email_dicts)) + + if args.outfile != None: + logg.info("output file: {}".format(outfile)) + logg.info("output file full: {}".format(full_outfile)) + logg.info("output file xlsx: {}".format(outfile_xlsx)) + + if args.log: + logg.info("output file log: {}".format(outfile_log)) + + + if (args.bandwidth == None): + usage() + logg.info("ERROR: Must specify bandwidths") + exit(1) + + if (args.channel == None): + usage() + logg.info("ERROR: Must specify channels") + exit(1) + + if (args.nss == None): + usage() + logg.info("ERROR: Must specify NSS") + exit(1) + + if (args.txpower == None): + usage() + logg.info("ERROR: Must specify txpower") + exit(1) + + if (args.pathloss == None): + logg.info("ERROR: Pathloss must be specified.") + exit(1) + + if (args.antenna_gain == None): + usage() + logg.info("ERROR: Antenna gain must be specified.") + exit(1) + + # Full spread-sheet data + csv = open(full_outfile, "w") + csv.write("Regulatory Domain\tCabling Pathloss\tAntenna Gain\tCfg-Channel\tCfg-NSS\tCfg-AP-BW\tTx Power\tBeacon-Signal\tCombined-Signal\tRSSI 1\tRSSI 2\tRSSI 3\tRSSI 4\tAP-BSSID\tRpt-BW\tRpt-Channel\tRpt-Mode\tRpt-NSS\tRpt-Noise\tRpt-Rxrate\tCtrl-AP-MAC\tCtrl-Channel\tCtrl-Power\tCtrl-dBm\tCalc-dBm-Combined\tDiff-dBm-Combined\tAnt-1\tAnt-2\tAnt-3\tAnt-4\tOffset-1\tOffset-2\tOffset-3\tOffset-4\tPASS/FAIL(+-%sdB)\tTimeStamp\tWarnings-and-Errors"%(pf_dbm)) + csv.write("\n"); + csv.flush() + + # Summary spread-sheet data + csvs = open(outfile, "w") + csvs.write("Regulatory Domain\tCabling Pathloss\tAntenna Gain\tAP Channel\tNSS\tAP BW\tTx Power\tAllowed Per-Path\tRSSI 1\tRSSI 2\tRSSI 3\tRSSI 4\tAnt-1\tAnt-2\tAnt-3\tAnt-4\tOffset-1\tOffset-2\tOffset-3\tOffset-4\tPASS/FAIL(+-%sdB)\tTimeStamp\tWarnings-and-Errors"%(pf_dbm)) + csvs.write("\n"); + csvs.flush() + + # XLSX file + workbook = xlsxwriter.Workbook(outfile_xlsx) + worksheet = workbook.add_worksheet() + + #bold = workbook.add_format({'bold': True, 'align': 'center'}) + dblue_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dblue_bold.set_bg_color("#b8cbe4") + dblue_bold.set_border(1) + dtan_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dtan_bold.set_bg_color("#dcd8c3") + dtan_bold.set_border(1) + dpeach_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dpeach_bold.set_bg_color("#ffd8bb") + dpeach_bold.set_border(1) + dpink_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dpink_bold.set_bg_color("#fcc8ca") + dpink_bold.set_border(1) + dyel_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dyel_bold.set_bg_color("#ffe699") + dyel_bold.set_border(1) + dgreen_bold = workbook.add_format({'bold': True, 'align': 'center'}) + dgreen_bold.set_bg_color("#c6e0b4") + dgreen_bold.set_border(1) + dgreen_bold_left = workbook.add_format({'bold': True, 'align': 'left'}) + dgreen_bold_left.set_bg_color("#c6e0b4") + dgreen_bold_left.set_border(1) + #center = workbook.add_format({'align': 'center'}) + center_blue = workbook.add_format({'align': 'center'}) + center_blue.set_bg_color("#dbe5f1") + center_blue.set_border(1) + center_tan = workbook.add_format({'align': 'center'}) + center_tan.set_bg_color("#edede1") + center_tan.set_border(1) + center_peach = workbook.add_format({'align': 'center'}) + center_peach.set_bg_color("#fce4d6") + center_peach.set_border(1) + center_yel = workbook.add_format({'align': 'center'}) + center_yel.set_bg_color("#fdf2cc") + center_yel.set_border(1) + center_yel_red = workbook.add_format({'align': 'center', 'color': 'red'}) + center_yel_red.set_bg_color("#fdf2cc") + center_yel_red.set_border(1) + center_pink = workbook.add_format({'align': 'center'}) + center_pink.set_bg_color("ffd2d3") + center_pink.set_border(1) + red = workbook.add_format({'color': 'red', 'align': 'center'}) + red.set_bg_color("#e0efda") + red.set_border(1) + red_left = workbook.add_format({'color': 'red', 'align': 'left'}) + red_left.set_bg_color("#e0efda") + red_left.set_border(1) + green = workbook.add_format({'color': 'green', 'align': 'center'}) + green.set_bg_color("#e0efda") + green.set_border(1) + green_left = workbook.add_format({'color': 'green', 'align': 'left'}) + green_left.set_bg_color("#e0efda") + green_left.set_border(1) + orange_left = workbook.add_format({'color': 'orange', 'align': 'left'}) + orange_left.set_bg_color("#e0efda") + orange_left.set_border(1) + + + worksheet.set_row(0, 45) # Set height + worksheet.set_column(0, 0, 10) # Set width + + col = 0 + row = 0 + worksheet.write(row, col, 'Regulatory\nDomain', dblue_bold); col += 1 + worksheet.set_column(col, col, 10) # Set width + worksheet.write(row, col, 'Controller', dblue_bold); col += 1 + worksheet.set_column(col, col, 12) # Set width + worksheet.write(row, col, 'Controller\nChannel', dblue_bold); col += 1 + worksheet.write(row, col, 'AP\nChannel', dblue_bold); col += 1 + worksheet.write(row, col, 'NSS', dblue_bold); col += 1 + worksheet.set_column(col, col, 10) # Set width + worksheet.write(row, col, 'Controller\nBW', dblue_bold); col += 1 + worksheet.write(row, col, 'STA\nRpt\nBW', dblue_bold); col += 1 + worksheet.write(row, col, 'Tx\nPower', dtan_bold); col += 1 + worksheet.write(row, col, 'Allowed\nPer\nPath', dtan_bold); col += 1 + worksheet.write(row, col, 'Cabling\nPathloss', dtan_bold); col += 1 + worksheet.write(row, col, 'Antenna\nGain', dtan_bold); col += 1 + worksheet.write(row, col, 'Noise\n', dpeach_bold); col += 1 + if (args.adjust_nf): + worksheet.write(row, col, 'Noise\nAdjust\n(vs -105)', dpeach_bold); col += 1 + + worksheet.set_column(col, col, 15) # Set width + worksheet.write(row, col, 'Last\nMCS\n', dpeach_bold); col += 1 + worksheet.set_column(col, col, 10) # Set width + worksheet.write(row, col, 'Beacon\nRSSI\n', dpeach_bold); col += 1 + worksheet.set_column(col, col, 10) # Set width + worksheet.write(row, col, 'Combined\nRSSI\n', dpeach_bold); col += 1 + worksheet.write(row, col, 'RSSI\n1', dpeach_bold); col += 1 + worksheet.write(row, col, 'RSSI\n2', dpeach_bold); col += 1 + worksheet.write(row, col, 'RSSI\n3', dpeach_bold); col += 1 + worksheet.write(row, col, 'RSSI\n4', dpeach_bold); col += 1 + worksheet.write(row, col, 'Ant\n1', dpink_bold); col += 1 + worksheet.write(row, col, 'Ant\n2', dpink_bold); col += 1 + worksheet.write(row, col, 'Ant\n3', dpink_bold); col += 1 + worksheet.write(row, col, 'Ant\n4', dpink_bold); col += 1 + worksheet.write(row, col, 'Offset\n1', dyel_bold); col += 1 + worksheet.write(row, col, 'Offset\n2', dyel_bold); col += 1 + worksheet.write(row, col, 'Offset\n3', dyel_bold); col += 1 + worksheet.write(row, col, 'Offset\n4', dyel_bold); col += 1 + worksheet.set_column(col, col, 10) # Set width + worksheet.write(row, col, 'Controller\n dBm', dblue_bold); col += 1 + worksheet.set_column(col, col, 10) # Set width + worksheet.write(row, col, 'Calculated\n dBm\n Beacon', dblue_bold); col += 1 + worksheet.set_column(col, col, 18) # Set width + worksheet.write(row, col, 'Diff Controller dBm\n & Beacon dBm \n (+/- {} dBm)'.format(args.beacon_dbm_diff), dblue_bold); col += 1 + worksheet.set_column(col, col, 14) # Set width + worksheet.write(row, col, 'Calculated\n dBm\n Combined', dblue_bold); col += 1 + worksheet.set_column(col, col, 14) # Set width + worksheet.write(row, col, 'Diff\nController dBm\n & Combined', dblue_bold); col += 1 + worksheet.set_column(col, col, 12) # Set width + worksheet.write(row, col, "PASS /\nFAIL\n( += %s dBm)"%(pf_dbm), dgreen_bold); col += 1 + worksheet.set_column(col, col, 24) # Set width + worksheet.write(row, col, 'Time Stamp\n', dgreen_bold); col += 1 + worksheet.set_column(col, col, 100) # Set width + worksheet.write(row, col, 'Information, Warnings, Errors', dgreen_bold_left); col += 1 + row += 1 + + bandwidths = args.bandwidth.split() + channels = args.channel.split() + nss = args.nss.split() + txpowers = args.txpower.split() + + # The script has the ability to create a station if one does not exist + if (args.create_station != None): + if (args.radio == None): + logg.info("WARNING --create needs a radio") + exit_test(workbook) + elif (args.vht160): + logg.info("creating station with VHT160 set: {} on radio {}".format(args.create_station,args.radio)) + subprocess.run(["./lf_associate_ap.pl", "--radio", args.radio, "--ssid", args.ssid , "--passphrase", args.ssidpw, + "--security", args.security, "--upstream", args.upstream_port, "--first_ip", "DHCP", + "--first_sta",args.create_station,"--action","add","--xsec","ht160_enable"], timeout=20, capture_output=True) + sleep(3) + else: + logg.info("creating station: {} on radio {}".format(args.create_station,args.radio)) + subprocess.run(["./lf_associate_ap.pl", "--radio", args.radio, "--ssid", args.ssid , "--passphrase", args.ssidpw, + "--security", args.security, "--upstream", args.upstream_port, "--first_ip", "DHCP", + "--first_sta",args.create_station,"--action","add"], timeout=20, capture_output=True) + sleep(3) + + + # Find LANforge station parent radio + parent = None + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", lfresource, "--port_name", lfstation, + "--show_port", "Parent/Peer"], capture_output=True); + pss = port_stats.stdout.decode('utf-8', 'ignore'); + for line in pss.splitlines(): + m = re.search('Parent/Peer:\s+(.*)', line) + if (m != None): + parent = m.group(1) + + + # Create downstream connection + # First, delete any old one + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "do_cmd", + "--cmd", "rm_cx all c-udp-power"], capture_output=True); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "do_cmd", + "--cmd", "rm_endp c-udp-power-A"], capture_output=True); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource2, "--action", "do_cmd", + "--cmd", "rm_endp c-udp-power-B"], capture_output=True); + + # Now, create the new connection + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "create_endp", "--port_name", lfstation, + "--endp_type", "lf_udp", "--endp_name", "c-udp-power-A", "--speed", "0", "--report_timer", "1000"], capture_output=True); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource2, "--action", "create_endp", "--port_name", upstream_port, + "--endp_type", "lf_udp", "--endp_name", "c-udp-power-B", "--speed", "1000000", "--report_timer", "1000"], capture_output=True); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "create_cx", "--cx_name", "c-udp-power", + "--cx_endps", "c-udp-power-A,c-udp-power-B", "--report_timer", "1000"], capture_output=True); + + myrd = "" + # The script supports both the 9800 series controller and the 3504 series controller , the controllers have different interfaces + if args.series == "9800": + + try: + logg.info("9800 wifi_ctl_9800_3504.py: no_logging_console") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "no_logging_console","--series",args.series,"--port",args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore'); + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("####################################################################################################") + logg.info("# CHECK IF CONTROLLER HAS TELNET CONNECTION ALREADY ACTIVE") + logg.info("####################################################################################################") + + logg.info("####################################################################################################") + logg.info("# Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + logg.info("####################################################################################################") + exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: line_console_0") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "line_console_0","--series",args.series,"--port",args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore'); + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: summary") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "summary","--series",args.series,"--port",args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore'); + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + # Find our current regulatory domain so we can report it properly + searchap = False + for line in pss.splitlines(): + if (line.startswith("---------")): + searchap = True + continue + # the summaries are different between the 9800 series controller and the 3504 series + # if the output changes then the following pattern/regular expression parcing needs to be changed + # this site may help: https://regex101.com/ + # when using https://regex101.com/ for tool beginning of string begins with ^ + if (searchap): + if args.series == "9800": + pat = "%s\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)"%(args.ap) + else: + pat = "%s\s+\S+\s+\S+\s+\S+\s+\S+.* (\S+)\s+\S+\s*\S+\s+\["%(args.ap) + m = re.search(pat, line) + if (m != None): + myrd = m.group(1) + + # Loop through all iterations and run txpower tests. + # The is the main loop of loops: Channels, spatial streams (nss), bandwidth (bw), txpowers (tx) + # Note: supports 9800 and 3504 controllers + wlan_created = False + for ch in channels: + pathloss = args.pathloss + antenna_gain = args.antenna_gain + ch_colon = ch.count(":") + if (ch_colon == 1): + cha = ch.split(":") + pathloss = cha[1] + ch = cha[0] + for n in nss: + for bw in bandwidths: + if (n != "NA"): + ni = int(n) + if (parent == None): + logg.info("ERROR: Skipping setting the spatial streams because cannot find Parent radio for station: %s."%(lfstation)) + else: + # Set nss on LANforge Station, not sure it can be done on AP + if (bw == "160"): + # 9984 hardware needs 2 chains to do one NSS at 160Mhz + if (ni > 2): + if(args.vht160): + ni = 2 + logg.info("NOTE: --vht160 set will set ni : {}".format(ni)) + # Set radio to 2x requested value + ni *=2 + logg.info("NOTE: --vht160 set will set ni * 2 : {}".format(ni)) + else: + logg.info("NOTE: Skipping NSS %s for 160Mhz, LANforge radios do not support more than 2NSS at 160Mhz currently."%(n)) + logg.info("NOTE: use --vht160 to force 2NSS at 160Mhz") + continue + else: + # Set radio to 2x requested value for 160Mhz + ni *= 2 + antset = 0 # all available + if (ni == 1): + antset = 1 + if (ni == 2): + antset = 4 + if (ni == 3): + antset = 7 + set_cmd = "set_wifi_radio 1 %s %s NA NA NA NA NA NA NA NA NA %s"%(lfresource, parent, antset) + logg.info("Setting LANforge radio to %s NSS with command: %s"%(ni, set_cmd)) + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", lfresource, "--port_name", parent, + "--cli_cmd", set_cmd], capture_output=True) + # tx power 1 is the highest power , 2 power is 1/2 of 1 power etc till power 8 the lowest. + for tx in txpowers: + # e_tot is the errors, w_tot is the warning, i_tot is information + e_tot = "" + w_tot = "" + i_tot = "" + + # Stop traffic , if traffic was running , this is on the lanforge side. Commands that start with lf_ are directed + # towards the lanforge + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "do_cmd", + "--cmd", "set_cx_state all c-udp-power STOPPED"], capture_output=True); + + # Down station + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", lfresource, "--port_name", lfstation, + "--set_ifstate", "down"]); + + # Disable AP, apply settings, enable AP + try: + logg.info("3504/9800 wifi_ctl_9800_3504.py: disable AP {}".format(args.ap)) + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "disable","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("####################################################################################################") + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + logg.info("####################################################################################################") + logg.info("####################################################################################################") + logg.info("#CHECK IF CONTROLLER HAS TELNET CONNECTION ALREADY ACTIVE") + logg.info("####################################################################################################") + + exit_test(workbook) + + if args.series == "9800": + # 9800 series need to "Configure radio for manual channel assignment" + logg.info("9800 Configure radio for manual channel assignment") + + try: + logg.info("9800 wifi_ctl_9800_3504.py: disable_wlan") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "disable_wlan","--wlan", args.wlan, "--wlanID", args.wlanID, "--wlanSSID", args.wlanSSID, + "--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + try: + logg.info("9800 wifi_ctl_9800_3504.py: disable_network_5ghz") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "disable_network_5ghz","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: disable_network_24ghz") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "disable_network_24ghz","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: manual") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "manual","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + else: + try: + logg.info("3504 wifi_ctl_9800_3504.py: config 802.11a disable network") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "cmd", "--value", "config 802.11a disable network","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("3504 wifi_ctl_9800_3504.py: config 802.11b disable network") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "cmd", "--value", "config 802.11b disable network","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + logg.info("9800/3504 test_parameters_summary: set : tx: {} ch: {} bw: {}".format(tx,ch,bw)) + if (tx != "NA"): + logg.info("9800/3504 test_parameters: set txPower: {}".format(tx)) + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: txPower {}".format(tx)) + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "txPower", "--value", tx, "--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + if (bw != "NA"): + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: bandwidth 20 prior to setting channel, some channels only support 20") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "bandwidth", "--value", "20", "--series" , args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + + # NSS is set on the station earlier... + if (ch != "NA"): + logg.info("9800/3504 test_parameters set channel: {}".format(ch)) + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: channel {}".format(ch)) + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "channel", "--value", ch, "--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + if (bw != "NA"): + logg.info("9800/3504 test_parameters bandwidth: set : {}".format(bw)) + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: bandwidth {}".format(bw)) + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "bandwidth", "--value", bw, "--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + # only create the wlan the first time + if args.series == "9800": + if wlan_created: + logg.info("wlan already present, no need to create wlanID {} wlan {} wlanSSID {} port {}".format(args.wlanID, args.wlan, args.wlanSSID, args.port)) + pass + else: + # Verify that a wlan does not exist on wlanID + # delete the wlan if already exists + try: + logg.info("9800 wifi_ctl_9800_3504.py: show_wlan_summary") + wlan_info = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "show_wlan_summary","--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = wlan_info.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + # "number of WLANs:\s+(\S+)" + search_wlan = False + for line in pss.splitlines(): + logg.info(line) + if (line.startswith("---------")): + search_wlan = True + continue + if (search_wlan): + pat = "{}\s+(\S+)\s+(\S+)".format(args.wlanID) + m = re.search(pat, line) + if (m != None): + cc_wlan = m.group(1) + cc_wlan_ssid = m.group(2) + # wlanID is in use + logg.info("###############################################################################") + logg.info("Need to remove wlanID: {} cc_wlan: {} cc_wlan_ssid: {}".format(args.wlanID, cc_wlan, cc_wlan_ssid)) + logg.info("###############################################################################") + try: + logg.info("9800 wifi_ctl_9800_3504.py: delete_wlan, wlan present at start of test: wlanID: {} cc_wlan {} cc_wlan_ssid: {}".format(args.wlanID, cc_wlan, cc_wlan_ssid)) + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "delete_wlan","--series",args.series, "--wlanID", args.wlanID, "--wlan", cc_wlan, "--wlanSSID", cc_wlan_ssid, + "--port",args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + # Create wlan + wlan_created = True + logg.info("create wlan {} wlanID {} port {}".format(args.wlan, args.wlanID, args.port)) + try: + logg.info("9800 wifi_ctl_9800_3504.py: create_wlan wlan {} wlanID {} wlanSSID {} port {}".format(args.wlan, args.wlanID, args.wlanSSID, args.port)) + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "create_wlan","--series",args.series, "--wlanID", args.wlanID, "--wlan", args.wlan, "--wlanSSID", args.wlanSSID, "--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: wireless_tag_policy") + ctl_output =subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "wireless_tag_policy","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + try: + logg.info("9800 wifi_ctl_9800_3504.py: enable_wlan") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "enable_wlan", "--wlanID", args.wlanID, "--wlan", args.wlan, "--wlanSSID", args.wlanSSID, + "--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + # enable transmission for the entier 802.11z network + if args.series == "9800": + try: + logg.info("9800 wifi_ctl_9800_3504.py: enable_network_5ghz") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "enable_network_5ghz","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: enable_network_24ghz") + ctl_output =subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "enable_network_24ghz","--series",args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + else: + try: + logg.info("3504 wifi_ctl_9800_3504.py: config 802.11a enable network") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "cmd", "--value", "config 802.11a enable network","--port", args.port,"--series",args.series,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("3504 wifi_ctl_9800_3504.py: config 802.11a enable network") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "cmd", "--value", "config 802.11b enable network","--port", args.port,"--series",args.series,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: enable") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "enable", "--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + # Wait a bit for AP to come back up + time.sleep(3) + loop_count = 0 + cc_dbm_rcv = False + if args.series == "9800": + while cc_dbm_rcv == False and loop_count <=3: + logg.info("9800 read controller dBm") + loop_count +=1 + time.sleep(1) + try: + logg.info("9800 wifi_ctl_9800_3504.py: advanced") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "advanced","--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + searchap = False + cc_mac = "" + cc_ch = "" + cc_bw = "" + cc_power = "" + cc_dbm = "" + for line in pss.splitlines(): + if (line.startswith("---------")): + searchap = True + continue + # if the pattern changes save the output of the advanced command and re parse https://regex101.com + if (searchap): + pat = "%s\s+(\S+)\s+(%s)\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)\s+dBm\)+\s+(\S+)+\s"%(args.ap,args.slot) + m = re.search(pat, line) + if (m != None): + if(m.group(2) == args.slot): + cc_mac = m.group(1) + cc_slot = m.group(2) + cc_ch = m.group(6); # (132,136,140,144) + cc_power = m.group(4) + cc_power = cc_power.replace("/", " of ") # spread-sheets turn 1/8 into a date + cc_dbm = m.group(5) + cc_dbm = cc_dbm.replace("(","") + + cc_ch_count = cc_ch.count(",") + 1 + cc_bw = m.group(3) + logg.info("group 1: {} 2: {} 3: {} 4: {} 5: {} 6: {}".format(m.group(1),m.group(2),m.group(3),m.group(4),m.group(5),m.group(6))) + logg.info("9800 test_parameters_summary: read: tx: {} ch: {} bw: {}".format(tx,ch,bw)) + + logg.info("9800 test_parameters cc_mac: read : {}".format(cc_mac)) + logg.info("9800 test_parameters cc_slot: read : {}".format(cc_slot)) + logg.info("9800 test_parameters cc_count: read : {}".format(cc_ch_count)) + logg.info("9800 test_parameters cc_bw: read : {}".format(cc_bw)) + logg.info("9800 test_parameters cc_power: read : {}".format(cc_power)) + logg.info("9800 test_parameters cc_dbm: read : {}".format(cc_dbm)) + logg.info("9800 test_parameters cc_ch: read : {}".format(cc_ch)) + break + + if (cc_dbm == ""): + if loop_count >= 3: + # Could not talk to controller? Not this may not be a reason to exit + # Some of the tests run for 32 plus hours , do not kill the whole test unless trying to + # debug an issue with the test. Sometimes the controller is taking time to configure. + err = "ERROR: Could not query dBm from controller, maybe controller died?" + logg.info(err) + logg.info("Check controller and AP , Command on AP to erase the config: capwap ap erase all") + e_tot += err + e_tot += " " + else: + logg.info("9800 read controller dBm loop_count {} try again".format(loop_count)) + else: + cc_dbm_rcv = True + try: + logg.info("9800 wifi_ctl_9800_3504.py: show_wlan_summary") + wlan_summary = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "show_wlan_summary","--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = wlan_summary.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + else: + try: + logg.info("3504 wifi_ctl_9800_3504.py: advanced") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "advanced","--port", args.port,"--series" , args.series,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + searchap = False + cc_mac = "" + cc_ch = "" + cc_bw = "" + cc_power = "" + cc_dbm = "" + ch_count = "" + for line in pss.splitlines(): + if (line.startswith("---------")): + searchap = True + continue + + if (searchap): + pat = "%s\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+\(\s*(\S+)\s+dBm"%(args.ap) + m = re.search(pat, line) + if (m != None): + cc_mac = m.group(1) + cc_ch = m.group(2); # (132,136,140,144) + cc_power = m.group(3) + cc_power = cc_power.replace("/", " of ", 1) # spread-sheets turn 1/8 into a date + cc_dbm = m.group(4) + + ch_count = cc_ch.count(",") + cc_bw = 20 * (ch_count + 1) + + break + + if (cc_dbm == ""): + # Could not talk to controller? + err = "ERROR: Could not query dBm from controller, maybe controller died?" + logg.info(err) + e_tot += err + e_tot += " " + + logg.info("3504 test_parameters cc_mac: read : {}".format(cc_mac)) + logg.info("3504 test_parameters cc_count: read : {}".format(ch_count)) + logg.info("3504 test_parameters cc_bw: read : {}".format(cc_bw)) + logg.info("3504 test_parameters cc_power: read : {}".format(cc_power)) + logg.info("3504 test_parameters cc_dbm: read : {}".format(cc_dbm)) + logg.info("3504 test_parameters cc_ch: read : {}".format(cc_ch)) + + + # Up station + subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", lfresource, "--port_name", lfstation, + "--set_ifstate", "up"]); + + i = 0 + wait_ip_print = False; + wait_assoc_print = False; + # Wait untill LANforge station connects + while True: + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", lfresource, "--port_name", lfstation, + "--show_port", "AP,IP,Mode,NSS,Bandwidth,Channel,Signal,Noise,Status,RX-Rate"],capture_output=True, check=True) + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + _status = None + _ip = None + + for line in pss.splitlines(): + m = re.search('Status:\s+(.*)', line) + if (m != None): + _status = m.group(1) + m = re.search('IP:\s+(.*)', line) + if (m != None): + _ip = m.group(1) + + #logg.info("IP %s Status %s"%(_ip, _status)) + + if (_status == "Authorized"): + if ((_ip != None) and (_ip != "0.0.0.0")): + logg.info("Station is associated with IP address.") + break + else: + if (not wait_ip_print): + logg.info("Waiting for station to get IP Address.") + wait_ip_print = True + else: + if (not wait_assoc_print): + logg.info("Waiting up to 180s for station to associate.") + wait_assoc_print = True + + i += 1 + # We wait a fairly long time since AP will take a long time to start on a CAC channel. + if (i > 180): + err = "ERROR: Station did not connect within 180 seconds." + logg.info(err) + e_tot += err + e_tot += " " + if args.series == "9800": + try: + logg.info("9800 wifi_ctl_9800_3504.py: advanced") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "advanced","--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + if (args.wait_forever): + logg.info("Will continue waiting, you may wish to debug the system...") + i = 0 + else: + break + + time.sleep(1) + + # Start traffic + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "do_cmd", + "--cmd", "set_cx_state all c-udp-power RUNNING"], capture_output=True, check=True) + + # Wait configured number of seconds more seconds + logg.info("Waiting {} seconds to let traffic run for a bit, Channel {} NSS {} BW {} TX-Power {}".format(args.duration,ch, n, bw, tx)) + time.sleep(int(args.duration)) + + # Gather probe results and record data, verify NSS, BW, Channel + i = 0 + beacon_sig = None + sig = None + pf = 1 + ants = [] + while True: + time.sleep(1) + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", lfresource, "--port_name", lfstation, + "--cli_cmd", "probe_port 1 %s %s"%(lfresource, lfstation)],capture_output=True, check=True) + pss = port_stats.stdout.decode('utf-8', 'ignore') + # for debug: print the output of lf_portmod.pl and the command used + if (args.show_lf_portmod): + logg.info("./lf_portmod.pl --manager {} --card {} --port_name {} --cli_cmd probe_port 1 {} {}".format(lfmgr, lfresource, lfstation,lfresource,lfstation)) + logg.info(pss) + + foundit = False + for line in pss.splitlines(): + #logg.info("probe-line: %s"%(line)) + m = re.search('signal avg:\s+(\S+)\s+\[(.*)\]\s+dBm', line) + if (m != None): + logg.info("search: signal ave: resulted in m = {}".format(m)) + sig = m.group(1) + ants = m.group(2).split() + q = 0 + for a in ants: + ants[q] = ants[q].replace(",", "", 1) + q += 1 + + logg.info("sig: %s ants: %s ants-len: %s n: %s"%(sig, m.group(2), len(ants), n)) + + if (len(ants) == int(n)): + foundit = True + else: + logg.info("Looking for %s spatial streams, signal avg reported fewer: %s"%(n, m.group(1))) + + m = re.search('beacon signal avg:\s+(\S+)\s+dBm', line) + if (m != None): + logg.info("search: beacon signal avg: resulted in m = {}".format(m)) + beacon_sig = m.group(1) + logg.info("beacon_sig: %s "%(beacon_sig)) + + if (foundit): + break + + i += 1 + if (i > 10): + err = "Tried and failed 10 times to find correct spatial streams, continuing." + logg.info(err) + e_tot += err + e_tot += " " + while (len(ants) < int(n)): + ants.append("") + break + + endp_stats = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--endp_vals", "rx_bps", + "--cx_name", "c-udp-power"],capture_output=True, check=True) + + pss = endp_stats.stdout.decode('utf-8', 'ignore') + #logg.info(pss) + + for line in pss.splitlines(): + #logg.info("probe-line: %s"%(line))From Lanforge probe, command ./lf_portmod.pl with cli parameter probe_port 1 (about line 1150) + m = re.search('Rx Bytes:\s+(\d+)', line) + if (m != None): + logg.info("Rx Bytes: result {}".format(m)) + rx_bytes = int(m.group(1)) + if (rx_bytes == 0): + err = "ERROR: No bytes received by data connection, test results may not be valid." + e_tot += err + e_tot += " " + + # Stop traffic + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "do_cmd", + "--cmd", "set_cx_state all c-udp-power STOPPED"],capture_output=True, check=True) + + antstr = "" + for x in range(4): + if (x < int(n)): + logg.info("x: %s n: %s len(ants): %s"%(x, n, len(ants))) + antstr += ants[x] + else: + antstr += " " + antstr += "\t" + + port_stats = subprocess.run(["./lf_portmod.pl", "--manager", lfmgr, "--card", lfresource, "--port_name", lfstation, + "--show_port", "AP,IP,Mode,NSS,Bandwidth,Channel,Signal,Noise,Status,RX-Rate"], capture_output=True, check=True) + pss = port_stats.stdout.decode('utf-8', 'ignore'); + + _ap = None + _bw = None + _ch = None + _mode = None + _nss = None + _noise = None + _rxrate = None + _noise_bare = None + + for line in pss.splitlines(): + m = re.search('AP:\s+(.*)', line) + if (m != None): + _ap = m.group(1) + logg.info("AP: {}".format(m)) + m = re.search('Bandwidth:\s+(.*)Mhz', line) + if (m != None): + _bw = m.group(1) + logg.info("Bandwidth: {}".format(m)) + m = re.search('Channel:\s+(.*)', line) + if (m != None): + _ch = m.group(1) + logg.info("Channel: {}".format(m)) + m = re.search('Mode:\s+(.*)', line) + if (m != None): + _mode = m.group(1) + logg.info("Mode: {}".format(m)) + m = re.search('NSS:\s+(.*)', line) + if (m != None): + _nss = m.group(1) + logg.info("NSS: {}".format(m)) + m = re.search('Noise:\s+(.*)', line) + if (m != None): + _noise = m.group(1) + logg.info("Noise: {}".format(m)) + m = re.search('Noise:\s+(.*)dBm', line) + if (m != None): + _noise_bare = m.group(1) + logg.info("Noise Bare: {}".format(m)) + m = re.search('RX-Rate:\s+(.*)', line) + if (m != None): + _rxrate = m.group(1) + logg.info("RX-Rate: {}".format(m)) + + # ath10k radios now take noise-floor into account, so adjust_nf + # should remain set to false when using those radios. Possibly other + # radios would need this, so leave code in place. + rssi_adj = 0 + if (args.adjust_nf and _noise_bare != None): + _noise_i = int(_noise_bare) + if (_noise_i == 0): + # Guess we could not detect noise properly? + e_tot += "WARNING: Invalid noise-floor, calculations may be inaccurate. " + pf = 0 + else: + rssi_adj = (_noise_i - nf_at_calibration) + + if (sig == None): + e_tot += "ERROR: Could not detect signal level. " + sig = -100 + pf = 0 + + if (beacon_sig == None): + e_tot += "ERROR: Could not detect beacon signal level. " + beacon_sig = -100 + pf = 0 + + pi = int(pathloss) + ag = int(antenna_gain) + calc_dbm_beacon = int(beacon_sig) + pi + rssi_adj + ag + logg.info("calc_dbm_beacon {}".format(calc_dbm_beacon)) + + logg.info("sig: %s"%sig) + calc_dbm = int(sig) + pi + rssi_adj + ag + logg.info("calc_dbm %s"%(calc_dbm)) + + + # Calculated per-antenna power is what we calculate the AP transmitted + # at (rssi + pathloss + antenna_gain ). So, if we see -30 rssi, with pathloss of 44 , + # with antenna gain of 6 + # then we calculate AP transmitted at +20 + calc_ant1 = 0 + if (ants[0] != ""): + calc_ant1 = int(ants[0]) + pi + rssi_adj + ag + logg.info("calc_ant1: {} = ants[0]: {} + pi: {} + rssi_adj: {} + ag: {}".format(calc_ant1,ants[0],pi, rssi_adj,ag)) + calc_ant2 = 0 + calc_ant3 = 0 + calc_ant4 = 0 + if (len(ants) > 1 and ants[1] != ""): + calc_ant2 = int(ants[1]) + pi + rssi_adj + ag + logg.info("calc_ant2: {} = ants[1]: {} + pi: {} + rssi_adj: {} + ag: {}".format(calc_ant2,ants[1],pi, rssi_adj,ag)) + + if (len(ants) > 2 and ants[2] != ""): + calc_ant3 = int(ants[2]) + pi + rssi_adj + ag + logg.info("calc_ant3: {} = ants[2]: {} + pi: {} + rssi_adj: {} + ag: {}".format(calc_ant3,ants[2],pi, rssi_adj,ag)) + + if (len(ants) > 3 and ants[3] != ""): + calc_ant4 = int(ants[3]) + pi + rssi_adj + ag + logg.info("calc_ant4: {} = ants[3]: {} + pi: {} + rssi_adj: {} + ag: {}".format(calc_ant4,ants[3],pi, rssi_adj,ag)) + + + diff_a1 = "" + diff_a2 = "" + diff_a3 = "" + diff_a4 = "" + + if (cc_dbm == ""): + cc_dbmi = 0 + else: + cc_dbmi = int(cc_dbm) + diff_dbm = calc_dbm - cc_dbmi + logg.info("diff_dbm {} calc_dbm {} - cc_dbmi {}".format(diff_dbm, calc_dbm, cc_dbmi)) + diff_dbm_beacon = calc_dbm_beacon - cc_dbmi + logg.info("diff_dbm_beacon {} calc_dbm_beacon {} - cc_dbmi {}".format(diff_dbm_beacon, calc_dbm_beacon, cc_dbmi)) + + if(int(abs(diff_dbm_beacon)) > int(args.beacon_dbm_diff)): + w_tot = "WARNING: Controller dBm and Calculated dBm Beacon power different by greater than +/- {} dBm".format(args.beacon_dbm_diff) + + pfs = "PASS" + pfrange = pf_dbm + + + + # Allowed per path is what we expect the AP should be transmitting at. + # calc_ant1 is what we calculated it actually transmitted at based on rssi + # pathloss and antenna gain. Allowed per-path is modified taking into account that multi + # NSS tranmission will mean that each chain should be decreased so that sum total + # of all chains is equal to the maximum allowed txpower. + allowed_per_path = cc_dbmi + logg.info("allowed_per_path: {} = cc_dbmi: {}".format(allowed_per_path,cc_dbmi)) + if (int(_nss) == 1): + diff_a1 = calc_ant1 - cc_dbmi + logg.info("(Offset 1) diff_a1 (): {} = calc_ant1: {} - allowed_per_path: {}".format(diff_a1, calc_ant1, allowed_per_path)) + + if (abs(diff_a1) > pfrange): + pf = 0 + if (int(_nss) == 2): + # NSS of 2 means each chain should transmit at 1/2 total power, thus the '- 3' + allowed_per_path = cc_dbmi - 3 + logg.info("allowed_per_path: {} = cc_dbmi: {} - 3".format(allowed_per_path,cc_dbmi)) + + diff_a1 = calc_ant1 - allowed_per_path + logg.info("(Offset 1) diff_a1: {} = calc_ant1: {} - allowed_per_path: {}".format(diff_a1, calc_ant1, allowed_per_path)) + + diff_a2 = calc_ant2 - allowed_per_path + logg.info("(Offset 2) diff_a2: {} = calc_ant2: {} - allowed_per_path: {}".format(diff_a2, calc_ant2, allowed_per_path)) + + if ((abs(diff_a1) > pfrange) or + (abs(diff_a2) > pfrange)): + pf = 0 + if (int(_nss) == 3): + # NSS of 3 means each chain should transmit at 1/3 total power, thus the '- 5' + allowed_per_path = cc_dbmi - 5 + logg.info("allowed_per_path: {} = cc_dbmi: {} - 5".format(allowed_per_path,cc_dbmi)) + + diff_a1 = calc_ant1 - allowed_per_path + logg.info("(Offset 1) diff_a1: {} = calc_ant1: {} - allowed_per_path: {}".format(diff_a1, calc_ant1, allowed_per_path)) + + diff_a2 = calc_ant2 - allowed_per_path + logg.info("(Offset 2) diff_a2: {} = calc_ant2: {} - allowed_per_path: {}".format(diff_a2, calc_ant2, allowed_per_path)) + + diff_a3 = calc_ant3 - allowed_per_path + logg.info("(Offset 3) diff_a3: {} = calc_ant3: {} - allowed_per_path: {}".format(diff_a3, calc_ant3, allowed_per_path)) + + if ((abs(diff_a1) > pfrange) or + (abs(diff_a2) > pfrange) or + (abs(diff_a3) > pfrange)): + pf = 0 + if (int(_nss) == 4): + # NSS of 4 means each chain should transmit at 1/4 total power, thus the '- 6' + allowed_per_path = cc_dbmi - 6 + logg.info("allowed_per_path: {} = cc_dbmi: {} - 6".format(allowed_per_path,cc_dbmi)) + + diff_a1 = calc_ant1 - allowed_per_path + logg.info("(Offset 1) diff_a1: {} = calc_ant1: {} - allowed_per_path: {}".format(diff_a1, calc_ant1, allowed_per_path)) + + diff_a2 = calc_ant2 - allowed_per_path + logg.info("(Offset 2) diff_a2: {} = calc_ant2: {} - allowed_per_path: {}".format(diff_a2, calc_ant2, allowed_per_path)) + + diff_a3 = calc_ant3 - allowed_per_path + logg.info("(Offset 3) diff_a3: {} = calc_ant3: {} - allowed_per_path: {}".format(diff_a3, calc_ant3, allowed_per_path)) + + diff_a4 = calc_ant4 - allowed_per_path + logg.info("(Offset 4) diff_a4: {} = calc_ant4: {} - allowed_per_path: {}".format(diff_a4, calc_ant4, allowed_per_path)) + + # Read AP to determine if there are less chains or spatial steams then expected + # Thus provide a passing result + failed_low = 0 + # least = 0 + if (diff_a1 < -pfrange): + failed_low += 1 + #least = diff_a1 #leave in code if want to move to least + if (diff_a2 < -pfrange): + failed_low += 1 + #least = min(least, diff_a2) + if (diff_a3 < -pfrange): + failed_low += 1 + #least = min(least, diff_a3) + if (diff_a4 < -pfrange): + failed_low += 1 + #least = min(least, diff_a4) + + failed_low_threshold = 0 + # + # + # If the ap dictionary is set the read the AP to see the number + # of spatial streams used. For tx power 1 the AP may determine to use + # fewer spatial streams + # + # + P1 = None + T1 = None + P2 = None + T2 = None + P3 = None + T3 = None + P4 = None + T4 = None + N_ANT = None + DAA_Pwr = None + DAA_N_TX = None + DAA_Total_pwr = None + if(bool(ap_dict)): + logg.info("ap_dict {}".format(ap_dict)) + logg.info("Read AP ap_scheme: {} ap_ip: {} ap_port: {} ap_user: {} ap_pw: {}".format(ap_dict['ap_scheme'],ap_dict['ap_ip'],ap_dict["ap_port"], + ap_dict['ap_user'],ap_dict['ap_pw'])) + logg.info("####################################################################################################") + logg.info("# READ AP POWERCFG") + logg.info("####################################################################################################") + + try: + logg.info("ap_ctl.py: read AP power information") + ap_info= subprocess.run(["./ap_ctl.py", "--scheme", ap_dict['ap_scheme'], "--prompt", ap_dict['ap_prompt'],"--dest", ap_dict['ap_ip'], "--port", ap_dict["ap_port"], + "--user", ap_dict['ap_user'], "--passwd", ap_dict['ap_pw'],"--action", "powercfg"],stdout=subprocess.PIPE) + try: + pss = ap_info.stdout.decode('utf-8', 'ignore') + except: + logg.info("ap_info was of type NoneType will set pss empty") + pss = "empty" + + except subprocess.CalledProcessError as process_error: + logg.info("####################################################################################################") + logg.info("# CHECK IF AP HAS TELNET CONNECTION ALREADY ACTIVE") + logg.info("####################################################################################################") + + logg.info("####################################################################################################") + logg.info("# Unable to commicate to AP error code: {} output {}".format(process_error.returncode, process_error.output)) + logg.info("####################################################################################################") + #exit_test(workbook) + pss = "empty_process_error" + + logg.info(pss) + for line in pss.splitlines(): + logg.info("ap {}".format(line)) + pat = '^\s+1\s+6\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)' + m = re.search(pat, line) + if (m != None): + P1 = m.group(1) + T1 = m.group(2) + P2 = m.group(3) + T2 = m.group(4) + P3 = m.group(5) + T3 = m.group(6) + P4 = m.group(7) + T4 = m.group(8) + N_ANT = m.group(9) + DAA_Pwr = m.group(10) + DAA_N_TX = m.group(11) # number of spatial streams + DAA_Total_pwr = m.group(12) + # adjust the fail criterial based on the number of spatial streams + if DAA_N_TX == "4": + failed_low_threshold = 0 + logg.info("4 failed_low_threshold {}".format(failed_low_threshold)) + if DAA_N_TX == "3": + failed_low_threshold = 1 + logg.info("3 failed_low_threshold {}".format(failed_low_threshold)) + if DAA_N_TX == "2": + failed_low_threshold = 2 + logg.info("2 failed_low_threshold {}".format(failed_low_threshold)) + if DAA_N_TX == "1": + failed_low_threshold = 3 + logg.info("1 failed_low_threshold {}".format(failed_low_threshold)) + + + + i_tot = "P1: {} T1: {} P2: {} T2: {} P3: {} T3: {} P4: {} T4: {} N_ANT: {} DAA_Pwr: {} DAA_N_TX: {} DAA_Total_pwr: {} ".format( + P1,T1,P2,T2,P3,T3,P4,T4,N_ANT,DAA_Pwr,DAA_N_TX,DAA_Total_pwr) + print(i_tot) + logg.info(i_tot) + else: + logg.info("AP Check using regular expressions") + + + # + # The controller may adjust the number of spatial streams to allow for the + # best power values + # + # for 4 spatial streams if the AP is read and the failed threshold is met then there is a failure + # the failure will be caugh below if the range is not correct. + # range check and reading the data from the AP may be used in conjunction thus it is coded to be non-exclusive + logg.info("failed_low: {} failed_low_threshold: {}".format(failed_low,failed_low_threshold)) + if bool(ap_dict) and failed_low > failed_low_threshold: + logg.info("failed_low: {} > failed_low_threshold: {}".format(failed_low,failed_low_threshold)) + pf = 0 + + if(pf_ignore_offset != 0): + logg.info("diff_a1: {} diff_a2: {} diff_a3: {} diff_a4: {} pfrange: {} pf_ignore_offset: {}".format(diff_a1,diff_a2,diff_a3,diff_a4,pfrange,pf_ignore_offset)) + if (diff_a1 < -pfrange): + if(diff_a1 < (-pfrange - pf_ignore_offset)): + logg.info("diff_a1: {} < -pfrange: {} - pf_ignore_offset: {}".format(diff_a1, pfrange, pf_ignore_offset)) + i_tot += "PASSED diff_a1({}) < -pfrange({}) - pf_ignore_offset({}) ".format(diff_a1, pfrange, pf_ignore_offset) + logg.info("i_tot {}".format(i_tot)) + else: + logg.info("diff_a1: {} failure".format(diff_a1)) + pf = 0 + if (diff_a2 < -pfrange): + if(diff_a2 < (-pfrange - pf_ignore_offset)): + logg.info("diff_a2: {} < -pfrange: {} - pf_ignore_offset: {}".format(diff_a2, pfrange, pf_ignore_offset)) + i_tot += "PASSED diff_a2({}) < -pfrange({}) - pf_ignore_offset({}) ".format(diff_a2, pfrange, pf_ignore_offset) + logg.info("i_tot {}".format(i_tot)) + else: + logg.info("diff_a2: {} failure".format(diff_a2)) + pf = 0 + if (diff_a3 < -pfrange): + if(diff_a3 < (-pfrange - pf_ignore_offset)): + logg.info("diff_a3: {} < -pfrange: {} - pf_ignore_offset: {}".format(diff_a3, pfrange, pf_ignore_offset)) + i_tot += "PASSED diff_a3({}) < -pfrange({}) - pf_ignore_offset({}) ".format(diff_a3, pfrange, pf_ignore_offset) + logg.info("i_tot {}".format(i_tot)) + else: + logg.info("diff_a3: {} failure".format(diff_a3)) + pf = 0 + if (diff_a4 < -pfrange): + if(diff_a4 < (-pfrange - pf_ignore_offset)): + logg.info("diff_a4: {} < -pfrange: {} - pf_ignore_offset: {}".format(diff_a4, pfrange, pf_ignore_offset)) + i_tot += "PASSED diff_a4({}) < -pfrange({}) - pf_ignore_offset({}) ".format(diff_a4, pfrange, pf_ignore_offset) + logg.info("i_tot {}".format(i_tot)) + else: + logg.info("diff_a4: {} failure".format(diff_a4)) + pf = 0 + + # check for range to high + if (diff_a1 > pfrange): + pf = 0 + if (diff_a2 > pfrange): + pf = 0 + if (diff_a3 > pfrange): + pf = 0 + if (diff_a4 > pfrange): + pf = 0 + + logg.info("_nss {} allowed_per_path (AP should be transmitting at) {}".format(_nss, allowed_per_path)) + + if (pf == 0 or e_tot != ""): + pfs = "FAIL" + + time_stamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "{:.3f}".format(time.time() - (math.floor(time.time())))[1:] + ln = "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s"%( + myrd, pathloss, antenna_gain, ch, n, bw, tx, beacon_sig, sig, + antstr, _ap, _bw, _ch, _mode, _nss, _noise, _rxrate, + cc_mac, cc_ch, cc_power, cc_dbm, + calc_dbm, diff_dbm, calc_ant1, calc_ant2, calc_ant3, calc_ant4, + diff_a1, diff_a2, diff_a3, diff_a4, pfs, time_stamp + ) + + #logg.info("RESULT: %s"%(ln)) + csv.write(ln) + csv.write("\t") + + ln = "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s"%( + myrd, pathloss, antenna_gain, _ch, _nss, _bw, tx, allowed_per_path, + antstr, + calc_ant1, calc_ant2, calc_ant3, calc_ant4, + diff_a1, diff_a2, diff_a3, diff_a4, pfs, time_stamp + ) + csvs.write(ln) + csvs.write("\t") + + + col = 0 + worksheet.write(row, col, myrd, center_blue); col += 1 + worksheet.write(row, col, args.series, center_blue); col += 1 + worksheet.write(row, col, cc_ch, center_blue); col += 1 + worksheet.write(row, col, _ch, center_blue); col += 1 + worksheet.write(row, col, _nss, center_blue); col += 1 + worksheet.write(row, col, cc_bw, center_blue); col += 1 + worksheet.write(row, col, _bw, center_blue); col += 1 + worksheet.write(row, col, tx, center_tan); col += 1 + worksheet.write(row, col, allowed_per_path, center_tan); col += 1 + worksheet.write(row, col, pathloss, center_tan); col += 1 + worksheet.write(row, col, antenna_gain, center_tan); col += 1 + worksheet.write(row, col, _noise, center_tan); col += 1 + if (args.adjust_nf): + worksheet.write(row, col, rssi_adj, center_tan); col += 1 + worksheet.write(row, col, _rxrate, center_tan); col += 1 + worksheet.write(row, col, beacon_sig, center_tan); col += 1 + worksheet.write(row, col, sig, center_tan); col += 1 + for x in range(4): + if (x < int(n)): + worksheet.write(row, col, ants[x], center_peach); col += 1 + else: + worksheet.write(row, col, " ", center_peach); col += 1 + worksheet.write(row, col, calc_ant1, center_pink); col += 1 + worksheet.write(row, col, calc_ant2, center_pink); col += 1 + worksheet.write(row, col, calc_ant3, center_pink); col += 1 + worksheet.write(row, col, calc_ant4, center_pink); col += 1 + + if (diff_a1 != "" and abs(diff_a1) > pfrange): + worksheet.write(row, col, diff_a1, center_yel_red); col += 1 + else: + worksheet.write(row, col, diff_a1, center_yel); col += 1 + if (diff_a2 != "" and abs(diff_a2) > pfrange): + worksheet.write(row, col, diff_a2, center_yel_red); col += 1 + else: + worksheet.write(row, col, diff_a2, center_yel); col += 1 + if (diff_a3 != "" and abs(diff_a3) > pfrange): + worksheet.write(row, col, diff_a3, center_yel_red); col += 1 + else: + worksheet.write(row, col, diff_a3, center_yel); col += 1 + if (diff_a4 != "" and abs(diff_a4) > pfrange): + worksheet.write(row, col, diff_a4, center_yel_red); col += 1 + else: + worksheet.write(row, col, diff_a4, center_yel); col += 1 + worksheet.write(row, col, cc_dbmi, center_blue); col +=1 + worksheet.write(row, col, calc_dbm_beacon, center_blue); col +=1 + worksheet.write(row, col, diff_dbm_beacon, center_blue); col +=1 + worksheet.write(row, col, calc_dbm, center_blue); col +=1 + worksheet.write(row, col, diff_dbm, center_blue); col +=1 + + if (pfs == "FAIL"): + worksheet.write(row, col, pfs, red); col += 1 + else: + worksheet.write(row, col, pfs, green); col += 1 + worksheet.write(row, col, time_stamp, green); col += 1 + if (_bw != bw): + err = "ERROR: Requested bandwidth: %s != station's reported bandwidth: %s. "%(bw, _bw) + e_tot += err + logg.info(err) + csv.write(err) + csvs.write(err) + if (_nss != n): + err = "ERROR: Station NSS: %s != configured: %s. "%(_nss, n) + logg.info(err) + csv.write(err) + csvs.write(err) + e_tot += err + + if (e_tot == ""): + e_w_tot = e_tot + w_tot + i_tot + if(w_tot == ""): + worksheet.write(row, col, e_w_tot, green_left); col += 1 + else: + worksheet.write(row, col, e_w_tot, orange_left); col += 1 + else: + e_w_tot = e_tot + w_tot + i_tot + worksheet.write(row, col, e_w_tot, red_left); col += 1 + row += 1 + + csv.write("\n"); + csv.flush() + + csvs.write("\n"); + csvs.flush() + + # write out the data and exit on error : error takes presidence over failure + if (e_tot != ""): + if(args.exit_on_error): + logg.info("EXITING ON ERROR, exit_on_error err: {} ".format(e_tot)) + if bool(email_dicts): + for email_dict in email_dicts: + try: + logg.info("Sending Email ") + subject = "Lanforge: Error {}".format(outfile_xlsx) + body = "Lanforeg: Error: AP: {} Channel: {} NSS: {} BW: {} TX-Power {}, pfs: {} time_stamp: {} {}".format(args.ap, ch, n, bw, tx, pfs, time_stamp, outfile_xlsx) + email_out = subprocess.run(["./lf_mail.py", "--user", email_dict['user'] , "--passwd", email_dict['passwd'], "--to",email_dict['to'] , + "--subject", subject, "--body", body , "--smtp", email_dict['smtp'], "--port", email_dict['port'] ], capture_output=cap_ctl_out, check=True) + pss = email_out.stdout.decode('utf-8','ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Unable to send email smtp {} port {} error code: {} output {}".format(email_dict['smtp'],email_dict['port'],process_error.returncode, process_error.output)) + exit_test(workbook) + + + # write out the data and exit on failure + if (pf == 0): + if(args.exit_on_fail): + if(e_tot != ""): + logg.info("EXITING ON FAILURE as a result of err {}".format(e_tot)) + else: + logg.info("EXITING ON FAILURE, exit_on_fail set there was no err ") + if bool(email_dicts): + for email_dict in email_dicts: + try: + logg.info("Sending Email ") + subject = "Lanforge: Failure Found {}".format(outfile_xlsx) + body = "Lanforge: Failure Found: AP: {} Channel: {} NSS: {} BW: {} TX-Power {}, pfs: {} time_stamp: {} {}".format(args.ap,ch, n, bw, tx, pfs, time_stamp,outfile_xlsx) + email_out =subprocess.run(["./lf_mail.py", "--user", email_dict['user'] , "--passwd", email_dict['passwd'], "--to",email_dict['to'] , + "--subject", subject, "--body", body , "--smtp", email_dict['smtp'], "--port", email_dict['port'] ], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = email_out.stdout.decode('utf-8','ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Unable to send email smtp {} port {} error code: {} output {}".format(email_dict['smtp'],email_dict['port'],process_error.returncode, process_error.output)) + + exit_test(workbook) + + + if bool(email_dicts): + for email_dict in email_dicts: + try: + logg.info("Sending Email ") + subject = "Lanforge Test Compete {}".format(outfile_xlsx) + body = "Lanforeg Test Complete : AP: {} time_stamp: {} {}".format(args.ap, time_stamp, outfile_xlsx) + email_out = subprocess.run(["./lf_mail.py", "--user", email_dict['user'] , "--passwd", email_dict['passwd'], "--to",email_dict['to'] , + "--subject", subject, "--body", body , "--smtp", email_dict['smtp'], "--port", email_dict['port'] ], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = email_out.stdout.decode('utf-8','ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Unable to send email smtp {} port {} error code: {} output {}".format(email_dict['smtp'],email_dict['port'],process_error.returncode, process_error.output)) + + + workbook.close() + + # check if keeping the existing state + if(args.keep_state): + logg.info("9800/3504 flag --keep_state set thus keeping state") + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: advanced") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "advanced","--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: summary") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "summary","--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + exit_test(workbook) + else: + # Set things back to defaults + # remove the station + if(args.cleanup): + logg.info("--cleanup set Deleting all stations on radio {}".format(args.radio)) + subprocess.run(["./lf_associate_ap.pl", "--action", "del_all_phy","--port_del", args.radio], timeout=20, capture_output=True) + + # Disable AP, apply settings, enable AP + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: disable AP {}".format(args.ap)) + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "disable", "--series" , args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + if args.series == "9800": + + try: + logg.info("9800 wifi_ctl_9800_3504.py: no_wlan_wireless_tag_policy") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "no_wlan_wireless_tag_policy","--series",args.series,"--wlan", args.wlan,"--port", args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: delete_wlan") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "delete_wlan","--series",args.series, "--wlanID", args.wlanID, "--wlan", args.wlan, "--wlanSSID", args.wlanSSID, + "--port",args.port,"--prompt",args.prompt], capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: disable_network_5ghz") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "disable_network_5ghz","--series",args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: disable_network_24ghz") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "disable_network_24ghz","--series",args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + else: + try: + logg.info("3504 wifi_ctl_9800_3504.py: config 802.11a disable network") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "cmd", "--value", "config 802.11a disable network","--series" , args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + try: + logg.info("3504 wifi_ctl_9800_3504.py: config 802.11b disable network") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "cmd", "--value", "config 802.11b disable network","--port", args.port,"--series" , args.series,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + if (tx != "NA"): + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: txPower tx 1") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "txPower", "--value", "1", "--series" , args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + # NSS is set on the station earlier... + if (ch != "NA"): + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: channel 36") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "channel", "--value", "36", "--series" , args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + if (bw != "NA"): + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: bandwidth 20") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "bandwidth", "--value", "20", "--series" , args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + if args.series == "9800": + try: + logg.info("9800 wifi_ctl_9800_3504.py: enable_network_5ghz") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "enable_network_5ghz","--series",args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: enable_network_24ghz") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "enable_network_24ghz","--series",args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + try: + logg.info("9800 wifi_ctl_9800_3504.py: auto") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "auto","--series",args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + #exit_test(workbook) + + else: + try: + logg.info("3504 wifi_ctl_9800_3504.py: config 802.11a enable network") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "cmd", "--value", "config 802.11a enable network","--port", args.port, "--series" , args.series,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("3504 wifi_ctl_9800_3504.py: config 802.11b enable network") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "cmd", "--value", "config 802.11b enable network","--series" , args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: enable") + ctl_output = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "enable", "--series" , args.series,"--port", args.port,"--prompt",args.prompt],capture_output=cap_ctl_out, check=True) + if cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + # Remove LANforge traffic connection + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "do_cmd", + "--cmd", "set_cx_state all c-udp-power DELETED"], capture_output=True); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "do_cmd", + "--cmd", "rm_endp c-udp-power-A"], capture_output=True); + subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--resource", lfresource, "--action", "do_cmd", + "--cmd", "rm_endp c-udp-power-B"], capture_output=True); + + # Show controller status + try: + logg.info("9800/3504 wifi_ctl_9800_3504.py: advanced") + advanced = subprocess.run(["./wifi_ctl_9800_3504.py", "--scheme", scheme, "-d", args.dest, "-u", args.user, "-p", args.passwd, "-a", args.ap, "--band", band, + "--action", "advanced", "--series" , args.series,"--port", args.port,"--prompt",args.prompt], capture_output=True, check=True) + pss = advanced.stdout.decode('utf-8', 'ignore'); + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}".format(process_error.returncode, process_error.output)) + exit_test(workbook) + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if __name__ == '__main__': + main() + print("Summary results stored in %s, full results in %s, xlsx file in %s"%(outfile, full_outfile, outfile_xlsx)) + +#### +#### +#### + diff --git a/lanforge/lanforge-scripts/lf_verify.pl b/lanforge/lanforge-scripts/lf_verify.pl new file mode 100755 index 000000000..8208c52ee --- /dev/null +++ b/lanforge/lanforge-scripts/lf_verify.pl @@ -0,0 +1,817 @@ +#!/usr/bin/perl + +# This program is used to verify LANforge configuration sub-systems. +# It uses the LANforge::Endpoint perl module to parse output from +# the CLI. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, and custom_tcp +# across 3 ports on 2 machines. +# It then changes values and checks to see if the values set correctly. + +# Un-buffer output +$| = 1; + +# use lib prepends to @INC, so put lower priority first +# This is before run-time, so cannot condition this with normal 'if' logic. +use lib '/home/lanforge/scripts'; +use lib "./"; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; + +use Net::Telnet (); +use Getopt::Long; + +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf_num = 1; + +# This sets up connections between 2 LANforge machines +my $lf1 = 1; +my $lf2 = 2; + +# Port pairs. These are the ports that should be talking to each other. +# Ie, the third column in lf1_ports talks to the third column in lf2_ports. +my @lf1_ports = ("wlan0"); +my @lf2_ports = ("vap0000"); + +my $ports_are_connected = 0; # Connected to each other. If true, we can test some + # ethernet driver settings more precisely. + +my $manual_check = 0; # If this is true, then user input will be asked for each time + # there is a test failure. Good for manually checking the script, etc. + +my $ip_base = "172.1"; + +# Set up one CX of each of these types on each port pair. +my @cx_types = ("lf", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp"); +my @min_pkt_szs = (64, 20, 20, 1, 1); +my @max_pkt_szs = (1514, 65507, 65535, 2048, 2048); + +my $min_rate = 0; +my $max_rate = 1024000; + +my $test_mgr = "ben_tm"; + +my $report_timer = 3000; # 3 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## +my $usage = "$0 [--host {lanforge-mgr-host}] + +Example: + $0 --host localhost\n"; + +my $i = 0; + +GetOptions +( + 'host|h=s' => \$lfmgr_host, +) || die("$usage"); + + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +my $fail_msg = ""; + +# Configure our utils. +our $utils = new LANforge::Utils(); +$::utils->connect($lfmgr_host, $lfmgr_port); +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + + +my $dt = ""; + +# Do discovery to make sure the server knows about all servers. Good for when +# you just restarted all the servers and want to run the test real fast now! +$utils->doCmd("discover"); +sleep(2); +$utils->doCmd("discover"); +sleep(2); + + +initToDefaults(); + +print "Sleeping 3 seconds to let port initialization complete.\n"; +sleep(3); # Let everything settle down a bit... + +# Now, add back the test manager we will be using +$utils->doCmd("add_tm $test_mgr"); +$utils->doCmd("tm_register $test_mgr default"); #Add default user +$utils->doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + +# $utils->doCmd("log_level 63"); + +# Change all kinds of things on the ports, they should end up configured +# and ready for endpoints to be added. +testPortModification(); + +testCxModification(); + +$dt = `date`; +chomp($dt); +print "\n\n\nCompleted at: $dt\n\n"; + +if (length($fail_msg) > 0) { + print "Some sub-tests failed:\n$fail_msg\n"; +} +else { + print "All tests passed successfully.\n"; +} + +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + $utils->doCmd("rm_cx $test_mgr all"); + $utils->doCmd("rm_endp YES_ALL"); + $utils->doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub initPortsToDefault { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + $utils->doCmd("set_port $shelf_num $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + $utils->doCmd("set_port $shelf_num $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } +} + +sub testFailed { + my $msg = shift; + my $should_fail = shift; + + if (defined($should_fail) && ($should_fail eq "YES")) { + print "\nGOOD: SUB-TEST FAILED correctly: $msg\n"; + $fail_msg .= "GOOD (should fail): $msg"; + } + else { + print "\nSUB-TEST FAILED: $msg\n"; + $fail_msg .= $msg; + + if ($manual_check) { + #$utils->doCmd("log_level 7"); + print "Press enter to continue with test: "; + <STDIN>; + } + } +}#testFailed + +sub testPortModification { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $tmp_ip = $i + 2; + my $tmp_ip2 = $i + 102; + + my $cmd = "set_port $shelf_num $lf1 $tmp $ip_base.1.$tmp_ip 255.255.255.0 $ip_base.1.1 NA NA NA"; + $utils->doCmd($cmd); + sleep(1); + + my $p1 = new LANforge::Port(); + + # Tell the port what it is so it decodes the right one.. + $utils->updatePort($p1, $shelf_num, $lf1, $tmp); + + verifyPortAttributes($p1, $shelf_num, $lf1, $tmp, "$ip_base.1.$tmp_ip", "255.255.255.0", + "$ip_base.1.1"); + testMacSettability($p1); + testMtuSettability($p1); + testQlenSettability($p1); + + $cmd = "set_port $shelf_num $lf2 $tmp2 $ip_base.1.$tmp_ip2 255.255.255.0 $ip_base.1.1 NA NA NA"; + $utils->doCmd($cmd); + + my $p2 = new LANforge::Port(); + + # Tell the port what it is so it decodes the right one.. + $utils->updatePort($p2, $shelf_num, $lf2, $tmp2); + + verifyPortAttributes($p2, $shelf_num, $lf2, $tmp2, "$ip_base.1.$tmp_ip2", "255.255.255.0", + "$ip_base.1.1"); + + testMacSettability($p2); + testMtuSettability($p2); + testQlenSettability($p2); + + testRateSettability($p1, $p2); + + } +}#testPortModification + + +sub testCxModification { + my $ep = 0; + my $cx = 0; + my $i = 0; + + for ($i = 0; $i<@cx_types; $i++) { + my $j = 0; + for ($j = 0; $j<@lf1_ports; $j++) { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + my $pattern = "INCREASING"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "CUSTOM"; + } + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf_num $lf1 " . $lf1_ports[$j] . " " . $cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + $utils->doCmd($cmd); + + my $endp1 = new LANforge::Endpoint(); + $utils->updateEndpoint($endp1, $ep1); + verifyEndpointAttributes($endp1, $ep1, $shelf_num, $lf1, $lf1_ports[$j], $cx_types[$i], -1, $burst, + $min_rate, $max_rate, $szrnd, $min_pkt_szs[$i], $max_pkt_szs[$i], $pattern, + "NO"); # last is use_checksum + testEndpointSettability($endp1); + + + $cmd = "add_endp $ep2 $shelf_num $lf2 " . $lf2_ports[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + + $utils->doCmd($cmd); + + my $endp2 = new LANforge::Endpoint(); + $utils->updateEndpoint($endp2, $ep2); + verifyEndpointAttributes($endp2, $ep2, $shelf_num, $lf2, $lf2_ports[$j], $cx_types[$i], -1, $burst, + $min_rate, $max_rate, $szrnd, $min_pkt_szs[$i], $max_pkt_szs[$i], $pattern, + "NO"); # last is use_checksum + testEndpointSettability($endp2); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + $utils->doCmd($cmd); + $utils->doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + + }#for all ports + }#for all endpoint types +}#addCrossConnects + + +sub testQlenSettability { + my $p1 = shift; + testQlenSettabilityHelper($p1, "100"); + testQlenSettabilityHelper($p1, "800"); + testQlenSettabilityHelper($p1, "400"); +}#testQlenSettability + +sub testMtuSettability { + my $p1 = shift; + testMtuSettabilityHelper($p1, "1500"); + testMtuSettabilityHelper($p1, "1400"); + testMtuSettabilityHelper($p1, "1496"); + + # It is not un-usual for these to fail + testMtuSettabilityHelper($p1, "1504"); + testMtuSettabilityHelper($p1, "4096"); + testMtuSettabilityHelper($p1, "8192"); + + # This should work, set it back to defaults. + testMtuSettabilityHelper($p1, "1500"); +}#testMtuSettability + + +sub testMtuSettabilityHelper { + my $p1 = shift; + my $mtu = shift; + + $p1->mtu($mtu); + my $cmd = $p1->getSetMtuCmd(); + $utils->doCmd($cmd); + $utils->updatePort($p1); + my $p = $p1->toStringBrief(); + + if ($p1->mtu() ne $mtu) { + # Give one more chance for things to be right, maybe the driver is slow... + print (" *** WARNING: $p: Failed to set MTU correctly, tried: $mtu got: " . + $p1->mtu() . "\n Going to wait 2 seconds and update the port again..\n"); + sleep(2); + $utils->updatePort($p1); + } + + ($p1->mtu() eq $mtu) or testFailed("$p: Failed to set MTU correctly, tried: $mtu got: " . + $p1->mtu() . "\n"); +}#testMtuSettability + +sub testQlenSettabilityHelper { + my $p1 = shift; + my $val = shift; + + $p1->tx_q_len($val); + my $cmd = $p1->getSetTxQueueLenCmd(); + $utils->doCmd($cmd); + $utils->updatePort($p1); + my $p = $p1->toStringBrief(); + + if ($p1->tx_q_len() ne $val) { + # Give one more chance for things to be right, maybe the driver is slow... + print (" *** WARNING: $p: Failed to set Tx-Queue-Length correctly, tried: $val got: " . + $p1->tx_q_len() . "\n Going to wait 2 seconds and update the port again..\n"); + sleep(2); + $utils->updatePort($p1); + } + + $p1->tx_q_len() eq $val or testFailed("$p: Failed to set Tx-Queue-Length correctly, tried: $val got: " . + $p1->tx_q_len() . "\n"); +} + + +sub testRateSettability { + my $p1 = shift; + my $p2 = shift; + + testSolitaryPortSettability($p1); + testSolitaryPortSettability($p2); + + if ($ports_are_connected) { + # TODO: Test partner flags + } +}#testRateSettability + + +sub testSolitaryPortSettability { + my $p1 = shift; + + my $gbfd = ""; + my $gbhd = ""; + my $fc = ""; + if ($p1->supported_flags() =~ /1000bt/) { + $gbfd = " 1000bt-FD"; + $gbhd = " 1000bt-HD"; + } + + if ($p1->supported_flags() =~ /FLOW-CONTROL/) { + $fc = " FLOW-CONTROL"; + } + + advertTestHelper($p1, "10bt-HD 10bt-FD 100bt-HD 100bt-FD" . $gbhd . $gbfd . $fc); + advertTestHelper($p1, "10bt-HD 10bt-FD 100bt-HD 100bt-FD" . $fc); + advertTestHelper($p1, "10bt-HD 10bt-FD 100bt-HD 100bt-FD"); + advertTestHelper($p1, "10bt-HD 10bt-FD 100bt-HD"); + advertTestHelper($p1, "10bt-HD 10bt-FD"); + advertTestHelper($p1, "10bt-HD"); + advertTestHelper($p1, "100bt-FD"); + advertTestHelper($p1, "100bt-HD"); + advertTestHelper($p1, "10bt-FD"); + advertTestHelper($p1, "10bt-HD"); + advertTestHelper($p1, "10bt-HD 10bt-FD 100bt-HD 100bt-FD" . $gbhd . $gbfd . $fc); + + if ($gbfd ne "") { + fixedTestHelper($p1, "1000bt-FD"); + fixedTestHelper($p1, "1000bt-HD"); + } + fixedTestHelper($p1, "100bt-FD"); + fixedTestHelper($p1, "100bt-HD"); + fixedTestHelper($p1, "10bt-FD"); + fixedTestHelper($p1, "10bt-HD"); + + advertTestHelper($p1, "10bt-HD 10bt-FD 100bt-HD 100bt-FD" . $gbhd . $gbfd . $fc); +}#testSolitaryPortSettability + + +sub fixedTestHelper { + my $p1 = shift; + my $adv = shift; + + $p1->setRate($adv); + my $cmd = $p1->getSetRateCmd(); + $utils->doCmd($cmd); + sleep(2); # Give the hardware a chance to do what it needs. + $utils->updatePort($p1); + + if (!$p1->isCurrent($adv)) { + # Give one more chance for things to be right, maybe the driver is slow... + print (" *** WARNING: $p: Failed to set fixed rate correctly, tried: $adv got: " . + $p1->cur_flags() . "\n Going to wait 2 seconds and update the port again..\n"); + sleep(2); + $utils->updatePort($p1); + } + + my $p = $p1->toStringBrief(); + $p1->isCurrent($adv) or testFailed("$p: Failed to set fixed rate correctly, tried: $adv got: " . + $p1->cur_flags() . "\n"); +}#fixedTestHelper + + +sub advertTestHelper { + my $p1 = shift; + my $adv = shift; + + $p1->setRate("auto"); + $p1->advert_flags("$adv"); + my $cmd = $p1->getSetRateCmd(); + $utils->doCmd($cmd); + $utils->updatePort($p1); + my $p = $p1->toStringBrief(); + $p1->isAdvertising($adv) or testFailed("$p: Failed to set advertise rates correctly, tried: $adv got: " . + $p1->advert_flags() . "\n"); +}#advertTestHelper + + +sub testMacSettability { + my $port = shift; + + # Get & save the original MAC + my $mac = $port->mac_addr(); + my $sn = $port->shelf_id(); + my $cn = $port->card_id(); + my $pn = $port->port_id(); + + my $new_mac = "00:11:22:$sn$sn:$cn$cn:$pn$pn"; + $port->mac_addr($new_mac); + $cmd = $port->getSetCmd(); + $utils->doCmd($cmd); + $utils->updatePort($port); + my $p = $port->toStringBrief(); + $port->mac_addr() eq $new_mac or testFailed("$p: Could not set MAC addr, current: " . $port->mac_addr() + . " desired: $new_mac\n"); + # Set it back to original value + $port->mac_addr($mac); + $cmd = $port->getSetCmd(); + $utils->doCmd($cmd); + $utils->updatePort($port); + $p = $port->toStringBrief(); + $port->mac_addr() eq $mac or testFailed("$p: Could not set MAC addr, current: " . $port->mac_addr() + . " desired: $mac\n"); + + print "Setting MAC for Port $sn.$cn.$pn verified as correct!\n"; + +}#testMacSettability + + +sub verifyPortAttributes { + my $port = shift; + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $ip = shift; + my $msk = shift; + my $gw = shift; + + my $_sn = $port->shelf_id(); + my $_cn = $port->card_id(); + my $_pn = $port->port_id(); + my $_ipa = $port->ip_addr(); + + my $p = $port->toStringBrief(); + + $_sn eq $sn or testFailed("$p: Shelf id: $_sn does not match: $sn\n"); + $_cn eq $cn or testFailed("$p: Card id: $_cn does not match: $cn\n"); + $_pn eq $pn or testFailed("$p: Port id: $_pn does not match: $pn\n"); + $_ipa eq $ip or testFailed("$p: IP Address: $_ipa does not match: $ip\n"); + $port->ip_mask() eq $msk or testFailed("$p: IP Mask: " . $port->ip_mask() . " does not match: $msk\n"); + $port->ip_gw() eq $gw or testFailed("$p: IP Gateway: " . $port->ip_gw() . " does not match: $gw\n"); + + print "$p verified as correct!\n"; +}#verifyPortAttributes + + +sub verifyEndpointAttributes { + my $endp = shift; + my $name = shift; + my $sn = shift; + my $cn = shift; + my $pn = shift; + my $type = shift; + my $ip_port = shift; + my $bursty = shift; + my $min_rate = shift; + my $max_rate = shift; + my $szrnd = shift; + my $min_pkt_sz = shift; + my $max_pkt_sz = shift; + my $pattern = shift; + my $using_csum = shift; + my $tos = shift; + my $should_fail = shift; + + my $_sn = $endp->shelf_id(); + my $_cn = $endp->card_id(); + my $_pn = $endp->port_id(); + + my $p = $endp->toStringBrief(); + + $_sn eq $sn or testFailed("$p: Shelf id: $_sn does not match: $sn\n", $should_fail); + $_cn eq $cn or testFailed("$p: Card id: $_cn does not match: $cn\n", $should_fail); + $_pn eq $pn or testFailed("$p: Port id: $_pn does not match: $pn\n", $should_fail); + $endp->isOfType($type) or testFailed("$p: Type: " . $endp->ep_type() . " does not match: $type\n", $should_fail); + if ($ip_port ne -1) { + $endp->ip_port() eq $ip_port or testFailed("$p: IP-Port: " . $endp->ip_port() . + " does not match: $ip_port\n", $should_fail); + } + $endp->getBursty() eq $bursty or testFailed("$p: Bursty: " . $endp->getBursty() . + " does not match: $bursty\n", $should_fail); + + $endp->min_tx_rate() eq $min_rate or testFailed("$p: Min-Tx-Rate: " . $endp->min_tx_rate() . + " does not match: $min_rate\n", $should_fail); + $endp->max_tx_rate() eq $max_rate or testFailed("$p: Max-Tx-Rate: " . $endp->max_tx_rate() . + " does not match: $max_rate\n", $should_fail); + + if ($endp->isCustom()) { + ($endp->size_random() eq "NO") or testFailed("$p: Size-Random: " . $endp->size_random() . + " but we are CUSTOM!!\n", $should_fail); + } + else { + $endp->size_random() eq $szrnd or testFailed("$p: Size-Random: " . $endp->size_random() . + " does not match: $szrnd\n", $should_fail); + } + + if (! $endp->isCustom()) { + $endp->min_pkt_size() eq $min_pkt_sz or testFailed("$p: Min-Packet-Size: " . $endp->min_pkt_size() . + " does not match: $min_pkt_sz\n", $should_fail); + $endp->max_pkt_size() eq $max_pkt_sz or testFailed("$p: Max-Packet-Size: " . $endp->max_pkt_size() . + " does not match: $max_pkt_sz\n", $should_fail); + } + $endp->pattern() eq $pattern or testFailed("$p: Pattern: " . $endp->pattern() . + " does not match: $pattern\n", $should_fail); + $endp->checksum() eq $using_csum or testFailed("$p: Using-Checksum: " . $endp->checksum() . + " does not match: $using_csum\n", $should_fail); + + if (defined($tos)) { + $endp->ip_tos() eq $tos or testFailed("$p: ToS: " . $endp->ip_tos() . + " does not match: $tos\n", $should_fail); + } + +}#verifyEndpointAttributes + + +sub testEndpointSettability { + my $endp = shift; + + print "\n*****\n >>Testing " . $endp->toStringBrief() . " rate settability.\n"; + + # Test setting the rates + testEndpRateSet($endp, 0, 0, "NO"); + testEndpRateSet($endp, 2000, 2000, "NO"); + testEndpRateSet($endp, 0, 10000000, "YES"); + testEndpRateSet($endp, 65000, 128000, "YES"); + testEndpRateSet($endp, 512000, 1024000, "YES"); + testEndpRateSet($endp, 1024000, 512000, "NO", "YES"); # Should fail + testEndpRateSet($endp, 512000, 1024000, "YES"); + + if ($endp->usesIP()) { + testEndpTosSet($endp, 0x01, "YES"); + testEndpTosSet($endp, 0x02, "NO"); + testEndpTosSet($endp, 0x04, "YES"); + testEndpTosSet($endp, 0x06, "NO"); + testEndpTosSet($endp, 0x0a, "NO"); + testEndpTosSet($endp, 0x12, "NO"); + testEndpTosSet($endp, 0x02, "NO"); + testEndpTosSet($endp, "DONT-SET", "NO"); + } + + # Test payload & payload size changes + if ($endp->isCustom()) { + testEndpPldSet($endp); + } + else { + testEndpPldSizeSet($endp, 67, 1457, "YES"); + testEndpPldSizeSet($endp, 500, 457, "YES", "YES"); # should fail + testEndpPldSizeSet($endp, 500, 500, "NO"); + testEndpPldSizeSet($endp, -1, 70000000, "YES", "YES"); #should fail + testEndpPldSizeSet($endp, 128, 1500, "YES"); + } + + # TODO: Change & check stuff +}#testEndpointSettability + + +sub testEndpPldSet { + my $endp = shift; + + my $pld = "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"; + + testEndpPldSetHelper($endp, $pld); + + $pld = genRandomHex(2048); + if ($endp->ep_type() =~ /CUSTOM_ETHER/) { + testEndpPldSetHelper($endp, $pld, "YES"); # Should fail + } + else { + testEndpPldSetHelper($endp, $pld, "NO"); #Shouldn't fail + } + + $pld = genRandomHex(17); + if ($endp->ep_type() =~ /CUSTOM_ETHER/) { # Too short for ethernet, should fail. + testEndpPldSetHelper($endp, $pld, "YES"); + } + else { + testEndpPldSetHelper($endp, $pld, "NO"); + } + $pld = genRandomHex(1000); + testEndpPldSetHelper($endp, $pld); + + $pld = genRandomHex(2049); + testEndpPldSetHelper($endp, $pld, "YES"); # Payload is too long, only support 2000 bytes at this time. + + $pld = "00 11 22 gg 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"; + + testEndpPldSetHelper($endp, $pld, "YES"); # Should fail, has 'gg' in it, which is not hex! + + $pld = "zz 11 22 gg 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"; + + testEndpPldSetHelper($endp, $pld, "YES"); # Should fail, has 'zz' in it, which is not hex! + + $pld = "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee zz"; + + testEndpPldSetHelper($endp, $pld, "YES"); # Should fail, has 'zz' in it, which is not hex! + + $pld = genRandomHex(1000); + testEndpPldSetHelper($endp, $pld); + +}#testEndpPldSet + +sub testEndpPldSetHelper { + my $endp = shift; + my $pld = shift; + my $should_fail = shift; + + $endp->payload($pld); + + my $cmd = $endp->getSetPayloadCmd(); + $utils->doCmd($cmd); + + $utils->updateEndpoint($endp); + + my $p = $endp->toStringBrief(); + if ($endp->payload() ne $pld) { + if (defined($should_fail) && ($should_fail eq "YES")) { + # This is very verbose if the payload is printed out, so not going to print it all here, + # but just the lengths instead. This is also expected behaviour (notice the should_fail == YES). + testFailed("$p: Payload does not match, lengths: " . length($endp->payload()) . " " + . length($pld) . "\n", $should_fail); + } + else { + testFailed("$p: Payload:\n-:" . $endp->payload() . ":- does not match:\n-:$pld:-\n", $should_fail); + } + } + else { + if (defined($should_fail) && ($should_fail eq "YES")) { + testFailed("$p: Payload:\n-:" . $endp->payload() . ":- does match (and should have failed)\n"); + } + } +}#testEndpPldSetHelper + + +sub testEndpPldSizeSet { + my $endp = shift; + my $min = shift; + my $max = shift; + my $rand = shift; + my $should_fail = shift; + + my $en = $endp->name(); + my $sn = $endp->shelf_id(); + my $cn = $endp->card_id(); + my $pn = $endp->port_id(); + my $et = $endp->ep_type(); + my $ipp = $endp->ip_port(); + my $minrt = $endp->min_tx_rate(); + my $mxrt = $endp->max_tx_rate(); + my $pt = $endp->pattern(); + my $cs = $endp->checksum(); + my $burst = $endp->getBursty(); + my $tos = $endp->ip_tos(); + + $endp->min_pkt_size($min); + $endp->max_pkt_size($max); + $endp->setRandom($rand); + + my @cmds = $endp->getSetCmds(); + my $i; + for ($i = 0; $i<@cmds; $i++) { + $utils->doCmd($cmds[$i]); + } + + $utils->updateEndpoint($endp); + + verifyEndpointAttributes($endp, $en, $sn, $cn, $pn, $et, $ipp, $burst, $minrt, $mxrt, $rand, + $min, $max, $pt, $cs, $tos, $should_fail); +}#testEndpPldSizeSet + + +sub testEndpRateSet { + my $endp = shift; + my $min = shift; + my $max = shift; + my $burst = shift; + my $should_fail = shift; + + my $en = $endp->name(); + my $sn = $endp->shelf_id(); + my $cn = $endp->card_id(); + my $pn = $endp->port_id(); + my $et = $endp->ep_type(); + my $ipp = $endp->ip_port(); + my $tos = $endp->ip_tos(); + + my $sr = $endp->size_random(); + my $minpkt = $endp->min_pkt_size(); + my $mxpkt = $endp->max_pkt_size(); + my $pt = $endp->pattern(); + my $cs = $endp->checksum(); + + $endp->min_tx_rate($min); + $endp->max_tx_rate($max); + $endp->setBursty($burst); + + my @cmds = $endp->getSetCmds(); + my $i; + for ($i = 0; $i<@cmds; $i++) { + $utils->doCmd($cmds[$i]); + } + + $utils->updateEndpoint($endp); + + verifyEndpointAttributes($endp, $en, $sn, $cn, $pn, $et, $ipp, $burst, $min, $max, $sr, + $minpkt, $mxpkt, $pt, $cs, $tos, $should_fail); + +}#testEndpRateSet + + +sub testEndpTosSet { + my $endp = shift; + my $tos = shift; + my $should_fail = shift; + + my $en = $endp->name(); + my $sn = $endp->shelf_id(); + my $cn = $endp->card_id(); + my $pn = $endp->port_id(); + my $et = $endp->ep_type(); + my $ipp = $endp->ip_port(); + + my $sr = $endp->size_random(); + my $minpkt = $endp->min_pkt_size(); + my $mxpkt = $endp->max_pkt_size(); + my $pt = $endp->pattern(); + my $cs = $endp->checksum(); + + $endp->ip_tos($tos); + + my @cmds = $endp->getSetCmds(); + my $i; + for ($i = 0; $i<@cmds; $i++) { + $utils->doCmd($cmds[$i]); + } + + $utils->updateEndpoint($endp); + + verifyEndpointAttributes($endp, $en, $sn, $cn, $pn, $et, $ipp, $burst, $min, $max, $sr, + $minpkt, $mxpkt, $pt, $cs, $tos, $should_fail); + +}#testEndpTosSet + + +sub genRandomHex { + my $bytes = shift; + + my @tbl = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"); + my $i; + my $pld = ""; + for ($i = 0; $i<$bytes; $i++) { + $pld .= $tbl[(rand() * 1000.0) % 16] . $tbl[(rand() * 1000.0) % 16]; #Generate some hex the hard way! + if ($i != ($bytes - 1)) { + $pld .= " "; + } + } + + return $pld; +}#genRandomHex diff --git a/lanforge/lanforge-scripts/lf_voip.pl b/lanforge/lanforge-scripts/lf_voip.pl new file mode 100755 index 000000000..dca39618a --- /dev/null +++ b/lanforge/lanforge-scripts/lf_voip.pl @@ -0,0 +1,882 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up connections of types: +# lf, lf_udp, lf_tcp, custom_ether, custom_udp, and custom_tcp +# across 1 real port and manny macvlan ports on 2 machines. +# It then continously starts and stops the connections. + +# Un-buffer output +$| = 1; + +use strict; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; + +#my $lfmgr_host = "localhost"; +my $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; + +my $shelf = 1; + +# set $STARTSTOP_LOOP = 1; to start and stop ALL endpoints after script finishes +# populating the database. +my $STARTSTOP_LOOP = 0; + +# This sets up connections between 2 LANforge machines +#my $lf1 = 4; my $lf2 = 15; my @lf1_ports = (0); my @lf2_ports = (0); + +# This sets up connections between 2 ports of a single machine; +# $lf1 and $lf2 are the minor number of the EIDs of the resource/card. +#my $lf1 = 4; my $lf2 = 4; my @lf1_ports = ("eth1"); my @lf2_ports = ("eth2"); +my $lf1 = 16; my $lf2 = 16; my @lf1_ports = ("eth2"); my @lf2_ports = ("eth3"); + +my @mac3 = (1, 2); + + +my $ignore_phys_ports = 1; # If 1, just muck with mac-vlans instead. +my $ip_base = "172.1"; +my $ip_lsb = 2; +my $ip_c = 2; +my $msk = "255.255.0.0"; + +# The number of macvlans is dependant on the number for port used. +# e.g. if two ports used, eth2 and eth3 then the number of vlans +# for 120 virtual hosts would be 60 since they will be evenly distributed +# between eth2 and eth3. +my $num_macvlans = 60; +my $codec = "g729a"; # Other options: G711U, SPEEX, g726-16, g726-24, g726-32, g726-40 +#my $codec = "G711U"; # Other options: G711U, SPEEX, g726-16, g726-24, g726-32, g726-40 + +my $mn_icg = 3; # minimum intercall gap +my $mx_icg = 3; # maximum intercall gap +my $min_call_duration = 0; # set to zero for 'file' +my $max_call_duration = 0; # Set to zero for 'file' + +my $no_send_rtp = 0; # Set to zero to send RTP traffic, 1 to suppress RTP +my $use_VAD = 0; # Set to zero to not use VAD, 1 to use VAD +my $vad_timer = 500; # how much silence (ms) before we start VAD (Silence Suppression) +my $vad_fs = 3000; # how often (ms) to force an rtp pkt send even if we are in VAD +my $use_PESQ = 0; # Set to 1 for PESQ, zero for not PESQ +my $pesq_server = "127.0.0.1"; +my $pesq_server_port = 3998; +my $vproto = "SIP"; +#my $vproto = "H323"; + +# If zero, will have one of EACH of the cx types on each port. +#my $one_cx_per_port = 1; +my $one_cx_per_port = 0; + +#my @cx_types = ("", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4"); +#my @min_pkt_szs = (64, 1, 1, 1, 1, 0); +#my @max_pkt_szs = (1514, 12000, 13000, 2048, 2048, 0); + +# Layer-4 only +#my @cx_types = ("l4", "l4"); +#my @min_pkt_szs = (0, 0); +#my @max_pkt_szs = (0, 0); + +# VOIP only +#my @cx_types = ("voip", "voip", "voip", "voip"); +#my @min_pkt_szs = (0, 0, 0, 0); +#my @max_pkt_szs = (0, 0, 0, 0); +my @cx_types = ("voip"); +my @min_pkt_szs = (0); +my @max_pkt_szs = (0); + +my $peer_to_peer_voip = 1; # Don't register with SIP proxy, but just call peer to peer. + +my @src_sound_files = ("media/female_voice_8khz.wav"); + +# URL will be acted on from machine $lf1 +#my $l4_url = "http://172.1.5.75"; +my $l4_url = "http://172.1.2.3"; # not used in lf_voip.pl script but makes it work + +my $min_rate = 64000; # not used in lf_voip.pl script but makes it work +my $max_rate = 512000; # not used in lf_voip.pl script but makes it work + +my $test_mgr = "voip_tm"; + +my $loop_max = 100; +my $start_stop_iterations = 100; +my $run_for_time = 1200; # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Run for XX seconds..then will be stopped again +my $report_timer = 5000; # 8 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 45); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + initToDefaults(); + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + addMacVlans(); + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + if ($STARTSTOP_LOOP) { + my $rl = 0; + for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + sleep($stop_for_time); + }# For some amount of start_stop iterations... + }# STARTSTOP_LOOP + else { + $dt = `date`; + chomp($dt); + print "Done at: $dt\n\n"; + exit(0); + }# STARTSTOP_LOOP +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + + +sub addMacVlans { + my $i; + my $q; + + my $v; + my $lsb = 10; + my $lsb2 = 10; + + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $pnum1 = $lf1_ports[$q]; + my $pnum2 = $lf2_ports[$q]; + for ($i = 0; $i<$num_macvlans; $i++) { + + $lsb++; + if ($lsb > 99) { + $lsb2++; + $lsb = 2; + } + + my $s2 = $shelf+10; + my $c2 = $lf1+10; + my $p2 = $mac3[0] + 10; + my $mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + doCmd("add_mvlan $shelf $lf1 $pnum1 $mc"); + + if ($lf2 ne "") { + $c2 = $lf2+10; + $p2 = $mac3[1] + 10; + $mc = "00:$s2:$c2:$p2:$lsb2:$lsb"; + doCmd("add_mvlan $shelf $lf2 $pnum2 $mc"); + + # Throttle ourself so we don't over-run the poor LANforge system. + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + $since_throttle = 0; + } + } + } + } + + doCmd("probe_ports"); + + # Wait untill we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = 0; $i<$num_macvlans; $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($lf2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } + + +}#addMacVlans + + +# Wait untill the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 0); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + if (!$ignore_phys_ports) { + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + my $wait_for_phantom = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + if ($ports[$i]->isPhantom()) { + # Wait a bit..hopefully it will go away. + if ($wait_for_phantom++ < 20) { + print "Sleeping a bit, found a phantom port."; + sleep(5); + doCmd("probe_ports"); + $found_one = 1; + } + } + else { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + } + } + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = ""; + if (!$ignore_phys_ports) { + $cmd = "set_port $shelf $lf1 $tmp $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($lf2 ne "") { + $cmd = "set_port $shelf $lf2 $tmp2 $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA"; + doCmd($cmd); + $ip_lsb++; + } + } + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $tmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = 25; + my $since_throttle = 0; + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf1 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA NA 400"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$q"); + $since_throttle = 0; + } + + } + + $ip_lsb++; + + if ($lf2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $tmp2); + $pname = $p1->{dev}; + + for ($q = 0; $q<$num_macvlans; $q++) { + $cmd = "set_port $shelf $lf2 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " . + "$ip_base.1.1 NA NA NA NA 400"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pname\#$q"); + $since_throttle = 0; + } + } + }# If we have an LF-2 defined. + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + + my $voip_phone = 3000; # Start here and count on up as needed. + my $rtp_port = 10000; # Starting RTP port. + my $sound_file_idx = 0; + + my @all_ports1 = @lf1_ports; + my $j; + my $pname; + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + + my @all_ports2 = @lf2_ports; + if ($lf2 ne "") { + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + + my $q; + for ($q = 0; $q<$num_macvlans; $q++) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + } + } + + print "About to start endpoints, all_ports1:\n" . join(" ", @all_ports1) . + "\nall_ports2: " . join(" ", @all_ports2) . "\n\n"; + + if ($one_cx_per_port) { + my $j = 0; + my $cxcnt = 0; + for ($j ; $j<@all_ports1; $j++) { + my $i = $cxcnt % @cx_types; + $cxcnt++; + + my $cxt = $cx_types[$i]; + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "l4e-${ep}-TX"; + $ep++; + my $ep2 = "D_l4e-${ep}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . + "dl $l4_url /tmp/$ep1' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "l4-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + elsif ($cxt eq "voip") { + # Create VOIP endpoint + + my $ep1 = "rtpe-${ep}-TX"; + $ep++; + my $ep2 = "rtpe-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_voip_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep2 $vad_timer $vad_fs"; + doCmd($cmd); + + $cmd = "set_voip_info $ep2 NA $mn_icg $mx_icg NA $codec $vproto NA NA $min_call_duration $max_call_duration /dev/null 20000 NA $pesq_server $pesq_server_port NA"; + doCmd($cmd); + + $cmd = "set_endp_flag $ep2 SavePCM 0"; + doCmd($cmd); + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep2 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 BindSIP 1"; + doCmd($cmd); + } + if ($no_send_rtp) { + $cmd = "set_endp_flag $ep2 nosendrtp 1"; + doCmd($cmd); + } + + if ($use_VAD) { + $cmd = "set_endp_flag $ep2 VAD 1"; + doCmd($cmd); + } + + if ($use_PESQ) { + $cmd = "set_endp_flag $ep2 pesq 1"; + doCmd($cmd); + } + + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + doCmd($cmd); + + $cmd = "add_voip_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep1 $vad_timer $vad_fs"; + doCmd($cmd); + + $cmd = "set_voip_info $ep1 NA $mn_icg $mx_icg NA $codec $vproto NA NA $min_call_duration $max_call_duration /dev/null 20000 NA $pesq_server $pesq_server_port NA"; + doCmd($cmd); + + $cmd = "set_endp_flag $ep1 SavePCM 0"; + doCmd($cmd); + + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep1 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep1 BindSIP 1"; + doCmd($cmd); + } + if ($no_send_rtp) { + $cmd = "set_endp_flag $ep1 nosendrtp 1"; + doCmd($cmd); + } + if ($use_VAD) { + $cmd = "set_endp_flag $ep1 VAD 1"; + doCmd($cmd); + } + + if ($use_PESQ) { + $cmd = "set_endp_flag $ep1 pesq 1"; + doCmd($cmd); + } + + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + # Now, add the cross-connects + my $cx_name = "rtp-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "l3e-${ep}-TX"; + $ep++; + my $ep2 = "l3e-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "l3-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for all ports + }#one_cx_per_port + else { + my $j = 0; + for ($j ; $j<@all_ports1; $j++) { + for ($i = 0; $i<@cx_types; $i++) { + my $cxt = $cx_types[$i]; + + if ($cxt eq "l4") { + # Create layer-4 endpoint + + my $ep1 = "l4e-${ep}-TX"; + $ep++; + my $ep2 = "D_l4e-${ep}-TX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + # Add the dummy endpoint + my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 unmanaged 1"; + doCmd($cmd); + + $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . + "dl $l4_url /tmp/$ep1' ' '"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "l4-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + elsif ($cxt eq "voip") { + # Create VOIP endpoint + + my $ep1 = "RTPE-${ep}-TX"; + $ep++; + my $ep2 = "RTPE-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_voip_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep2 $vad_timer $vad_fs"; + doCmd($cmd); + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + $cmd = "set_voip_info $ep2 NA $mn_icg $mx_icg NA $codec $vproto NA NA $min_call_duration $max_call_duration /dev/null 20000 NA $pesq_server $pesq_server_port NA"; + doCmd($cmd); + + $cmd = "set_endp_flag $ep2 SavePCM 0"; + doCmd($cmd); + + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep2 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 BindSIP 1"; + doCmd($cmd); + } + + if ($no_send_rtp) { + $cmd = "set_endp_flag $ep2 nosendrtp 1"; + doCmd($cmd); + } + + if ($use_VAD) { + $cmd = "set_endp_flag $ep2 VAD 1"; + doCmd($cmd); + } + + if ($use_PESQ) { + $cmd = "set_endp_flag $ep2 pesq 1"; + doCmd($cmd); + } + + my $cmd = "add_voip_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep1"; + doCmd($cmd); + + $cmd = "set_voip_info $ep1 NA $mn_icg $mx_icg NA $codec $vproto NA NA $min_call_duration $max_call_duration /dev/null 20000 NA $pesq_server $pesq_server_port NA"; + doCmd($cmd); + + $cmd = "set_endp_flag $ep1 SavePCM 0"; + doCmd($cmd); + + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep1 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep1 BindSIP 1"; + doCmd($cmd); + } + if ($no_send_rtp) { + $cmd = "set_endp_flag $ep1 nosendrtp 1"; + doCmd($cmd); + } + + if ($use_VAD) { + $cmd = "set_endp_flag $ep1 VAD 1"; + doCmd($cmd); + } + + if ($use_PESQ) { + $cmd = "set_endp_flag $ep1 pesq 1"; + doCmd($cmd); + } + + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + # Now, add the cross-connects + my $cx_name = "rtp-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + else { + my $burst = "NO"; + if ($min_rate != $max_rate) { + $burst = "YES"; + } + my $szrnd = "NO"; + if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { + $szrnd = "YES"; + } + + my $pattern = "increasing"; + if ($cx_types[$i] =~ /custom/) { + $pattern = "custom"; + } + + my $ep1 = "l3e-${ep}-TX"; + $ep++; + my $ep2 = "l3e-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . + " $pattern NO"; + doCmd($cmd); + + if ($lf2 == "") { + die("Must lave lf2 defined if using non-l4 endpoints."); + } + + $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . + $max_pkt_szs[$i] . " $pattern NO"; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "l3-cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + } + }#for cx types + }#for each port + }# each cx per port + +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lf_voip_test.pl b/lanforge/lanforge-scripts/lf_voip_test.pl new file mode 100755 index 000000000..78e8496d9 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_voip_test.pl @@ -0,0 +1,1121 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# This script sets up VoIP connections. +# It then continously starts and stops the connections. + +# Un-buffer output +$| = 1; + +use strict; +use Switch; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; + +my $script_speed = 25; # Increase to issue commands faster + +my $lfmgr_host = undef; +my $lfmgr_port = 4001; + +my $shelf = 1; + +my $INIT = 1; # If true, removes all previous tests!!! + +# This sets up connections between 2 LANforge machines with minor EIDs of 4 and 15 +#my $lf1 = 4; my $lf2 = 15; my @lf1_ports = (0); my @lf2_ports = (0); + +# This sets up connections between 2 ports of a single machine; +# $lf1 and $lf2 are the minor number of the EIDs of the resource/card. +#my $lf1 = 4; my $lf2 = 4; my @lf1_ports = ("eth1"); my @lf2_ports = ("eth2"); +#my $lf1 = 1; my $lf2 = 2; my @lf1_ports = ("ad0"); my @lf2_ports = ("ad0"); +my $lf1 = 1; my $lf2 = 1; my @lf1_ports = ("eth1"); my @lf2_ports = ("eth2"); + +my $ignore_phys_ports = 0; # If 1, just muck with mac-vlans instead. +my $mac1 = 0x00; # Starting MAC address 00:m5:m4:m3:m2:m1 where: +my $mac2 = 0x00; # m5 is shelf EID, m4 is card EID, m3 is $mac3, +my $mac3 = 0x00; # m2 is $mac2 and m1 is $mac1. +my $ip_base = "10.0"; +my $ip_c = 1; +my $ip_lsb = 10; +my $msk = "255.0.0.0"; +my $default_gw = "0.0.0.0"; + +my $start_mvlan = 0; # Starting MACVLAN index for VoIP endpoints. +my $num_cxs = 70; # Overrides $num_mvlans. +my $num_mvlans = 0; # Only used if $num_cxs is zero: The number of MACVLANs per interface. + # Representing the total number of VoIP CXs. VoIP CXs are created + # across the two physical interfaces + the MACVLANs per interface. + + +my $codec = "G711U"; # Other options: G711U, g729a, SPEEX, g726-16, g726-24, g726-32, g726-40 +my $jB_size = 1; # Set jitter buffer size in 20ms packets. Default value is 8 packets, 160ms. +my $tos = 0xBE; # Set ToS/QoS for VoIP can be decimal or 0xNN for hexadecimal but values will display in decimal in the GUI. + + +my $mn_icg = 3; # minimum intercall gap +my $mx_icg = 3; # maximum intercall gap +my $min_call_duration = 0; # set to zero for 'file' +my $max_call_duration = 0; # Set to zero for 'file' + +my $start_dly = 3; # seconds to delay call start +my $start_dly_inc = 0; # seconds to increase delay by for each test + +my $no_send_rtp = 0; # Set to zero to send RTP traffic, 1 to suppress RTP +my $use_VAD = 0; # Set to zero to not use VAD, 1 to use VAD +my $vad_timer = 500; # how much silence (ms) before we start VAD (Silence Suppression) +my $vad_fs = 3000; # how often (ms) to force an rtp pkt send even if we are in VAD +my $use_PESQ = 0; # Set to 1 for PESQ, zero for not PESQ +my $pesq_server = "127.0.0.1"; +my $pesq_server_port = 3998; +my $vproto = "SIP"; # set $vproto = "H323"; for H.323 +my $bsip_port_a = "5066"; # Base SIP port for endpoint-A +my $bsip_port_b = "5067"; # Base SIP port for endpoint-B +my $i_sip_port_a = 0; # If zero, do not increment, otherwise increment by assigned value. +my $i_sip_port_b = 0; # If zero, do not increment, otherwise increment by assigned value. +my $brtp_port = "AUTO"; # Base RTP port +my $i_rtp_port = 0; # If zero, do not increment, otherwise increment by assgined value + + +# If zero, will have one of EACH of the cx types on each port. +#my $one_cx_per_port = 1; +#my $one_cx_per_port = 0; + +#my @cx_types = ("", "lf_udp", "lf_tcp", "custom_udp", "custom_tcp", "l4"); +#my @min_pkt_szs = (64, 1, 1, 1, 1, 0); +#my @max_pkt_szs = (1514, 12000, 13000, 2048, 2048, 0); + +# Layer-4 only +#my @cx_types = ("l4", "l4"); +#my @min_pkt_szs = (0, 0); +#my @max_pkt_szs = (0, 0); + +# VOIP only +#my @cx_types = ("voip", "voip", "voip", "voip"); +#my @min_pkt_szs = (0, 0, 0, 0); +#my @max_pkt_szs = (0, 0, 0, 0); +#my @cx_types = ("voip"); +#my @min_pkt_szs = (0); +#my @max_pkt_szs = (0); + +my $peer_to_peer_voip = 1; # Don't register with SIP proxy, but just call peer to peer. + +my @src_sound_files = ("media/female_voice_8khz.wav"); + +# URL will be acted on from machine $lf1 +#my $l4_url = "http://172.1.5.75"; +#my $l4_url = "http://172.1.2.3"; # not used in lf_voip.pl script but makes it work + +#my $min_rate = 64000; # not used in lf_voip.pl script but makes it work +#my $max_rate = 512000; # not used in lf_voip.pl script but makes it work + +my $test_mgr = "voip_tm"; + +my $STARTSTOP_LOOP = 0; # set $STARTSTOP_LOOP = 1; to start and stop ALL endpoints + # after script finishes populating the database. +my $loop_max = 100; +my $start_stop_iterations = 100; +my $run_for_time = 1200; # Run for XX seconds..then will be stopped again +my $stop_for_time = 5; # Run for XX seconds..then will be stopped again +my $report_timer = 5000; # 8 seconds + + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## +my $script_name = $0; + +# Parse cmd-line args +my $i; +for ($i = 0; $i<@ARGV; $i++) { + my $var = $ARGV[$i]; + if ($var =~ m/(\S+)=(.*)/) { + my $arg = $1; + my $val = $2; + handleCmdLineArg($arg, $val); + } + else { + handleCmdLineArg($var); + } +} + +if ($lfmgr_host == undef) { + print "\nYou must define a LANforge Manager!!!\n\n" + . "For example:\n" + . "./$script_name mgr=locahost\n" + . "OR\n" + . "./$script_name mgr=192.168.1.101\n\n"; + printHelp(); + exit (1); +} + +my @num = (); #make sorting by name easier :P +my $num_len = 0; +my $total = 0; + +if ($num_mvlans == 0 && $num_cxs == 0) { + printHelp(); + print "\nYou must specify a non-zero value for: num_cxs: $num_cxs OR num_mvl: $num_mvlans\n\n"; + exit (1); +} + +if ($num_cxs != 0) { + $total = $num_cxs*2; + $num_len = length ($total); + $num_mvlans = $num_cxs-1; +} +else { + $total = (($num_mvlans+1)*2); + $num_len = length ($total); +} + +my $i = 0; +switch ($num_len) { + case 1 { + for ($i=0;$i<$total;$i++) { + $num[$i] = sprintf("%01d", $i); + } + } + case 2 { + for ($i=0;$i<$total;$i++) { + $num[$i] = sprintf("%02d", $i); + } + } + case 3 { + for ($i=0;$i<$total;$i++) { + $num[$i] = sprintf("%03d", $i); + } + } + case 4 { + for ($i=0;$i<$total;$i++) { + $num[$i] = sprintf("%04d", $i); + } + } + else { print '***** Error Invalid Number of MAC VLANS i.e. >10,000 !!!!'; } +} + + +print + . "init: $INIT\n" + . "\nmanager: $lfmgr_host\n" + . "\nlf1: $lf1\nlf2: $lf2\n" + . "\nlf1_ports: " . join(" ", @lf1_ports) + . "\nlf2_ports: " . join(" ", @lf2_ports) . "\n" + . "\nstart_macvlans: $start_mvlan" + . "\nnum_mvlans: $num_mvlans\n\n"; + + +#my $junk=0; +#for ($junk=0;$junk<$total;$junk++) { +# printf "$num[$junk],"; +#} +#printf "\n"; +#exit(0); + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Timeout => 45, + Prompt => '/default\@btbits\>\>/'); + + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 45); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + if ($INIT) { + initToDefaults(); + } + #exit(0); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + if ($num_mvlans != 0) { + addMacVlans(); + # Add some IP addresses to the ports + initIpAddresses(); + } + + # Add our endpoints + addCrossConnects(); + + if ($STARTSTOP_LOOP) { + my $rl = 0; + for ($rl = 0; $rl<$start_stop_iterations; $rl++) { + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all RUNNING"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + } + } + + print "Done starting endpoints...sleeping $run_for_time seconds.\n"; + sleep($run_for_time); + + # Now, stop them... + + if (($rl % 2) == 0) { + doCmd("set_cx_state $test_mgr all STOPPED"); + } + else { + # Do one at a time + my $q = 0; + for ($q = 0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"; + doCmd($cmd); + } + } + sleep($stop_for_time); + }# For some amount of start_stop iterations... + }# STARTSTOP_LOOP + else { + $dt = `date`; + chomp($dt); + print "Done at: $dt\n\n"; + exit(0); + }# STARTSTOP_LOOP +}# for some amount of loop iterations + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); +}#initToDefaults + +my $lsb1 = sprintf("%d", $mac1); +my $lsb2 = sprintf("%d", $mac2); +my $lsb3 = sprintf("%d", $mac3); + +# Return a unique MAC address using last 3 octets +sub getNextMac { + $lsb1++; + if ($lsb1 > 255) { + $lsb2++; + $lsb1 = 0; + if ($lsb2 > 255) { + $lsb3++; + $lsb2 = 0; + if ($lsb3 > 255) { + print "*** WARNING, MAC address rolling over XX:YY:ZZ:ff:ff:ff ***\n"; + $lsb3 = 0; + } + } + } + $mac1 = sprintf("%02x", $lsb1); + $mac2 = sprintf("%02x", $lsb2); + $mac3 = sprintf("%02x", $lsb3); + return "$mac3:$mac2:$mac1"; +} # getNextMac + +sub addMacVlans { + my $i; + my $q; + my $v; + my $throttle = $script_speed; + my $since_throttle = 0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $pnum1 = $lf1_ports[$q]; + my $pnum2 = $lf2_ports[$q]; + for ($i = $start_mvlan; $i<($num_mvlans + $start_mvlan); $i++) { + + my $shlf = sprintf("%02x", $shelf); + my $card = sprintf("%02x", $lf1); + my $mac_index = getNextMac(); + my $mac_addr = "00:$shlf:$card:$mac_index"; + doCmd("add_mvlan $shelf $lf1 $pnum1 $mac_addr $i"); + + if ($lf2 ne "") { + $card = sprintf("%02x", $lf2); + $mac_index = getNextMac(); + $mac_addr = "00:$shlf:$card:$mac_index"; + doCmd("add_mvlan $shelf $lf2 $pnum2 $mac_addr $i"); + } + + # Throttle ourself so we don't over-run the poor LANforge system. + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $pnum1); + if ($lf2 ne "") { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $pnum2); + } + $since_throttle = 0; + } + } + } + + doCmd("probe_ports"); + + # Wait until we discover all the ports... + + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + + for ($i = 0; $i<$num_mvlans; $i++) { + while (1) { + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$i"); + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, "$pname2\#$i"); + } + if ($p1->isPhantom() || (($lf2 ne "") && $p2->isPhantom())) { + sleep(1); + } + else { + last; + } + } + } + } +}#addMacVlans + +# Wait untill the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 0); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + if (!$ignore_phys_ports) { + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for card: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + doCmd($ports[$i]->getDeleteCmd()); + } #fi isMacVlan + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = ""; + if (!$ignore_phys_ports) { + $cmd = "set_port $shelf $lf1 $tmp $ip_base.$ip_c.$ip_lsb $msk " . + "$default_gw NA NA NA"; + doCmd($cmd); + $ip_lsb++; + + if ($lf2 ne "") { + $cmd = "set_port $shelf $lf2 $tmp2 $ip_base.$ip_c.$ip_lsb $msk " . + "$default_gw NA NA NA"; + doCmd($cmd); + $ip_lsb++; + } + } + + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $tmp); + my $pname = $p1->{dev}; + + my $q; + my $throttle = $script_speed; + my $since_throttle = 0; + for ($q = 0; $q<$num_mvlans; $q++) { + $cmd = "set_port $shelf $lf1 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " + . "$default_gw NA NA NA NA 400"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, "$pname\#$q"); + $since_throttle = 0; + } + } + + $ip_lsb++; + + if ($lf2 ne "") { + $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $tmp2); + $pname = $p1->{dev}; + + for ($q = 0; $q<$num_mvlans; $q++) { + $cmd = "set_port $shelf $lf2 $pname\#$q $ip_base.$ip_c.$ip_lsb $msk " + . "$default_gw NA NA NA NA 400"; + doCmd($cmd); + $ip_lsb++; + + if ($ip_lsb > 250) { + $ip_c++; + $ip_lsb = 2; + } + + if ($since_throttle++ > $throttle) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, "$pname\#$q"); + $since_throttle = 0; + } + } + }# If we have an LF-2 defined. + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + + my $voip_phone = 3000; # Start here and count on up as needed. + my $rtp_port = 10000; # Starting RTP port. + my $sound_file_idx = 0; + my $sip_port_a = $bsip_port_a; + my $sip_port_b = $bsip_port_b; + + + my @all_ports1 = @lf1_ports; + my $j; + my $pname; + for ($j = 0; $j<@lf1_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$j]); + $pname = $p1->{dev}; + + my $q; + my $q_end = 0; + + if ($num_mvlans == 0) { + $q_end = $num_cxs; + } + else { + $q_end = $num_mvlans; + } + + for ($q = 0; $q<$q_end; $q++) { + @all_ports1 = (@all_ports1, "$pname\#$q"); + } + } + + my @all_ports2 = @lf2_ports; + if ($lf2 ne "") { + for ($j = 0; $j<@lf2_ports; $j++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf2, $lf2_ports[$j]); + $pname = $p1->{dev}; + + my $q; + my $q_end = 0; + + if ($num_mvlans == 0) { + $q_end = $num_cxs; + } + else { + $q_end = $num_mvlans; + } + + for ($q = 0; $q<$q_end; $q++) { + @all_ports2 = (@all_ports2, "$pname\#$q"); + } + } + } + + print "About to start endpoints, all_ports1:\n" . join(" ", @all_ports1) . + "\nall_ports2: " . join(" ", @all_ports2) . "\n\n"; + +# if ($one_cx_per_port) { +# my $j = 0; +# my $cxcnt = 0; +# for ($j ; $j<@all_ports1; $j++) { +# my $i = $cxcnt % @cx_types; +# $cxcnt++; +# +# my $cxt = $cx_types[$i]; +# if ($cxt eq "l4") { +# # Create layer-4 endpoint +# +# my $ep1 = "l4e-${ep}-TX"; +# $ep++; +# my $ep2 = "D_l4e-${ep}-TX"; +# $ep++; +# +# @endpoint_names = (@endpoint_names, $ep1, $ep2); +# +# # Add the dummy endpoint +# my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; +# doCmd($cmd); +# $cmd = "set_endp_flag $ep2 unmanaged 1"; +# doCmd($cmd); +# +# $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . +# "dl $l4_url /tmp/$ep1' ' '"; +# doCmd($cmd); +# +# # Now, add the cross-connects +# my $cx_name = "l4-cx-${cx}"; +# $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; +# doCmd($cmd); +# doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); +# +# $cx++; +# +# @cx_names = (@cx_names, $cx_name); +# } +# elsif ($cxt eq "voip") { +# # Create VOIP endpoint +# +# my $ep1 = "rtpe-${num[$ep]}-TX"; +# $ep++; +# my $ep2 = "rtpe-${num[$ep]}-RX"; +# $ep++; +# +# @endpoint_names = (@endpoint_names, $ep1, $ep2); +# +# my $cmd = "add_voip_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . +# " $voip_phone $rtp_port AUTO " . +# $src_sound_files[$sound_file_idx % @src_sound_files] . +# " " . $src_sound_files[$sound_file_idx % @src_sound_files] . +# ".$ep2 $vad_timer $vad_fs"; +# doCmd($cmd); +# +# $cmd = "set_voip_info $ep2 NA $mn_icg $mx_icg NA $codec $vproto NA NA $min_call_duration $max_call_duration /dev/null 20000 $sip_port_b $pesq_server $pesq_server_port NA $jB_size"; +# doCmd($cmd); +# +# if ($i_sip_port_b != 0) { +# $sip_port_b = $sip_port_b + $i_sip_port_b; +# } +# +# $cmd = "set_endp_flag $ep2 SavePCM 0"; +# doCmd($cmd); +# +# $cmd = "set_endp_tos $ep2 ${tos} 0"; +# doCmd($cmd); +# +# if ($peer_to_peer_voip) { +# $cmd = "set_endp_flag $ep2 DoNotRegister 1"; +# doCmd($cmd); +# $cmd = "set_endp_flag $ep2 BindSIP 1"; +# doCmd($cmd); +# } +# if ($no_send_rtp) { +# $cmd = "set_endp_flag $ep2 nosendrtp 1"; +# doCmd($cmd); +# } +# +# if ($use_VAD) { +# $cmd = "set_endp_flag $ep2 VAD 1"; +# doCmd($cmd); +# } +# +# if ($use_PESQ) { +# $cmd = "set_endp_flag $ep2 pesq 1"; +# doCmd($cmd); +# } +# +# $voip_phone++; +# $rtp_port += 2; +# $sound_file_idx++; +# +# doCmd($cmd); +# +# $cmd = "add_voip_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . +# " $voip_phone $rtp_port AUTO " . +# $src_sound_files[$sound_file_idx % @src_sound_files] . +# " " . $src_sound_files[$sound_file_idx % @src_sound_files] . +# ".$ep1 $vad_timer $vad_fs"; +# doCmd($cmd); +# +# $cmd = "set_voip_info $ep1 NA $mn_icg $mx_icg NA $codec $vproto NA NA $min_call_duration $max_call_duration /dev/null 20000 $sip_port_a $pesq_server $pesq_server_port NA $jB_size"; +# doCmd($cmd); +# +# if ($i_sip_port_a != 0) { +# $sip_port_a = $sip_port_a + $i_sip_port_a; +# } +# +# $cmd = "set_endp_flag $ep1 SavePCM 0"; +# doCmd($cmd); +# +# $cmd = "set_endp_tos $ep1 ${tos} 0"; +# doCmd($cmd); +# +# if ($peer_to_peer_voip) { +# $cmd = "set_endp_flag $ep1 DoNotRegister 1"; +# doCmd($cmd); +# $cmd = "set_endp_flag $ep1 BindSIP 1"; +# doCmd($cmd); +# } +# if ($no_send_rtp) { +# $cmd = "set_endp_flag $ep1 nosendrtp 1"; +# doCmd($cmd); +# } +# if ($use_VAD) { +# $cmd = "set_endp_flag $ep1 VAD 1"; +# doCmd($cmd); +# } +# +# if ($use_PESQ) { +# $cmd = "set_endp_flag $ep1 pesq 1"; +# doCmd($cmd); +# } +# +# $voip_phone++; +# $rtp_port += 2; +# $sound_file_idx++; +# +# # Now, add the cross-connects +# my $cx_name = "rtp-cx-${num[$cx]}"; +# $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; +# doCmd($cmd); +# doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); +# +# $cx++; +# +# @cx_names = (@cx_names, $cx_name); +# } +# else { +# my $burst = "NO"; +# if ($min_rate != $max_rate) { +# $burst = "YES"; +# } +# my $szrnd = "NO"; +# if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { +# $szrnd = "YES"; +# } +# +# my $pattern = "increasing"; +# if ($cx_types[$i] =~ /custom/) { +# $pattern = "custom"; +# } +# +# my $ep1 = "l3e-${ep}-TX"; +# $ep++; +# my $ep2 = "l3e-${ep}-RX"; +# $ep++; +# +# @endpoint_names = (@endpoint_names, $ep1, $ep2); +# +# my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . +# " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . +# " $pattern NO"; +# doCmd($cmd); +# +# +# if ($lf2 == "") { +# die("Must lave lf2 defined if using non-l4 endpoints."); +# } +# +# $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . +# " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . +# $max_pkt_szs[$i] . " $pattern NO"; +# doCmd($cmd); +# +# # Now, add the cross-connects +# my $cx_name = "l3-cx-${cx}"; +# $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; +# doCmd($cmd); +# doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); +# +# $cx++; +# +# @cx_names = (@cx_names, $cx_name); +# } +# }#for all ports +# }#one_cx_per_port +# else { + my $j = 0; + for ($j ; $j<@all_ports1; $j++) { +# for ($i = 0; $i<@cx_types; $i++) { +# my $cxt = $cx_types[$i]; +# +# if ($cxt eq "l4") { +# # Create layer-4 endpoint +# +# my $ep1 = "l4e-${ep}-TX"; +# $ep++; +# my $ep2 = "D_l4e-${ep}-TX"; +# $ep++; +# +# @endpoint_names = (@endpoint_names, $ep1, $ep2); +# +# # Add the dummy endpoint +# my $cmd = "add_l4_endp $ep2 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 0 0 ' ' ' '"; +# doCmd($cmd); +# $cmd = "set_endp_flag $ep2 unmanaged 1"; +# doCmd($cmd); +# +# $cmd = "add_l4_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " l4_generic 0 10000 100 '" . +# "dl $l4_url /tmp/$ep1' ' '"; +# doCmd($cmd); +# +# # Now, add the cross-connects +# my $cx_name = "l4-cx-${cx}"; +# $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; +# doCmd($cmd); +# doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); +# +# $cx++; +# +# @cx_names = (@cx_names, $cx_name); +# } # cx type l4 +# elsif ($cxt eq "voip") { + # Create VOIP endpoint + + my $ep1 = "RTPE-${num[$ep]}-TX"; + $ep++; + my $ep2 = "RTPE-${num[$ep]}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_voip_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep2 $vad_timer $vad_fs"; + doCmd($cmd); + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + $cmd = "set_voip_info $ep2 $start_dly $mn_icg $mx_icg NA $codec $vproto NA NA $min_call_duration $max_call_duration /dev/null 20000 $sip_port_b $pesq_server $pesq_server_port NA $jB_size"; + doCmd($cmd); + + if ($i_sip_port_b != 0) { + $sip_port_b = $sip_port_b + $i_sip_port_b; + } + + $cmd = "set_endp_flag $ep2 SavePCM 0"; + doCmd($cmd); + + $cmd = "set_endp_tos $ep2 ${tos} 0"; + doCmd($cmd); + + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep2 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep2 BindSIP 1"; + doCmd($cmd); + } + + if ($no_send_rtp) { + $cmd = "set_endp_flag $ep2 nosendrtp 1"; + doCmd($cmd); + } + + if ($use_VAD) { + $cmd = "set_endp_flag $ep2 VAD 1"; + doCmd($cmd); + } + + if ($use_PESQ) { + $cmd = "set_endp_flag $ep2 pesq 1"; + doCmd($cmd); + } + + my $cmd = "add_voip_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . + " $voip_phone $rtp_port AUTO " . + $src_sound_files[$sound_file_idx % @src_sound_files] . + " " . $src_sound_files[$sound_file_idx % @src_sound_files] . + ".$ep1"; + doCmd($cmd); + + $cmd = "set_voip_info $ep1 $start_dly $mn_icg $mx_icg NA $codec $vproto NA NA $min_call_duration $max_call_duration /dev/null 20000 $sip_port_a $pesq_server $pesq_server_port NA $jB_size"; + doCmd($cmd); + + $start_dly += $start_dly_inc; + + if ($i_sip_port_a != 0) { + $sip_port_a = $sip_port_a + $i_sip_port_a; + } + + $cmd = "set_endp_flag $ep1 SavePCM 0"; + doCmd($cmd); + + $cmd = "set_endp_tos $ep1 ${tos} 0"; + doCmd($cmd); + + if ($peer_to_peer_voip) { + $cmd = "set_endp_flag $ep1 DoNotRegister 1"; + doCmd($cmd); + $cmd = "set_endp_flag $ep1 BindSIP 1"; + doCmd($cmd); + } + if ($no_send_rtp) { + $cmd = "set_endp_flag $ep1 nosendrtp 1"; + doCmd($cmd); + } + + if ($use_VAD) { + $cmd = "set_endp_flag $ep1 VAD 1"; + doCmd($cmd); + } + + if ($use_PESQ) { + $cmd = "set_endp_flag $ep1 pesq 1"; + doCmd($cmd); + } + + $voip_phone++; + $rtp_port += 2; + $sound_file_idx++; + + # Now, add the cross-connects + my $cx_name = "rtp-cx-${num[$cx]}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); +# } +# else { +# my $burst = "NO"; +# if ($min_rate != $max_rate) { +# $burst = "YES"; +# } +# my $szrnd = "NO"; +# if ($min_pkt_szs[$i] != $max_pkt_szs[$i]) { +# $szrnd = "YES"; +# } +# +# my $pattern = "increasing"; +# if ($cx_types[$i] =~ /custom/) { +# $pattern = "custom"; +# } +# +# my $ep1 = "l3e-${ep}-TX"; +# $ep++; +# my $ep2 = "l3e-${ep}-RX"; +# $ep++; +# +# @endpoint_names = (@endpoint_names, $ep1, $ep2); +# +# my $cmd = "add_endp $ep1 $shelf $lf1 " . $all_ports1[$j] . " " . @cx_types[$i] . +# " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . $max_pkt_szs[$i] . +# " $pattern NO"; +# doCmd($cmd); +# +# if ($lf2 == "") { +# die("Must lave lf2 defined if using non-l4 endpoints."); +# } +# +# $cmd = "add_endp $ep2 $shelf $lf2 " . $all_ports2[$j] . " " . @cx_types[$i] . +# " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] . " " . +# $max_pkt_szs[$i] . " $pattern NO"; +# doCmd($cmd); +# +# # Now, add the cross-connects +# my $cx_name = "l3-cx-${cx}"; +# $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; +# doCmd($cmd); +# doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); +# +# $cx++; +# +# @cx_names = (@cx_names, $cx_name); +# } +# }#for cx types + }#for each port +# }# each cx per port +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + + my @rslt = $t->waitfor('/ \>\>RSLT:(.*)/'); + print "**************\n @rslt ................\n\n"; + #sleep(1); +} + + +sub printHelp { + print + "\n$script_name\n" + . "USAGE: mgr=[ip-of-mgr] init=[0|1] speed=25\n" + . " lf1=X lf2=Y\n" + . " lf1_ports=[\"1 2 3\"|\"eth2 eth3\"] lf2_ports=[\"4 5 6\"|\"eth4 eth5\"]\n" + . " start_mvl=X num_cxs=[N|0 num_mvl=Y]\n" + . " mac3=0xf0 mac2=0xbe mac1=0xef\n" + . " ip_base=192.168 ip_c=1 ip_lsb=2 ip_msk=255.255.0.0\n" + . "\n"; + +} + +sub handleCmdLineArg { + my $arg = $_[0]; + my $val = $_[1]; + + if ($arg eq "mgr") { + $lfmgr_host = $val; + } + elsif ($arg eq "init") { + $INIT = $val; + } + elsif ($arg eq "speed") { + $script_speed = $val; + } + elsif ($arg eq "lf1") { + $lf1 = $val; + } + elsif ($arg eq "lf2") { + $lf2 = $val; + if ($lf1 == $lf2) { + print "\nINVALID: First and second resource are the same !!!\n\n"; + exit (1); + } + } + elsif ($arg eq "mac3") { + $mac3 = $val; + } + elsif ($arg eq "mac2") { + $mac2 = $val; + } + elsif ($arg eq "mac1") { + $mac1 = $val; + } + elsif ($arg eq "ip_base") { + $ip_base = $val; + } + elsif ($arg eq "ip_lsb") { + $ip_lsb = $val; + } + elsif ($arg eq "ip_c") { + $ip_c = $val; + } + elsif ($arg eq "ip_msk") { + $msk = $val; + } + elsif ($arg eq "lf1_ports") { + @lf1_ports = split(/ /, $val); + } + elsif ($arg eq "lf2_ports") { + if ($lf2 == "" || $lf1 == $lf2) { + print "\nINVALID: Either second resource is not defined\nor first and second resource are the same !!!\n\n"; + exit (1); + } + else { + @lf2_ports = split(/ /, $val); + } + } + elsif ($arg eq "start_mvl") { + $start_mvlan = $val; + } + elsif ($arg eq "num_cxs") { + $num_cxs = $val; + } + elsif ($arg eq "num_mvl") { + $num_mvlans = $val; + } + else { + printHelp(); + exit(1); + } +} # handleCmdLineArg diff --git a/lanforge/lanforge-scripts/lf_vue_mod.sh b/lanforge/lanforge-scripts/lf_vue_mod.sh new file mode 100755 index 000000000..2d33cc5a4 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_vue_mod.sh @@ -0,0 +1,383 @@ +#!/bin/bash +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# vUE operations script actions: +# create a station +# print out stations attributes +# print list of station names +# print list of connections +# bring a station up/down +# create L3/L4 connection +# start/stop connection +# print packets rx/tx for station +# print packets rx/tx for connection +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +Q='"' +A="'" +SCRIPTDIR="/home/lanforge/scripts" + +function usage() { + echo "$0: + --create_sta --name <name> --radio <wiphyX> --security <open|wpa2> --ssid <ssid> --passphrase <wpa2 pass> --ip <DHCP | IP-address> + --delete_sta --name <name> + --show_port --name <name> + --list_ports + --list_cx + --list_l4 + --log_cli <filename> + --poll_endp --name <name> [--endp_vals tx_bytes,tx_pkts,rx_bytes,rx_pkts] + --up --name <name> + --down --name <name> + --create_cx --name <name> --sta <name> --port <name> --tcp|--udp --bps <speed-a>,<speed-b> + --create_l4 --name <name> --sta <name> --url <name> --utm <urls/10 min> --l4bps <speed> + --start_cx --name <cx name> + --start_l4 --name <l4 name> + --stop_cx --name <cx name> + --stop_l4 --name <l4 name> + --mgr <localhost or ip> + --resource <1=manager, 2+:resource> + +Examples: + $0 --list_ports --mgr 192.168.1.102 --resource 2 + $0 --create_sta --resource 2 --name sta100 --radio wiphy0 --security wpa2 --ssid jedtest --passphrase jedtest1 --ip DHCP + $0 --create_sta --resource 2 --name sta100 --radio wiphy0 --security wpa2 --ssid jedtest --passphrase jedtest1 --ip 10.1.1.10 --netmask 255.255.255.0 + $0 --delete_sta --resource 2 --name sta100 + $0 --up --name sta100 + $0 --create_cx --name tcp10 --sta sta100 --port eth1 --tcp --bps 1000000 + $0 --create_l4 --name web10 --sta sta100 --url http://www.example.com --utm 2400 --l4bps 1000000 + $0 --poll_endp --name tcp10 --endp_vals tx_pkts,rx_pkts + Use --log_cli to print out CLI commands + Use --log_cli /tmp/clilog.txt to log CLI commands to /tmp/clilog.txt +* Stations created with WPA2 and DHCP by default +" +} +## M A I N +OPTS="`getopt -o hm:r:n:ud -l help,mgr:,resource:,quiet:,\ +create_sta,delete_sta,ip:,radio:,name:,ssid:,passphrase:,security:,ip:,netmask:,\ +list_ports,list_cx,list_l4,\ +show_port,endp_vals:,poll_endp,log_cli:,\ +create_cx,port:,sta:,tcp,udp,bps:,\ +create_l4,url:,utm:,l4bps:,\ +up,down,start_cx,start_l4,stop_cx,stop_l4 \ +--name \"$0\" -- \"$@\"`" +if [ $? != 0 ]; then + usage + exit 1 +fi +#echo "OPTS: $OPTS" +eval set -- "$OPTS" + +# defualts +netmask="255.255.0.0" +resource="1" +mgr="localhost" +action="list" +ip="DHCP" +security="wpa2" +proto="lf_udp" +bps=2000000 +l4bps=0 +utm=2400 +clilog='' +quiet="--quiet yes" + +function do_firemod() { + echo "./lf_firemod.pl --mgr \"$manager\" --resource \"$resource\" $clilog $quiet $@" + ./lf_firemod.pl --mgr "$manager" --resource "$resource" $clilog $quiet $@ +} + +function do_portmod() { + echo "./lf_portmod.pl --manager \"$manager\" --card \"$resource\" $clilog $quiet $@" + ./lf_portmod.pl --manager "$manager" --card "$resource" $clilog $quiet $@ +} + +function do_associate() { + echo "./lf_associate_ap.pl --mgr \"$manager\" --resource \"$resource\" $clilog $quiet $@" + ./lf_associate_ap.pl --mgr "$manager" --resource "$resource" $clilog $quiet $@ +} + +function do_cmd() { + newcmd="" + for c in "$@"; do + newcmd="$newcmd '$c'" + done + ./lf_firemod.pl --mgr "$manager" --resource "$resource" $quiet $clilog --action do_cmd --cmd "$newcmd" +} + +while true; do + case "$1" in + --name) + name="$2" + shift 2;; + --ssid) + ssid="$2" + shift 2;; + --passphrase) + passphrase="$2" + shift 2;; + --security) + security="$2" + shift 2;; + --radio) + radio="$2" + shift 2;; + --ip) + ip="$2" + shift 2;; + --show_port) + action="show_port" + shift;; + --list_ports) + action="list_ports" + shift;; + --list_cx) + action="list_cx" + shift;; + --poll_endp) + action="poll_endp" + shift;; + --endp_vals) + endp_vals="$2" + shift 2;; + --list_l4) + action="list_l4" + shift;; + --create_sta) + action="create_sta" + shift;; + --delete_sta) + action="delete_sta" + shift;; + --sta) + sta="$2" + shift 2;; + --ip) + ip="$2" + shift 2;; + --netmask) + netmask="$2" + shift 2;; + --port) + port="$2" + shift 2;; + --up) + action="up" + shift;; + --down) + action="down" + shift;; + --create_cx) + action="create_cx" + shift;; + --tcp) + proto="lf_tcp" + shift;; + --udp) + proto="lf_udp" + shift;; + --bps) + IFS=',' read -a speeds <<< "$2" + #if [ ${#speeds} -gt 1 ] ; then + # echo "found TWO speeds: ${speeds[0]}, ${speeds[1]}" + #fi + shift 2;; + --l4bps) + l4bps="$2" + shift 2;; + --create_l4) + action="create_l4" + shift;; + --url) + url="$2" + shift 2;; + --utm) + utm="$2" + shift 2;; + --start_cx) + action="start_cx" + shift;; + --stop_cx) + action="stop_cx" + shift;; + --start_l4) + action="start_l4" + shift;; + --stop_l4) + action="stop_l4" + shift;; + --mgr) + manager="$2" + shift 2;; + --resource) + resource="$2" + shift 2;; + --log_cli) + if [[ $2 != --* ]]; then + clilog="--log_cli ${2}" + shift 2; + else + clilog="--log_cli" + shift; + fi + ;; + --quiet) + quiet="--quiet $2" + shift 2;; + --help) + usage; exit 0;; + -h) + usage; exit 0;; + --) shift; + break;; + *) echo "Unknown Option [$1]" + exit 1;; + esac +done +#echo "Action: $action Mgr $manager Resource $resource Name $name IP $ip SSID $ssid" + +if [ -z "$action" ]; then + usage + echo "No action specified." + exit 1 +fi + +if [ -z "$manager" ]; then + usage + echo "No LANforge Manager specified." + exit 1 +fi + +if [ -z "$resource" ]; then + usage + echo "No resource specified." + exit 1 +fi + +# Assume we are already in the right directory...doing a cd here breaks +# running scripts from any other directory. --Ben +#cd $SCRIPTDIR +case "$action" in + list_ports) + do_firemod --action list_ports + ;; + + list_cx) + do_firemod --action list_cx + ;; + + list_l4) + do_firemod --action list_endp | grep -v UN-MANAGED + ;; + + show_port) + [ -z "$name" ] && usage && echo "No station name specified." && exit 1 + do_portmod --port_name "$name" --show_port + ;; + + poll_endp) + [ -z "$name" ] && usage && echo "No station name specified." && exit 1 + do_firemod --action list_endp | egrep -q " \[${name}\] " + if [ $? -ne 0 ]; then + do_firemod --action list_endp + echo "Endpoint $name not found." + exit 1 + fi + echo "Press <control-c> to stop." + while true; do + if [ ! -z "$endp_vals" ]; then + do_firemod --action show_endp --endp_name "$name" --endp_vals "$endp_vals" + else + do_firemod --action show_endp --endp_name "$name" | egrep -v '>>' + fi + sleep 3 + done + ;; + + create_sta) + [ -z "$name" ] && usage && echo "No station name specified." && exit 1 + [ -z "$ssid" ] && usage && echo "No SSID specified." && exit 1 + [ -z "$security" ] && usage && echo "No WiFi security specified." && exit 1 + [ -z "$radio" ] && usage && echo "No radio specified." && exit 1 + do_associate --action add \ + --radio "$radio" --security "$security" --ssid "$ssid" --passphrase "$passphrase" \ + --first_sta "$name" --first_ip "$ip" --netmask "$netmask" --num_stations 1 + ;; + + delete_sta) + [ -z "$name" ] && usage && echo "No station name specified." && exit 1 + do_associate --action del --port_del "$name" + ;; + + create_cx) + [ -z "$name" ] && usage && echo "No connection name specified." && exit 1 + [ -z "$sta" ] && usage && echo "No station name specified." && exit 1 + [ -z "$port" ] && usage && echo "No upstream port name specified." && exit 1 + [ -z "$proto" ] && usage && echo "No connection protocol (tcp|udp) specified" && exit 1 + [ -z "${speeds[0]}" ] && usage && echo "No bitrate provided for L3 connection" && exit 1 + if [ -z "${speeds[1]}" ]; then + speeds+=(${speeds[0]}) + fi + #echo "Speed-a: ${speeds[0]} Speed-b: ${speeds[1]}" + + do_firemod \ + --action create_endp --endp_name "${name}-A" --speed "${speeds[0]}" \ + --endp_type "$proto" --port_name "$sta" || exit 1 + + do_firemod \ + --action create_endp --endp_name "${name}-B" --speed "${speeds[1]}" \ + --endp_type "$proto" --port_name "$port" || exit 1 + + do_firemod --action create_cx --cx_name "$name" --cx_endps "${name}-A,${name}-B" + ;; + + create_l4) + [ -z "$name" ] && usage && echo "No connection name specified." && exit 1 + [ -z "$url" ] && usage && echo "No URL specified." && exit 1 + [ -z "$utm" ] && usage && echo "No requests/10min rate define (--utm)." && exit 1 + + # remember do_cmd is alias for ./lf_firemod --action do_cmd --cmd + url2="dl $url /dev/null" + do_cmd add_l4_endp "$name" 1 "$resource" "$sta" l4_generic 0 10000 "$utm" "$url2" NA NA 'ca-bundle.crt' NA 0 0 60 "$l4bps" 512 ' ' 0.0.0.0 + do_cmd set_endp_tos "$name" DONT-SET 0 + do_cmd set_endp_flag "$name" L4Enable404 0 + do_cmd set_endp_report_timer "$name" 5000 + do_cmd set_endp_flag "$name" ClearPortOnStart 0 + do_cmd set_endp_quiesce "$name" 3 + do_cmd add_cx "CX_$name" default_tm "$name" + ;; + + start_cx) + [ -z "$name" ] && usage && echo "No connection name specified." && exit 1 + do_cmd set_cx_state default_tm $name RUNNING + ;; + + stop_cx) + [ -z "$name" ] && usage && echo "No connection name specified." && exit 1 + do_cmd set_cx_state default_tm $name STOPPED + ;; + + start_l4) + [ -z "$name" ] && usage && echo "No connection name specified." && exit 1 + do_cmd set_cx_state default_tm CX_$name RUNNING + ;; + + stop_l4) + [ -z "$name" ] && usage && echo "No connection name specified." && exit 1 + do_cmd set_cx_state default_tm CX_$name STOPPED + ;; + + down) + [ -z "$name" ] && usage && echo "No port name specified." && exit 1 + do_portmod --port_name $name --set_ifstate down --quiet 1 + ;; + + up) + [ -z "$name" ] && usage && echo "No port name specified." && exit 1 + do_portmod --port_name $name --set_ifstate up --quiet 1 + ;; + + *) + echo "Unimplemented Action. Please contact support@candelatech.com" + exit 1 + ;; +esac +# eof diff --git a/lanforge/lanforge-scripts/lf_wifi_dot1x.bash b/lanforge/lanforge-scripts/lf_wifi_dot1x.bash new file mode 100755 index 000000000..87d8310a6 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_wifi_dot1x.bash @@ -0,0 +1,212 @@ +#!/bin/bash + +# This script is an attempt to simplify the creation of stations and connections for said stations. +# One UDP connection will be created for each station. +# The number of stations, station SSID, encryption type and passphrase, number of packets to send, and transmit rates +# can all be configured with the below options. +# Required values are SSID, radio, and upstream port. +# Note: The upstream port will be designated to Endpoint A and station to Endpoint B. +# -m Manager IP or hostname. +# -r Resource number. +# -w Which radio to use i.e. wiphy0 wiphy1 etc. +# -n Number of stations to create. +# -s SSID for stations. +# -e Encryption type: open|wep|wpa|wpa2. +# -k Passphrase for when AP is encrypted. +# -a The upstream port to which station(s) will connect. +# -A Transmit rate from upstream port. +# -B Transmit rate from station. +# -p Number of default UDP sized packets to send. +# -h Help information. + +# Example usage: +# ./lf_wifi_fire.bash -m lf0350-1234 -r 1 -w wiphy0 -n 40 -s test-SSID -e wpa2 -k hello123 -a eth1 -A 56000 -B 2000000 -p 10000 + +set -e +#set -u +set -o pipefail + +clilog="--log_cli /tmp/clilog.txt" + +#default values +mgr=localhost +resource=1 +num_stas=40 +num_packets=Infinite +encryption=open +passphrase='[BLANK]' +rate_A=1000000 +rate_B=1000000 + +#other variables +first_sta=100 +flag_radio=false +flag_ssid=false +flag_port=false + +show_help="This script is an attempt to simplify the creation of stations and connections for said stations. +One UDP connection will be created for each station. +The number of stations, station SSID, encryption type and passphrase, number of packets to send, and transmit rates +can all be configured with the below options. +Required values are SSID, radio, and upstream port. +Note: The upstream port will be designated to Endpoint A and station to Endpoint B. +-m Manager IP or hostname. +-r Resource number. +-w Which radio to use i.e. wiphy0 wiphy1 etc. +-n Number of stations to create. +-s SSID for stations. +-e Encryption type: open|wep|wpa|wpa2. +-k Passphrase for when AP is encrypted. +-a The upstream port to which station(s) will connect. +-A Transmit rate from upstream port. +-B Transmit rate from station. +-p Number of default UDP sized packets to send. +-h Help information. + +Example usage: +./lf_wifi_fire.bash -m lf0350-1234 -r 1 -w wiphy0 -n 40 -s test-SSID -e wpa2 -k hello123 -a eth1 -A 56000 -B 2000000 -p 10000" + +while getopts 'm:r:n:p:a:e:k:w:s:A:B:h' OPTION; do + case "$OPTION" in + m) + #manager + mgr="$OPTARG" + ;; + r) + #resource + resource="$OPTARG" + ;; + n) + #num stations + num_stas="$OPTARG" + ;; + p) + #packets + num_packets="$OPTARG" + ;; + a) + #upstream port + flag_port=true + port_A="$OPTARG" + ;; + e) + #encryption + encryption="$OPTARG" + ;; + k) + #encryption passphrase + passphrase="$OPTARG" + ;; + w) + #radio + flag_radio=true + radio="$OPTARG" + ;; + s) + #ssid + flag_ssid=true + ssid="$OPTARG" + ;; + A) + #transmit rate for endpoint A + rate_A="$OPTARG" + ;; + B) + #transmit rate for endpoint B + rate_B="$OPTARG" + ;; + h) + #send help message + echo "$show_help" + exit 1 + ;; +esac +done +shift "$(($OPTIND -1))" + +#check for required getopts +if [ "$flag_ssid" = false ] || [ "$flag_radio" = false ] || [ "$flag_port" = false ] ; +then + echo "Please provide at minimum the upstream port (-a), ssid (-s), and radio (-w). Run the script with -h for more information." + exit 1 +fi + +echo "Deleting old stations." +./lf_associate_ap.pl --mgr $mgr --resource $resource $clilog --action del_all_phy --port_del $radio + +./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd $clilog \ + --cmd "nc_show_ports 1 $resource all 1" &>/dev/null + +sleep 2 + +echo "Creating new stations." +# this command is too limited to be useful for creating roaming stations at the moment +#./lf_associate_ap.pl --mgr $mgr --resource $resource $clilog \ +# --ssid $ssid --security $encryption --passphrase $passphrase \ +# --num_stations $num_stas --first_sta "sta$first_sta" \ +# --first_ip DHCP --radio $radio --action add --xsec use-dot1x + +#sleep 2 + +key_mgt="WPA-EAP" +pairwise="CCMP" +group="CCMP" +psk=NA +key=NA +ca_cert="/home/lanforge/apu2-a-ca.pem" +eap="TLS" +identity="lanforge@lanforge.com" +anon_id=NA +phase1=NA +phase2=NA +eap_passwd=NA +pin=NA +pac_file=NA +private_key="/home/lanforge/apu2-a-client.p12" +pk_passwd="lanforge" +hessid=NA +realm=NA +client_cert=NA +imsi=NA +milenage=NA +domain=NA +roaming_consortium=NA +venue_group=NA +venue_type=NA +network_type=NA +ipaddr_type_avail=NA +network_auth_type=NA +anqp_3gpp_cell_net=NA + +for n in `seq $first_sta $(($first_sta -1 + $num_stas))` ; do + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action do_cmd --cmd \ + "add_sta 1 8 wiphy0 sta$n 2181039104 'ja2a-8021x' NA [BLANK] DEFAULT NA 00:0e:8e:41:0d:47 8 NA NA NA NA NA 2181039104" + + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action do_cmd --cmd \ + "set_port 1 8 sta100 0.0.0.0 255.255.0.0 NA NA 1424969217212417 '00 0e 8e 03 32 47' NA NA BLANK 8548171820 8000 0 65535 4294967295 -1 -1 -1 -1 255 AUTO AUTO AUTO NA 0 192.168.100.1 0 NONE 9007199254740992 NONE" + + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action do_cmd --cmd \ + "set_wifi_extra 1 $resource sta$n $key_mgt $pairwise $group $psk $key $ca_cert $eap $identity $anon_id $phase1 $phase2 $eap_passwd $pin $pac_file $private_key $pk_passwd $hessid $realm $client_cert" +done + +bssid1="00:0e:8e:14:08:da" +bssid2="00:0e:8e:2e:84:19" + +#roam commands: see http://ctlocal/lfcli_ug.php#wifi_cli_cmd +for n in `seq $first_sta $(($first_sta -1 + $num_stas))` ; do + # roam to bssid1 + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action do_cmd --cmd \ + "wifi_cli_cmd 1 $resource sta$n 'roam $bssid1'" + sleep 2 # your sleep interval here should match the report timer you set in the GUI + # scanning the port before it reports will probably result in stale (previous to roam) data + # this is the point you do a 'nc_show_port 1 $resource sta$n' and grep for BSSID + # roam to bssid2 + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action do_cmd --cmd \ + "wifi_cli_cmd 1 $resource sta$n 'roam $bssid2'" + sleep 2 # your sleep interval here should match the report timer you set in the GUI + # scanning the port before it reports will probably result in stale (previous to roam) data + # this is the point you do a 'nc_show_port 1 $resource sta$n' and grep for BSSID + +done + +# diff --git a/lanforge/lanforge-scripts/lf_wifi_fire.bash b/lanforge/lanforge-scripts/lf_wifi_fire.bash new file mode 100755 index 000000000..951fba414 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_wifi_fire.bash @@ -0,0 +1,197 @@ +#!/bin/bash + +# This script is an attempt to simplify the creation of stations and connections for said stations. +# One UDP connection will be created for each station. +# The number of stations, station SSID, encryption type and passphrase, number of packets to send, and transmit rates +# can all be configured with the below options. +# Required values are SSID, radio, and upstream port. +# Note: The upstream port will be designated to Endpoint A and station to Endpoint B. +# -m Manager IP or hostname. +# -r Resource number. +# -w Which radio to use i.e. wiphy0 wiphy1 etc. +# -n Number of stations to create. +# -s SSID for stations. +# -e Encryption type: open|wep|wpa|wpa2. +# -k Passphrase for when AP is encrypted. +# -a The upstream port to which station(s) will connect. +# -A Transmit rate from upstream port. +# -B Transmit rate from station. +# -p Number of default UDP sized packets to send. +# -h Help information. + +# Example usage: +# ./lf_wifi_fire.bash -m lf0350-1234 -r 1 -w wiphy0 -n 40 -s test-SSID -e wpa2 -k hello123 -a eth1 -A 56000 -B 2000000 -p 10000 + +set -e +#set -u +set -o pipefail + +clilog="--log_cli /tmp/clilog.txt" + +#default values +mgr=localhost +resource=1 +num_stas=40 +num_packets=Infinite +encryption=open +passphrase='[BLANK]' +rate_A=1000000 +rate_B=1000000 + +#other variables +first_sta=100 +flag_radio=false +flag_ssid=false +flag_port=false + +show_help="This script is an attempt to simplify the creation of stations and connections for said stations. +One UDP connection will be created for each station. +The number of stations, station SSID, encryption type and passphrase, number of packets to send, and transmit rates +can all be configured with the below options. +Required values are SSID, radio, and upstream port. +Note: The upstream port will be designated to Endpoint A and station to Endpoint B. +-m Manager IP or hostname. +-r Resource number. +-w Which radio to use i.e. wiphy0 wiphy1 etc. +-n Number of stations to create. +-s SSID for stations. +-e Encryption type: open|wep|wpa|wpa2. +-k Passphrase for when AP is encrypted. +-a The upstream port to which station(s) will connect. +-A Transmit rate from upstream port. +-B Transmit rate from station. +-p Number of default UDP sized packets to send. +-h Help information. + +Example usage: +./lf_wifi_fire.bash -m lf0350-1234 -r 1 -w wiphy0 -n 40 -s test-SSID -e wpa2 -k hello123 -a eth1 -A 56000 -B 2000000 -p 10000" + +while getopts 'm:r:n:p:a:e:k:w:s:A:B:h' OPTION; do + case "$OPTION" in + m) + #manager + mgr="$OPTARG" + ;; + r) + #resource + resource="$OPTARG" + ;; + n) + #num stations + num_stas="$OPTARG" + ;; + p) + #packets + num_packets="$OPTARG" + ;; + a) + #upstream port + flag_port=true + port_A="$OPTARG" + ;; + e) + #encryption + encryption="$OPTARG" + ;; + k) + #encryption passphrase + passphrase="$OPTARG" + ;; + w) + #radio + flag_radio=true + radio="$OPTARG" + ;; + s) + #ssid + flag_ssid=true + ssid="$OPTARG" + ;; + A) + #transmit rate for endpoint A + rate_A="$OPTARG" + ;; + B) + #transmit rate for endpoint B + rate_B="$OPTARG" + ;; + h) + #send help message + echo "$show_help" + exit 1 + ;; +esac +done +shift "$(($OPTIND -1))" + +#check for required getopts +if [ "$flag_ssid" = false ] || [ "$flag_radio" = false ] || [ "$flag_port" = false ] ; +then + echo "Please provide at minimum the upstream port (-a), ssid (-s), and radio (-w). Run the script with -h for more information." + exit 1 +fi + +echo "Deleting old stations." +./lf_associate_ap.pl --mgr $mgr --resource $resource $clilog --action del_all_phy --port_del $radio + +./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes --action do_cmd $clilog \ + --cmd "nc_show_ports 1 $resource all 1" &>/dev/null + +sleep 2 + +echo "Creating new stations." +./lf_associate_ap.pl --mgr $mgr --resource $resource $clilog \ + --ssid $ssid --security $encryption --passphrase $passphrase \ + --num_stations $num_stas --first_sta "sta$first_sta" \ + --first_ip DHCP --radio $radio --action add + +sleep 2 + +function new_cx(){ + local cx=$1 + local portA=$2 + local portB=$3 + + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog \ + --action create_endp --endp_name "$cx-A" --port_name $portA \ + --speed $rate_A --endp_type lf_udp --report_timer 1000 + + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog \ + --action create_endp --endp_name "$cx-B" --port_name $portB \ + --speed $rate_B --endp_type lf_udp --report_timer 1000 + + ./lf_firemod.pl --mgr $mgr $clilog --action create_cx --cx_name $cx --cx_endps "$cx-A,$cx-B" --report_timer 1000 + + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --quiet yes --action do_cmd \ + --cmd "set_endp_details $cx-A NA NA NA $num_packets" &>/dev/null + + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --quiet yes --action do_cmd \ + --cmd "set_endp_details $cx-B NA NA NA $num_packets" &>/dev/null +} + +# Delete all connections and endpoints that have 'bg' in the name +echo "Deleting old connections." +cx_array=( `./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action list_cx | awk '/bg/ { print $ 2 }' | sed 's/,$//'` ) +for i in "${cx_array[@]}" + do + : + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action delete_cx --cx_name $i + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action delete_endp --endp_name "$i-A" + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action delete_endp --endp_name "$i-B" + done + +./lf_firemod.pl --mgr $mgr --resource $resource $clilog --quiet yes --action do_cmd --cmd 'nc_show_endpoints all' &>/dev/null + +sleep 5 + +echo "Creating new connections." +last_sta=$((first_sta + num_stas - 1)) +for i in `seq $first_sta $last_sta`; do + new_cx bg$i $port_A sta$i +done + +echo "All stations and connections have been created." + +/lf_firemod.pl --mgr $mgr --resource $resource $clilog --quiet yes --action do_cmd --cmd 'nc_show_endpoints all' &>/dev/null + +# diff --git a/lanforge/lanforge-scripts/lf_wifi_portal.bash b/lanforge/lanforge-scripts/lf_wifi_portal.bash new file mode 100755 index 000000000..45d2ca992 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_wifi_portal.bash @@ -0,0 +1,129 @@ +#!/bin/bash + +# This script is an attempt to simplify the creation of stations and connections for said stations. +# One UDP connection will be created for each station. +# The number of stations, station SSID, encryption type and passphrase, number of packets to send, and transmit rates +# can all be configured with the below options. +# Required values are SSID, radio, and upstream port. +# Note: The upstream port will be designated to Endpoint A and station to Endpoint B. +# -m Manager IP or hostname. +# -r Resource number. +# -w Which radio to use i.e. wiphy0 wiphy1 etc. +# -n Number of stations to create. +# -s SSID for stations. +# -e Encryption type: open|wep|wpa|wpa2. +# -k Passphrase for when AP is encrypted. +# -a The upstream port to which station(s) will connect. +# -A Transmit rate from upstream port. +# -B Transmit rate from station. +# -p Number of default UDP sized packets to send. +# -h Help information. + +# Example usage: +# ./lf_wifi_fire.bash -m lf0350-1234 -r 1 -w wiphy0 -n 40 -s test-SSID -e wpa2 -k hello123 -a eth1 -A 56000 -B 2000000 -p 10000 + +set -e +#set -u +set -o pipefail + +clilog="--log_cli /tmp/clilog.txt" + +#default values +mgr=localhost +resource=1 +num_stas=40 +num_packets=Infinite +encryption=open +passphrase='[BLANK]' +rate_A=1000000 +rate_B=1000000 + +#other variables +first_sta=100 +flag_radio=false +flag_ssid=false +flag_port=false + +show_help="This script is an attempt to simplify the creation of stations and connections for said stations. +One UDP connection will be created for each station. +The number of stations, station SSID, encryption type and passphrase, number of packets to send, and transmit rates +can all be configured with the below options. +Required values are SSID, radio, and upstream port. +Note: The upstream port will be designated to Endpoint A and station to Endpoint B. +-m Manager IP or hostname. +-r Resource number. +-w Which radio to use i.e. wiphy0 wiphy1 etc. +-n Number of stations to create. +-s SSID for stations. +-h Help information. + +Example usage: +$0 -m lf0350-1234 -r 1 -w wiphy0 -n 40 -s test-SSID" + +while getopts 'm:r:n:p:a:w:s:A:B:h' OPTION; do + case "$OPTION" in + m) + #manager + mgr="$OPTARG" + ;; + r) + #resource + resource="$OPTARG" + ;; + n) + #num stations + num_stas="$OPTARG" + ;; + w) + #radio + flag_radio=true + radio="$OPTARG" + ;; + s) + #ssid + flag_ssid=true + ssid="$OPTARG" + ;; + h) + #send help message + echo "$show_help" + exit 1 + ;; +esac +done +shift "$(($OPTIND -1))" + +#check for required getopts +if [ "$flag_ssid" = false ] || [ "$flag_radio" = false ]; then + echo "Please provide at minimum the ssid (-s), and radio (-w). Run the script with -h for more information." + exit 1 +fi + +echo "Deleting old stations." +./lf_associate_ap.pl --mgr $mgr --resource $resource $clilog --action del_all_phy --port_del $radio + +./lf_firemod.pl --mgr $mgr --resource $resource --quiet yes $clilog --action do_cmd \ + --cmd "nc_show_ports 1 $resource all 1" &>/dev/null + +sleep 2 + +echo "Creating new stations." + +for n in `seq $first_sta $(($first_sta -1 + $num_stas))` ; do + # set disable_roam, mac, blank password, default rate, mode abgnAC + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action do_cmd --cmd \ + "add_sta 1 $resource $radio sta$n 2147483648 $ssid NA [BLANK] NA DEFAULT xx:xx:xx:xx:*:xx 8 DEFAULT NA NA NA NA 2147483648" + + # sets no_dhcp_restart, no_ifup_post, use_dhcp, current_flags, dhcp, dhcp_rls, no_dhcp_conn, skip_ifup_roam + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action do_cmd --cmd \ + "set_port 1 $resource sta$n NA NA NA NA 1407377031036928 NA NA NA NA 5435834370" + + # see http://www.candelatech.com/cookbook.php?vol=cli&book=Changing+Station+POST_IFUP+Script+with+the+CLI+API + # see http://www.candelatech.com/lfcli_ug.php#set_wifi_extra2 + ./lf_firemod.pl --mgr $mgr --resource $resource $clilog --action do_cmd --cmd \ + "set_wifi_extra2 1 $resource sta$n 0 NA NA NA NA NA NA NA NA NA './portal-bot.pl --bot bp.pm --user username --pass secret --start_url http://www.google.com/ --ap_url http://10.41.3.250/ --login_form login.php --login_action login.php --logout_form logout.php'" +done + +echo "Updating stations to portal_bot..." + +# diff --git a/lanforge/lanforge-scripts/lf_wifi_rest_example.pl b/lanforge/lanforge-scripts/lf_wifi_rest_example.pl new file mode 100755 index 000000000..0ffca7ed8 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_wifi_rest_example.pl @@ -0,0 +1,597 @@ +#!/usr/bin/perl + +# This program is used to stress test the LANforge system, and may be used as +# an example for others who wish to automate LANforge tests. + +# If Net::Telnet is not found, try: yum install "perl(Net::Telnet)" + +# If the LANforge libraries are not found, make sure you are running +# from the /home/lanforge directory (or where-ever you installed LANforge) + +# Contact: support@candelatech.com if you have any questions or suggestions +# for improvement. + +# Written by Candela Technologies Inc. +# Updated by: greearb@candelatech.com +# +# +# This script creates some stations, creates some connections on them, runs them, gathers +# some upload/download results, and then stops the connections. It is a good example of +# how to call other LANforge scripts to more easily get work done. +# +# +# You may need to install perl-JSON: dnf install perl-JSON +# + + +use strict; +use warnings; +#use Carp; +# Un-buffer output +$| = 1; + +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); +use Getopt::Long; +use JSON; +use Data::Dumper; +use LANforge::GuiJson qw(GuiResponseToHash GetHeaderMap GetRecordsMatching GetFields); + +#use constant NL => "\n"; +my $lfmgr_port = 4001; +my $shelf_num = 1; +# Specify 'card' numbers for this configuration. + +my $amt_resets_sofar = 0; +my $report_timer = 1000; # 1 second report timer, hard-coded for now. + +# Default values for ye ole cmd-line args. + +my $lfmgr_host = "localhost"; +my $card = 1; +my $upstream = "eth1"; +my $port_name = ""; +my $station_count = ""; +my $radio = "wiphy0"; +our $quiet = 0; +my $amt_resets = 1; +my $min_sleep = 30; +my $max_sleep = 30; +my $fail_msg = ""; +my $manual_check = 0; +my $show_port = undef; +my @port_stats = (); +my $cmd_log_name = ""; #= "lf_portmod.txt"; +my $set_speed = "NA"; +my $wifi_mode = "NA"; +my $security = "open"; +my $passwd = "NA"; +my $ssid = "NA"; +my $ap = "NA"; +my $eap_identity = "NA"; +my $eap_passwd = "NA"; +my $cx_type = "udp"; +my $speedA = "64000"; +my $speedB = "64000"; +my $log_file = ""; +my $NOT_FOUND = "-not found-"; +my $load = ""; + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + + +my $usage = "$0 --port_name {name | number} +[--manager { network address of LANforge manager} ] +[--amt_resets { number (0 means forever) } ] +[--upstream { port-name } ] +[--radio { radio-name } ] +[--station_count { number } ] +[--cx_type { lf_tcp, lf_udp, lf_tcp6, lf_udp6 } ] +[--speedA { transmit speed for endpoint A } +[--speedB { transmit speed for endpoint B } +[--min_sleep { minimum number (seconds) to run the connections } ] +[--max_sleep { maximum number (seconds) to run the connections} ] +[--load|-L { db-name } ] +[--card { card-id } ] +[--quiet { level } ] +[--set_ifstate {up | down} ] +[--show_port [key,key,key]] + # show all port stats or just those matching /key:value/ +[--set_speed {wifi port speed, see GUI port-modify drop-down for possible values. Common + examples: 'OS Defaults', '6 Mbps a/g', '1 Stream /n', '2 Streams /n', MCS-0 (x1 15 M), MCS-10 (x2 90 M), + 'v-MCS-0 (x1 32.5 M)', 'v-1 Stream /AC', 'v-2 Streams /AC', ... } +[--wifi_mode {wifi mode: 0: AUTO, 1: 802.11a, 2: b, 3: g, 4: abg, 5: abgn, + 6: bgn 7: bg, 8: abgnAC, 9 anAC, 10 an} + # wifi-mode option is applied when --set_speed is used. +[--security {open|wep|wpa|wpa2} +[--passwd {WiFi WPA/WPA2/ password} +[--ssid {WiFi SSID} +[--ap {BSSID of AP, or 'DEFAULT' for any.} +[--eap_identity {value|[BLANK]}] +[--eap_passwd {value|[BLANK]}] +[--log_file|--log {value}] # disabled by default +[--help|-h # show help ] + +Examples: +./lf_wifi_rest_example.pl --manager localhost --card 1 --port_name sta010 --station_count 5 --ssid Lede-apu2-AC \ + --radio wiphy0 --quiet 1 --upstream eth5 --speedB 15000000 +"; + +my $i = 0; +my $log_cli = 'unset'; +my $show_help =0; +GetOptions +( + 'help|h' => \$show_help, + 'ap=s' => \$ap, + 'port_name|e=s' => \$port_name, + 'upstream=s' => \$upstream, + 'radio=s' => \$radio, + 'station_count=s' => \$station_count, + 'cx_type=s' => \$cx_type, + 'manager|m=s' => \$lfmgr_host, + 'load|L=s' => \$load, + 'quiet|q=s' => \$::quiet, + 'card|C=i' => \$card, + 'amt_resets=i' => \$amt_resets, + 'min_sleep=i' => \$min_sleep, + 'max_sleep=i' => \$max_sleep, + 'passwd=s' => \$passwd, + 'set_speed=s' => \$set_speed, + 'speedA=s' => \$speedA, + 'speedB=s' => \$speedB, + 'ssid=s' => \$ssid, + 'show_port:s' => \$show_port, + 'port_stats=s{1,}' => \@port_stats, + 'eap_identity|i=s' => \$eap_identity, + 'eap_passwd|p=s' => \$eap_passwd, + 'log_file|log=s' => \$log_file, + 'log_cli=s{0,1}' => \$log_cli, + 'wifi_mode=i' => \$wifi_mode, + ) || (print($usage) && exit(1)); + +if ($show_help) { + print $usage; + exit 0; +} + + if ($::quiet eq "0") { + $::quiet = "no"; + } + elsif ($::quiet eq "1") { + $::quiet = "yes"; + } + +# Configure logging... +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } +} + +# Open connection to the LANforge server. We use this for direct +# calls to the LANforge CLI. +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); + +$t->waitfor("/btbits\>\>/"); + +my $dt = ""; + +# Configure our utils. +our $utils = new LANforge::Utils(); +$::utils->telnet($t); +if ($::utils->isQuiet()) { + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + $::utils->cli_send_silent(0); + } + else { + $::utils->cli_send_silent(1); # Do not show input to telnet + } + $::utils->cli_rcv_silent(1); # Repress output from telnet +} +else { + $::utils->cli_send_silent(0); # Show input to telnet + $::utils->cli_rcv_silent(0); # Show output from telnet +} +$::utils->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); +if (defined $log_file && ($log_file ne "")) { + open(CMD_LOG, ">$log_file") or die("Can't open $log_file for writing...\n"); + $cmd_log_name = $log_file; + if (!$::utils->isQuiet()) { + print "History of all commands can be found in $log_file\n"; + } +} + +if (length($port_name) == 0) { + print "ERROR: Must specify port name.\n"; + die("$usage"); +} + +# Create a file in which we can store data for generating graphs and such. +my $data_fname = "_graph_data.csv"; +open(PLOT_DATA, ">$data_fname"); + + +# Load an initial DB if requested. +if ($load ne "") { + my $cli_cmd = "load $load overwrite"; + $utils->doAsyncCmd($cli_cmd); + my @rslt = $t->waitfor("/LOAD-DB: Load attempt has been completed./"); + if (!$utils->isQuiet()) { + print @rslt; + print "\n"; + } +} + +# lf_associate names ports thus, and we need to access these ports, +# so build the names here. This is one place where 'internal' changes +# to lf_associate could cause issues. +my $offset = 100; +if ($port_name =~ /^.*?(\d+)\s*$/) { + $offset = $1; +} +my @stations = (); +my @cxs = (); +my @epa = (); +my @epb = (); + +for ($i = 0; $i < $station_count; $i++) { + my $suffix = 0 + $i + $offset; + $stations[$i] = sprintf("sta%03d", $suffix); + $cxs[$i] = sprintf("cx-%03d", $suffix); + $epa[$i] = sprintf("ep-A%03d", $suffix); + $epb[$i] = sprintf("ep-B%03d", $suffix); +} + +# Create some stations using the lf_associate.pl script. +my $cmd = "./lf_associate_ap.pl --mgr $lfmgr_host --mgr_port $lfmgr_port --resource $card " . + "--action add --radio $radio --ssid $ssid --first_sta $port_name --first_ip DHCP --num_stations " . + " $station_count --passphrase \"$passwd\" --security $security --wifi_mode $wifi_mode --log_cli"; +my $rslt = run_cmd($cmd); + +if ($set_speed ne "NA") { + # lf-associate cannot set the speed currently, so use lf_portmod.pl + for ($i = 0; $i<@stations; $i++) { + $cmd = "./lf_portmod.pl --manager $lfmgr_host --card $card --port_name " . $stations[$i] . " --set_speed \"$set_speed\""; + $rslt = run_cmd($cmd); + } +} + +# Make sure stations are admin up, in case they were previously created and admin-down. +for ($i = 0; $i<@stations; $i++) { + $cmd = "./lf_portmod.pl --manager $lfmgr_host --card $card --port_name " . $stations[$i] . " --set_ifstate up"; + $rslt = run_cmd($cmd); +} + +# Create some Layer-3 connections for data generation. +for ($i = 0; $i<@stations; $i++) { + # Remove any old ones first + # A-side connection on station. + $cmd = "rm_cx all " . $cxs[$i]; + $utils->doCmd($cmd); + $cmd = "rm_endp " . $epa[$i]; + $utils->doCmd($cmd); + $cmd = "rm_endp " . $epb[$i]; + $utils->doCmd($cmd); + + # And create some new ones... + # A-side connection on station. + $cmd = "./lf_firemod.pl --mgr $lfmgr_host --mgr_port $lfmgr_port --resource $card --action create_endp --endp_name " + . $epa[$i] . " --speed $speedA --endp_type $cx_type --report_timer $report_timer --port_name " . $stations[$i]; + $rslt = run_cmd($cmd); + + # B-side connection on upstream port + $cmd = "./lf_firemod.pl --mgr $lfmgr_host --mgr_port $lfmgr_port --resource $card --action create_endp --endp_name " + . $epb[$i] . " --speed $speedB --endp_type $cx_type --report_timer $report_timer --port_name $upstream"; + $rslt = run_cmd($cmd); + + # Create a connection. + $cmd = "./lf_firemod.pl --mgr $lfmgr_host --mgr_port $lfmgr_port --resource $card --report_timer $report_timer --action create_cx --cx_name " + . $cxs[$i] . " --cx_endps " . $epa[$i] . "," . $epb[$i]; + $rslt = run_cmd($cmd); +} + +# Wait for ports to associate. +my $max_wait = 30; +for ($i = 0; ; $i++) { + my $q; + my $not_assoc = 0; + my $no_ip = 0; + for ($q = 0; $q < @stations; $q++) { + $cmd = "./lf_portmod.pl --manager $lfmgr_host --card $card -q yes --port_name " . $stations[$q] . " --show_port AP,IP"; + $rslt = run_cmd($cmd); + if ($rslt =~ /Not-Associated/) { + $not_assoc++; + } + if ($rslt =~ /IP:\s+0.0.0.0/) { + $no_ip++; + } + } + if ($not_assoc || $no_ip) { + if ($i > $max_wait) { + print("ERROR: Could not connect or get IPs for all stations, continuing...\n"); + last; + } + sleep(1); + } + else { + print("All ports are associated and have IP...\n"); + last; + } +} + + +# Start with slow speed previously set so ARP can complete easily.... +# Start our cross-connects by directly calling into LANforge CLI. +for ($i = 0; $i<@cxs; $i++) { + my $cmd = "set_cx_state all " . $cxs[$i] . " running"; + $utils->doAsyncCmd($cmd); +} + +# LANforge 5.3.7 GUI has 'cli' cmd to run arbitrary commands. So, ask it +# to refresh the endpoints and cx now that they are running. This will only +# work on a unix-like system, but one could use a real tcp connection with a bit +# more work. Assumes GUI is running with option: -cli-socket 3990 on the local machine. +$cmd = "echo -e cli show_endp\\\\ncli show_port\\\\ncli show_cx\\\\nexit | nc localhost 3990"; +#print("Running cmd: $cmd\n"); +$rslt = `$cmd`; +#print $rslt; + + +print("Sleeping 5 seconds to let connections initialize...\n"); +sleep(5); + +# Clear port counters, this will make their running averages more accurate, +# and any byte/pkt totals gathered at the end would also be more useful. +for ($i = 0; $i<@stations; $i++) { + my $cmd = "clear_port_counters $shelf_num $card " . $stations[$i]; + $utils->doCmd($cmd); +} + +$cmd = "clear_port_counters $shelf_num $card $upstream"; +$utils->doCmd($cmd); + +# Set connections to desired speed and clear counters. +for ($i = 0; $i<@cxs; $i++) { + my $cmd = "add_endp " . $epa[$i] . " NA NA NA NA NA NA NA $speedA"; + $utils->doAsyncCmd($cmd); + $cmd = "add_endp " . $epb[$i] . " NA NA NA NA NA NA NA $speedB"; + $utils->doAsyncCmd($cmd); + + $cmd = "clear_cx " . $cxs[$i]; + $utils->doAsyncCmd($cmd); +} + +my $start = time(); + +# Calculate how long to run the connections. +my $run_time = $min_sleep; +if ($max_sleep > $min_sleep) { + $run_time += int(rand($max_sleep - $min_sleep)); +} + +my $total_dl; +my $total_ul; +do { + # Gather some stats. Note that connections do not start exactly + # at the same time, nor exactly when we ask them to, so we query the + # connection for the 'running-for' time and calculate stats based on that + # for best precision. Once a connection has been running for at least 60 seconds, + # then we can just use the pre-calculated 60-second running average. + # + # For LANforge 5.3.6 and earlier, the 'RunningFor' output is in whole seconds only, + # so there will be some rounding errors when we have only been running for a few seconds. + # LANforge 5.3.7 and above will provide a fractional-second output to make the stats + # more precise. + my $total_dl = 0; + my $total_ul = 0; + my $total_dl_bps = 0; + my $total_ul_bps = 0; + + for ($i = 0; $i<@cxs; $i++) { + + # Grab stats for endpoint A. This could be made into a method call to + # decrease duplicated code. + $rslt = $utils->doAsyncCmd("nc_show_endp " . $epa[$i] . "\n"); + if ($rslt =~ /Rx Bytes:\s+Total: (\d+)\s+Time: 60s\s+Cur: (\d+)\s+(\d+)\/s/) { + my $bytes = $1; + my $cur = $2; + my $per_min = $3; + my $rf = -1; + my $avg = 0; + if (($rslt =~ /RunningFor:\s+(\d+)s/) || + ($rslt =~ /RunningFor:\s+(\d+.\d+)s/)) { + $rf = $1; + } + if ($rf < 60) { + if ($rf > 0) { + $avg = (($cur * 8) / $rf); + } + else { + $avg = 0; + } + } + else { + $avg = $per_min * 8; + } + #print("endp: " . $epa[$i] . " rx-bytes: $bytes running-for: $rf avg-bps: $avg\n"); + $total_dl += ($bytes * 8); + $total_dl_bps += $avg; + } + else { + print("ERROR: Cannot parse result: $rslt\n"); + } + + # Grab stats for endpoint B + $rslt = $utils->doAsyncCmd("nc_show_endp " . $epb[$i] . "\n"); + if ($rslt =~ /Rx Bytes:\s+Total: (\d+)\s+Time: 60s\s+Cur: (\d+)\s+(\d+)\/s/) { + my $bytes = $1; + my $cur = $2; + my $per_min = $3; + my $rf = -1; + my $avg = 0; + if (($rslt =~ /RunningFor:\s+(\d+)s/) || + ($rslt =~ /RunningFor:\s+(\d+.\d+)s/)) { + $rf = $1; + } + if ($rf < 60) { + if ($rf > 0) { + $avg = (($cur * 8) / $rf); + } + else { + $avg = 0; + } + } + else { + $avg = $per_min * 8; + } + + #print(" endp: " . $epb[$i] . " rx-bytes: $bytes running-for: $rf avg-bps: $avg\n"); + $total_ul += ($bytes * 8); + $total_ul_bps += $avg; + } + else { + print("ERROR: Cannot parse result: $rslt\n"); + } + } + + # Print and store bps data for this loop iteration. + my $now = time(); + print("$now: 60-sec running average: total-download-bps: $total_dl_bps total-upload-bps: $total_ul_bps\n"); + my $rel_t = $now - $start; + if ($rel_t) { # Skip 0 time, no data available. + # Convert to mbps + $total_dl_bps /= 1000000; + $total_ul_bps /= 1000000; + my $tot_ul_dl = $total_dl_bps + $total_ul_bps; + print PLOT_DATA "$rel_t\t$total_dl_bps\t$total_ul_bps\t$tot_ul_dl\n"; + } + + sleep(1); +} while (time() < ($start + $run_time)); + + +# Stop our cross-connects by directly calling into LANforge CLI. +for ($i = 0; $i<@cxs; $i++) { + my $cmd = "set_cx_state all " . $cxs[$i] . " stopped"; + $utils->doCmd($cmd); +} + +# Refresh GUI stats before we query JSON. +$cmd = "echo -e cli show_port\\\\ncli show_cx\\\\nexit | nc localhost 3990"; +#print("Running cmd: $cmd\n"); +$rslt = `$cmd`; + +# Wait 2 seconds for reports to come back to the GUI. +sleep(2); + +# Gather some stats using JSON. This assumes the GUI is running on the local machine on port 8080 +# [lanforge@lf0313-6477 LANforgeGUI_5.3.7]$ pwd +# /home/lanforge/LANforgeGUI_5.3.7 +# [lanforge@lf0313-6477 LANforgeGUI_5.3.7]$ ./lfclient.bash -httpd 8080 +# + +# Get a JSON dump of all rows and columns on the LANforge GUI Ports Tab. +my $gjson = new LANforge::GuiJson(); +$gjson->Request("http://localhost:8080/PortTab"); + +# Grab data for these fields for all of our ports in use in this test. +my @field_names = ("bps TX", "bps RX", "TX-Rate", "RX-Rate", "AP", "Channel", "CX Time.*"); +my @port_names = (@stations, $upstream); +my $ra_fields = $gjson->GetFields('Device', \@port_names, \@field_names); + +# And print out the JSON data on the console. This is just an example, you may +# instead wish to grab different data and graph it and/or poke it into some long-term +# storage for future comparisons. +print "Fields (".join(", ", @field_names).") from records matching Device (".join(", ", @port_names)."):\n"; +print Dumper($ra_fields); + + +# Create some gnuplot graphs. Probably there is a more clever way to do this by +# passing arguments to gnuplot, but I am faster at perl than understanding gnuplot +# at this point... + +my $gp_base = "# gnuplot script file for plotting bandwidth over time +#!/usr/bin/gnuplot +reset +set terminal png + +set xdata time +set timefmt \"\%s\" +set format x \"\%M:\%S\" + +set xlabel \"Date\" +set ylabel \"__YLABEL__\" + +set title \"__TITLE__\" +set key below +set grid +plot \"$data_fname\" using __USING__ title \"__TITLE__\" with lines +"; + +# Do text substitution of the gnuplot script for each graph. +my $script_fname = "_gnuplot_script.txt"; +open(GP, ">$script_fname") || die("Can't open $script_fname for writing...\n"); +my $gpd = $gp_base; +$gpd =~ s/__YLABEL__/Total Mbps Download/g; +$gpd =~ s/__TITLE__/Total Mbps Download over Time/g; +$gpd =~ s/__USING__/1\:2/g; + +print GP $gpd; +close(GP); +system("gnuplot \"$script_fname\" > download_bps.png"); + +open(GP, ">$script_fname") || die("Can't open $script_fname for writing...\n"); +$gpd = $gp_base; +$gpd =~ s/__YLABEL__/Total Mbps Upload/g; +$gpd =~ s/__TITLE__/Total Mbps Upload over Time/g; +$gpd =~ s/__USING__/1\:3/g; + +print GP $gpd; +close(GP); +system("gnuplot \"$script_fname\" > upload_bps.png"); + +open(GP, ">$script_fname") || die("Can't open $script_fname for writing...\n"); +$gpd = $gp_base; +$gpd =~ s/__YLABEL__/Total Mbps Upload+Download/g; +$gpd =~ s/__TITLE__/Total Mbps Upload+Download over Time/g; +$gpd =~ s/__USING__/1\:4/g; + +print GP $gpd; +close(GP); +system("gnuplot \"$script_fname\" > ul_dl_bps.png"); + +print("See gnuplot generated files: ul_dl_bps.png, download_bps.png, upload_bps.png\n"); + +close(CMD_LOG); +exit(0); + + +sub run_cmd { + my $cmd = shift; + if (!$utils->isQuiet()) { + print $cmd; + print "\n"; + } + my $rslt = `$cmd`; + if (!$utils->isQuiet()) { + print $rslt; + print "\n"; + } + return $rslt; +} diff --git a/lanforge/lanforge-scripts/lf_zlt_binary.pl b/lanforge/lanforge-scripts/lf_zlt_binary.pl new file mode 100755 index 000000000..f7a54aae3 --- /dev/null +++ b/lanforge/lanforge-scripts/lf_zlt_binary.pl @@ -0,0 +1,394 @@ +#!/usr/bin/perl + +# IMIX Zero Loss Throughput Test +# Uses a binary search algorithm to determine the throughput at which +# zero packet loss occurs for a given theoretical throughput rate +# and max allowable latency. +# +# USAGE: +# perl lf_zlt_binary.pl lf_host theoretical_rate max_latency +# binary_search_attempts endpoint_duration test_loops +# +# Example: perl lf_zlt_binary.pl 192.168.100.192 10000000 200 9 10 1 + + +# Un-buffer output +$| = 1; + +use strict; + +use Net::Telnet (); +use LANforge::Port; +use LANforge::Utils; +use LANforge::Endpoint; + +my $lfmgr_host = "$ARGV[0]"; #localhost or IP +my $lfmgr_port = 4001; + +my $shelf = 1; + +# The LANforge resources +my $lf1 = 1; +my $lf2 = 1; + +# Port pairs. These are the ports that should be talking to each other. +# Ie, lf1_ports talks to lf2_ports. +my @lf1_ports = (6); +my @lf2_ports = (7); + +my @lf1_port_ips = ("172.1.1.6"); +my @lf2_port_ips = ("172.1.1.7"); + +my @lf1_port_gws = ("172.1.1.1"); +my @lf2_port_gws = ("172.1.1.1"); + +# IMIX Type Definition for UDP +# Packet sizes are in bytes of UDP payload +my @cx_types = ("lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp", "lf_udp"); +my @min_pkt_szs = (22, 86, 214, 470, 982, 1238, 1458, 1472); +my @max_pkt_szs = (22, 86, 214, 470, 982, 1238, 1458, 1472); + +# Network Under Test Maximum Theoretical Throughput +my $min_rate = $ARGV[1]; # a rate such as 1544000 +my $max_rate = $ARGV[1]; + +# Maximum Latency in miliseconds, allowed before adjusting rate down +my $max_latency = $ARGV[2]; # in milliseconds + +my $test_mgr = "zlt_tm"; + +my $binary_search_attempts = $ARGV[3]; # number of attempts to find zlt for a given pkt size +my $pause_sec = 10; # seconds for endpoints to update +my $endp_duration = $ARGV[4]; # seconds endpoints are allowed to run, can affect results +my $loop_max = $ARGV[5]; # number of times the entire test will be run +my $report_timer = 1000; +my @endp_drops = (); + +if (@ARGV != 6) { + print("USAGE: perl lf_zlt_binary.pl lf_host theoretical_rate max_latency "); + print("binary_search_attempts endpoint_duration test_loops\n"); + print("Example: perl lf_zlt_binary.pl 192.168.100.192 10000000 200 "); + print("9 10 1\n"); + exit 1; +} + +######################################################################## +# Nothing to configure below here, most likely. +######################################################################## + +my @endpoint_names = (); #will be added to as they are created +my @cx_names = (); + +# Open connection to the LANforge server. + +my $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/'); + +my $timeout = 60; + +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => $timeout); + +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +my $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +$utils->cli_send_silent(0); # Do show input to CLI +$utils->cli_rcv_silent(0); # Repress output from CLI ?? + + +my $dt = ""; + +my $loop = 0; +for ($loop = 0; $loop<$loop_max; $loop++) { + $dt = `date`; + chomp($dt); + print "\n\n***** Starting loop: $loop at: $dt *****\n\n"; + + @endpoint_names = (); + @cx_names = (); + + initToDefaults(); + + # Now, add back the test manager we will be using + doCmd("add_tm $test_mgr"); + doCmd("tm_register $test_mgr default"); #Add default user + doCmd("tm_register $test_mgr default_gui"); #Add default GUI user + + + # Add some IP addresses to the ports + initIpAddresses(); + + # Add our endpoints + addCrossConnects(); + + print "Loop $loop: Done adding CXs.\n"; + print "Pause $pause_sec seconds for ports to update.\n"; + sleep($pause_sec); + + # Start Cross-Connects + for (my $q=0; $q<@cx_names; $q++) { + my $cmd = "set_cx_state $test_mgr " . $cx_names[$q] . " RUNNING"; + doCmd($cmd); + + my @next_adj = (int($max_rate / 2), int($max_rate / 2)); + my @current_rate = ($max_rate, $max_rate); + my $last_current_rate = 0; + my @new_rate = (0,0); + my $adj_count = 0; + my $p1 = $q+$q; + my $p2 = $p1+1; + + + for ($adj_count=0; $adj_count < $binary_search_attempts; $adj_count++) { + + doCmd("clear_endp_counters"); + print "sleep $endp_duration seconds\n"; + sleep($endp_duration); + + for (my $p=$p1; $p<=$p2; $p++) { + my $endp1 = new LANforge::Endpoint(); + $utils->updateEndpoint($endp1, $endpoint_names[$p]); + my $en1 = $endp1->rx_dropped_pkts(); + my $en2 = $endp1->port_id(); + my $en3 = $endp1->real_rx_rate(); + my $lat = $endp1->avg_latency(); + + my $i = $p-$p1; + if ( $en1 != 0 || $lat > $max_latency ) { + print "Drops! en1 is $en1 : en2 is $en2 : Real RX Rate is: $en3 : Latency: $lat\n"; + $new_rate[$i] = $current_rate[$i] - $next_adj[$i]; + } + elsif ( $current_rate[$i] < $max_rate ) { + print "No Drops! en1 is $en1 : en2 is $en2 : Real RX Rate is: $en3 : Latency: $lat\n"; + $last_current_rate = $current_rate[$i]; + $new_rate[$i] = $current_rate[$i] + $next_adj[$i]; + } + else { + print "Max Rate of $max_rate bps is too high for $min_pkt_szs[$q] byte packet size.\n"; + $adj_count = $binary_search_attempts; + last; + } + + $next_adj[$i] = int($next_adj[$i] / 2); + $current_rate[$i] = $new_rate[$i]; + + } #for $endpoint_names + + # set both endpoints to zero rate to quiesce + my $cmd = "add_endp " . $endpoint_names[$p1] . " $shelf $lf1 " . " NA lf_udp " . + " -1 NO 0 0 NA NA NA NA "; + doCmd($cmd); + $cmd = "add_endp " . $endpoint_names[$p2] . " $shelf $lf1 " . " NA lf_udp " . + " -1 NO 0 0 NA NA NA NA "; + doCmd($cmd); + sleep(3); + + # set both endpoints to new rate + $cmd = "add_endp " . $endpoint_names[$p1] . " $shelf $lf1 " . " NA lf_udp " . + " -1 NO " . $new_rate[0] . " " . $new_rate[0] . " NA NA NA NA "; + doCmd($cmd); + $cmd = "add_endp " . $endpoint_names[$p2] . " $shelf $lf1 " . " NA lf_udp " . + " -1 NO " . $new_rate[1] . " " . $new_rate[1] . " NA NA NA NA "; + doCmd($cmd); + } #for $adj_count + + doCmd("set_cx_state $test_mgr " . $cx_names[$q] . " STOPPED"); + doCmd("clear_cx_counters"); + doCmd("clear_port_counters"); + print "\n\n*********************************************************\n"; + print "Theoretical Throughput: $min_rate bps.\n"; + print "Zero-Loss Throughput: $last_current_rate bps for $min_pkt_szs[$q] byte packets.\n\n"; + sleep(10); + + } #for cross-connects +} #for $loop_max + +$dt = `date`; +chomp($dt); +print "Done at: $dt\n\n"; +exit(0); + + +sub initToDefaults { + # Clean up database if stuff exists + + doCmd("rm_cx $test_mgr all"); + doCmd("rm_endp YES_ALL"); + doCmd("rm_test_mgr $test_mgr"); + + initPortsToDefault(); + +}#initToDefaults + + + + doCmd("probe_ports"); + + # Wait untill we discover all the ports... + + my $q=0; + for ($q = 0; $q<@lf1_ports; $q++) { + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $shelf, $lf1, $lf1_ports[$q]); + my $pname = $p1->{dev}; + + my $p2 = new LANforge::Port(); + my $pname2; + if ($lf2 ne "") { + $utils->updatePort($p2, $shelf, $lf2, $lf2_ports[$q]); + $pname2 = $p2->{dev}; + } + } + + + +# Wait untill the system can update a port.. +sub throttleCard { + my $s = shift; + my $c = shift; + my $p1 = new LANforge::Port(); + $utils->updatePort($p1, $s, $c, 1); +}#throttle + +sub initPortsToDefault { + clearMacVlanPorts($shelf, $lf1); + if ($lf2 ne "") { + clearMacVlanPorts($shelf, $lf2); + } + + throttleCard($shelf, $lf1); + + if ($lf2 ne "") { + throttleCard($shelf, $lf2); + } + + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + doCmd("set_port $shelf $lf1 $tmp 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + if ($lf2 ne "") { + doCmd("set_port $shelf $lf2 $tmp2 0.0.0.0 0.0.0.0 0.0.0.0 NA NA NA"); + } + } +} + +sub clearMacVlanPorts { + my $s = shift; + my $c = shift; + + my $i; + my $found_one = 1; + my @ports = (); + while ($found_one) { + $found_one = 0; + doCmd("probe_ports"); + # Clear out any existing MAC-VLAN ports. + $utils->error(""); + @ports = $utils->getPortListing($s, $c); + my $mx = @ports; + print "Found $mx ports for resource: $shelf.$lf1\n"; + + if (($mx == 0) || ($utils->error() =~ /Timed out/g)) { + # System is too backlogged to answer, wait a bit + print " Will try listing ports again in a few seconds...system is backlogged now!\n"; + sleep(5); + $found_one = 1; + next; + } + + my $throttle = 0; + my $wait_for_phantom = 0; + for ($i = 0; $i<$mx; $i++) { + if ($ports[$i]->isMacVlan()) { + if ($ports[$i]->isPhantom()) { + # Wait a bit..hopefully it will go away. + if ($wait_for_phantom++ < 20) { + print "Sleeping a bit, found a phantom port."; + sleep(5); + doCmd("probe_ports"); + $found_one = 1; + } + } + else { + doCmd($ports[$i]->getDeleteCmd()); + $found_one = 1; + } + } + } + } +} + + +sub initIpAddresses { + # Set all ports we are messing with to known state. + my $i = 0; + for ($i = 0; $i<@lf1_ports; $i++) { + my $tmp = $lf1_ports[$i]; + my $tmp2 = $lf2_ports[$i]; + my $cmd = "set_port $shelf $lf1 $tmp " . $lf1_port_ips[$i] . " 255.255.255.0 " . + $lf1_port_gws[$i] . " NA NA NA"; + doCmd($cmd); + $cmd = "set_port $shelf $lf2 $tmp2 " . $lf2_port_ips[$i] . " 255.255.255.0 " . $lf2_port_gws[$i] . " NA NA NA"; + doCmd($cmd); + } +} + +sub addCrossConnects { + my $ep = 0; + my $cx = 0; + my $i = 0; + for ($i = 0; $i<@cx_types; $i++) { + my $j = 0; + for ($j = 0; $j<@lf1_ports; $j++) { + my $burst = "NO"; + my $szrnd = "NO"; + my $pattern = "increasing"; + + my $ep1 = "endp-${ep}-TX"; + $ep++; + my $ep2 = "endp-${ep}-RX"; + $ep++; + + @endpoint_names = (@endpoint_names, $ep1, $ep2); + + my $cmd = "add_endp $ep1 $shelf $lf1 " . $lf1_ports[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] + . " " . $max_pkt_szs[$i] . " $pattern "; + doCmd($cmd); + + $cmd = "add_endp $ep2 $shelf $lf2 " . $lf2_ports[$j] . " " . @cx_types[$i] . + " -1 $burst $min_rate $max_rate $szrnd " . $min_pkt_szs[$i] + . " " . $max_pkt_szs[$i] . " $pattern "; + doCmd($cmd); + + # Now, add the cross-connects + my $cx_name = "cx-${cx}"; + $cmd = "add_cx $cx_name $test_mgr $ep1 $ep2"; + doCmd($cmd); + doCmd("set_cx_report_timer $test_mgr $cx_name $report_timer"); + + $cx++; + + @cx_names = (@cx_names, $cx_name); + + }#for all ports + }#for all endpoint types +}#addCrossConnects + + +sub doCmd { + my $cmd = shift; + + print ">>> $cmd\n"; + + $t->print($cmd); + my @rslt = $t->waitfor(Match => '/ \>\>RSLT:(.*)/', + Timeout => $timeout); + + print "**************\n @rslt ................\n\n"; + #sleep(1); +} diff --git a/lanforge/lanforge-scripts/lib_vrf.bash b/lanforge/lanforge-scripts/lib_vrf.bash new file mode 100755 index 000000000..4e30a0cf2 --- /dev/null +++ b/lanforge/lanforge-scripts/lib_vrf.bash @@ -0,0 +1,51 @@ +#!/bin/bash + +# create an associative array of vrf interfaces and their ports + +IFLINES=() +declare -A IFNAMES +export IFNAMES +declare -A VRFNAMES +export VRFNAMES + +while read line; do + IFLINES+=("$line") +done < <(ip -o link show) + +RE_MASTER=' master ([^ ]+) state ' +for item in "${IFLINES[@]}"; do + #echo -e "\t$item" + [[ x$item = x ]] && continue + + IFS=': ' hunks=($item) + [[ "${hunks[1]}" = "" ]] && continue + + ifname="${hunks[1]}" + [[ "$ifname" = *NOARP,MASTER* ]] && continue + + IFNAMES["$ifname"]="unknown" + + if [[ $item = *master* ]] && [[ $item = *vrf* ]]; then + #echo "Looking for vrf in $ifname" + if [[ $item =~ $RE_MASTER ]]; then + [[ x${BASH_REMATCH[1]} = x ]] && continue; + vrfname=${BASH_REMATCH[1]}; + #echo "[[[$ifname]]] [[[$vrfname]]]" + IFNAMES["$ifname"]="$vrfname" + VRFNAMES["$vrfname"]="$ifname" + fi + fi +done + +if [[ x$VRF_DEBUG = x1 ]]; then + echo "Interfaces: " + for ifname in "${!IFNAMES[@]}"; do + echo "IFN $ifname => ${IFNAMES[$ifname]}" + done + + echo "virtual routers: " + for vrfname in "${!VRFNAMES[@]}"; do + echo "VRF $vrfname => ${VRFNAMES[$vrfname]}" + done +fi +# diff --git a/lanforge/lanforge-scripts/license.txt b/lanforge/lanforge-scripts/license.txt new file mode 100644 index 000000000..931f37cf9 --- /dev/null +++ b/lanforge/lanforge-scripts/license.txt @@ -0,0 +1,26 @@ +Copyright (c) 2000-2017, Candela Technologies Inc <support@candelatech.com> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. diff --git a/lanforge/lanforge-scripts/list_phy_sta.sh b/lanforge/lanforge-scripts/list_phy_sta.sh new file mode 100755 index 000000000..6c1d6cc3e --- /dev/null +++ b/lanforge/lanforge-scripts/list_phy_sta.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# this script lists wiphy stations per radio + +[ -z "$MGR" ] && echo "$0 wants MGR set, bye" && exit 1 +[ -z "$RESRC" ] && echo "$0 wants RESRC set, bye" && exit 1 +[ -z "$RADIO" ] && echo "$0 wants RADIO set, bye" && exit 1 + +. ~/scripts/common.bash + +LINKUP="link=UP" +LINKDOWN="link=DOWN" +LINKANY="" +DEF_OUTFILE="${DEF_OUTFILE:-/tmp/wiphyNN-names.txt}" + +OUTFILE="${DEF_OUTFILE/NN/$RADIO}" + +[ -z "$OUTFILE" ] && echo "$0 wants OUTFILE set, use 'stdout' for stdout, bye" && exit 1 + +function helpquit() { + echo "${D}MGR=localhost ${D}RESRC=1 ${D}RADIO=0 ${D}DEF_OUTFILE=$DEF_OUTFILE $0 --up|--down|--all\n" + exit 1 +} + +function firemod_list() { + ./lf_firemod.pl --mgr $MGR --resource $RESRC --action list_ports \ + | /usr/bin/perl -ne "/^((sta${RESRC}${RADIO}|wlan${RADIO})\d*) ${STATUS}/ && print ${Q}${D}1${N}${Q}" +} + +case "$1" in + *up|*UP) + STATUS=$LINKUP + ;; + *down|*DOWN) + STATUS=$LINKDOWN + ;; + *all|*any|*ALL|*ANY) + STATUS=$LINKANY + ;; + *) + helpquit + ;; +esac + +cd `dirname $0` + +if [ "$OUTFILE" = "stdout" ]; then + firemod_list | sort +else + firemod_list | sort > "$OUTFILE" +fi + +# diff --git a/lanforge/lanforge-scripts/mem-info.sh b/lanforge/lanforge-scripts/mem-info.sh new file mode 100755 index 000000000..68cf40ac4 --- /dev/null +++ b/lanforge/lanforge-scripts/mem-info.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -x +java_pid=`pgrep java` +[ -z "$java_pid" ] && echo "no running java process" && exit 1 + +p_jmap=`which jmap` +[ -z "$p_jmap" ] && echo "jmap not found" && exit 1 + +now=`date +heap_+%Y-%m-%d_%I-%M-%S` +$p_jmap -dump:live,format=b,file=/home/lanforge/Documents/$now.bin $java_pid + +jstack $java_pid +echo done diff --git a/lanforge/lanforge-scripts/min_max_ave_station.pl b/lanforge/lanforge-scripts/min_max_ave_station.pl new file mode 100755 index 000000000..0328b0854 --- /dev/null +++ b/lanforge/lanforge-scripts/min_max_ave_station.pl @@ -0,0 +1,257 @@ +#!/usr/bin/perl -w +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# This script looks for min-max-average bps for rx_rate in +# a station csv data file +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +package main; +$| = 1; # unbuffer output +use strict; +use warnings; +use diagnostics; +use Carp; +use Getopt::Long; +use POSIX qw(locale_h); +use locale; +use Number::Format qw(format_number); + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +our $TimeStamp = 0; +our $Name = 1; +our $Resource = 3; +our $Tx_Pkts = 4; +our $Tx_Packets = 4; +our $Rx_Pkts = 5; +our $Rx_Packets = 5; +our $Tx_Bytes = 6; +our $Rx_Bytes = 7; +our $Rx_Signal = 25; +our $Link_Speed = 26; +our $Rx_Link_Speed = 27; + +our $filename; +our $start_time = 0; +our $finish_time = time() * 1000; + +our $usage = "$0 [-f|--filename # name of staX csv file] + [-s|--start_time # timestamp milliseconds point to begin] + [-e|--finish_time # timestamp milliseconds point to finish] + +Example: +$0 -f ./sta100_1.1.5_1429826436.csv # collect all entries + +$0 -s 1429820000 -e 1429828000 -f ./sta100_1.1.5_1429826436.csv + +We can use expanded unix datestamps as well: +$0 -s \`date -d \"2014/11/25 10:00:00\" \"+%s000\"\` \\ + -e \`date -d \"2014/11/25 11:00:00\" \"+%s000\"\` \\ + -f ./sta100_1.1.5_1429826436.csv +"; + +sub do_err_exit { + my $msg = shift; + print $msg."\n"; + exit(1); +} + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# takes a reference to a string +sub printRow { + my $rs_line = shift; + #print "LINE: $$rs_line\n"; + my @hunks = split(',', $$rs_line); + my $msg = +"TimeStamp : $hunks[$::TimeStamp] +Name : $hunks[$::Name] +Resource : $hunks[$::Resource] +Tx_Pkts : $hunks[$::Tx_Pkts] +Rx_Pkts : $hunks[$::Rx_Pkts] +Tx_Bytes : $hunks[$::Tx_Bytes] +Rx_Bytes : $hunks[$::Rx_Bytes] +Rx_Signal : $hunks[$::Rx_Signal] +Link_Speed : $hunks[$::Link_Speed] +Rx_Link_Speed : $hunks[$::Rx_Link_Speed]\n\n"; + + print $msg; +} +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# takes a reference to an array + sub printRowAt { + my $ra_rows = shift; + my $index = shift; + my $row = $ra_rows->[$index]; + printRow( \$row ); +} +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +# M A I N # +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # +GetOptions ( + 'filename|f=s' => \$::filename, + 'start_time|s=i' => \$::start_time, + 'finish_time|e=i' => \$::finish_time +) || do_err_exit("$usage"); + +if ( ! defined $::filename || $::filename eq "" ) { + do_err_exit($::usage); +} +if ( ! -f $::filename ) { + do_err_exit("file not found"); +} +open (my $input_fh, "<", $::filename) || do_err_exit($!); +my @lines = <$input_fh>; +close($input_fh); + +#my $first_line = $lines[0]; +#printRow( \$first_line ); +#printRowAt( \@lines, 0 ); + +our $Orig = 0; +our $Min = 1; +our $Max = 2; +our $Tot = 3; +our $Total = 3; +our $Ave = 4; +our $Avg = 4; +our $Delta = 5; +our $Dt = 5; + +my @begin_rx_bytes; +my @begin_rx_packets; +my @begin_rx_signal; +my $begin_time = 0; + +my @cur_rx_bytes; +my @cur_rx_packets; +my @cur_rx_signal; + +my @prev_rx_bytes; +my @prev_rx_packets; +my @prev_rx_signal; + +# first entry +#my @hunks = split(',', $lines[ 1 ]); +my $counted = 0; +for( my $i = 2 ; $i < $#lines ; $i++ ) { + my @hunks = split(',', $lines[$i]); + + #print "start time: $::start_time\nfinish time: $::finish_time\ntime stamp : $hunks[$::TimeStamp]\n"; + next if ($hunks[ $::TimeStamp ] < $::start_time ); + last if ($hunks[ $::TimeStamp ] > $::finish_time ); + + if ($counted == 0) { + $begin_time = $hunks[$::TimeStamp]; + $begin_rx_bytes[ $::Orig ] = $hunks[$::Rx_Bytes]; + $begin_rx_bytes[ $::Min ] = $hunks[$::Rx_Bytes]; + $begin_rx_bytes[ $::Max ] = $hunks[$::Rx_Bytes]; + $begin_rx_bytes[ $::Tot ] = $hunks[$::Rx_Bytes]; + $begin_rx_bytes[ $::Ave ] = $hunks[$::Rx_Bytes]; + $begin_rx_bytes[ $::Delta ] = 0; + + $begin_rx_packets[ $::Orig ] = $hunks[$::Rx_Packets]; + $begin_rx_packets[ $::Min ] = $hunks[$::Rx_Packets]; + $begin_rx_packets[ $::Max ] = $hunks[$::Rx_Packets]; + $begin_rx_packets[ $::Tot ] = $hunks[$::Rx_Packets]; + $begin_rx_packets[$::Delta ] = 0; + + $begin_rx_signal[ $::Orig ] = $hunks[$::Rx_Signal]; + $begin_rx_signal[ $::Min ] = $hunks[$::Rx_Signal]; + $begin_rx_signal[ $::Max ] = $hunks[$::Rx_Signal]; + $begin_rx_signal[ $::Tot ] = $hunks[$::Rx_Signal]; + $begin_rx_signal[$::Delta ] = 0; + @cur_rx_bytes = @begin_rx_bytes; + @cur_rx_packets = @begin_rx_packets; + @cur_rx_signal = @begin_rx_signal; + } + @prev_rx_bytes = @cur_rx_bytes; + @prev_rx_packets = @cur_rx_packets; + @prev_rx_signal = @cur_rx_signal; + + #printRowAt( \@lines, $i ); + + $cur_rx_bytes[ $::Orig ] = $hunks[ $::Rx_Bytes ]; + my $diff_rx = $hunks[ $::Rx_Bytes ] - $prev_rx_bytes[ $::Orig ]; + $cur_rx_bytes[ $::Delta ] = $diff_rx; + + if ($hunks[$::Rx_Bytes]==0) { + print "TimeStamp $hunks[$::TimeStamp] zero bytes\n"; + $cur_rx_bytes[ $::Min ] = 0; + } + elsif (($diff_rx < $prev_rx_bytes[ $::Delta ]) && ($diff_rx < $prev_rx_bytes[ $::Min ])) { + if ($diff_rx == 0) { + print "TimeStamp $hunks[$::TimeStamp] zero bytes diff\n"; + $cur_rx_bytes[ $::Min ] = $prev_rx_bytes[ $::Delta ]; + } else { + $cur_rx_bytes[ $::Min ] = $diff_rx; + } + } + if (($diff_rx > $prev_rx_bytes[ $::Delta ]) && ($diff_rx > $prev_rx_bytes[ $::Max ])) { + $cur_rx_bytes[ $::Max ] = $diff_rx; + } + $cur_rx_bytes[ $::Tot ] = $hunks[ $::Rx_Bytes ] - $begin_rx_bytes[ $::Orig]; + + + + $cur_rx_packets[ $::Orig ] = $hunks[ $::Rx_Packets ]; + $diff_rx = $hunks[ $::Rx_Packets ] - $prev_rx_packets[ $::Orig ]; + $cur_rx_packets[ $::Delta ] = $diff_rx; + + if ($hunks[$::Rx_Packets]==0) { + print "TimeStamp $hunks[$::TimeStamp] zero packets\n"; + $cur_rx_packets[ $::Min ] = 0; + } + elsif (($diff_rx < $prev_rx_packets[ $::Delta ]) && ($diff_rx < $prev_rx_packets[ $::Min ])) { + if ($diff_rx == 0) { + print "TimeStamp $hunks[$::TimeStamp] zero packets diff\n"; + $cur_rx_packets[ $::Min ] = $prev_rx_packets[ $::Delta ]; + } else { + $cur_rx_packets[ $::Min ] = $diff_rx; + } + } + if (($diff_rx > $prev_rx_packets[ $::Delta ]) && ($diff_rx > $prev_rx_packets[ $::Max ])) { + $cur_rx_packets[ $::Max ] = $diff_rx; + } + $cur_rx_packets[ $::Tot ] = $hunks[ $::Rx_Packets ] - $begin_rx_packets[ $::Orig ]; + + + $cur_rx_signal[ $::Orig ] = $hunks[ $::Rx_Signal ]; + $cur_rx_signal[ $::Min ] = $hunks[ $::Rx_Signal ] if ( $hunks[ $::Rx_Signal ] < $prev_rx_signal[ $::Min ]); + $cur_rx_signal[ $::Max ] = $hunks[ $::Rx_Signal ] if ( $hunks[ $::Rx_Signal ] > $prev_rx_signal[ $::Max ]); + $cur_rx_signal[ $::Tot ] = $prev_rx_signal[ $::Tot ] + $hunks[ $::Rx_Signal ]; + + $counted++; +} +my $seconds = ($finish_time - $begin_time + 1000) / 1000; + +if ($seconds <= 0 || $counted <= 0) { + do_err_exit("No records in range"); +} +$cur_rx_bytes[ $Ave ] = $cur_rx_bytes[ $::Tot ] / $counted; +$cur_rx_packets[ $Ave ] = $cur_rx_packets[ $::Tot ] / $counted; +$cur_rx_signal[ $Ave ] = $cur_rx_signal[ $::Tot ] / $counted; + +printf "Rx Bytes: Min_Bps: %15s Max_Bps: %15s Ave_Bps: %15s Total: %15s\n", + format_number($cur_rx_bytes[ $::Min ]), + format_number($cur_rx_bytes[ $::Max ]), + format_number($cur_rx_bytes[ $Ave ]), + format_number($cur_rx_bytes[ $::Tot ]); + +printf "Rx bits/sec Min_bps: %15s Max_bps: %15s Ave_bps: %15s\n", + format_number($cur_rx_bytes[ $::Min ] * 8), + format_number($cur_rx_bytes[ $::Max ] * 8), + format_number($cur_rx_bytes[ $Ave ] * 8); + +printf "Rx Packets: Min_Pps: %15s Max_Pps: %15s Ave_Pps: %15s Total: %15s\n", + format_number($cur_rx_packets[ $::Min ]), + format_number($cur_rx_packets[ $::Max ]), + format_number($cur_rx_packets[ $Ave ]), + format_number($cur_rx_packets[ $::Tot ]); + +printf "Rx Signal: Min_dB: %15s Max_dB: %16s Ave_dB: %15s\n", + format_number($cur_rx_signal[ $::Min ]), + format_number($cur_rx_signal[ $::Max ]), + format_number($cur_rx_signal[ $Ave ]); +print format_number($counted)." samples in ".format_number($seconds)." seconds\n"; +## +## +## diff --git a/lanforge/lanforge-scripts/multi_endp.bash b/lanforge/lanforge-scripts/multi_endp.bash new file mode 100755 index 000000000..66702bf7d --- /dev/null +++ b/lanforge/lanforge-scripts/multi_endp.bash @@ -0,0 +1,36 @@ +#!/bin/bash + +# script creates a series of connections between stations and an upstream resource + +set -x # turn debugging on + +cx_prefix=tcp_apple +number_of_cx=10 +endp_type=lf_tcp +manager=idtest + +resource_a=3 # holds upstream eth3 +upstream_port=eth3 +tx_speed_a=1000000 + +resource_b=6 # holds wiphy0 with stations 0-24 +tx_speed_b=15400 +first_sta=0 # becomes "sta+$sta_num", up to number_of_cx-1 + + +for sta_num in `seq $first_sta $(($number_of_cx + $first_sta - 1))`; do + + ./lf_firemod.pl --mgr $manager --resource $resource_a --action create_endp --report_timer 1000 \ + --port_name $upstream_port --endp_type $endp_type --endp_name "${cx_prefix}${sta_num}-A" \ + --speed $tx_speed_a + + ./lf_firemod.pl --mgr $manager --resource $resource_b --action create_endp --report_timer 1000 \ + --port_name "sta${sta_num}" --endp_type $endp_type --endp_name "${cx_prefix}${sta_num}-B" \ + --speed $tx_speed_b + + ./lf_firemod.pl --mgr $manager --action create_cx --report_timer 1000 \ + --cx_name "${cx_prefix}${sta_num}" --cx_endps ${cx_prefix}${sta_num}-A,${cx_prefix}${sta_num}-B + +done +# flush information to LFclients +./lf_firemod.pl --mgr $manager --action do_cmd --cmd "nc_show_endpoints all" &>/dev/null diff --git a/lanforge/lanforge-scripts/multi_routers.pl b/lanforge/lanforge-scripts/multi_routers.pl new file mode 100755 index 000000000..da65392a9 --- /dev/null +++ b/lanforge/lanforge-scripts/multi_routers.pl @@ -0,0 +1,181 @@ +#!/usr/bin/perl + +use strict; + +# Clean up routing tables + +remove_local_routing_table("rddC1"); +remove_local_routing_table("rddA2"); +remove_local_routing_table("rddA1"); +remove_local_routing_table("rddB1"); +remove_local_routing_table("rddD1"); +remove_local_routing_table("rddD2"); +remove_local_routing_table("rddE1"); +remove_routing_table(1001); +remove_routing_table(1002); +remove_routing_table(1003); + +do_cmd("ip ru show"); +do_cmd("ip route show table 1001"); +do_cmd("ip route show table 1002"); + +# Set up router 1001 +set_ip("rddC1", "10.0.4.1", "10.0.4.0", "24", "10.0.4.255", "10.0.4.2", 1001); +set_ip("rddA2", "10.0.3.1", "10.0.3.0", "24", "10.0.3.255", "10.0.3.2", 1001); +set_ip("rddD1", "10.0.5.1", "10.0.5.0", "24", "10.0.5.255", "10.0.5.2", 1001); +do_cmd("ip rule add to 10.0.5.1 iif rddC1 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. +do_cmd("ip rule add to 10.0.5.1 iif rddA2 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. +do_cmd("ip rule add to 10.0.3.1 iif rddC1 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. +do_cmd("ip rule add to 10.0.3.1 iif rddD1 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. +do_cmd("ip rule add to 10.0.4.1 iif rddA2 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. +do_cmd("ip rule add to 10.0.4.1 iif rddD1 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. + +# Set up router 1002 +set_ip("rddA1", "10.0.3.2", "10.0.3.0", "24", "10.0.3.255", "10.0.3.1", 1002); +set_ip("rddB1", "10.0.2.1", "10.0.2.0", "24", "10.0.2.255", "10.0.2.2", 1002); +do_cmd("ip rule add to 10.0.3.2 iif rddB1 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. +do_cmd("ip rule add to 10.0.2.1 iif rddA1 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. + +# Set up router 1003 +set_ip("rddD2", "10.0.5.2", "10.0.5.0", "24", "10.0.5.255", "10.0.5.1", 1003); +set_ip("rddE1", "10.0.6.1", "10.0.6.0", "24", "10.0.6.255", "10.0.6.2", 1003); +do_cmd("ip rule add to 10.0.5.2 iif rddE1 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. +do_cmd("ip rule add to 10.0.6.1 iif rddD2 lookup local pref 10"); # use local routing table if it arrives here and is destined to peer. + + +add_subnet_route("10.0.2.0/24", "10.0.3.2", "rddA2", 1001); +add_subnet_route("10.0.6.0/24", "10.0.5.2", "rddD1", 1001); + +add_subnet_route("10.0.4.0/24", "10.0.3.1", "rddA1", 1002); +add_subnet_route("10.0.5.0/24", "10.0.3.1", "rddA1", 1002); +add_subnet_route("10.0.6.0/24", "10.0.3.1", "rddA1", 1002); + +add_subnet_route("10.0.4.0/24", "10.0.5.1", "rddD2", 1003); +add_subnet_route("10.0.3.0/24", "10.0.5.1", "rddD2", 1003); +add_subnet_route("10.0.2.0/24", "10.0.5.1", "rddD2", 1003); + + +sub add_subnet_route { + my $sn = shift; + my $sn_gw = shift; + my $dev = shift; + my $rt = shift; + + do_cmd("ip route add $sn via $sn_gw dev $dev table $rt"); # subnet route +} + + +sub set_ip { + my $dev = shift; # network device name + my $ip = shift; # ip + my $sn = shift; # subnet addr + my $mbits = shift; # mask bits (ie, 24) + my $bcast = shift; # broadcast addr + my $sn_gw = shift; # next hot for this subnet route + my $rt = shift; # routing table + + # Set it's IP address. + do_cmd("ip link set $dev down"); + do_cmd("ip link set $dev up"); + do_cmd("ip addr flush dev $dev"); + do_cmd("ip addr add $ip/$mbits broadcast $bcast dev $dev"); + do_cmd("ip rule add to $ip iif $dev lookup local pref 10"); # use local routing table if it arrives here and is destined here. + do_cmd("ip rule add iif $dev lookup $rt pref 20"); # use this table for pkts rx on this interface. + do_cmd("ip rule add from $ip/32 table $rt pref 30"); # use this table for pkts from this IP + do_cmd("ip route add $sn/$mbits via $ip table $rt"); # subnet route + # Do default gateway on a per-router basis, not per-interface. + + # Enable arp filtering. + do_cmd("echo 1 > /proc/sys/net/ipv4/conf/$dev/arp_filter"); +} + + +sub remove_routing_table { + my $tid = shift; + + my $listing = `ip ru list`; + my @listings = split(/\n/, $listing); + my $q = 0; + for ($q = 0; $q<@listings; $q++) { + my $line = $listings[$q]; + chomp($line); + #print "Processing ip-ru-list line -:$line:-\n"; + my $num; + my $from; + my $arg; + my @rest; + + if ($line =~ /\S+:\s+\S+\s+(\S+)\s+.*lookup\s+(\S+)/) { + my $a = $1; + my $mtid = $2; + + if ($a eq "all") { + $a = "0/0"; + } + + if ($tid eq $mtid) { + my $cmd = "ip ru del from $a lookup $tid"; + do_cmd("$cmd"); + } + } + } + + $listing = `ip route show table $tid`; + @listings = split(/\n/, $listing); + $q = 0; + for ($q = 0; $q<@listings; $q++) { + my $line = $listings[$q]; + chomp($line); + #print "Processing ip-ru-list line -:$line:-\n"; + + if ($line =~ /(\S+)\s+/) { + my $key = $1; + + if ($a eq "all") { + $a = "0/0"; + } + + my $cmd = "ip route del $key table $tid"; + do_cmd("$cmd"); + } + } + +} + + +sub remove_local_routing_table { + my $dev = shift; + + my $listing = `ip ru list`; + my @listings = split(/\n/, $listing); + my $q = 0; + for ($q = 0; $q<@listings; $q++) { + my $line = $listings[$q]; + chomp($line); + #print "Processing ip-ru-list line -:$line:-\n"; + my $num; + my $from; + my $arg; + my @rest; + + if ($line =~ /.*\s+iif $dev\s+.*/) { + if ($line =~ /\S+:\s+\S+\s+(\S+)\s+(.*)lookup local/) { + my $a = $1; + my $match = $2; + + if ($a eq "all") { + $a = "0/0"; + } + + my $cmd = "ip ru del from $a $match lookup local"; + do_cmd("$cmd"); + } + } + } +} + +sub do_cmd { + my $cmd = shift; + print "$cmd\n"; + system("$cmd"); +} diff --git a/lanforge/lanforge-scripts/ocean-text.csv b/lanforge/lanforge-scripts/ocean-text.csv new file mode 100644 index 000000000..301cd176a --- /dev/null +++ b/lanforge/lanforge-scripts/ocean-text.csv @@ -0,0 +1,154 @@ +,,,,,,,, +This data is from a rig with a CIR of 3.5mb down and 2mb up. The circuit does allow for busrting over the CIR,,,,,,,, +,,,,,,,, +"The vendor will not say what the max burst is, so for this test let's use a 100kb burst allowance when it is over the CIR.",,,,,,,, +,,,,,,,, +,,,,,,,, +,,,,,,,, +,,,,,,,, +,,,,,,,, +DATE / TIME,,Downlink,Downlink 3.5Mb CIR minus actual,,Uplink,Uplink 2Mb CIR minus actual,,Delay +16-Apr-18,7:40 PM,"2,600,514","899,486 ",,"1,518,710","481,290 ",,690.0 ms +16-Apr-18,7:50 PM,"2,777,036","722,964 ",,"1,437,606","562,394 ",,712.0 ms +16-Apr-18,8:00 PM,"2,792,650","707,350 ",,"1,520,660","479,340 ",,691.0 ms +16-Apr-18,8:10 PM,"2,517,649","982,351 ",,"1,632,793","367,207 ",,745.0 ms +16-Apr-18,8:20 PM,"2,670,139","829,861 ",,"1,841,709","158,292 ",,625.0 ms +16-Apr-18,8:30 PM,"3,274,754","225,246 ",,"2,135,108","(135,108)",,674.0 ms +16-Apr-18,8:40 PM,"3,922,926","(422,926)",,"1,858,062","141,938 ",,593.0 ms +16-Apr-18,8:50 PM,"3,866,847","(366,847)",,"1,426,749","573,251 ",,608.0 ms +16-Apr-18,9:00 PM,"3,883,646","(383,646)",,"1,436,534","563,466 ",,644.0 ms +16-Apr-18,9:10 PM,"3,681,093","(181,093)",,"1,598,254","401,746 ",,639.0 ms +16-Apr-18,9:20 PM,"3,261,169","238,831 ",,"1,637,315","362,685 ",,668.0 ms +16-Apr-18,9:30 PM,"3,690,312","(190,312)",,"1,317,902","682,098 ",,670.0 ms +16-Apr-18,9:40 PM,"3,289,575","210,425 ",,"1,140,167","859,833 ",,662.0 ms +16-Apr-18,9:50 PM,"2,401,459","1,098,541 ",,"1,348,942","651,059 ",,623.0 ms +16-Apr-18,10:00 PM,"1,898,071","1,601,929 ",,"1,117,990","882,010 ",,606.0 ms +16-Apr-18,10:10 PM,"1,066,482","2,433,518 ",,"725,632","1,274,368 ",,636.0 ms +16-Apr-18,10:20 PM,"1,245,697","2,254,304 ",,"1,124,613","875,387 ",,604.0 ms +16-Apr-18,10:30 PM,"1,427,543","2,072,457 ",,"849,334","1,150,666 ",,609.0 ms +16-Apr-18,10:40 PM,"1,546,955","1,953,045 ",,"988,516","1,011,484 ",,609.0 ms +16-Apr-18,10:50 PM,"1,012,614","2,487,386 ",,"867,008","1,132,992 ",,623.0 ms +16-Apr-18,11:00 PM,"1,445,872","2,054,128 ",,"1,155,422","844,578 ",,601.0 ms +16-Apr-18,11:10 PM,"1,900,101","1,599,899 ",,"1,125,875","874,125 ",,604.0 ms +16-Apr-18,11:20 PM,"2,037,872","1,462,128 ",,"1,104,803","895,197 ",,620.0 ms +16-Apr-18,11:30 PM,"3,256,726","243,274 ",,"1,169,765","830,235 ",,638.0 ms +16-Apr-18,11:40 PM,"1,902,679","1,597,321 ",,"888,733","1,111,267 ",,593.0 ms +16-Apr-18,11:50 PM,"1,205,878","2,294,122 ",,"959,926","1,040,074 ",,576.0 ms +17-Apr-18,12:00 AM,"2,324,684","1,175,316 ",,"588,073","1,411,928 ",,634.0 ms +17-Apr-18,12:10 AM,"2,577,053","922,948 ",,"844,843","1,155,157 ",,637.0 ms +17-Apr-18,12:20 AM,"1,157,237","2,342,764 ",,"627,647","1,372,353 ",,652.0 ms +17-Apr-18,12:30 AM,"927,775","2,572,225 ",,"593,496","1,406,504 ",,574.0 ms +17-Apr-18,12:40 AM,"864,361","2,635,639 ",,"419,723","1,580,277 ",,606.0 ms +17-Apr-18,12:50 AM,"1,135,970","2,364,030 ",,"426,511","1,573,489 ",,602.0 ms +17-Apr-18,1:00 AM,"2,004,468","1,495,532 ",,"504,124","1,495,876 ",,569.0 ms +17-Apr-18,1:10 AM,"1,461,714","2,038,286 ",,"506,975","1,493,025 ",,592.0 ms +17-Apr-18,1:20 AM,"904,944","2,595,056 ",,"548,061","1,451,939 ",,584.0 ms +17-Apr-18,1:30 AM,"700,362","2,799,639 ",,"451,073","1,548,927 ",,569.0 ms +17-Apr-18,1:40 AM,"984,911","2,515,089 ",,"397,699","1,602,301 ",,586.0 ms +17-Apr-18,1:50 AM,"870,841","2,629,160 ",,"383,039","1,616,961 ",,564.0 ms +17-Apr-18,2:00 AM,"1,364,401","2,135,599 ",,"417,438","1,582,562 ",,577.0 ms +17-Apr-18,2:10 AM,"1,497,757","2,002,243 ",,"498,692","1,501,308 ",,582.0 ms +17-Apr-18,2:20 AM,"1,181,329","2,318,671 ",,"429,862","1,570,138 ",,570.0 ms +17-Apr-18,2:30 AM,"1,129,873","2,370,127 ",,"445,128","1,554,872 ",,573.0 ms +17-Apr-18,2:40 AM,"890,576","2,609,424 ",,"381,279","1,618,721 ",,579.0 ms +17-Apr-18,2:50 AM,"987,721","2,512,279 ",,"393,268","1,606,733 ",,596.0 ms +17-Apr-18,3:00 AM,"1,494,144","2,005,856 ",,"501,257","1,498,743 ",,581.0 ms +17-Apr-18,3:10 AM,"1,744,916","1,755,084 ",,"562,605","1,437,395 ",,584.0 ms +17-Apr-18,3:20 AM,"2,722,958","777,042 ",,"607,292","1,392,709 ",,602.0 ms +17-Apr-18,3:30 AM,"898,361","2,601,639 ",,"422,438","1,577,562 ",,605.0 ms +17-Apr-18,3:40 AM,"1,040,523","2,459,477 ",,"431,052","1,568,948 ",,573.0 ms +17-Apr-18,3:50 AM,"1,697,251","1,802,749 ",,"622,899","1,377,101 ",,586.0 ms +17-Apr-18,4:00 AM,"1,682,599","1,817,401 ",,"801,674","1,198,326 ",,622.0 ms +17-Apr-18,4:10 AM,"1,439,555","2,060,445 ",,"1,001,518","998,482 ",,573.0 ms +17-Apr-18,4:20 AM,"930,146","2,569,854 ",,"606,053","1,393,947 ",,561.0 ms +17-Apr-18,4:30 AM,"769,941","2,730,059 ",,"420,037","1,579,963 ",,556.0 ms +17-Apr-18,4:40 AM,"2,416,049","1,083,951 ",,"619,314","1,380,686 ",,614.0 ms +17-Apr-18,4:50 AM,"4,084,152","(584,152)",,"1,095,479","904,521 ",,569.0 ms +17-Apr-18,5:00 AM,"4,662,133","(1,162,133)",,"1,232,967","767,033 ",,593.0 ms +17-Apr-18,5:10 AM,"3,478,798","21,202 ",,"2,080,277","(80,277)",,597.0 ms +17-Apr-18,5:20 AM,"2,686,292","813,709 ",,"1,336,034","663,966 ",,603.0 ms +17-Apr-18,5:30 AM,"2,194,813","1,305,187 ",,"664,953","1,335,047 ",,602.0 ms +17-Apr-18,5:40 AM,"1,440,980","2,059,020 ",,"583,833","1,416,167 ",,615.0 ms +17-Apr-18,5:50 AM,"2,015,722","1,484,278 ",,"671,825","1,328,175 ",,606.0 ms +17-Apr-18,6:00 AM,"1,901,107","1,598,893 ",,"688,833","1,311,167 ",,589.0 ms +17-Apr-18,6:10 AM,"2,269,723","1,230,277 ",,"1,025,737","974,263 ",,583.0 ms +17-Apr-18,6:20 AM,"3,834,887","(334,887)",,"1,213,174","786,826 ",,585.0 ms +17-Apr-18,6:30 AM,"2,077,471","1,422,529 ",,"949,886","1,050,114 ",,586.0 ms +17-Apr-18,6:40 AM,"3,310,354","189,646 ",,"1,101,783","898,217 ",,599.0 ms +17-Apr-18,6:50 AM,"4,955,484","(1,455,484)",,"1,299,510","700,490 ",,606.0 ms +17-Apr-18,7:00 AM,"2,914,083","585,918 ",,"1,322,355","677,646 ",,594.0 ms +17-Apr-18,7:10 AM,"2,001,109","1,498,891 ",,"894,655","1,105,345 ",,579.0 ms +17-Apr-18,7:20 AM,"3,466,822","33,178 ",,"1,111,712","888,288 ",,592.0 ms +17-Apr-18,7:30 AM,"2,452,903","1,047,097 ",,"876,566","1,123,434 ",,581.0 ms +17-Apr-18,7:40 AM,"1,431,392","2,068,608 ",,"842,184","1,157,816 ",,580.0 ms +17-Apr-18,7:50 AM,"2,382,830","1,117,171 ",,"1,114,027","885,973 ",,590.0 ms +17-Apr-18,8:00 AM,"3,039,440","460,560 ",,"1,143,912","856,088 ",,620.0 ms +17-Apr-18,8:10 AM,"2,579,218","920,782 ",,"1,420,126","579,874 ",,592.0 ms +17-Apr-18,8:20 AM,"2,326,956","1,173,044 ",,"1,447,227","552,774 ",,604.0 ms +17-Apr-18,8:30 AM,"1,667,593","1,832,408 ",,"1,194,381","805,619 ",,595.0 ms +17-Apr-18,8:40 AM,"2,049,805","1,450,195 ",,"958,651","1,041,349 ",,632.0 ms +17-Apr-18,8:50 AM,"2,073,694","1,426,306 ",,"1,180,958","819,042 ",,606.0 ms +17-Apr-18,9:00 AM,"2,537,934","962,066 ",,"1,410,595","589,405 ",,604.0 ms +17-Apr-18,9:10 AM,"2,191,306","1,308,694 ",,"1,609,124","390,876 ",,602.0 ms +17-Apr-18,9:20 AM,"3,765,222","(265,222)",,"1,461,295","538,706 ",,588.0 ms +17-Apr-18,9:30 AM,"3,169,856","330,145 ",,"863,035","1,136,965 ",,602.0 ms +17-Apr-18,9:40 AM,"1,756,523","1,743,478 ",,"908,697","1,091,303 ",,570.0 ms +17-Apr-18,9:50 AM,"1,476,638","2,023,362 ",,"888,162","1,111,838 ",,581.0 ms +17-Apr-18,10:00 AM,"2,155,493","1,344,507 ",,"1,032,559","967,441 ",,592.0 ms +17-Apr-18,10:10 AM,"2,989,562","510,438 ",,"999,232","1,000,768 ",,605.0 ms +17-Apr-18,10:20 AM,"2,073,988","1,426,012 ",,"948,741","1,051,259 ",,616.0 ms +17-Apr-18,10:30 AM,"2,265,421","1,234,579 ",,"1,276,777","723,223 ",,589.0 ms +17-Apr-18,10:40 AM,"2,209,960","1,290,040 ",,"1,364,715","635,286 ",,584.0 ms +17-Apr-18,10:50 AM,"1,626,577","1,873,423 ",,"1,039,820","960,180 ",,611.0 ms +17-Apr-18,11:00 AM,"2,725,881","774,119 ",,"1,060,675","939,325 ",,633.0 ms +17-Apr-18,11:10 AM,"2,154,282","1,345,718 ",,"752,533","1,247,467 ",,658.0 ms +17-Apr-18,11:20 AM,"2,527,923","972,078 ",,"756,302","1,243,698 ",,611.0 ms +17-Apr-18,11:30 AM,"3,380,812","119,188 ",,"1,852,023","147,977 ",,676.0 ms +17-Apr-18,11:40 AM,"2,744,875","755,125 ",,"1,445,407","554,593 ",,600.0 ms +17-Apr-18,11:50 AM,"3,279,473","220,528 ",,"1,744,807","255,193 ",,608.0 ms +17-Apr-18,12:00 PM,"2,882,423","617,577 ",,"1,384,511","615,489 ",,593.0 ms +17-Apr-18,12:10 PM,"2,470,756","1,029,244 ",,"1,148,503","851,497 ",,586.0 ms +17-Apr-18,12:20 PM,"1,991,357","1,508,643 ",,"1,242,656","757,344 ",,639.0 ms +17-Apr-18,12:30 PM,"3,270,372","229,628 ",,"1,140,534","859,466 ",,584.0 ms +17-Apr-18,12:40 PM,"2,237,235","1,262,766 ",,"928,873","1,071,127 ",,587.0 ms +17-Apr-18,12:50 PM,"1,904,759","1,595,242 ",,"960,196","1,039,804 ",,660.0 ms +17-Apr-18,1:00 PM,"2,088,172","1,411,828 ",,"892,583","1,107,418 ",,615.0 ms +17-Apr-18,1:10 PM,"1,580,005","1,919,995 ",,"795,515","1,204,485 ",,589.0 ms +17-Apr-18,1:20 PM,"2,508,150","991,851 ",,"921,377","1,078,623 ",,590.0 ms +17-Apr-18,1:30 PM,"2,448,018","1,051,982 ",,"827,108","1,172,892 ",,611.0 ms +17-Apr-18,1:40 PM,"2,260,934","1,239,066 ",,"906,409","1,093,591 ",,608.0 ms +17-Apr-18,1:50 PM,"3,283,461","216,539 ",,"776,923","1,223,077 ",,594.0 ms +17-Apr-18,2:00 PM,"1,565,823","1,934,177 ",,"682,332","1,317,668 ",,590.0 ms +17-Apr-18,2:10 PM,"1,401,374","2,098,626 ",,"690,451","1,309,549 ",,592.0 ms +17-Apr-18,2:20 PM,"1,306,519","2,193,481 ",,"616,082","1,383,918 ",,586.0 ms +17-Apr-18,2:30 PM,"1,560,210","1,939,791 ",,"1,265,202","734,798 ",,583.0 ms +17-Apr-18,2:40 PM,"2,100,363","1,399,637 ",,"785,970","1,214,030 ",,590.0 ms +17-Apr-18,2:50 PM,"1,668,537","1,831,464 ",,"778,367","1,221,633 ",,593.0 ms +17-Apr-18,3:00 PM,"1,872,999","1,627,001 ",,"1,239,986","760,014 ",,586.0 ms +17-Apr-18,3:10 PM,"2,222,087","1,277,913 ",,"1,306,714","693,286 ",,615.0 ms +17-Apr-18,3:20 PM,"2,995,972","504,029 ",,"1,556,904","443,096 ",,622.0 ms +17-Apr-18,3:30 PM,"2,935,047","564,953 ",,"1,931,347","68,654 ",,604.0 ms +17-Apr-18,3:40 PM,"2,466,646","1,033,354 ",,"1,295,864","704,136 ",,607.0 ms +17-Apr-18,3:50 PM,"1,851,795","1,648,206 ",,"851,563","1,148,438 ",,622.0 ms +17-Apr-18,4:00 PM,"3,747,583","(247,583)",,"1,542,219","457,781 ",,644.0 ms +17-Apr-18,4:10 PM,"2,649,470","850,530 ",,"1,453,825","546,175 ",,632.0 ms +17-Apr-18,4:20 PM,"2,124,627","1,375,374 ",,"1,734,159","265,841 ",,614.0 ms +17-Apr-18,4:30 PM,"1,308,558","2,191,442 ",,"758,859","1,241,141 ",,588.0 ms +17-Apr-18,4:40 PM,"2,109,877","1,390,123 ",,"980,328","1,019,672 ",,625.0 ms +17-Apr-18,4:50 PM,"2,413,123","1,086,877 ",,"1,015,367","984,633 ",,587.0 ms +17-Apr-18,5:00 PM,"3,337,943","162,057 ",,"1,091,971","908,029 ",,579.0 ms +17-Apr-18,5:10 PM,"2,874,119","625,882 ",,"873,155","1,126,845 ",,571.0 ms +17-Apr-18,5:20 PM,"2,104,804","1,395,197 ",,"839,095","1,160,905 ",,579.0 ms +17-Apr-18,5:30 PM,"2,206,066","1,293,934 ",,"919,711","1,080,289 ",,599.0 ms +17-Apr-18,5:40 PM,"3,197,907","302,093 ",,"1,288,191","711,809 ",,593.0 ms +17-Apr-18,5:50 PM,"2,290,624","1,209,376 ",,"980,317","1,019,683 ",,587.0 ms +17-Apr-18,6:00 PM,"4,485,331","(985,331)",,"1,234,945","765,055 ",,604.0 ms +17-Apr-18,6:10 PM,"4,148,476","(648,476)",,"2,033,158","(33,158)",,647.0 ms +17-Apr-18,6:20 PM,"3,837,679","(337,679)",,"1,829,976","170,024 ",,667.0 ms +17-Apr-18,6:30 PM,"3,526,733","(26,733)",,"2,174,302","(174,302)",,596.0 ms +17-Apr-18,6:40 PM,"4,176,621","(676,621)",,"2,518,787","(518,787)",,594.0 ms +17-Apr-18,6:50 PM,"3,689,362","(189,362)",,"1,939,759","60,241 ",,614.0 ms +17-Apr-18,7:00 PM,"3,958,383","(458,383)",,"2,158,793","(158,793)",,640.0 ms +17-Apr-18,7:10 PM,"3,676,295","(176,295)",,"2,143,085","(143,085)",,732.0 ms +17-Apr-18,7:20 PM,"4,766,842","(1,266,842)",,"1,972,280","27,720 ",,596.0 ms +17-Apr-18,7:30 PM,"4,545,931","(1,045,931)",,"2,205,265","(205,265)",,596.0 ms diff --git a/lanforge/lanforge-scripts/openwrt_ctl.py b/lanforge/lanforge-scripts/openwrt_ctl.py new file mode 100755 index 000000000..f84fa664d --- /dev/null +++ b/lanforge/lanforge-scripts/openwrt_ctl.py @@ -0,0 +1,366 @@ +#!/usr/bin/python3 +''' + +make sure pexpect is installed: +$ sudo yum install python3-pexpect + +You might need to install pexpect-serial using pip: +$ pip3 install pexpect-serial + +./openwrt_ctl.py -l stdout -u root -p TIP -s serial --tty ttyUSB0 + +# Set up reverse ssh tunnel +./openwrt_ctl.py --tty /dev/ttyAP1 --action ssh-tunnel \ + --value "ssh -y -y -f -N -T -M -R 9999:localhost:22 lanforge@10.28.3.100" \ + --value2 password-for-10.28.3.100 --log stdout --scheme serial --prompt root@Open +''' + + +import sys +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +import re +import logging +import time +from time import sleep +import pprint +import telnetlib +import argparse +import pexpect + +default_host = "localhost" +default_ports = { + "serial": None, + "ssh": 22, + "telnet": 23 +} +NL = "\n" +CR = "\r\n" +Q = '"' +A = "'" +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' +prompt = "root@OpenWrt:" + +def usage(): + print("$0 used connect to OpenWrt AP or similar Linux machine:") + print("-d|--dest IP address of the OpenWrt AP, for ssh/telnet scheme") + print("-o|--port IP port of the OpenWrt AP, for ssh/telnet scheme") + print("-t|--tty Serial port, if using serial scheme") + print("-u|--user login name") + print("-p|--pass password") + print("--prompt Prompt to look for when commands are done (default: root@OpenWrt)") + print("-s|--scheme (serial|telnet|ssh): connect via serial, ssh or telnet") + print("-l|--log file log messages here") + print("--action (logread | journalctl | lurk | sysupgrade | download | upload | reboot | cmd | ssh-tunnel") + print("--value (option to help complete the action") + print("--value2 (option to help complete the action, dest filename for download, passwd for ssh-tunnel") + print("-h|--help") + +# see https://stackoverflow.com/a/13306095/11014343 +class FileAdapter(object): + def __init__(self, logger): + self.logger = logger + def write(self, data): + # NOTE: data can be a partial line, multiple lines + data = data.strip() # ignore leading/trailing whitespace + if data: # non-blank + self.logger.info(data) + def flush(self): + pass # leave it to logging to flush properly + +def main(): + global prompt + + parser = argparse.ArgumentParser(description="OpenWrt AP Control Script") + parser.add_argument("-d", "--dest", type=str, help="address of the cisco controller") + parser.add_argument("-o", "--port", type=int, help="control port on the controller") + parser.add_argument("-u", "--user", type=str, help="credential login/username") + parser.add_argument("-p", "--passwd", type=str, help="credential password") + parser.add_argument("-P", "--prompt", type=str, help="Prompt to look for") + parser.add_argument("-s", "--scheme", type=str, choices=["serial", "ssh", "telnet"], help="Connect via serial, ssh or telnet") + parser.add_argument("-t", "--tty", type=str, help="tty serial device") + parser.add_argument("-l", "--log", type=str, help="logfile for messages, stdout means output to console") + parser.add_argument("--action", type=str, help="perform action", + choices=["logread", "journalctl", "lurk", "sysupgrade", "sysupgrade-n", "download", "upload", "reboot", "cmd", "ssh-tunnel" ]) + parser.add_argument("--value", type=str, help="set value") + parser.add_argument("--value2", type=str, help="set value2") + tty = None + + args = None + try: + args = parser.parse_args() + host = args.dest + scheme = args.scheme + port = args.port + #port = (default_ports[scheme], args.port)[args.port != None] + user = args.user + passwd = args.passwd + logfile = args.log + tty = args.tty; + if (args.prompt != None): + prompt = args.prompt + filehandler = None + except Exception as e: + logging.exception(e); + usage() + exit(2); + + console_handler = logging.StreamHandler() + formatter = logging.Formatter(FORMAT) + logg = logging.getLogger(__name__) + logg.setLevel(logging.DEBUG) + file_handler = None + if (logfile is not None): + if (logfile != "stdout"): + file_handler = logging.FileHandler(logfile, "w") + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + logg.addHandler(file_handler) + logging.basicConfig(format=FORMAT, handlers=[file_handler]) + else: + # stdout logging + logging.basicConfig(format=FORMAT, handlers=[console_handler]) + + CCPROMPT=prompt + + ser = None + egg = None # think "eggpect" + try: + if (scheme == "serial"): + #eggspect = pexpect.fdpexpect.fdspan(telcon, logfile=sys.stdout.buffer) + import serial + from pexpect_serial import SerialSpawn + ser = serial.Serial(tty, 115200, timeout=5) + + egg = SerialSpawn(ser); + egg.logfile = FileAdapter(logg) + egg.sendline(NL) + has_reset = False + try: + logg.info("prompt: %s user: %s passwd: %s"%(prompt, user, passwd)) + while True: + i = egg.expect([prompt, "Please press Enter to activate", "login:", "Password:", "IPQ6018#"], timeout=3) + logg.info("expect-0: %i"%(i)) + if (i == 0): + logg.info("Found prompt, login complete.") + break + if (i == 1): + logg.info("Sending newline") + egg.setdline(NL) + if (i == 2): + logg.info("Sending username: %s"%(user)) + egg.sendline(user) + if (i == 3): + logg.info("Sending password: %s"%(passwd)) + egg.sendline(passwd) + if (i == 4): # in bootloader + if has_reset: + logg.info("ERROR: Have reset once already, back in bootloader?") + sys.exit(1) + has_reset = True + logg.info("In boot loader, will reset and sleep 30 seconds") + egg.sendline("reset") + time.sleep(30) + egg.sendline(NL) + + except Exception as e: + # maybe something like 'logread -f' is running? + # send ctrl-c + egg.send(chr(3)) + + elif (scheme == "ssh"): + # Not implemented/tested currently. --Ben + if (port is None): + port = 22 + cmd = "ssh -p%d %s@%s"%(port, user, host) + logg.info("Spawn: "+cmd+NL) + egg = pexpect.spawn(cmd) + #egg.logfile_read = sys.stdout.buffer + egg.logfile = FileAdapter(logg) + i = egg.expect(["password:", "continue connecting (yes/no)?"], timeout=3) + time.sleep(0.1) + if i == 1: + egg.sendline('yes') + egg.expect('password:') + sleep(0.1) + egg.sendline(passwd) + + elif (scheme == "telnet"): + # Not implemented/tested currently. --Ben + if (port is None): + port = 23 + cmd = "telnet %s %d"%(host, port) + logg.info("Spawn: "+cmd+NL) + egg = pexpect.spawn(cmd) + egg.logfile = FileAdapter(logg) + time.sleep(0.1) + egg.sendline(' ') + egg.expect('User\:') + egg.sendline(user) + egg.expect('Password\:') + egg.sendline(passwd) + egg.sendline('config paging disable') + else: + usage() + exit(1) + except Exception as e: + logging.exception(e); + + command = None + + CLOSEDBYREMOTE = "closed by remote host." + CLOSEDCX = "Connection to .* closed." + + try: + egg.expect(CCPROMPT) + except Exception as e: + egg.sendline(NL) + + TO=10 + wait_forever = False + + # Clean pending output + egg.sendline("echo __hello__") + egg.expect("__hello__") + egg.expect(CCPROMPT) + + logg.info("Action[%s] Value[%s] Value2[%s]"%(args.action, args.value, args.value2)) + + if (args.action == "reboot"): + command = "reboot" + TO=60 + + if (args.action == "cmd"): + if (args.value is None): + raise Exception("cmd requires value to be set.") + command = "%s"%(args.value) + + if (args.action == "logread"): + command = "logread -f" + TO=1 + wait_forever = True + + if (args.action == "journalctl"): + command = "journalctl -f" + TO=1 + wait_forever = True + + if (args.action == "lurk"): + command = "date" + TO=1 + wait_forever = True + + if (args.action == "ssh-tunnel"): + command = "%s"%(args.value) + passwd = "%s"%(args.value2) + logg.info("Command[%s]"%command) + egg.sendline(command); + + i = egg.expect(["password:", "Do you want to continue connecting"], timeout=5) + if i == 1: + egg.sendline("y") + egg.expect("password:", timeout=5) + egg.sendline(passwd) + egg.expect(CCPROMPT, timeout=20) + return + + if ((args.action == "sysupgrade") or (args.action == "sysupgrade-n")): + command = "scp %s /tmp/new_img.bin"%(args.value) + logg.info("Command[%s]"%command) + egg.sendline(command); + + i = egg.expect(["password:", "Do you want to continue connecting"], timeout=5) + if i == 1: + egg.sendline("y") + egg.expect("password:", timeout=5) + egg.sendline("lanforge") + egg.expect(CCPROMPT, timeout=20) + if (args.action == "sysupgrade-n"): + egg.sendline("sysupgrade -n /tmp/new_img.bin") + else: + egg.sendline("sysupgrade /tmp/new_img.bin") + egg.expect("link becomes ready", timeout=100) + return + + if (args.action == "download"): + command = "scp %s /tmp/%s"%(args.value, args.value2) + logg.info("Command[%s]"%command) + egg.sendline(command); + + i = egg.expect(["password:", "Do you want to continue connecting", "Network unreachable"], timeout=5) + if i == 2: + print("Network unreachable, wait 15 seconds and try again.") + time.sleep(15) + command = "scp %s /tmp/%s"%(args.value, args.value2) + logg.info("Command[%s]"%command) + egg.sendline(command); + + i = egg.expect(["password:", "Do you want to continue connecting", "Network unreachable"], timeout=5) + if i == 2: + print("ERROR: Could not connect to LANforge to get download file") + exit(2) + if i == 1: + egg.sendline("y") + egg.expect("password:", timeout=5) + egg.sendline("lanforge") + egg.expect(CCPROMPT, timeout=20) + return + + if (args.action == "upload"): + command = "scp %s %s"%(args.value, args.value2) + logg.info("Command[%s]"%command) + egg.sendline(command); + + i = egg.expect(["password:", "Do you want to continue connecting", "Network unreachable"], timeout=5) + if i == 2: + print("Network unreachable, wait 15 seconds and try again.") + time.sleep(15) + command = "scp /tmp/%s %s"%(args.value, args.value2) + logg.info("Command[%s]"%command) + egg.sendline(command); + + i = egg.expect(["password:", "Do you want to continue connecting", "Network unreachable"], timeout=5) + if i == 2: + print("ERROR: Could not connect to LANforge to put upload file") + exit(2) + if i == 1: + egg.sendline("y") + egg.expect("password:", timeout=5) + egg.sendline("lanforge") + egg.expect(CCPROMPT, timeout=20) + return + + if (command is None): + logg.info("No command specified, going to log out.") + else: + logg.info("Command[%s]"%command) + egg.sendline(command); + while True: + try: + i = egg.expect([CCPROMPT, "kmodloader: done loading kernel", "\n"], timeout=TO) + print (egg.before.decode('utf-8', 'ignore')) + if i == 1: + egg.sendline(' ') + egg.expect(CCPROMPT, timeout=20) + print (egg.before.decode('utf-8', 'ignore')) + if i == 2: # new line of text, just print and continue + continue + + if not wait_forever: + break + + except Exception as e: + # Some commands take a long time (logread -f) + if not wait_forever: + logging.exception(e) + break + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if __name__ == '__main__': + main() + +#### +#### +#### diff --git a/lanforge/lanforge-scripts/portal-check.pl b/lanforge/lanforge-scripts/portal-check.pl new file mode 100755 index 000000000..65f2df1fe --- /dev/null +++ b/lanforge/lanforge-scripts/portal-check.pl @@ -0,0 +1,235 @@ +#!/usr/bin/perl -w +=pod +------------------------------------------------------------------------------- + Use this script to survey a captive portal station: + * check DNS settings via dig + * check initial page redirect with curl + ## (C) 2017, Candela Technologies Inc. support@candelatech.com +------------------------------------------------------------------------------- +=cut +package main; +use strict; +use warnings; +use diagnostics; +use Carp; +#$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +#$SIG{ __WARN__ } = sub { Carp::confess( @_ ) }; +use POSIX qw(ceil floor); +use Scalar::Util; #::looks_like_number; +use Getopt::Long; +use Socket; + +# Un-buffer output +$| = 1; +use Cwd qw(getcwd); +my $cwd = getcwd(); + + # this is pedantic necessity for the following use statements +if ( $cwd =~ q(.*LANforge-Server\scripts$)) { + use lib '/home/lanforge/scripts' +} +else { + use lib '/home/lanforge/scripts'; +} +use List::Util qw(first); +use LANforge::Endpoint; +use LANforge::Port; +use LANforge::Utils; +use Net::Telnet (); + +my $usage = qq($0 [--mgr {host-name | IP}] + [--mgr_port {ip port}] # use if on non-default management port + [--resource {resource}] # use if multiple lanforge systems; defaults to 1 + [--quiet { yes | no }] # debug output; -q + [--log_cli] # enables CLI command printing to STDOUT + # same effect when setting env var LOG_CLI=STDOUT + [--radio {name}] # radio parent of sta1000 e.g. wiphy0 + [--sta {name}] # station to use e.g. sta1000 or wlan0 + [--ssid {ssid}] # ssid to set station on + [--upstream {dev}] # e.g. eth1 # attempt to ping upstream port from station + [--verbose|v] + ); + +if (@ARGV < 2) { + print $usage; + exit 0; +} +my $help; +our $lfmgr_host = "localhost"; +my $lfmgr_port = 4001; +our $resource = 1; +our $quiet = "yes"; +our $sta_wiphy = "wiphy0"; +our $ssid = ""; +our $sta; +our $upstream_port = ""; +my $log_cli; +our $verbose = 0; +GetOptions +( + 'mgr|m=s' => \$::lfmgr_host, + 'mgr_port|p=i' => \$lfmgr_port, + 'resource|r=i' => \$::resource, + 'quiet|q=s' => \$::quiet, + 'radio|o=s' => \$::sta_wiphy, + 'ssid|s=s' => \$::ssid, + 'upstream|t=s' => \$::upstream_port, + 'log_cli=s{0,1}' => \$log_cli, # use ENV{LOG_CLI} elsewhere + 'sta|w=s' => \$::sta, + 'verbose|v=i' => \$::verbose, + 'help|?' => \$help, +) || (print($usage) && exit(1)); + +if ($help) { + print($usage) && exit(0); +} +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; + +if ($::quiet eq "0") { + $::quiet = "no"; +} +elsif ($::quiet eq "1") { + $::quiet = "yes"; +} + +# Open connection to the LANforge server. +if (defined $log_cli) { + if ($log_cli ne "unset") { + # here is how we reset the variable if it was used as a flag + if ($log_cli eq "") { + $ENV{'LOG_CLI'} = 1; + } + else { + $ENV{'LOG_CLI'} = $log_cli; + } + } +} + +our $t = new Net::Telnet(Prompt => '/default\@btbits\>\>/', + Timeout => 20); +$t->open(Host => $lfmgr_host, + Port => $lfmgr_port, + Timeout => 10); +$t->waitfor("/btbits\>\>/"); + +# Configure our utils. +our $utils = new LANforge::Utils(); +$utils->telnet($t); # Set our telnet object. +if ($utils->isQuiet()) { + if (defined $ENV{'LOG_CLI'} && $ENV{'LOG_CLI'} ne "") { + $utils->cli_send_silent(0); + } + else { + $utils->cli_send_silent(1); # Do not show input to telnet + } + $utils->cli_rcv_silent(1); # Repress output from telnet +} +else { + $utils->cli_send_silent(0); # Show input to telnet + $utils->cli_rcv_silent(0); # Show output from telnet +} +$utils->log_cli("# $0 ".`date "+%Y-%m-%d %H:%M:%S"`); + +# this is the --show_port options ("") +my @port_txt = (); +our $port_mac = ''; +our $port_dev = ''; +our $port_ip = ''; +our $port_dns = ''; +our $port_gateway = ''; +if ((defined $::sta) && ("$sta" ne "")) { + @port_txt = split("\n", $utils->doAsyncCmd("nc_show_port 1 $::resource $::sta")); +} +my @likely = (); +if ($::verbose > 0) { + print "--------------------------------------------\n"; + print join("\n", @port_txt); + print "--------------------------------------------\n"; +} + +@likely = grep {/MAC: .* DEV: .*/} @port_txt; +die("Unable to find port named $::sta") + if (!@likely); +if (@likely) { + ($port_mac) = $likely[0] =~ /MAC: ([^ ]+) /; + $port_mac = '' + if (!defined $port_ip); + ($port_dev) = $likely[0] =~ /DEV: ([^ ]+) /; + $port_dev = '' + if (!defined $port_ip); +} + +@likely = grep {/IP: .* MASK:/} @port_txt; +if (@likely) { + ($port_ip) = $likely[0] =~ /IP: ([^ ]+) /; + $port_ip = '' + if (!defined $port_ip); +} + +@likely = grep {/DNS Servers:/} @port_txt; +if (@likely) { + if ($verbose) { + print "LIKELY DNS SERVERS:\n"; + print join("\n", @likely)."\n"; + } + ($port_dns) = $likely[0] =~ /DNS Servers: (.*)$/; + $port_dns ='' + if (!defined $port_dns) +} +@likely = grep {/GW: /} @port_txt; +if (@likely) { + ($port_gateway) = $likely[0] =~ /GW: ([^ ]+) /; + $port_gateway = '' + if (!defined $port_gateway); +} + +print qq(Port name: $sta IP: $port_ip; DNS: $port_dns; GW: $port_gateway;\n\n); + +if ($port_ip eq '') { + print "\nNo tests possible, bye\n"; + exit(1); +} + +if ($port_gateway ne '') { + print "\nChecking gateway: $port_gateway\n"; + print("ping -c2 -w2 -t2 -I $port_ip $port_gateway \n"); + system("ping -c2 -w2 -t2 -I $port_ip $port_gateway"); +} +else { + print "Not checking gateway\n"; +} + +if ($port_dns ne '') { + my @dns = split(',', $port_dns); + if (@dns) { + print "\nChecking DNS: $dns[0]\n"; + print("\nping -c2 -w2 -t2 -I $port_ip $dns[0]\n"); + system("ping -c2 -w2 -t2 -I $port_ip $dns[0]"); + print("\ndig -b $port_ip -q www.slashdot.org \@$dns[0]\n"); + system("dig -b $port_ip -q www.slashdot.org \@$dns[0]"); + } +} +else { + print "Not checking DNS\n"; +} + +sub assembleCurl { + return qq(/home/lanforge/local/bin/curl -svLki -4 -m30 --connect-timeout 15 ) + .qq( --interface $port_dev ) + .qq( --localaddr $port_ip ) + .qq( --dns-interface $port_dev ) + .qq( --dns-ipv4-addr $port_ip ) + .qq( --dns-servers $::port_dns ) + .join(" ", @_); +} +print "\nChecking redirect to http://1.1.1.1/ ...\n"; +my $cmd = assembleCurl(qw("http://1.1.1.1/")); +print("\n$cmd\n"); +system("$cmd"); + +print "\n\nChecking redirect to http://www.slashdot.org/ ...\n"; +$cmd = assembleCurl(qw("http://www.slashdot.org/")); +print("\n$cmd\n"); +system("$cmd"); +print "\n\n"; +# diff --git a/lanforge/lanforge-scripts/print_udev.sh b/lanforge/lanforge-scripts/print_udev.sh new file mode 100755 index 000000000..9eb57bc35 --- /dev/null +++ b/lanforge/lanforge-scripts/print_udev.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +li_show_lines=$(ip -o li show) + +while read line ; do + #echo "* $line" + line=${line#*: } + ifname='' + mac='' + case $line in + eth* | enp* | wlan*) + #echo "LIKE: $line" + hunks=($line); + ifname="${hunks[0]}" + ifname="${ifname%:*}" + #echo "N: ${#hunks[@]}" + for i in `seq 1 ${#hunks[@]}`; do + #echo "$i ${hunks[$i]}" + if [ ! -z "${hunks[$i]}" -a "${hunks[$i]}" = "link/ether" ]; then + mac="${hunks[ $[ $i + 1] ]}" + break; + fi + done + #echo "Hi! $ifname has [$mac]" + echo 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="'$mac'", ATTR{dev_id}=="0x0", ATTR{type}=="1", NAME="'$ifname'" ENV{NM_UNMANAGED}="1"' + ;; + *) + #echo "IGNORING: $line" + ;; + esac +done <<< "$li_show_lines" diff --git a/lanforge/lanforge-scripts/pulse_detect.py b/lanforge/lanforge-scripts/pulse_detect.py new file mode 100755 index 000000000..cecd96aa3 --- /dev/null +++ b/lanforge/lanforge-scripts/pulse_detect.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +################################################## +# GNU Radio Python Flow Graph +# Title: Pulse Detect +# GNU Radio version: 3.7.13.5 +################################################## +hackrf_sn='000000000000000087c867dc2a44625f' +center_freq=5180e6 +gnuradio_points=100000 + +if __name__ == '__main__': + import ctypes + import sys + import os + import argparse + + # test for QT_X11_NO_MITSHM=1 + if 'QT_X11_NO_MITSHM' not in os.environ: + print("QT_X11_NO_MITSHM not set. Please export QT_X11_NO_MITSHM=1") + exit(1); + if os.environ['QT_X11_NO_MITSHM'] is not "1": + print("QT_X11_NO_MITSHM not 1. Please export QT_X11_NO_MITSHM=1") + exit(1); + + if sys.platform.startswith('linux'): + try: + x11 = ctypes.cdll.LoadLibrary('libX11.so') + x11.XInitThreads() + except: + print("Warning: failed to XInitThreads()") + + unixOptions = "f:s:p:" + gnuOptions = [ "freq=", "serno=", "points=" ] + parser = argparse.ArgumentParser(description='pulse watcher') + parser.add_argument("--serno", type=str, help="hackrf serial number") + parser.add_argument("--freq", type=int, help="center frequency in Hz") + parser.add_argument("--points", type=int, help="number of x-axis points") + args = parser.parse_args() + hackrf_sn = args.serno + center_freq = args.freq + gnuradio_points = args.points + +from PyQt4 import Qt +from gnuradio import blocks +from gnuradio import eng_notation +from gnuradio import gr +from gnuradio import qtgui +from gnuradio.eng_option import eng_option +from gnuradio.filter import firdes +from optparse import OptionParser +import osmosdr +import os +import sip +import sys +import argparse +import time +from gnuradio import qtgui + +class pulse_detect(gr.top_block, Qt.QWidget): + + # divide sample rate by points to get time scale in millisec + gnuradio_points = args.points + + def __init__(self): + gr.top_block.__init__(self, "Pulse Detect") + Qt.QWidget.__init__(self) + self.setWindowTitle("Pulse Detect") + qtgui.util.check_set_qss() + try: + self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc')) + except: + pass + self.top_scroll_layout = Qt.QVBoxLayout() + self.setLayout(self.top_scroll_layout) + self.top_scroll = Qt.QScrollArea() + self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame) + self.top_scroll_layout.addWidget(self.top_scroll) + self.top_scroll.setWidgetResizable(True) + self.top_widget = Qt.QWidget() + self.top_scroll.setWidget(self.top_widget) + self.top_layout = Qt.QVBoxLayout(self.top_widget) + self.top_grid_layout = Qt.QGridLayout() + self.top_layout.addLayout(self.top_grid_layout) + + self.settings = Qt.QSettings("GNU Radio", "pulse_detect") + self.restoreGeometry(self.settings.value("geometry").toByteArray()) + + + ################################################## + # Variables + ################################################## + self.samp_rate = samp_rate = 3e6 + + ################################################## + # Blocks + ################################################## + self.qtgui_time_sink_x_0 = qtgui.time_sink_f( + self.gnuradio_points, + samp_rate, #samp_rate + "", #name + 1 #number of inputs + ) + self.qtgui_time_sink_x_0.set_update_time(0.10) + self.qtgui_time_sink_x_0.set_y_axis(0, 2) + + self.qtgui_time_sink_x_0.set_y_label('Amplitude', "") + + self.qtgui_time_sink_x_0.enable_tags(-1, True) + self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_NORM, qtgui.TRIG_SLOPE_POS, 0.5, 0, 0, "") + self.qtgui_time_sink_x_0.enable_autoscale(False) + self.qtgui_time_sink_x_0.enable_grid(False) + self.qtgui_time_sink_x_0.enable_axis_labels(True) + self.qtgui_time_sink_x_0.enable_control_panel(True) + self.qtgui_time_sink_x_0.enable_stem_plot(False) + + if not True: + self.qtgui_time_sink_x_0.disable_legend() + + labels = ['', '', '', '', '', + '', '', '', '', ''] + widths = [1, 1, 1, 1, 1, + 1, 1, 1, 1, 1] + colors = ["blue", "red", "green", "black", "cyan", + "magenta", "yellow", "dark red", "dark green", "blue"] + styles = [1, 1, 1, 1, 1, + 1, 1, 1, 1, 1] + markers = [-1, -1, -1, -1, -1, + -1, -1, -1, -1, -1] + alphas = [1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0] + + for i in xrange(1): + if len(labels[i]) == 0: + self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i)) + else: + self.qtgui_time_sink_x_0.set_line_label(i, labels[i]) + self.qtgui_time_sink_x_0.set_line_width(i, widths[i]) + self.qtgui_time_sink_x_0.set_line_color(i, colors[i]) + self.qtgui_time_sink_x_0.set_line_style(i, styles[i]) + self.qtgui_time_sink_x_0.set_line_marker(i, markers[i]) + self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i]) + + self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget) + self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win) + osmodr_source_args = "numchan=1 hackrf=%s"%(hackrf_sn) + self.osmosdr_source_0 = osmosdr.source( args=osmodr_source_args ) # serno into variable + self.osmosdr_source_0.set_sample_rate(samp_rate) + self.osmosdr_source_0.set_center_freq(center_freq, 0) # turn into variable + self.osmosdr_source_0.set_freq_corr(0, 0) + self.osmosdr_source_0.set_dc_offset_mode(0, 0) + self.osmosdr_source_0.set_iq_balance_mode(0, 0) + self.osmosdr_source_0.set_gain_mode(False, 0) + self.osmosdr_source_0.set_gain(10, 0) + self.osmosdr_source_0.set_if_gain(32, 0) + self.osmosdr_source_0.set_bb_gain(32, 0) + self.osmosdr_source_0.set_antenna('', 0) + self.osmosdr_source_0.set_bandwidth(0, 0) + + self.blocks_complex_to_mag_0 = blocks.complex_to_mag(1) + + + + ################################################## + # Connections + ################################################## + self.connect((self.blocks_complex_to_mag_0, 0), (self.qtgui_time_sink_x_0, 0)) + self.connect((self.osmosdr_source_0, 0), (self.blocks_complex_to_mag_0, 0)) + + def closeEvent(self, event): + self.settings = Qt.QSettings("GNU Radio", "pulse_detect") + self.settings.setValue("geometry", self.saveGeometry()) + event.accept() + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate) + self.osmosdr_source_0.set_sample_rate(self.samp_rate) + + +def main(top_block_cls=pulse_detect, options=None): + + from distutils.version import StrictVersion + if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"): + style = gr.prefs().get_string('qtgui', 'style', 'raster') + Qt.QApplication.setGraphicsSystem(style) + qapp = Qt.QApplication(sys.argv) + + tb = top_block_cls() + tb.start() + tb.show() + + def quitting(): + tb.stop() + tb.wait() + qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting) + qapp.exec_() + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-dashboard/GhostRequest.py b/lanforge/lanforge-scripts/py-dashboard/GhostRequest.py new file mode 100644 index 000000000..ce0dc586a --- /dev/null +++ b/lanforge/lanforge-scripts/py-dashboard/GhostRequest.py @@ -0,0 +1,600 @@ +#!/usr/bin/env python3 +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Class holds default settings for json requests to Ghost - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +import sys +import os +import importlib +import requests +import jwt +from datetime import datetime +import json +import subprocess +from scp import SCPClient +import paramiko +import time +from collections import Counter +import shutil +import itertools + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +GrafanaRequest = importlib.import_module("py-dashboard.GrafanaRequest") +InfluxRequest = importlib.import_module("py-dashboard.InfluxRequest") +RecordInflux = InfluxRequest.RecordInflux + + +class CSVReader: + def read_csv(self, + file, + sep='\t'): + df = open(file).read().split('\n') + rows = list() + for x in df: + if len(x) > 0: + rows.append(x.split(sep)) + return rows + + def get_column(self, + df, + value): + index = df[0].index(value) + values = [] + for row in df[1:]: + values.append(row[index]) + return values + + def get_columns(self, df, targets): + target_index = [] + for item in targets: + target_index.append(df[0].index(item)) + results = [] + for row in df: + row_data = [] + for x in target_index: + row_data.append(row[x]) + results.append(row_data) + return results + + def to_html(self, df): + html = '' + html = html + ('<table style="border:1px solid #ddd">' + '<colgroup>' + '<col style="width:25%">' + '<col style="width:25%">' + '<col style="width:50%">' + '</colgroup>' + '<tbody>' + '<tr>') + for row in df: + for item in row: + html = html + ('<td style="border:1px solid #ddd">%s</td>' % item) + html = html + '</tr>\n<tr>' + html = html + ('</tbody>' + '</table>') + return html + + def filter_df(self, df, column, expression, target): + target_index = df[0].index(column) + counter = 0 + targets = [0] + for row in df[1:]: + try: + if expression == 'less than': + if float(row[target_index]) < target: + targets.append(counter) + if expression == 'greater than': + if float(row[target_index]) > target: + targets.append(counter) + if expression == 'greater than or equal to': + if float(row[target_index]) >= target: + targets.append(counter) + finally: + pass + counter += 1 + return list(map(df.__getitem__, targets)) + + def concat(self, dfs): + return list(itertools.chain.from_iterable(dfs)) + + +class GhostRequest: + def __init__(self, + _ghost_json_host, + _ghost_json_port, + _api_token=None, + _overwrite='false', + debug_=False, + die_on_error_=False, + influx_host=None, + influx_port=8086, + influx_org=None, + influx_token=None, + influx_bucket=None): + self.debug = debug_ + self.die_on_error = die_on_error_ + self.ghost_json_host = _ghost_json_host + self.ghost_json_port = _ghost_json_port + self.ghost_json_url = "http://%s:%s/ghost/api/v3" % (_ghost_json_host, _ghost_json_port) + self.data = dict() + self.data['overwrite'] = _overwrite + self.ghost_json_login = self.ghost_json_url + '/admin/session/' + self.api_token = _api_token + self.images = list() + self.webpages = list() + self.pdfs = list() + self.influx_host = influx_host + self.influx_port = influx_port + self.influx_org = influx_org + self.influx_token = influx_token + self.influx_bucket = influx_bucket + + def encode_token(self): + + # Split the key into ID and SECRET + key_id, secret = self.api_token.split(':') + + # Prepare header and payload + iat = int(datetime.now().timestamp()) + + header = {'alg': 'HS256', 'typ': 'JWT', 'kid': key_id} + payload = { + 'iat': iat, + 'exp': iat + 5 * 60, + 'aud': '/v3/admin/' + } + token = jwt.encode(payload, bytes.fromhex(secret), algorithm='HS256', headers=header) + return token + + def create_post(self, + title=None, + text=None, + status="published"): + ghost_json_url = self.ghost_json_url + '/admin/posts/?source=html' + post = dict() + posts = list() + datastore = dict() + datastore['html'] = text + datastore['title'] = title + datastore['status'] = status + posts.append(datastore) + post['posts'] = posts + + headers = dict() + + token = self.encode_token() + headers['Authorization'] = 'Ghost {}'.format(token) + response = requests.post(ghost_json_url, json=post, headers=headers) + if self.debug: + print(datastore) + print(ghost_json_url) + print('\n') + print(post) + print('\n') + print(headers) + print(response.headers) + + def upload_image(self, + image): + if self.debug: + print(image) + ghost_json_url = self.ghost_json_url + '/admin/images/upload/' + + token = self.encode_token() + bash_command = "curl -X POST -F 'file=@%s' -H \"Authorization: Ghost %s\" %s" % (image, token, ghost_json_url) + + proc = subprocess.Popen(bash_command, shell=True, stdout=subprocess.PIPE) + output = proc.stdout.read().decode('utf-8') + if self.debug: + print(output) + self.images.append(json.loads(output)['images'][0]['url']) + + def upload_images(self, + folder): + for image in os.listdir(folder): + if 'kpi' in image: + if 'png' in image: + self.upload_image(folder + '/' + image) + if self.debug: + print('images %s' % self.images) + + def custom_post(self, + folder, + authors, + title='custom'): + self.upload_images(folder) + head = '''This is a custom post created via a script''' + for picture in self.images: + head = head + '<img src="%s"></img>' % picture + head = head + '''This is the end of the example''' + self.create_post(title=title, + text=head) + + def kpi_to_ghost(self, + authors, + folders, + parent_folder=None, + title=None, + server_pull=None, + ghost_host=None, + port=22, + user_push=None, + password_push=None, + customer=None, + testbed=None, + test_run=None, + target_folders=list(), + grafana_token=None, + grafana_host=None, + grafana_port=3000, + grafana_datasource='InfluxDB', + grafana_bucket=None): + global dut_hw, dut_sw, dut_model, dut_serial + + now = datetime.now() + + text = '' + csvreader = CSVReader() + if self.debug: + print('Folders: %s' % folders) + + ssh_push = paramiko.SSHClient() + ssh_push.set_missing_host_key_policy(paramiko.client.AutoAddPolicy) + ssh_push.connect(ghost_host, + port, + username=user_push, + password=password_push, + allow_agent=False, + look_for_keys=False) + scp_push = SCPClient(ssh_push.get_transport()) + + if parent_folder is not None: + files = os.listdir(parent_folder) + if self.debug: + print("parent_folder %s" % parent_folder) + print(files) + for file in files: + if os.path.isdir(parent_folder + '/' + file) is True: + if os.path.exists(file): + shutil.rmtree(file) + shutil.copytree(parent_folder + '/' + file, file) + target_folders.append(file) + if self.debug: + print('Target folders: %s' % target_folders) + else: + for folder in folders: + if self.debug: + print(folder) + target_folders.append(folder) + + testbeds = list() + web_pages_and_pdfs = list() + high_priority_list = list() + low_priority_list = list() + images = list() + times = list() + test_pass_fail = list() + subtest_pass_fail = list() + subtest_pass_total = 0 + subtest_fail_total = 0 + test_tag_1 = list() + columns = ['test-rig', 'dut-hw-version', 'dut-sw-version', + 'dut-model-num', 'dut-serial-num'] + duts = dict() + + for target_folder in target_folders: + try: + target_file = '%s/kpi.csv' % target_folder + df = csvreader.read_csv(file=target_file, sep='\t') + test_id = csvreader.get_column(df, 'test-id')[0] + for column in columns: + try: + column_data = csvreader.get_column(df, column)[0] + duts[column] = column_data + except: + print('no column named %s' % column) + test_tag_1.append([test_id, list(set(csvreader.get_column(df, 'test-tag')))]) + pass_fail = Counter(csvreader.get_column(df, 'pass/fail')) + test_pass_fail.append(pass_fail) + subtest_pass = csvreader.get_column(df, 'Subtest-Pass') + subtest_fail = csvreader.get_column(df, 'Subtest-Fail') + for result in subtest_pass: + subtest_pass_total += int(result) + for result in subtest_fail: + subtest_fail_total += int(result) + subtest_pass_fail_list = dict() + subtest_pass_fail_list['PASS'] = subtest_pass_total + subtest_pass_fail_list['FAIL'] = subtest_fail_total + subtest_pass_fail.append(subtest_pass_fail_list) + times_append = csvreader.get_column(df, 'Date') + if len(times_append) == 0: + print(LookupError("%s/kpi.csv has no time points" % target_folder)) + break + for target_time in times_append: + times.append(float(target_time) / 1000) + if pass_fail['PASS'] + pass_fail['FAIL'] > 0: + text = text + 'Tests passed: %s<br />' % pass_fail['PASS'] + text = text + 'Tests failed: %s<br />' % pass_fail['FAIL'] + text = text + 'Percentage of tests passed: %s<br />' % ( + pass_fail['PASS'] / (pass_fail['PASS'] + pass_fail['FAIL'])) + else: + text = text + 'Tests passed: 0<br />' \ + 'Tests failed : 0<br />' \ + 'Percentage of tests passed: Not Applicable<br />' + testbeds.append(duts['test-rig']) + if testbed is None: + testbed = duts['test-rig'] + + if test_run is None: + test_run = now.strftime('%B-%d-%Y-%I-%M-%p-report') + local_path = '/home/%s/%s/%s/%s' % (user_push, customer, testbed, test_run) + + transport = paramiko.Transport(ghost_host, port) + transport.connect(None, user_push, password_push) + sftp = paramiko.sftp_client.SFTPClient.from_transport(transport) + + if self.debug: + print(local_path) + print(target_folder) + + try: + sftp.mkdir('/home/%s/%s/%s' % (user_push, customer, testbed)) + except: + pass + + try: + sftp.mkdir(local_path) + except: + pass + scp_push.put(target_folder, local_path, recursive=True) + files = sftp.listdir(local_path + '/' + target_folder) + pdfs = list() + webpages = list() + for file in files: + if 'pdf' in file: + url = 'http://%s/%s/%s/%s/%s/%s' % ( + ghost_host, customer.strip('/'), testbed, test_run, target_folder, file) + pdfs.append('<a href="%s">PDF</a>' % url) + if 'index.html' in files: + url = 'http://%s/%s/%s/%s/%s/%s' % ( + ghost_host, customer.strip('/'), testbed, test_run, target_folder, 'index.html') + webpages.append('<a href="%s">HTML</a>' % url) + web_pages_and_pdfs_append = dict() + web_pages_and_pdfs_append[test_id] = pdfs + webpages + web_pages_and_pdfs.append(web_pages_and_pdfs_append) + scp_push.close() + self.upload_images(target_folder) + for image in self.images: + if 'kpi-' in image: + if '-print' not in image: + images.append('<img src="%s"></img>' % image) + self.images = [] + + results = csvreader.get_columns(df, ['short-description', 'numeric-score', 'test details', 'pass/fail', + 'test-priority']) + + results[0] = ['Short Description', 'Score', 'Test Details', 'Pass or Fail', 'test-priority'] + for row in results: + try: + row[1] = round(float(row[1]), 2) + except: + pass + + low_priority = csvreader.filter_df(results, 'test-priority', 'less than', 94) + if self.debug: + print('Low Priority results %s' % len(low_priority)) + high_priority = csvreader.filter_df(results, 'test-priority', 'greater than or equal to', 95) + high_priority_list.append(high_priority) + low_priority_list.append(low_priority) + + except: + print("Failed to process %s" % target_folder) + target_folders.remove(target_folder) + failuredict = dict() + failuredict[target_folder] = ['Failure'] + web_pages_and_pdfs.append(failuredict) + test_tag = dict() + for x in list(set([x[0] for x in test_tag_1])): + l3 = list() + for sublist in test_tag_1: + if sublist[0] == x: + l3 += sublist[1] + test_tag[x] = l3 + if len(times) == 0: + return ArithmeticError("There are no datapoints in any folders passed into Ghost") + + test_pass_fail_results = sum((Counter(test) for test in test_pass_fail), Counter()) + subtest_pass_fail_results = sum((Counter(test) for test in subtest_pass_fail), Counter()) + + if self.debug: + print(times) + end_time = max(times) + start_time = '2021-07-01' + end_time = datetime.utcfromtimestamp(end_time) + now = time.time() + offset = datetime.fromtimestamp(now) - datetime.utcfromtimestamp(now) + end_time = end_time + offset + + high_priority = csvreader.concat(high_priority_list) + low_priority = csvreader.concat(low_priority_list) + + if len(high_priority) > 0: + high_priority = csvreader.get_columns(high_priority, + ['Short Description', 'Score', 'Test Details']) + low_priority = csvreader.get_columns(low_priority, + ['Short Description', 'Score', 'Test Details']) + high_priority.append(['Total Passed', test_pass_fail_results['PASS'], 'Total subtests passed during this run']) + high_priority.append(['Total Failed', test_pass_fail_results['FAIL'], 'Total subtests failed during this run']) + high_priority.append( + ['Subtests Passed', subtest_pass_fail_results['PASS'], 'Total subtests passed during this run']) + high_priority.append( + ['Subtests Failed', subtest_pass_fail_results['FAIL'], 'Total subtests failed during this run']) + + if title is None: + title = end_time.strftime('%B %d, %Y %I:%M %p report') + + # create Grafana Dashboard + target_files = [] + for folder in target_folders: + target_file=folder.split('/')[-1] + '/kpi.csv' + try: + open(target_file) + target_files.append(target_file) + except: + pass + if self.debug: + print('Target files: %s' % target_files) + + text = 'Testbed: %s<br />' % testbeds[0] + if self.influx_token is not None: + influxdb = RecordInflux(_influx_host=self.influx_host, + _influx_port=self.influx_port, + _influx_org=self.influx_org, + _influx_token=self.influx_token, + _influx_bucket=self.influx_bucket) + try: + short_description = 'Tests passed' # variable name + numeric_score = test_pass_fail_results['PASS'] # value + tags = dict() + if self.debug: + print(datetime.utcfromtimestamp(max(times))) + tags['testbed'] = testbeds[0] + tags['script'] = 'GhostRequest' + tags['Graph-Group'] = 'PASS' + date = datetime.utcfromtimestamp(max(times)).isoformat() + influxdb.post_to_influx(short_description, numeric_score, tags, date) + + short_description = 'Tests failed' # variable name + numeric_score = test_pass_fail_results['FAIL'] # value + tags = dict() + tags['testbed'] = testbeds[0] + tags['script'] = 'GhostRequest' + tags['Graph-Group'] = 'FAIL' + date = datetime.utcfromtimestamp(max(times)).isoformat() + influxdb.post_to_influx(short_description, numeric_score, tags, date) + + short_description = 'Subtests passed' # variable name + numeric_score = subtest_pass_fail_results['PASS'] # value + tags = dict() + if self.debug: + print(datetime.utcfromtimestamp(max(times))) + tags['testbed'] = testbeds[0] + tags['script'] = 'GhostRequest' + tags['Graph-Group'] = 'Subtest PASS' + date = datetime.utcfromtimestamp(max(times)).isoformat() + influxdb.post_to_influx(short_description, numeric_score, tags, date) + + short_description = 'Subtests failed' # variable name + numeric_score = subtest_pass_fail_results['FAIL'] # value + tags = dict() + tags['testbed'] = testbeds[0] + tags['script'] = 'GhostRequest' + tags['Graph-Group'] = 'Subtest FAIL' + date = datetime.utcfromtimestamp(max(times)).isoformat() + influxdb.post_to_influx(short_description, numeric_score, tags, date) + except Exception as err: + influx_error = err + text += '''InfluxDB Error: %s<br /> + Influx Host: %s<br /> + Influx Port: %s<br /> + Influx Organization: %s<br /> + Influx Bucket: %s<br />''' % (influx_error, self.influx_host, self.influx_port, self.influx_org, self.influx_bucket) + + raw_test_tags = list() + test_tag_table = '' + for tag in test_tag.values(): + for value in tag: + raw_test_tags.append(value) + for value in list(set(raw_test_tags)): + test_tag_table += ( + '<tr><td style="border-color: gray; border-style: solid; border-width: 1px; ">Test Tag</td><td colspan="3" style="border-color: gray; border-style: solid; border-width: 1px; ">%s</td></tr>' % value) + dut_table_column_names = {'test-rig': 'Testbed', + 'dut-hw-version': 'DUT HW', + 'dut-sw-version': 'DUT SW', + 'dut-model-num': 'DUT Model', + 'dut-serial-num': 'DUT Serial'} + dut_table_columns = '' + for column in columns: + if column in dut_table_column_names.keys(): + column_name = dut_table_column_names[column] + else: + column_name = column + dut_table_columns += ( + '<tr><td style="border-color: gray; border-style: solid; border-width: 1px; ">%s</td><td colspan="3" style="border-color: gray; border-style: solid; border-width: 1px; ">%s</td></tr>' % ( + column_name, duts[column]) + ) + + dut_table = '<table width="700px" border="1" cellpadding="2" cellspacing="0" ' \ + 'style="border-color: gray; border-style: solid; border-width: 1px; "><tbody>' \ + '<tr><th colspan="2">Test Information</th></tr>' \ + '%s' \ + '%s' \ + '<tr><td style="border-color: gray; border-style: solid; border-width: 1px; ">Tests passed</td>' \ + '<td colspan="3" style="border-color: gray; border-style: solid; border-width: 1px; ">%s</td></tr>' \ + '<tr><td style="border-color: gray; border-style: solid; border-width: 1px; ">Tests failed</td>' \ + '<td colspan="3" style="border-color: gray; border-style: solid; border-width: 1px; ">%s</td></tr>' \ + '<tr><td style="border-color: gray; border-style: solid; border-width: 1px; ">Subtests passed</td>' \ + '<td colspan="3" style="border-color: gray; border-style: solid; border-width: 1px; ">%s</td></tr>' \ + '<tr><td style="border-color: gray; border-style: solid; border-width: 1px; ">Subtests failed</td>' \ + '<td colspan="3" style="border-color: gray; border-style: solid; border-width: 1px; ">%s</td></tr>' \ + '</tbody></table>' % ( + dut_table_columns, test_tag_table, test_pass_fail_results['PASS'], + test_pass_fail_results['FAIL'], subtest_pass_total, subtest_fail_total) + text = text + dut_table + + for dictionary in web_pages_and_pdfs: + text += list(dictionary.keys())[0] + ' report: ' + for value in dictionary.values(): + for webpage in value: + text += webpage + if value.index(webpage) + 1 != len(value): + text += ' | ' + text += '<br />' + + for image in images: + text = text + image + + text = text + 'High priority results: %s' % csvreader.to_html(high_priority) + + if grafana_token is not None: + grafana = GrafanaRequest(grafana_token, + grafana_host, + grafanajson_port=grafana_port, + debug_=self.debug + ) + if self.debug: + print('Test Tag: %s' % test_tag) + try: + grafana.create_custom_dashboard(target_csvs=target_files, + title=title, + datasource=grafana_datasource, + bucket=grafana_bucket, + from_date=start_time, + to_date=end_time.strftime('%Y-%m-%d %H:%M:%S'), + pass_fail='GhostRequest', + testbed=testbeds[0], + test_tag=test_tag) + # get the details of the dashboard through the API, and set the end date to the youngest KPI + grafana.list_dashboards() + + grafana.create_snapshot(title='Testbed: ' + title) + time.sleep(3) + snapshot = grafana.list_snapshots()[-1] + text = text + '<iframe src="http://%s:3000/dashboard/snapshot/%s" width="100%s" height=1500></iframe><br />' % ( + grafana_host, snapshot['key'], '%') + except Exception as err: + grafana_error = err + text = text + '''Grafana Error: %s<br /> + Grafana credentials:<br /> + Grafana Host: %s<br /> + Grafana Bucket: %s<br /> + Grafana Database: %s<br />''' % (grafana_error, grafana_host, grafana_bucket, grafana_datasource) + + text = text + 'Low priority results: %s' % csvreader.to_html(low_priority) + + self.create_post(title=title, + text=text) diff --git a/lanforge/lanforge-scripts/py-dashboard/GrafanaRequest.py b/lanforge/lanforge-scripts/py-dashboard/GrafanaRequest.py new file mode 100644 index 000000000..727818d21 --- /dev/null +++ b/lanforge/lanforge-scripts/py-dashboard/GrafanaRequest.py @@ -0,0 +1,469 @@ +#!/usr/bin/env python3 + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Class holds default settings for json requests to Grafana - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +import sys + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +import requests + +import json +import string +import random + + +class CSVReader: + def __init__(self): + self.shape = None + + def read_csv(self, + file, + sep='\t'): + df = open(file).read().split('\n') + rows = list() + for x in df: + if len(x) > 0: + rows.append(x.split(sep)) + length = list(range(0, len(df[0]))) + columns = dict(zip(df[0], length)) + self.shape = (length, columns) + return rows + + def get_column(self, + df, + value): + index = df[0].index(value) + values = [] + for row in df[1:]: + values.append(row[index]) + return values + + +class GrafanaRequest: + def __init__(self, + _grafana_token, + _grafanajson_host, + grafanajson_port=3000, + _folderID=0, + _headers=dict(), + _overwrite='false', + debug_=False, + die_on_error_=False): + self.debug = debug_ + self.die_on_error = die_on_error_ + self.headers = _headers + self.headers['Authorization'] = 'Bearer ' + _grafana_token + self.headers['Content-Type'] = 'application/json' + self.grafanajson_host = _grafanajson_host + self.grafanajson_port = grafanajson_port + self.grafanajson_token = _grafana_token + self.grafanajson_url = "http://%s:%s" % (_grafanajson_host, grafanajson_port) + self.data = dict() + self.data['overwrite'] = _overwrite + self.csvreader = CSVReader() + self.units = dict() + + def create_bucket(self, + bucket_name=None): + # Create a bucket in Grafana + if bucket_name is not None: + pass + + def list_dashboards(self): + url = self.grafanajson_url + '/api/search' + print(url) + return json.loads(requests.get(url, headers=self.headers).text) + + def create_dashboard(self, + dashboard_name=None, + ): + grafanajson_url = self.grafanajson_url + "/api/dashboards/db" + datastore = dict() + dashboard = dict() + dashboard['id'] = None + dashboard['title'] = dashboard_name + dashboard['tags'] = ['templated'] + dashboard['timezone'] = 'browser' + dashboard['schemaVersion'] = 27 + dashboard['version'] = 4 + datastore['dashboard'] = dashboard + datastore['overwrite'] = False + data = json.dumps(datastore, indent=4) + return requests.post(grafanajson_url, headers=self.headers, data=data, verify=False) + + def delete_dashboard(self, + dashboard_uid=None): + grafanajson_url = self.grafanajson_url + "/api/dashboards/uid/" + dashboard_uid + return requests.post(grafanajson_url, headers=self.headers, verify=False) + + def create_dashboard_from_data(self, + json_file=None): + grafanajson_url = self.grafanajson_url + '/api/dashboards/db' + datastore = dict() + dashboard = dict(json.loads(open(json_file).read())) + datastore['dashboard'] = dashboard + datastore['overwrite'] = False + data = json.dumps(datastore, indent=4) + return requests.post(grafanajson_url, headers=self.headers, data=data, verify=False) + + def create_dashboard_from_dict(self, + dictionary=None, + overwrite=False): + grafanajson_url = self.grafanajson_url + '/api/dashboards/db' + datastore = dict() + dashboard = dict(json.loads(dictionary)) + datastore['dashboard'] = dashboard + datastore['overwrite'] = overwrite + data = json.dumps(datastore, indent=4) + return requests.post(grafanajson_url, headers=self.headers, data=data, verify=False) + + def get_graph_groups(self, target_csvs): # Get the unique values in the Graph-Group column + dictionary = dict() + for target_csv in target_csvs: + if len(target_csv) > 1: + csv = self.csvreader.read_csv(target_csv) + # Unique values in the test-id column + scripts = list(set(self.csvreader.get_column(csv, 'test-id'))) + # we need to make sure we match each Graph Group to the script it occurs in + for script in scripts: + # Unique Graph Groups for each script + graph_groups = self.csvreader.get_column(csv, 'Graph-Group') + dictionary[script] = list(set(graph_groups)) + units = self.csvreader.get_column(csv, 'Units') + self.units[script] = dict() + for index in range(0, len(graph_groups)): + self.units[script][graph_groups[index]] = units[index] + subtests = 0 + for score in list(self.csvreader.get_column(csv, 'Subtest-Pass')): + subtests += int(score) + for score in list(self.csvreader.get_column(csv, 'Subtest-Fail')): + subtests += int(score) + if subtests > 0: + dictionary[script].append('Subtests passed') + dictionary[script].append('Subtests failed') + print(subtests) + for item in dictionary[script]: + print('%s, %s' % (item, type(item))) + print(dictionary) + return dictionary + + def maketargets(self, + bucket, + scriptname, + groupBy, + index, + graph_group, + testbed, + test_tag=None): + query = ( + 'from(bucket: "%s")\n ' + '|> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n ' + '|> filter(fn: (r) => r["script"] == "%s")\n ' + '|> group(columns: ["_measurement"])\n ' + % (bucket, scriptname)) + queryend = ('|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n ' + '|> yield(name: "mean")\n ') + if graph_group is not None: + graphgroup = ('|> filter(fn: (r) => r["Graph-Group"] == "%s")\n' % graph_group) + query += graphgroup + if test_tag is not None and len(test_tag) > 0: + graphgroup = ('|> filter(fn: (r) => r["Test Tag"] == "%s")\n' % test_tag) + query += graphgroup + if testbed is not None: + query += ('|> filter(fn: (r) => r["testbed"] == "%s")\n' % testbed) + targets = dict() + targets['delimiter'] = ',' + targets['groupBy'] = groupBy + targets['header'] = True + targets['ignoreUnknown'] = False + targets['orderByTime'] = 'ASC' + targets['policy'] = 'default' + targets['query'] = query + queryend + targets['refId'] = dict(enumerate(string.ascii_uppercase, 1))[index + 1] + targets['resultFormat'] = "time_series" + targets['schema'] = list() + targets['skipRows'] = 0 + targets['tags'] = list() + return targets + + def groupby(self, params, grouptype): + dic = dict() + dic['params'] = list() + dic['params'].append(params) + dic['type'] = grouptype + return dic + + def create_custom_dashboard(self, + scripts=None, + title=None, + bucket=None, + graph_groups=None, + graph_groups_file=None, + target_csvs=None, + testbed=None, + datasource='InfluxDB', + from_date='now-1y', + to_date='now', + graph_height=8, + graph_width=12, + pass_fail=None, + test_tag=None): + options = string.ascii_lowercase + string.ascii_uppercase + string.digits + uid = ''.join(random.choice(options) for i in range(9)) + input1 = dict() + annotations = dict() + annotations['builtIn'] = 1 + annotations['datasource'] = '-- Grafana --' + annotations['enable'] = True + annotations['hide'] = True + annotations['iconColor'] = 'rgba(0, 211, 255, 1)' + annotations['name'] = 'Annotations & Alerts' + annotations['type'] = 'dashboard' + annot = dict() + annot['list'] = list() + annot['list'].append(annotations) + + templating = dict() + templating['list'] = list() + + timedict = dict() + timedict['from'] = from_date + timedict['to'] = to_date + + panels = list() + index = 1 + if graph_groups_file: + print("graph_groups_file: %s" % graph_groups_file) + target_csvs = open(graph_groups_file).read().split('\n') + graph_groups = self.get_graph_groups( + target_csvs) # Get the list of graph groups which are in the tests we ran + if target_csvs: + print('Target CSVs: %s' % target_csvs) + graph_groups = self.get_graph_groups( + target_csvs) # Get the list of graph groups which are in the tests we ran + if pass_fail is not None: + graph_groups[pass_fail] = ['PASS', 'FAIL'] + + print('Test Tag in Grafana: %s' % test_tag) + + for scriptname in graph_groups.keys(): + print(scriptname) + if scriptname in test_tag.keys(): + for tag in test_tag[scriptname]: + print('Script: %s, Tag: %s' % (scriptname, tag)) + panel = self.create_panel(graph_groups, + graph_height, + graph_width, + scriptname, + bucket, + testbed, + tag, + datasource, + index) + panels.append(panel) + index = index + 1 + else: + panel = self.create_panel(graph_groups, + graph_height, + graph_width, + scriptname, + bucket, + testbed, + None, + datasource, + index) + panels.append(panel) + index = index + 1 + + input1['annotations'] = annot + input1['editable'] = True + input1['gnetId'] = None + input1['graphTooltip'] = 0 + input1['links'] = list() + input1['panels'] = panels + input1['refresh'] = False + input1['schemaVersion'] = 27 + input1['style'] = 'dark' + input1['tags'] = list() + input1['templating'] = templating + input1['time'] = timedict + input1['timepicker'] = dict() + input1['timezone'] = '' + input1['title'] = ("Testbed: %s" % title) + input1['uid'] = uid + input1['version'] = 11 + return self.create_dashboard_from_dict(dictionary=json.dumps(input1)) + + def create_panel(self, + graph_groups, + graph_height, + graph_width, + scriptname, + bucket, + testbed, + test_tag, + datasource, + index): + print('Test Tag: %s' % test_tag) + for graph_group in graph_groups[scriptname]: + panel = dict() + + gridpos = dict() + gridpos['h'] = graph_height + gridpos['w'] = graph_width + gridpos['x'] = 0 + gridpos['y'] = 0 + + legend = dict() + legend['avg'] = False + legend['current'] = False + legend['max'] = False + legend['min'] = False + legend['show'] = True + legend['total'] = False + legend['values'] = False + + options = dict() + options['alertThreshold'] = True + + groupBy = list() + groupBy.append(self.groupby('$__interval', 'time')) + groupBy.append(self.groupby('null', 'fill')) + + targets = list() + counter = 0 + try: + new_target = self.maketargets(bucket, + scriptname, + groupBy, + counter, + graph_group, + testbed, + test_tag=test_tag) + except: + new_target = self.maketargets(bucket, scriptname, groupBy, counter, graph_group, testbed) + targets.append(new_target) + + fieldConfig = dict() + fieldConfig['defaults'] = dict() + fieldConfig['overrides'] = list() + + transformation = dict() + transformation['id'] = "renameByRegex" + transformation_options = dict() + transformation_options['regex'] = "(.*) value.*" + transformation_options['renamePattern'] = "$1" + transformation['options'] = transformation_options + + xaxis = dict() + xaxis['buckets'] = None + xaxis['mode'] = "time" + xaxis['name'] = None + xaxis['show'] = True + xaxis['values'] = list() + + yaxis = dict() + yaxis['format'] = 'short' + try: + yaxis['label'] = self.units[scriptname][graph_group] + except: + pass + yaxis['logBase'] = 1 + yaxis['max'] = None + yaxis['min'] = None + yaxis['show'] = True + + yaxis1 = dict() + yaxis1['align'] = False + yaxis1['alignLevel'] = None + + panel['aliasColors'] = dict() + panel['bars'] = False + panel['dashes'] = False + panel['dashLength'] = 10 + panel['datasource'] = datasource + panel['fieldConfig'] = fieldConfig + panel['fill'] = 0 + panel['fillGradient'] = 0 + panel['gridPos'] = gridpos + panel['hiddenSeries'] = False + panel['id'] = index + panel['legend'] = legend + panel['lines'] = True + panel['linewidth'] = 1 + panel['nullPointMode'] = 'null' + panel['options'] = options + panel['percentage'] = False + panel['pluginVersion'] = '7.5.4' + panel['pointradius'] = 2 + panel['points'] = True + panel['renderer'] = 'flot' + panel['seriesOverrides'] = list() + panel['spaceLength'] = 10 + panel['stack'] = False + panel['steppedLine'] = False + panel['targets'] = targets + panel['thresholds'] = list() + panel['timeFrom'] = None + panel['timeRegions'] = list() + panel['timeShift'] = None + + if graph_group is not None: + scriptname = '%s: %s' % (scriptname, graph_group) + if test_tag is not None: + scriptname = '%s: %s' % (scriptname, test_tag) + scriptname = '%s: %s' % (scriptname, testbed) + panel['title'] = scriptname + + if self.debug: + print(panel['title']) + panel['transformations'] = list() + panel['transformations'].append(transformation) + panel['type'] = "graph" + panel['xaxis'] = xaxis + panel['yaxes'] = list() + panel['yaxes'].append(yaxis) + panel['yaxes'].append(yaxis) + panel['yaxis'] = yaxis1 + return panel + + def create_snapshot(self, title): + print('create snapshot') + grafanajson_url = self.grafanajson_url + '/api/snapshots' + data = self.get_dashboard(title) + data['expires'] = False + data['external'] = False + data['timeout'] = 15 + if self.debug: + print(data) + return requests.post(grafanajson_url, headers=self.headers, json=data, verify=False).text + + def list_snapshots(self): + grafanajson_url = self.grafanajson_url + '/api/dashboard/snapshots' + print(grafanajson_url) + return json.loads(requests.get(grafanajson_url, headers=self.headers, verify=False).text) + + def get_dashboard(self, target): + dashboards = self.list_dashboards() + print(target) + for dashboard in dashboards: + if dashboard['title'] == target: + uid = dashboard['uid'] + grafanajson_url = self.grafanajson_url + '/api/dashboards/uid/' + uid + print(grafanajson_url) + return json.loads(requests.get(grafanajson_url, headers=self.headers, verify=False).text) + + def get_units(self, csv): + df = self.csvreader.read_csv(csv) + units = self.csvreader.get_column(df, 'Units') + test_id = self.csvreader.get_column(df, 'test-id') + maxunit = max(set(units), key=units.count) + maxtest = max(set(test_id), key=test_id.count) + d = dict() + d[maxunit] = maxtest + print(maxunit, maxtest) + return d \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-dashboard/InfluxRequest.py b/lanforge/lanforge-scripts/py-dashboard/InfluxRequest.py new file mode 100644 index 000000000..378381592 --- /dev/null +++ b/lanforge/lanforge-scripts/py-dashboard/InfluxRequest.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 + +# pip3 install influxdb-client + +# Version 2.0 influx DB Client + +import sys +import os + +import pandas as pd + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + + +import requests +import json +import influxdb_client +from influxdb_client.client.write_api import SYNCHRONOUS +import time + +import datetime + +def influx_add_parser_args(parser): + parser.add_argument('--influx_host', help='Hostname for the Influx database', default=None) + parser.add_argument('--influx_port', help='IP Port for the Influx database', default=8086) + parser.add_argument('--influx_org', help='Organization for the Influx database', default=None) + parser.add_argument('--influx_token', help='Token for the Influx database', default=None) + parser.add_argument('--influx_bucket', help='Name of the Influx bucket', default=None) + parser.add_argument('--influx_tag', action='append', nargs=2, + help='--influx_tag <key> <val> Can add more than one of these.', default=[]) + +class RecordInflux(): + def __init__(self, + _influx_host="localhost", + _influx_port=8086, + _influx_org=None, + _influx_token=None, + _influx_bucket=None, + _debug_on=False, + _exit_on_fail=False): + self.influx_host = _influx_host + self.influx_port = _influx_port + self.influx_org = _influx_org + self.influx_token = _influx_token + self.influx_bucket = _influx_bucket + self.url = "http://%s:%s" % (self.influx_host, self.influx_port) + self.client = influxdb_client.InfluxDBClient(url=self.url, + token=self.influx_token, + org=self.influx_org, + debug=_debug_on) + self.write_api = self.client.write_api(write_options=SYNCHRONOUS) + + def post_to_influx(self, key, value, tags, time): + p = influxdb_client.Point(key) + for tag_key, tag_value in tags.items(): + p.tag(tag_key, tag_value) + print(tag_key, tag_value) + p.time(time) + p.field("value", value) + self.write_api.write(bucket=self.influx_bucket, org=self.influx_org, record=p) + + def csv_to_influx(self, csv): + df = pd.read_csv(csv, sep='\t') + df['Date'] = [datetime.datetime.utcfromtimestamp(int(date) / 1000).isoformat() for date in df['Date']] + items = list(df.reset_index().transpose().to_dict().values()) + influx_variables = ['script', 'short-description', 'test_details', 'Graph-Group', + 'DUT-HW-version', 'DUT-SW-version', 'DUT-Serial-Num', 'testbed', 'Test Tag', 'Units'] + csv_variables = ['test-id', 'short-description', 'test details', 'Graph-Group', + 'dut-hw-version', 'dut-sw-version', 'dut-serial-num', 'test-rig', 'test-tag', 'Units'] + csv_vs_influx = dict(zip(csv_variables, influx_variables)) + columns = list(df.columns) + for item in items: + tags = dict() + short_description = item['short-description'] + numeric_score = item['numeric-score'] + date = item['Date'] + for variable in csv_variables: + if variable in columns: + influx_variable = csv_vs_influx[variable] + tags[influx_variable] = item[variable] + self.post_to_influx(short_description, numeric_score, tags, date) + + + def set_bucket(self, b): + self.influx_bucket = b + + # Don't use this unless you are sure you want to. + # More likely you would want to generate KPI in the + # individual test cases and poke those relatively small bits of + # info into influxdb. + # This will not end until the 'longevity' timer has expired. + # This function pushes data directly into the Influx database and defaults to all columns. + def monitor_port_data(self, + lanforge_host="localhost", + devices=None, + longevity=None, + monitor_interval=None, + bucket=None, + tags=None): # dict + url = 'http://' + lanforge_host + ':8080/port/1/1/' + end = datetime.datetime.now() + datetime.timedelta(0, longevity) + while datetime.datetime.now() < end: + for station in devices: + url1 = url + station + response = json.loads(requests.get(url1).text) + + current_time = str(datetime.datetime.utcnow().isoformat()) + + # Poke everything into influx db + for key in response['interface'].keys(): + self.post_to_influx("%s-%s" % (station, key), response['interface'][key], tags, current_time) + + time.sleep(monitor_interval) diff --git a/lanforge/lanforge-scripts/py-json/.gitignore b/lanforge/lanforge-scripts/py-json/.gitignore new file mode 100644 index 000000000..02a5d1dc5 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/.gitignore @@ -0,0 +1,3 @@ +*.pyc +__pycache__ +__pycache__/* diff --git a/lanforge/lanforge-scripts/py-json/LANforge/LFRequest.py b/lanforge/lanforge-scripts/py-json/LANforge/LFRequest.py new file mode 100644 index 000000000..7ea796c53 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/LFRequest.py @@ -0,0 +1,427 @@ +#!/usr/bin/env python3 + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Class holds default settings for json requests - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +import sys +import os +import importlib +import urllib +from urllib import request +import json + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") + + +class LFRequest: + Default_Base_URL = "http://localhost:8080" + No_Data = {'No Data':0} + requested_url = "" + post_data = No_Data + default_headers = { 'Accept': 'application/json'} + proxies = None + + def __init__(self, url=None, + uri=None, + proxies_=None, + debug_=False, + die_on_error_=False): + self.debug = debug_ + self.die_on_error = die_on_error_ + self.error_list = [] + + # please see this discussion on ProxyHandlers: + # https://docs.python.org/3/library/urllib.request.html#urllib.request.ProxyHandler + # but this makes much more sense: + # https://gist.github.com/aleiphoenix/4159510 + + # if debug_: + # if proxies_ is None: + # print("LFRequest_init_: no proxies_") + # else: + # print("LFRequest: proxies_: ") + # pprint.pprint(proxies_) + + if (proxies_ is not None) and (len(proxies_) > 0): + if ("http" not in proxies_) and ("https" not in proxies_): + raise ValueError("Neither http or https set in proxy definitions. Expects proxy={'http':, 'https':, }") + self.proxies = proxies_ + + # if debug_: + # if self.proxies is None: + # print("LFRequest_init_: no proxies") + # else: + # print("LFRequest: proxies: ") + # pprint.pprint(self.proxies) + + if not url.startswith("http://") and not url.startswith("https://"): + print("No http:// or https:// found, prepending http:// to "+url) + url = "http://" + url + if uri is not None: + if not url.endswith('/') and not uri.startswith('/'): + url += '/' + self.requested_url = url + uri + else: + self.requested_url = url + + if self.requested_url is None: + raise Exception("Bad LFRequest of url[%s] uri[%s] -> None" % (url, uri)) + + if self.requested_url.find('//'): + protopos = self.requested_url.find("://") + self.requested_url = self.requested_url[:protopos + 2] + self.requested_url[protopos + 2:].replace("//", "/") + + # finding '#' prolly indicates a macvlan (eth1#0) + # finding ' ' prolly indicates a field name that should imply %20 + if (self.requested_url.find('#') >= 1): + self.requested_url = self.requested_url.replace('#', '%23') + if (self.requested_url.find(' ') >= 1): + self.requested_url = self.requested_url.replace(' ', '+') + if self.debug: + print("new LFRequest[%s]" % self.requested_url ) + + # request first url on stack + def formPost(self, show_error=True, debug=False, die_on_error_=False): + return self.form_post(show_error=show_error, debug=debug, die_on_error_=die_on_error_) + + def form_post(self, show_error=True, debug=False, die_on_error_=False): + if self.die_on_error: + die_on_error_ = True + if (debug == False) and (self.debug == True): + debug = True + responses = [] + urlenc_data = "" + # https://stackoverflow.com/a/59635684/11014343 + if (self.proxies is not None) and (len(self.proxies) > 0): + # https://stackoverflow.com/a/59635684/11014343 + opener = request.build_opener(request.ProxyHandler(self.proxies)) + request.install_opener(opener) + + + if (debug): + print("formPost: url: "+self.requested_url) + if ((self.post_data != None) and (self.post_data is not self.No_Data)): + urlenc_data = urllib.parse.urlencode(self.post_data).encode("utf-8") + if (debug): + print("formPost: data looks like:" + str(urlenc_data)) + print("formPost: url: "+self.requested_url) + myrequest = request.Request(url=self.requested_url, + data=urlenc_data, + headers=self.default_headers) + else: + myrequest = request.Request(url=self.requested_url, headers=self.default_headers) + print("No data for this formPost?") + + myrequest.headers['Content-type'] = 'application/x-www-form-urlencoded' + + resp = '' + try: + resp = urllib.request.urlopen(myrequest) + responses.append(resp) + return responses[0] + + except urllib.error.HTTPError as error: + print_diagnostics(url_=self.requested_url, + request_=myrequest, + responses_=responses, + error_=error, + error_list_=self.error_list, + debug_=debug) + + except urllib.error.URLError as uerror: + print_diagnostics(url_=self.requested_url, + request_=myrequest, + responses_=responses, + error_=uerror, + error_list_=self.error_list, + debug_=debug) + + if (die_on_error_ == True) or (self.die_on_error == True): + exit(1) + return None + + def jsonPost(self, show_error=True, debug=False, die_on_error_=False, response_json_list_=None): + return self.json_post(show_error=show_error, debug=debug, die_on_error_=die_on_error_, response_json_list_=response_json_list_) + + def json_post(self, show_error=True, debug=False, die_on_error_=False, response_json_list_=None, method_='POST'): + if (debug == False) and (self.debug == True): + debug = True + if self.die_on_error: + die_on_error_ = True + responses = [] + if (self.proxies is not None) and (len(self.proxies) > 0): + opener = urllib.request.build_opener(request.ProxyHandler(self.proxies)) + urllib.request.install_opener(opener) + + if ((self.post_data != None) and (self.post_data is not self.No_Data)): + myrequest = request.Request(url=self.requested_url, + method=method_, + data=json.dumps(self.post_data).encode("utf-8"), + headers=self.default_headers) + else: + myrequest = request.Request(url=self.requested_url, headers=self.default_headers) + print("No data for this jsonPost?") + + myrequest.headers['Content-type'] = 'application/json' + + # https://stackoverflow.com/a/59635684/11014343 + + try: + resp = urllib.request.urlopen(myrequest) + resp_data = resp.read().decode('utf-8') + if (debug and die_on_error_): + print("----- LFRequest::json_post:128 debug: --------------------------------------------") + print("URL: %s :%d "% (self.requested_url, resp.status)) + if resp.status != 200: + LFUtils.debug_printer.pprint(resp.getheaders()) + print("----- resp_data:128 -------------------------------------------------") + print(resp_data) + print("-------------------------------------------------") + responses.append(resp) + if response_json_list_ is not None: + if type(response_json_list_) is not list: + raise ValueError("reponse_json_list_ needs to be type list") + j = json.loads(resp_data) + if debug: + print("----- LFRequest::json_post:140 debug: --------------------------------------------") + LFUtils.debug_printer.pprint(j) + print("-------------------------------------------------") + response_json_list_.append(j) + return responses[0] + + except urllib.error.HTTPError as error: + print_diagnostics(url_=self.requested_url, + request_=myrequest, + responses_=responses, + error_=error, + debug_=debug) + + except urllib.error.URLError as uerror: + print_diagnostics(url_=self.requested_url, + request_=myrequest, + responses_=responses, + error_=uerror, + debug_=debug) + + if die_on_error_ == True: + exit(1) + return None + + def json_put(self, show_error=True, debug=False, die_on_error_=False, response_json_list_=None): + return self.json_post(show_error=show_error, + debug=debug, + die_on_error_=die_on_error_, + response_json_list_=response_json_list_, + method_='PUT') + + def json_delete(self, show_error=True, debug=False, die_on_error_=False, response_json_list_=None): + return self.get_as_json(debug_=debug, + die_on_error_=die_on_error_, + method_='DELETE') + + def get(self, debug=False, die_on_error_=False, method_='GET'): + if self.debug == True: + debug = True + if self.die_on_error == True: + die_on_error_ = True + if debug: + print("LFUtils.get: url: "+self.requested_url) + + # https://stackoverflow.com/a/59635684/11014343 + if (self.proxies is not None) and (len(self.proxies) > 0): + opener = request.build_opener(request.ProxyHandler(self.proxies)) + #opener = urllib.request.build_opener(myrequest.ProxyHandler(self.proxies)) + request.install_opener(opener) + + myrequest = request.Request(url=self.requested_url, + headers=self.default_headers, + method=method_) + myresponses = [] + try: + myresponses.append(request.urlopen(myrequest)) + return myresponses[0] + + except urllib.error.HTTPError as error: + print_diagnostics(url_=self.requested_url, + request_=myrequest, + responses_=myresponses, + error_=error, + error_list_=self.error_list, + debug_=debug) + + except urllib.error.URLError as uerror: + print_diagnostics(url_=self.requested_url, + request_=myrequest, + responses_=myresponses, + error_=uerror, + error_list_=self.error_list, + debug_=debug) + + if die_on_error_ == True: + exit(1) + return None + + def getAsJson(self, die_on_error_=False, debug_=False): + return self.get_as_json(die_on_error_=die_on_error_, debug_=debug_) + + def get_as_json(self, die_on_error_=False, debug_=False, method_='GET'): + responses = [] + j = self.get(debug=debug_, die_on_error_=die_on_error_, method_=method_) + responses.append(j) + if len(responses) < 1: + if debug_ and self.has_errors(): + self.print_errors() + return None + if responses[0] == None: + if debug_: + print("No response from "+self.requested_url) + return None + json_data = json.loads(responses[0].read().decode('utf-8')) + return json_data + + def addPostData(self, data): + self.add_post_data(data=data) + + def add_post_data(self, data): + """ + TODO: this is a setter and should be named 'set_post_data' + :param data: dictionary of parameters for post + :return: nothing + """ + self.post_data = data + + def has_errors(self): + return (True, False)[len(self.error_list)>0] + + def print_errors(self): + if not self.has_errors: + print("---------- no errors ----------") + return + for err in self.error_list: + print("error: %s" % err) + +def plain_get(url_=None, debug_=False, die_on_error_=False, proxies_=None): + """ + This static method does not respect LFRequest.proxy, it is not set in scope here + :param url_: + :param debug_: + :param die_on_error_: + :return: + """ + myrequest = request.Request(url=url_) + myresponses = [] + try: + if (proxies_ is not None) and (len(proxies_) > 0): + # https://stackoverflow.com/a/59635684/11014343 + opener = myrequest.build_opener(myrequest.ProxyHandler(proxies_)) + myrequest.install_opener(opener) + + myresponses.append(request.urlopen(myrequest)) + return myresponses[0] + + except urllib.error.HTTPError as error: + print_diagnostics(url_=url_, + request_=request, + responses_=myresponses, + error_=error, + debug_=debug_) + + except urllib.error.URLError as uerror: + print_diagnostics(url_=url_, + request_=request, + responses_=myresponses, + error_=uerror, + debug_=debug_) + + if die_on_error_ == True: + exit(1) + return None + + +def print_diagnostics(url_=None, request_=None, responses_=None, error_=None, error_list_=None, debug_=False): + if debug_: + print("LFRequest::print_diagnostics: error_.__class__: %s"%error_.__class__) + LFUtils.debug_printer.pprint(error_) + + if url_ is None: + print("WARNING LFRequest::print_diagnostics: url_ is None") + if request_ is None: + print("WARNING LFRequest::print_diagnostics: request_ is None") + if error_ is None: + print("WARNING LFRequest::print_diagnostics: error_ is None") + + method = 'NA' + if (hasattr(request_, 'method')): + method = request_.method + err_code = 0 + err_reason = 'NA' + err_headers = [] + err_full_url = url_ + if hasattr(error_, 'code'): + err_code = error_.code + if hasattr(error_, 'reason'): + err_reason = error_.reason + if hasattr(error_, 'headers'): + err_headers = error_.headers + if hasattr(error_, 'get_full_url'): + err_full_url = error_.get_full_url() + xerrors = [] + if err_code == 404: + xerrors.append("[%s HTTP %s] <%s> : %s" % (method, err_code, err_full_url, err_reason)) + else: + if (len(err_headers) > 0): + for headername in sorted(err_headers.keys()): + if headername.startswith("X-Error-"): + xerrors.append("%s: %s" % (headername, err_headers.get(headername))) + if len(xerrors) > 0: + print(" = = LANforge Error Messages = =") + for xerr in xerrors: + print(xerr) + if (error_list_ is not None) and isinstance(error_list_, list): + error_list_.append(xerr) + print(" = = = = = = = = = = = = = = = =") + + if (error_.__class__ is urllib.error.HTTPError): + if debug_: + print("----- LFRequest: HTTPError: --------------------------------------------") + print("%s <%s> HTTP %s: %s" % (method, err_full_url, err_code, err_reason)) + + if err_code == 404: + if (error_list_ is not None) and isinstance(error_list_, list): + error_list_.append("[%s HTTP %s] <%s> : %s" % (method, err_code, err_full_url, err_reason)) + else: + if debug_: + print(" Content-type:[%s] Accept[%s]" % (request_.get_header('Content-type'), request_.get_header('Accept'))) + + if hasattr(request_, "data") and (request_.data is not None): + print(" Data:") + LFUtils.debug_printer.pprint(request_.data) + elif debug_: + print(" <no request data>") + + if debug_ and (len(err_headers) > 0): + # the HTTPError is of type HTTPMessage a subclass of email.message + print(" Response Headers: ") + for headername in sorted(err_headers.keys()): + print(" %s: %s" % (headername, err_headers.get(headername))) + + if len(responses_) > 0: + print("----- Response: --------------------------------------------------------") + LFUtils.debug_printer.pprint(responses_[0].reason) + if debug_: + print("------------------------------------------------------------------------") + return + + if (error_.__class__ is urllib.error.URLError): + print("----- LFRequest: URLError: ---------------------------------------------") + print("%s <%s> HTTP %s: %s" % (method, err_full_url, err_code, err_reason)) + print("------------------------------------------------------------------------") + +# ~LFRequest diff --git a/lanforge/lanforge-scripts/py-json/LANforge/LFUtils.py b/lanforge/lanforge-scripts/py-json/LANforge/LFUtils.py new file mode 100644 index 000000000..e5d7bb354 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/LFUtils.py @@ -0,0 +1,808 @@ +#!/usr/bin/env python3 + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Define useful common methods - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +import sys +import os +import importlib +import pprint +import time +from time import sleep +from random import seed, randint +import re +import ipaddress + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../../"))) + +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") + +debug_printer = pprint.PrettyPrinter(indent=2) + +seed(int(round(time.time() * 1000))) +NA = "NA" # used to indicate parameter to skip +ADD_STA_FLAGS_DOWN_WPA2 = 68719477760 +REPORT_TIMER_MS_FAST = 1500 +REPORT_TIMER_MS_SLOW = 3000 + +#Used for Speed +def parse_size_bps(size_val): + if isinstance(size_val, str): + size_val.upper() + # print(size_string) + pattern = re.compile(r"^(\d+)([MGKmgk]?)bps$") + td = pattern.match(size_val) + if td is not None: + size = int(td.group(1)) + unit = str(td.group(2)).lower() + # print(1, size, unit) + if unit == 'g': + size *= 10000000 + elif unit == 'm': + size *= 100000 + elif unit == 'k': + size *= 1000 + # print(2, size, unit) + return size + else: + return size_val + +#Used for Size of file +def parse_size(size_val): + if isinstance(size_val, str): + size_val.upper() + pattern = re.compile(r"^(\d+)([MGKmgk]?b?$)") + td = pattern.match(size_val) + if td is not None: + size = int(td.group(1)) + unit = str(td.group(2)).lower() + # print(1, size, unit) + if unit == 'g': + size *= 1073741824 + elif unit == 'm': + size *= 1048576 + elif unit == 'k': + size *= 1024 + # print(2, size, unit) + return size + else: + return size_val + + +class PortEID: + shelf = 1 + resource = 1 + port_id = 0 + port_name = "" + + def __init__(self, p_resource=1, p_port_id=0, p_port_name=""): + resource = p_resource + port_id = p_port_id + port_name = p_port_name + + def __init__(self, json_response): + if json_response == None: + raise Exception("No json input") + json_s = json_response + if json_response['interface'] != None: + json_s = json_response['interface'] + + debug_printer(json_s) + resource = json_s['resource'] + port_id = json_s['id'] + port_name = json_s['name'] + + +# end class PortEID + +def staNewDownStaRequest(sta_name, resource_id=1, radio="wiphy0", ssid="", passphrase="", debug_on=False): + return sta_new_down_sta_request(sta_name, resource_id, radio, ssid, passphrase, debug_on) + +def sta_new_down_sta_request(sta_name, resource_id=1, radio="wiphy0", ssid="", passphrase="", debug_on=False): + """ + For use with add_sta. If you don't want to generate mac addresses via patterns (xx:xx:xx:xx:81:*) + you can generate octets using random_hex.pop(0)[2:] and gen_mac(parent_radio_mac, octet) + See http://localhost:8080/help/add_sta + :param passphrase: + :param ssid: + :type sta_name: str + """ + data = { + "shelf": 1, + "resource": resource_id, + "radio": radio, + "sta_name": sta_name, + "flags": ADD_STA_FLAGS_DOWN_WPA2, # note flags for add_sta do not match set_port + "ssid": ssid, + "key": passphrase, + "mac": "xx:xx:xx:xx:*:xx", # "NA", #gen_mac(parent_radio_mac, random_hex.pop(0)) + "mode": 0, + "rate": "DEFAULT" + } + if (debug_on): + debug_printer.pprint(data) + return data + + +def portSetDhcpDownRequest(resource_id, port_name, debug_on=False): + return port_set_dhcp_down_request(resource_id, port_name, debug_on) + +def port_set_dhcp_down_request(resource_id, port_name, debug_on=False): + """ + See http://localhost:8080/help/set_port + :param resource_id: + :param port_name: + :return: + """ + print("portSetDhcpDownRequest") + data = { + "shelf": 1, + "resource": resource_id, + "port": port_name, + "current_flags": 2147483649, # 0x1 = interface down + 2147483648 use DHCP values + "interest": 75513858, # includes use_current_flags + dhcp + dhcp_rls + ifdown + "report_timer": REPORT_TIMER_MS_FAST + } + if (debug_on): + debug_printer.pprint(data) + return data + + +def portDhcpUpRequest(resource_id, port_name, debug_on=False): + return port_dhcp_up_request(resource_id, port_name, debug_on) + +def port_dhcp_up_request(resource_id, port_name, debug_on=False): + """ + See http://localhost:8080/help/set_port + :param resource_id: + :param port_name: + :return: + """ + if (debug_on): + print("portDhcpUpRequest") + data = { + "shelf": 1, + "resource": resource_id, + "port": port_name, + "current_flags": 2147483648, # vs 0x1 = interface down + use_dhcp + "interest": 75513858, # includes use_current_flags + dhcp + dhcp_rls + ifdown + "report_timer": REPORT_TIMER_MS_FAST, + } + if (debug_on): + debug_printer.pprint(data) + return data + + +def portUpRequest(resource_id, port_name, debug_on=False): + return port_up_request(resource_id, port_name, debug_on) + +def port_up_request(resource_id, port_name, debug_on=False): + """ + See http://localhost:8080/help/set_port + :param resource_id: + :param port_name: + :return: + """ + data = { + "shelf": 1, + "resource": resource_id, + "port": port_name, + "current_flags": 0, # vs 0x1 = interface down + "interest": 8388610, # includes use_current_flags + dhcp + dhcp_rls + ifdown + "report_timer": REPORT_TIMER_MS_FAST, + } + if (debug_on): + print("Port up request") + debug_printer.pprint(data) + return data + +def portDownRequest(resource_id, port_name, debug_on=False): + return port_down_request(resource_id, port_name, debug_on) + +def port_down_request(resource_id, port_name, debug_on=False): + """ + Does not change the use_dhcp flag + See http://localhost:8080/help/set_port + :param resource_id: + :param port_name: + :return: + """ + + data = { + "shelf": 1, + "resource": resource_id, + "port": port_name, + "current_flags": 1, # vs 0x0 = interface up + "interest": 8388610, # = current_flags + ifdown + "report_timer": REPORT_TIMER_MS_FAST, + } + if (debug_on): + print("Port down request") + debug_printer.pprint(data) + return data + +def port_reset_request(resource_id, port_name, debug_on=False): + """ + Does not change the use_dhcp flag + See http://localhost:8080/help/reset_port + :param resource_id: + :param port_name: + :return: + """ + + data = { + "shelf": 1, + "resource": resource_id, + "port": port_name + } + if (debug_on): + print("Port reset request") + debug_printer.pprint(data) + return data + + +def generateMac(parent_mac, random_octet, debug=False): + return generate_mac(parent_mac=parent_mac, random_octet=random_octet, debug=debug) + +def generate_mac(parent_mac, random_octet, debug=False): + if debug: + print("************ random_octet: %s **************" % (random_octet)) + my_oct = random_octet + if (len(random_octet) == 4): + my_oct = random_octet[2:] + octets = parent_mac.split(":") + octets[4] = my_oct + return ":".join(octets) + + +def portNameSeries(prefix_="sta", start_id_=0, end_id_=1, padding_number_=10000, radio=None): + """ + This produces a named series similar to "sta000, sta001, sta002...sta0(end_id)" + the padding_number is added to the start and end numbers and the resulting sum + has the first digit trimmed, so f(0, 1, 10000) => {"0000", "0001"} + @deprecated -- please use port_name_series + :param prefix_: + :param start_id_: + :param end_id_: + :param padding_number_: + :return: + """ + return port_name_series(prefix=prefix_, start_id=start_id_, end_id=end_id_, padding_number=padding_number_, radio=radio) + + +def port_name_series(prefix="sta", start_id=0, end_id=1, padding_number=10000, radio=None): + """ + This produces a named series similar to "sta000, sta001, sta002...sta0(end_id)" + the padding_number is added to the start and end numbers and the resulting sum + has the first digit trimmed, so f(0, 1, 10000) => {"0000", "0001"} + @deprecated -- please use port_name_series + :param prefix: defaults to 'sta' + :param start_id: beginning id + :param end_id: ending_id + :param padding_number: used for width of resulting station number + :return: list of stations + """ + + eid = None + if radio is not None: + eid = name_to_eid(radio) + + name_list = [] + for i in range((padding_number + start_id), (padding_number + end_id + 1)): + sta_name = "%s%s" % (prefix, str(i)[1:]) + if eid is None: + name_list.append(sta_name) + else: + name_list.append("%i.%i.%s" % (eid[0], eid[1], sta_name)) + return name_list + + +def gen_ip_series(ip_addr, netmask, num_ips=None): + ip_list = [str(ip) for ip in ipaddress.IPv4Network(ip_addr + '/' + netmask, strict=False)] + chosen_ips = [] + if num_ips is None: + return ip_list + else: + for i in range(ip_list.index(ip_addr), num_ips + ip_list.index(ip_addr)): + chosen_ips.append(ip_list[i]) + return chosen_ips + +def generateRandomHex(): + return generate_random_hex() + +# generate random hex if you need it for mac addresses +def generate_random_hex(): + # generate a few random numbers and convert them into hex: + random_hex = [] + for rn in range(0, 254): + random_dec = randint(0, 254) + random_hex.append(hex(random_dec)) + return random_hex + + +# return reverse map of aliases to port records +# +# expect nested records, which is an artifact of some ORM +# that other customers expect: +# [ +# { +# "1.1.eth0": { +# "alias":"eth0" +# } +# }, +# { ... } +def portListToAliasMap(json_list, debug_=False): + return port_list_to_alias_map(json_list=json_list, debug_=debug_) + + +def port_list_to_alias_map(json_list, debug_=False): + reverse_map = {} + if (json_list is None) or (len(json_list) < 1): + if debug_: + print("port_list_to_alias_map: no json_list provided") + raise ValueError("port_list_to_alias_map: no json_list provided") + return reverse_map + + json_interfaces = json_list + if 'interfaces' in json_list: + json_interfaces = json_list['interfaces'] + + for record in json_interfaces: + if len(record.keys()) < 1: + continue + record_keys = record.keys() + k2 = "" + # we expect one key in record keys, but we can't expect [0] to be populated + json_entry = None + for k in record_keys: + k2 = k + json_entry = record[k] + # skip uninitialized port records + if k2.find("Unknown") >= 0: + continue + port_json = record[k2] + reverse_map[k2] = json_entry + + return reverse_map + +def list_to_alias_map(json_list=None, from_element=None, debug_=False): + reverse_map = {} + if (json_list is None) or (len(json_list) < 1): + if debug_: + print("port_list_to_alias_map: no json_list provided") + raise ValueError("port_list_to_alias_map: no json_list provided") + return reverse_map + + if debug_: + pprint.pprint(("list_to_alias_map:json_list: ", json_list)) + json_interfaces = json_list + if from_element in json_list: + json_interfaces = json_list[from_element] + + for record in json_interfaces: + if debug_: + pprint.pprint(("list_to_alias_map: %s record:" % from_element, record)) + if len(record.keys()) < 1: + if debug_: + print("list_to_alias_map: no record.keys") + continue + record_keys = record.keys() + k2 = "" + # we expect one key in record keys, but we can't expect [0] to be populated + json_entry = None + for k in record_keys: + k2 = k + json_entry = record[k] + # skip uninitialized port records + if k2.find("Unknown") >= 0: + continue + port_json = record[k2] + reverse_map[k2] = json_entry + if debug_: + pprint.pprint(("list_to_alias_map: reverse_map", reverse_map)) + return reverse_map + + +def findPortEids(resource_id=1, base_url="http://localhost:8080", port_names=(), debug=False): + return find_port_eids(resource_id=resource_id, base_url=base_url, port_names=port_names, debug=debug) + + +def find_port_eids(resource_id=1, base_url="http://localhost:8080", port_names=(), debug=False): + port_eids = [] + if len(port_names) < 0: + return [] + port_url = "/port/1" + for port_name in port_names: + uri = "%s/%s/%s" % (port_url, resource_id, port_name) + lf_r = LFRequest.LFRequest(base_url, uri) + try: + response = lf_r.getAsJson(debug) + if response is None: + continue + port_eids.append(PortEID(response)) + except: + print("Not found: " + port_name) + return port_eids + + +def waitUntilPortsAdminDown(resource_id=1, base_url="http://localhost:8080", port_list=()): + return wait_until_ports_admin_down(resource_id=resource_id, base_url=base_url, port_list=port_list) + + +def wait_until_ports_admin_down(resource_id=1, base_url="http://localhost:8080", debug_=False, port_list=()): + print("Waiting until ports appear admin-down...") + up_stations = port_list.copy() + sleep(1) + port_url = "/port/1" + while len(up_stations) > 0: + up_stations = [] + for port_name in port_list: + uri = "%s/%s/%s?fields=device,down" % (port_url, resource_id, port_name) + lf_r = LFRequest.LFRequest(base_url, uri) + json_response = lf_r.getAsJson(debug_=False) + if json_response == None: + if debug_: + print("port %s disappeared" % port_name) + continue + if "interface" in json_response: + json_response = json_response['interface'] + if json_response['down'] == "false": + up_stations.append(port_name) + sleep(1) + return None + + +def waitUntilPortsAdminUp(resource_id=1, base_url="http://localhost:8080", port_list=()): + return wait_until_ports_admin_up(resource_id=resource_id, base_url=base_url, port_list=port_list) + +def wait_until_ports_admin_up(resource_id=1, base_url="http://localhost:8080", port_list=(), debug_=False): + print("Waiting until ports appear admin-up...") + down_stations = port_list.copy() + sleep(1) + port_url = "/port/1" + # url = /%s/%s?fields=device,down" % (resource_id, port_name) + while len(down_stations) > 0: + down_stations = [] + for port_name in port_list: + uri = "%s/%s/%s?fields=device,down" % (port_url, resource_id, port_name) + lf_r = LFRequest.LFRequest(base_url, uri) + json_response = lf_r.getAsJson(debug_=False) + if json_response == None: + if debug_: + print("port %s appeared" % port_name) + continue + if "interface" in json_response: + json_response = json_response['interface'] + if json_response['down'] == "true": + down_stations.append(port_name) + sleep(1) + return None + +def waitUntilPortsDisappear(base_url="http://localhost:8080", port_list=(), debug=False): + wait_until_ports_disappear(base_url, port_list, debug) + +def wait_until_ports_disappear(base_url="http://localhost:8080", port_list=(), debug=False): + if (port_list is None) or (len(port_list) < 1): + if debug: + print("LFUtils: wait_until_ports_disappear: empty list, zipping back") + return + + print("LFUtils: Waiting until %s ports disappear..." % len(port_list)) + url = "/port/1" + if isinstance(port_list, list): + found_stations = port_list.copy() + else: + found_stations = [port_list] + + temp_names_by_resource = {1:[]} + temp_query_by_resource = {1:""} + for port_eid in port_list: + eid = name_to_eid(port_eid) + # shelf = eid[0] + resource_id = eid[1] + if resource_id == 0: + continue + if resource_id not in temp_names_by_resource.keys(): + temp_names_by_resource[resource_id] = [] + port_name = eid[2] + temp_names_by_resource[resource_id].append(port_name) + temp_query_by_resource[resource_id] = "%s/%s/%s?fields=alias" % (url, resource_id, ",".join(temp_names_by_resource[resource_id])) + if debug: + pprint.pprint(("temp_query_by_resource", temp_query_by_resource)) + while len(found_stations) > 0: + found_stations = [] + for (resource, check_url) in temp_query_by_resource.items(): + if debug: + pprint.pprint([ + ("base_url", base_url), + ("check_url", check_url), + ]) + lf_r = LFRequest.LFRequest(base_url, check_url, debug_=debug) + json_response = lf_r.get_as_json(debug_=debug, die_on_error_=False) + if (json_response == None): + print("LFUtils::wait_until_ports_disappear:: Request returned None: [{}]".format(base_url + check_url)) + else: + if debug: + pprint.pprint(("wait_until_ports_disappear json_response:", json_response)) + if "interface" in json_response: + found_stations.append(json_response["interface"]) + elif "interfaces" in json_response: + mapped_list = list_to_alias_map(json_response, from_element="interfaces", debug_=debug) + found_stations.extend(mapped_list.keys()) + if debug: + pprint.pprint([("port_list", port_list), + ("found_stations", found_stations)]) + if len(found_stations) > 0: + if debug: + pprint.pprint(("wait_until_ports_disappear found_stations:", found_stations)) + sleep(1) # safety + return + + +def waitUntilPortsAppear(base_url="http://localhost:8080", port_list=(), debug=False): + """ + Deprecated + :param base_url: + :param port_list: + :param debug: + :return: + """ + return wait_until_ports_appear(base_url, port_list, debug=debug) + +def name_to_eid(input, non_port=False): + rv = [1, 1, "", ""] + info = [] + if (input is None) or (input == ""): + raise ValueError("name_to_eid wants eid like 1.1.sta0 but given[%s]" % input) + if type(input) is not str: + raise ValueError("name_to_eid wants string formatted like '1.2.name', not a tuple or list or [%s]" % type(input)) + + info = input.split('.') + if len(info) == 1: + rv[2] = info[0] # just port name + return rv + + if (len(info) == 2) and info[0].isnumeric() and not info[1].isnumeric(): # resource.port-name + rv[1] = int(info[0]) + rv[2] = info[1] + return rv + + elif (len(info) == 2) and not info[0].isnumeric(): # port-name.qvlan + rv[2] = info[0]+"."+info[1] + return rv + + if (len(info) == 3) and info[0].isnumeric() and info[1].isnumeric(): # shelf.resource.port-name + rv[0] = int(info[0]) + rv[1] = int(info[1]) + rv[2] = info[2] + return rv + + elif (len(info) == 3) and info[0].isnumeric() and not info[1].isnumeric(): # resource.port-name.qvlan + rv[1] = int(info[0]) + rv[2] = info[1]+"."+info[2] + return rv + + if non_port: + # Maybe attenuator or similar shelf.card.atten.index + rv[0] = int(info[0]) + rv[1] = int(info[1]) + rv[2] = int(info[2]) + if (len(info) >= 4): + rv[3] = int(info[3]) + return rv + + if len(info) == 4: # shelf.resource.port-name.qvlan + rv[0] = int(info[0]) + rv[1] = int(info[1]) + rv[2] = info[2]+"."+info[3] + + return rv + +def wait_until_ports_appear(base_url="http://localhost:8080", port_list=(), debug=False): + """ + Use this method to pause until the LANforge system has caught up and implemented the + ports you have requested to create. This determines the presence of interfaces, it + does not inspect their state. It is appropriate to use when creating stations in + the admin-down state. Remember physical port changes, mac-vlans, and 1Qvlans might + not appear if they are created admin-down. + :param base_url: + :param port_list: + :param debug: + :return: + """ + if debug: + print("Waiting until ports appear...") + found_stations = [] + port_url = "/port/1" + ncshow_url = "/cli-json/nc_show_ports" + if base_url.endswith('/'): + port_url = port_url[1:] + ncshow_url = ncshow_url[1:] + + while len(found_stations) < len(port_list): + found_stations = [] + for port_eid in port_list: + eid = name_to_eid(port_eid) + shelf = eid[0] + resource_id = eid[1] + port_name = eid[2] + # print("waiting for sta sta "+port_eid) + uri = "%s/%s/%s" % (port_url, resource_id, port_name) + lf_r = LFRequest.LFRequest(base_url, uri) + json_response = lf_r.getAsJson(debug_=False) + if (json_response != None): + found_stations.append(port_name) + else: + lf_r = LFRequest.LFRequest(base_url, ncshow_url) + lf_r.addPostData({"shelf": shelf, "resource": resource_id, "port": port_name, "probe_flags": 5}) + lf_r.jsonPost() + if (len(found_stations) < len(port_list)): + sleep(2) + + if debug: + print("These stations appeared: " + ", ".join(found_stations)) + return + +def wait_until_endps(base_url="http://localhost:8080", endp_list=(), debug=False): + """ + + :param base_url: + :param endp_list: + :param debug: + :return: + """ + print("Waiting until endpoints appear...") + found_endps = [] + port_url = "/port/1" + ncshow_url = "/cli-form/show_endp" + if base_url.endswith('/'): + port_url = port_url[1:] + ncshow_url = ncshow_url[1:] + found_stations = [] + while len(found_stations) < len(endp_list): + found_stations = [] + for port_eid in endp_list: + + eid = name_to_eid(port_eid) + shelf = eid[0] + resource_id = eid[1] + port_name = eid[2] + + uri = "%s/%s/%s" % (port_url, resource_id, port_name) + lf_r = LFRequest.LFRequest(base_url, uri) + json_response = lf_r.getAsJson(debug_=False) + if (json_response != None): + found_stations.append(port_name) + else: + lf_r = LFRequest.LFRequest(base_url, ncshow_url) + lf_r.addPostData({"shelf": shelf, "resource": resource_id, "port": port_name, "flags": 1}) + lf_r.formPost() + if (len(found_stations) < len(endp_list)): + sleep(2) + + if debug: + print("These stations appeared: " + ", ".join(found_stations)) + return + + +def removePort(resource, port_name, baseurl="http://localhost:8080/", debug=False): + remove_port(resource=resource, port_name=port_name, baseurl=baseurl, debug=debug) + + +def remove_port(resource, port_name, baseurl="http://localhost:8080/", debug=False): + if debug: + print("Removing port %d.%s" % (resource, port_name)) + url = "/cli-json/rm_vlan" + lf_r = LFRequest.LFRequest(baseurl, url) + lf_r.addPostData({ + "shelf": 1, + "resource": resource, + "port": port_name + }) + lf_r.jsonPost(debug) + + +def removeCX(baseurl, cx_names, debug=False): + remove_cx(baseurl=baseurl, cx_names=cx_names, debug=debug) + + +def remove_cx(baseurl, cx_names, debug=False): + if debug: + print("Removing cx %s" % ", ".join(cx_names)) + url = "/cli-json/rm_cx" + for name in cx_names: + data = { + "test_mgr": "all", + "cx_name": name + } + lf_r = LFRequest.LFRequest(baseurl, url) + lf_r.addPostData(data) + lf_r.jsonPost(debug) + + +def removeEndps(baseurl, endp_names, debug=False): + remove_endps(baseurl=baseurl, endp_names=endp_names, debug=debug) + + +def remove_endps(baseurl, endp_names, debug=False): + if debug: + print("Removing endp %s" % ", ".join(endp_names)) + url = "/cli-json/rm_endp" + lf_r = LFRequest.LFRequest(baseurl, url) + for name in endp_names: + data = { + "endp_name": name + } + lf_r.addPostData(data) + lf_r.jsonPost(debug) + + +def execWrap(cmd): + exec_wrap(cmd=cmd) + + +def exec_wrap(cmd): + if os.system(cmd) != 0: + print("\nError with '" + cmd + "', bye\n") + exit(1) + + +def expand_endp_histogram(distribution_payload=None): + """ + Layer 3 endpoints can contain DistributionPayloads that appear like + "rx-silence-5m" : { + # "histo_category_width" : 1, + # "histogram" : [ + # 221, + # 113, + # 266, + # 615, + # 16309, + # 56853, + # 7954, + # 1894, + # 29246, + # 118, + # 12, + # 2, + # 0, + # 0, + # 0, + # 0 + # ], + # "time window ms" : 300000, + # "window avg" : 210.285, + # "window max" : 228, + # "window min" : 193 + + These histogbrams are a set of linear categorys roughly power-of-two categories. + :param distribution_payload: dictionary requiring histo_category_width and histogram + :return: dictionary containing expanded category ranges and values for categories + """ + if distribution_payload is None: + return None + if ("histogram" not in distribution_payload) \ + or ("histo_category_width" not in distribution_payload): + raise ValueError("Unexpected histogram format.") + multiplier = int(distribution_payload["histo_category_width"]) + formatted_dict = { + #"00000 <= x <= 00001" : "0" + } + for bucket_index in range(len(distribution_payload["histogram"]) - 1): + pow1 = (2**bucket_index) * multiplier + pow2 = (2**(bucket_index+1)) * multiplier + if bucket_index == 0: + category_name = "00000 <= x <= {:-05.0f}".format(pow2) + else: + category_name = "{:-05.0f} < x <= {:-05.0f}".format(pow1, pow2) + formatted_dict[category_name] = distribution_payload["histogram"][bucket_index] + + pprint.pprint([("historgram", distribution_payload["histogram"]), + ("formatted", formatted_dict)]) + return formatted_dict +### diff --git a/lanforge/lanforge-scripts/py-json/LANforge/__init__.py b/lanforge/lanforge-scripts/py-json/LANforge/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lanforge/lanforge-scripts/py-json/LANforge/add_dut.py b/lanforge/lanforge-scripts/py-json/LANforge/add_dut.py new file mode 100644 index 000000000..21caf300d --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/add_dut.py @@ -0,0 +1,93 @@ +# define list of DUT keys +from enum import Enum +from collections import namedtuple + +add_dut_flags = { + 'STA_MODE' : 0x1, # (1) DUT acts as Station., + 'AP_MODE' : 0x2, # (2) DUT acts as AP. + 'INACTIVE' : 0x4, # (3) Ignore this in ChamberView, etc + 'WEP' : 0x8, # Use WEP encryption on all ssids, deprecated, see add_dut_ssid. + 'WPA' : 0x10, # Use WPA encryption on all ssids, deprecated, see add_dut_ssid. + 'WPA2' : 0x20, # Use WPA2 encryption on all ssids, deprecated, see add_dut_ssid. + 'DHCPD-LAN' : 0x40, # Provides DHCP server on LAN port + 'DHCPD-WAN' : 0x80, # Provides DHCP server on WAN port + 'WPA3' : 0x100, # Use WPA3 encryption on all ssids, deprecated, see add_dut_extras. + '11r' : 0x200, # Use .11r connection logic on all ssids, deprecated, see add_dut_ssid. + 'EAP-TTLS' : 0x400, # Use EAP-TTLS connection logic on all ssids, deprecated, see add_dut_ssid. + 'EAP-PEAP' : 0x800, # Use EAP-PEAP connection logic on all ssids, deprecated, see add_dut_ssid. + 'NOT-DHCPCD' : 0x1000, # Station/edge device that is NOT using DHCP. + # Otherwise, automation logic assumes it is using dhcp client.' +} +class dut_params(namedtuple("dut_params", "key maxlen"), Enum): + name = "name", 48 + flags = "flags", 256 + img_file = "img_file", 128 + sw_version = "sw_version", 40 + hw_version = "hw_version", 40 + model_num = "model_num", 40 + serial_num = "serial_num", 40 + serial_port = "serial_port", 20 + wan_port = "wan_port", 40 + lan_port = "lan_port", 40 + ssid1 = "ssid1", 33 + ssid2 = "ssid2", 33 + ssid3 = "ssid3", 33 + passwd1 = "passwd1", 64 + passwd2 = "passwd2", 64 + passwd3 = "passwd3", 64 + mgt_ip = "mgt_ip", 40 + api_id = "api_id", 20 + flags_mask = "flags_mask", 256 + antenna_count1 = "antenna_count1", 1 + antenna_count2 = "antenna_count2", 1 + antenna_count3 = "antenna_count3", 1 + bssid1 = "bssid1", 18 + bssid2 = "bssid2", 18 + bssid3 = "bssid3", 18 + top_left_x = "top_left_x", 20 + top_left_y = "top_left_y", 20 + eap_id = "eap_id", 64 + + def has(name): + for param in dut_params: + if name == param.key: + return True + return False + + def to_flag(name): + for param in dut_params: + # print("checking %s =? %s"%(name, param.key)) + if name == param.key: + return param + return None + +class dut_flags(namedtuple("dut_flags", "name value"), Enum): + STA_MODE = "STA_MODE", 0x1 # (1) DUT acts as Station. + AP_MODE = "AP_MODE", 0x2 # (2) DUT acts as AP. + INACTIVE = "INACTIVE", 0x4 # (3) Ignore this in ChamberView, etc + WEP = "WEP", 0x8 # Use WEP encryption + WPA = "WPA", 0x10 # Use WPA encryption + WPA2 = "WPA2", 0x20 # Use WPA2 encryption + DHCPD_LAN = "DHCPD-LAN", 0x40 # Provides DHCP server on LAN port + DHCPD_WAN = "DHCPD-WAN", 0x80 # Provides DHCP server on WAN port + WPA3 = "WPA3", 0x100 # Use WPA3 encryption + IEEE80211r = "11r", 0x200 # Use .11r connection logic. + EAP_TTLS = "EAP-TTLS", 0x400 # Use EAP-TTLS connection logic. + EAP_PEAP = "EAP-PEAP", 0x800 # Use EAP-PEAP connection logic. + NOT_DHCPCD = "NOT-DHCPCD", 0x1000 # Station/edge device that is NOT using DHCP. + # Otherwise, automation logic assumes it is using dhcp client. + def has(name): + for flag in dut_flags: + # print("checking %s =? %s"%(name, flag.name)) + if name == flag.name: + return True + return False + + def to_flag(name): + for flag in dut_flags: + # print("checking %s =? %s"%(name, flag.name)) + if name == flag.name: + return flag + return None + +#eof \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-json/LANforge/add_file_endp.py b/lanforge/lanforge-scripts/py-json/LANforge/add_file_endp.py new file mode 100644 index 000000000..d551942d4 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/add_file_endp.py @@ -0,0 +1,77 @@ +from enum import Enum +from collections import namedtuple + +# This is a surprising technique that is not an obvious extension to the language +class fe_fstype(namedtuple("fe_fstype", "id name"), Enum): + EP_FE_GENERIC = 8, "generic" + EP_FE_NFS = 9, "fe_nfs" + EP_FE_ISCSI = 10, "fe_iscsi" + EP_FE_CIFS = 24, "fe_cifs" + EP_FE_NFS4 = 25, "fe_nfs4" + EP_FE_CIFSipv6 = 26, "fe_cifs/ip6" + EP_FE_NFSipv6 = 27, "fe_nfs/ip6" + EP_FE_NFS4ipv6 = 28, "fe_nfs4/ip6" + EP_FE_SMB2 = 29, "fe_smb2" + EP_FE_SMB2ipv6 = 30, "fe_smb2/ip6" + EP_FE_SMB21 = 35, "fe_smb21" + EP_FE_SMB21ipv6 = 36, "fe_smb21/ip6" + EP_FE_SMB30 = 37, "fe_smb30" + EP_FE_SMB30ipv6 = 38, "fe_smb30/ip6" + + +class fe_payload_list(Enum): + increasing = 1 # bytes start at 00 and increase, wrapping if needed. + decreasing = 2 # bytes start at FF and decrease, wrapping if needed. + random = 3 # generate a new random payload each time sent. + + random_fixed = 4 # Means generate one random payload, and send it over + # and over again. + + zeros = 5 # Payload is all zeros (00). + ones = 6 # Payload is all ones (FF). + + PRBS_4_0_3 = 7 # Use linear feedback shift register to generate pseudo random sequence. + # First number is bit-length of register, second two are TAPS (zero-based indexs) + # Seed value is always 1. + + PRBS_7_0_6 = 8 # PRBS (see above) + PRBS_11_8_10 = 9 # PRBS (see above) + PRBS_15_0_14 = 10 # PRBS (see above) + custom = 11 # Enter your own payload with the set_endp_payload cmd. + + +class fe_fio_flags(Enum): + CHECK_MOUNT = 0x1, # (1) Attempt to verify NFS and SMB mounts match the configured values. + AUTO_MOUNT = 0x2, # (2) Attempt to mount with the provided information if not already mounted. + AUTO_UNMOUNT = 0x4, # (4) Attempt to un-mount when stopping test. + O_DIRECT = 0x8, # (8) Open file with O_DIRECT flag, disables caching. Must use block-size read/write calls. + UNLINK_BW = 0x10, # (16) Unlink file before writing. This works around issues with CIFS for some file-servers. + O_LARGEFILE = 0x20, # (32) Open files with O_LARGEFILE. This allows greater than 2GB files on 32-bit systems. + UNMOUNT_FORCE = 0x40, # (64) Use -f flag when calling umount + UNMOUNT_LAZY = 0x80, # (128) Use -l flag when calling umount + USE_FSTATFS = 0x100, # (256) Use fstatfs system call to verify file-system type when opening files. + # This can take a bit of time on some file systems, but it can be used + # to detect un-expected file-system unmounts and such. + + O_APPEND = 0x200 # (512) Open files for writing with O_APPEND instead + # of O_TRUNC. This will cause files to grow ever larger. + + + # base_endpoint_types cribbed from BaseEndpoint.java + # we are unlikely to need this dictionary +class fe_base_endpoint_types(Enum): + EP_FE_GENERIC = 8 + EP_FE_NFS = 9 + EP_FE_ISCSI = 10 + EP_FE_CIFS = 24 + EP_FE_NFS4 = 25 + EP_FE_CIFS6 = 26 + EP_FE_NFS6 = 27 + EP_FE_NFS46 = 28 + EP_FE_SMB2 = 29 + EP_FE_SMB26 = 30 + EP_FE_SMB21 = 35 + EP_FE_SMB216 = 36 + EP_FE_SMB30 = 37 + EP_FE_SMB306 = 38 + diff --git a/lanforge/lanforge-scripts/py-json/LANforge/add_l4_endp.py b/lanforge/lanforge-scripts/py-json/LANforge/add_l4_endp.py new file mode 100644 index 000000000..cd63f8c6e --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/add_l4_endp.py @@ -0,0 +1,21 @@ +# Flags for the add_l4_endp command +HTTP_auth_flags = { + "BASIC" : 0x1, # Basic authentication + "DIGEST" : 0x2, # Digest (MD5) authentication + "GSSNEGOTIATE" : 0x4, # GSS authentication + "NTLM" : 0x8, # NTLM authentication +} +proxy_auth_type_flags = { + "BASIC" : 0x1, # 1 Basic authentication + "DIGEST" : 0x2, # 2 Digest (MD5) authentication + "GSSNEGOTIATE" : 0x4, # 4 GSS authentication + "NTLM" : 0x8, # 8 NTLM authentication + "USE_PROXY_CACHE" : 0x20, # 32 Use proxy cache + "USE_GZIP_COMPRESSION" : 0x40, # 64 Use gzip compression + "USE_DEFLATE_COMPRESSION" : 0x80, # 128 Use deflate compression + "INCLUDE_HEADERS" : 0x100, # 256 especially for IMAP + "BIND_DNS" : 0x200, # 512 Make DNS requests go out endpoints Port. + "USE_IPV6" : 0x400, # 1024 Resolve URL is IPv6. Will use IPv4 if not selected. + "DISABLE_PASV" : 0x800, # 2048 Disable FTP PASV option (will use PORT command) + "DISABLE_EPSV" : 0x1000, # 4096 Disable FTP EPSV option +} \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-json/LANforge/add_monitor.py b/lanforge/lanforge-scripts/py-json/LANforge/add_monitor.py new file mode 100644 index 000000000..0127727c0 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/add_monitor.py @@ -0,0 +1,7 @@ +# flags relating to adding a monitor +flags = { + "disable_ht40": 0x800, + "disable_ht80": 0x8000000, + "ht160_enable": 0x100000000, +} +default_flags_mask = 0xFFFFFFFFFFFF diff --git a/lanforge/lanforge-scripts/py-json/LANforge/add_sta.py b/lanforge/lanforge-scripts/py-json/LANforge/add_sta.py new file mode 100644 index 000000000..5421fb964 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/add_sta.py @@ -0,0 +1,54 @@ +# Flags for the add_sta command +add_sta_flags = { + "wpa_enable" : 0x10, # Enable WPA + "custom_conf" : 0x20, # Use Custom wpa_supplicant config file. + "wep_enable" : 0x200, # Use wpa_supplicant configured for WEP encryption. + "wpa2_enable" : 0x400, # Use wpa_supplicant configured for WPA2 encryption. + "ht40_disable" : 0x800, # Disable HT-40 even if hardware and AP support it. + "scan_ssid" : 0x1000, # Enable SCAN-SSID flag in wpa_supplicant. + "passive_scan" : 0x2000, # Use passive scanning (don't send probe requests). + "disable_sgi" : 0x4000, # Disable SGI (Short Guard Interval). + "lf_sta_migrate" : 0x8000, # OK-To-Migrate (Allow station migration between LANforge radios) + "verbose" : 0x10000, # Verbose-Debug: Increase debug info in wpa-supplicant and hostapd logs. + "80211u_enable" : 0x20000, # Enable 802.11u (Interworking) feature. + "80211u_auto" : 0x40000, # Enable 802.11u (Interworking) Auto-internetworking feature. Always enabled currently. + "80211u_gw" : 0x80000, # AP Provides access to internet (802.11u Interworking) + "80211u_additional" : 0x100000, # AP requires additional step for access (802.11u Interworking) + "80211u_e911" : 0x200000, # AP claims emergency services reachable (802.11u Interworking) + "80211u_e911_unauth" : 0x400000, # AP provides Unauthenticated emergency services (802.11u Interworking) + "hs20_enable" : 0x800000, # Enable Hotspot 2.0 (HS20) feature. Requires WPA-2. + "disable_gdaf" : 0x1000000, # AP: Disable DGAF (used by HotSpot 2.0). + "8021x_radius" : 0x2000000, # Use 802.1x (RADIUS for AP). + "80211r_pmska_cache" : 0x4000000, # Enable oportunistic PMSKA caching for WPA2 (Related to 802.11r). + "disable_ht80" : 0x8000000, # Disable HT80 (for AC chipset NICs only) + "ibss_mode" : 0x20000000, # Station should be in IBSS mode. + "osen_enable" : 0x40000000, # Enable OSEN protocol (OSU Server-only Authentication) + "disable_roam" : 0x80000000, # Disable automatic station roaming based on scan results. + "ht160_enable" : 0x100000000, # Enable HT160 mode. + "disable_fast_reauth" : 0x200000000, # Disable fast_reauth option for virtual stations. + "mesh_mode" : 0x400000000, # Station should be in MESH mode. + "power_save_enable" : 0x800000000, # Station should enable power-save. May not work in all drivers/configurations. + "create_admin_down" : 0x1000000000, # Station should be created admin-down. + "wds-mode" : 0x2000000000, # WDS station (sort of like a lame mesh), not supported on ath10k + "no-supp-op-class-ie" : 0x4000000000, # Do not include supported-oper-class-IE in assoc requests. May work around AP bugs. + "txo-enable" : 0x8000000000, # Enable/disable tx-offloads, typically managed by set_wifi_txo command + "use-wpa3" : 0x10000000000, # Enable WPA-3 (SAE Personal) mode. + "use-bss-transition" : 0x80000000000 # Enable BSS transition. +} +add_sta_modes = { + "AUTO" : 0, # 802.11g + "802.11a" : 1, # 802.11a + "b" : 2, # 802.11b + "g" : 3, # 802.11g + "abg" : 4, # 802.11abg + "abgn" : 5, # 802.11abgn + "bgn" : 6, # 802.11bgn + "bg" : 7, # 802.11bg + "abgnAC" : 8, # 802.11abgn-AC + "anAC" : 9, # 802.11an-AC + "an" : 10, # 802.11an + "bgnAC" : 11, # 802.11bgn-AC + "abgnAX" : 12, # 802.11abgn-AX, a/b/g/n/AC/AX (dual-band AX) support + "bgnAX" : 13, # 802.11bgn-AX + "anAX" : 14, # 802.11an-AX +} diff --git a/lanforge/lanforge-scripts/py-json/LANforge/add_vap.py b/lanforge/lanforge-scripts/py-json/LANforge/add_vap.py new file mode 100644 index 000000000..8797b1ac8 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/add_vap.py @@ -0,0 +1,30 @@ +add_vap_flags = { +"enable_wpa" : 0x10, # Enable WPA +"hostapd_config" : 0x20, # Use Custom hostapd config file. +"enable_80211d" : 0x40, # Enable 802.11D to broadcast country-code & channels in VAPs +"short_preamble" : 0x80, # Allow short-preamble +"pri_sec_ch_enable" : 0x100, # Enable Primary/Secondary channel switch. +"wep_enable" : 0x200, # Enable WEP Encryption +"wpa2_enable" : 0x400, # Enable WPA2 Encryption +"disable_ht40" : 0x800, # Disable HT-40 (will use HT-20 if available). +"verbose" : 0x10000, # Verbose-Debug: Increase debug info in wpa-supplicant and hostapd logs. +"80211u_enable" : 0x20000, # Enable 802.11u (Interworking) feature. +"80211u_auto" : 0x40000, # Enable 802.11u (Interworking) Auto-internetworking feature. Always enabled currently. +"80211u_gw" : 0x80000, # AP Provides access to internet (802.11u Interworking) +"80211u_additional" : 0x100000, # AP requires additional step for access (802.11u Interworking) +"80211u_e911" : 0x200000, # AP claims emergency services reachable (802.11u Interworking) +"80211u_e911_unauth" : 0x400000, # AP provides Unauthenticated emergency services (802.11u Interworking) +"hs20_enable" : 0x800000, # Enable Hotspot 2.0 (HS20) feature. Requires WPA-2. +"disable_dgaf" : 0x1000000, # AP Disable DGAF (used by HotSpot 2.0). +"8021x_radius" : 0x2000000, # Use 802.1x (RADIUS for AP). +"80211r_pmska_cache" : 0x4000000, # Enable oportunistic PMSKA caching for WPA2 (Related to 802.11r). +"disable_ht80" : 0x8000000, # Disable HT80 (for AC chipset NICs only) +"80211h_enable" : 0x10000000, # Enable 802.11h (needed for running on DFS channels) Requires 802.11d. +"osen_enable" : 0x40000000, # Enable OSEN protocol (OSU Server-only Authentication) +"ht160_enable" : 0x100000000, # Enable HT160 mode. +"create_admin_down" : 0x1000000000, # Station should be created admin-down. +"use-wpa3" : 0x10000000000, # Enable WPA-3 (SAE Personal) mode. +"use-bss-load" : 0x20000000000, # Enable BSS Load IE in Beacons and Probe Responses (.11e). +"use-rrm-report" : 0x40000000000, # Enable Radio measurements IE in beacon and probe responses. +"use-bss-transition" : 0x80000000000, # Enable BSS transition. +} diff --git a/lanforge/lanforge-scripts/py-json/LANforge/lf_json_autogen.py b/lanforge/lanforge-scripts/py-json/LANforge/lf_json_autogen.py new file mode 100644 index 000000000..7d3cb307d --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/lf_json_autogen.py @@ -0,0 +1,13335 @@ +#!/usr/bin/env python3 +"""----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Generated by LANforge JsonApiPythonGenerator, Mon Sep 13 15:28:41 PDT 2021 + - - WORK IN PROGRESS - - + The API this library provides is actively being changed. + This file expects to live in py-json/LANforge directory. +----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" +import sys +import os +import importlib +from enum import Enum +from enum import IntFlag + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class LFJsonGet(LFCliBase): + def __init__(self, lfclient_host='localhost', + lfclient_port=8080, + debug_=False, + _exit_on_error=False, + _exit_on_fail=False, + _proxy_str=None, + _capture_signal_list=()): + super().__init__(_lfjson_host=lfclient_host, + _lfjson_port=lfclient_port, + _debug=debug_, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail, + _proxy_str=_proxy_str, + _capture_signal_list=_capture_signal_list) + + @staticmethod + def extract_values(response: dict = None, + singular_key: str = None, + plural_key: str = None) -> list: + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Extract fields from this response using the expected keys: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + if (singular_key is None) or (plural_key is None) or (not singular_key) or (not plural_key): + raise ValueError("extract_values wants non-empty response, singular_key and plural_key") + if (singular_key in response) and (not response[singular_key]): + return [] + elif (singular_key in response) and (type(response[singular_key]) is dict): + return [response[singular_key]] + elif (plural_key in response) and (not response[plural_key]): + return [] + else: + return response[plural_key] + + # TODO: rename me to make_port_eid_url + @staticmethod + def make_eid_url(eid_list=()): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Convert a list of EIDs into a URL: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + if not len(eid_list): + return "/list" + url = "/" + if isinstance(eid_list, str): + return url + eid_list.replace('.', '/') + + # The first in a series has to define the resource number, + # but the remainder of a series has to match that resource number + for i in range(0, len(eid_list)): + eid = eid_list[i] + if i == 0: + url += eid.replace('.', '/') + elif eid.find('.') > 0: + url += ',' + eid.split('.')[-1] + else: + url += ','+eid + + return url + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <ATTENUATOR> type requests + + If you need to call the URL directly, + request one of these URLs: + /attenuator/ + /attenuator/$shelf_id + /attenuator/$shelf_id/$resource_id + /attenuator/$shelf_id/$resource_id/$port_id + /attenuators/ + /attenuators/$shelf_id + /attenuators/$shelf_id/$resource_id + /attenuators/$shelf_id/$resource_id/$port_id + + When requesting specific column names, they need to be URL encoded: + entity+id, module+1, module+2, module+3, module+4, module+5, module+6, module+7, + module+8, name, script, state, temperature + Example URL: /attenuator?fields=entity+id,module+1 + + Example py-json call (it knows the URL): + record = LFJsonGet.get_attenuator(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'entity id': # Entity ID + 'module 1': # Reported attenuator dB settings. + 'module 2': # Reported attenuator dB settings. + 'module 3': # Reported attenuator dB settings. + 'module 4': # Reported attenuator dB settings. + 'module 5': # Reported attenuator dB settings. + 'module 6': # Reported attenuator dB settings. + 'module 7': # Reported attenuator dB settings. + 'module 8': # Reported attenuator dB settings. + 'name': # Attenuator module identifier (shelf . resource . serial-num). + 'script': # Attenuator script state. + 'state': # Attenuator state. + 'temperature': # Temperature in degres Farenheight reported in Attenuator unit. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_attenuator(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/attenuator" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="attenuator", + plural_key="attenuators") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CHAMBER> type requests + + If you need to call the URL directly, + request one of these URLs: + /chamber/ + /chamber/$chamber_name + + When requesting specific column names, they need to be URL encoded: + chamber, chamber+connections, chamber+resources, chamber+type, duts, entity+id, + flags, hide, isolation, marked, open, reported+rotation+%28deg%29, reported+rpm, + reported+tilt+%28deg%29, resource, rotation+%28deg%29, rpm, smas, tilt+%28deg%29, turntable, + turntable+type, virtual + Example URL: /chamber?fields=chamber,chamber+connections + + Example py-json call (it knows the URL): + record = LFJsonGet.get_chamber(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'chamber': # - + 'chamber connections': # - + 'chamber resources': # - + 'chamber type': # - + 'duts': # - + 'entity id': # - + 'flags': # - + 'hide': # - + 'isolation': # - + 'marked': # - + 'open': # - + 'reported rotation (deg)': # - + 'reported rpm ': # - + 'reported tilt (deg)': # - + 'resource': # - + 'rotation (deg)': # - + 'rpm': # - + 'smas': # - + 'tilt (deg)': # - + 'turntable': # - + 'turntable type': # - + 'virtual': # - + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_chamber(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/chamber" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="chamber", + plural_key="chambers") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CONTROL> type requests + + If you need to call the URL directly, + request one of these URLs: + /control/$command + + + Example py-json call (it knows the URL): + record = LFJsonGet.get_control(eid_list=['1.234', '1.344'], + debug_=True) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_control(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/control" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="", + plural_key="") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CX> type requests + + If you need to call the URL directly, + request one of these URLs: + /cx/ + /cx/$cx_id + + When requesting specific column names, they need to be URL encoded: + avg+rtt, bps+rx+a, bps+rx+b, drop+pkts+a, drop+pkts+b, eid, endpoints+%28a%C2%A0%E2%86%94%C2%A0b%29, + entity+id, name, pkt+rx+a, pkt+rx+b, rpt+timer, rx+drop+%25+a, rx+drop+%25+b, + state, type + Example URL: /cx?fields=avg+rtt,bps+rx+a + + Example py-json call (it knows the URL): + record = LFJsonGet.get_cx(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'avg rtt': # Average Round-Trip-Time (latency) for this connection (ms). + 'bps rx a': # Endpoint A's real receive rate (bps). + 'bps rx b': # Endpoint B's real receive rate (bps). + 'drop pkts a': # The number of packets Endpoint B sent minus the number Endpoint A + # received.This number is not 100% correct as long as packets are in + # flight.After a Quiesce of the test, the number should be perfectly + # accurate. + 'drop pkts b': # The number of packets Endpoint A sent minus the number Endpoint B + # received.This number is not 100% correct as long as packets are in + # flight.After a Quiesce of the test, the number should be perfectly + # accurate. + 'eid': # Cross Connect's Name. + 'endpoints (a ↔ b)': # Endpoints that make up this Cross Connect. + 'entity id': # Cross Connect's Name. + 'name': # Cross Connect's Name. + 'pkt rx a': # Endpoint A's Packets Recieved. + 'pkt rx b': # Endpoint B's Packets Recieved. + 'rpt timer': # Cross Connect's Report Timer (milliseconds).This is how often the GUI + # will ask for updates from the LANforge processes.If the GUI is sluggish, + # increasing the report timers may help. + 'rx drop % a': # Endpoint A percentage packet loss.Calculated using the number of PDUs + # Endpoint B sent minus the number Endpoint A received.This number is not + # 100% correct as long as packets are in flight.After a Quiesce of the + # test, the number should be perfectly accurate. + 'rx drop % b': # Endpoint B percentage packet loss.Calculated using the number of PDUs + # Endpoint A sent minus the number Endpoint B received.This number is not + # 100% correct as long as packets are in flight.After a Quiesce of the + # test, the number should be perfectly accurate. + 'state': # Current State of the connection.UninitializedHas not yet been + # started/stopped.InitializingBeing set up.StartingStarting the + # test.RunningTest is actively running.StoppedTest has been + # stopped.QuiesceTest will gracefully stop soon.HW-BYPASSTest is in + # hardware-bypass mode (WanLinks only)FTM_WAITTest wants to run, but is + # phantom, probably due to non-existent interface or resource.WAITINGWill + # restart as soon as resources are available.PHANTOMTest is stopped, and + # is phantom, probably due to non-existent interface or resource. + 'type': # Cross-Connect type. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_cx(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/cx" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="", + plural_key="") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <DUT> type requests + + If you need to call the URL directly, + request one of these URLs: + /dut/ + /dut/$name + + When requesting specific column names, they need to be URL encoded: + api+version, bssid-1, bssid-2, bssid-3, bssid-4, bssid-5, bssid-6, bssid-7, + bssid-8, dut, eap-id, entity+id, hw+info, image+file, lan, mgt+ip, model+number, + notes, num+ant+radio+1, num+ant+radio+2, num+ant+radio+3, password-1, password-2, + password-3, password-4, password-5, password-6, password-7, password-8, serial+number, + serial+port, ssid-1, ssid-2, ssid-3, ssid-4, ssid-5, ssid-6, ssid-7, ssid-8, + sw+info, wan + Example URL: /dut?fields=api+version,bssid-1 + + Example py-json call (it knows the URL): + record = LFJsonGet.get_dut(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'api version': # API Version + 'bssid-1': # WiFi BSSID for DUT. + 'bssid-2': # WiFi BSSID for DUT. + 'bssid-3': # WiFi BSSID for DUT. + 'bssid-4': # WiFi BSSID for DUT. + 'bssid-5': # WiFi BSSID for DUT. + 'bssid-6': # WiFi BSSID for DUT. + 'bssid-7': # WiFi BSSID for DUT. + 'bssid-8': # WiFi BSSID for DUT. + 'dut': # Devices Under Test + 'eap-id': # EAP Identifier, only used when one of the EAP options are selected. + 'entity id': # Entity ID + 'hw info': # DUT Hardware Info + 'image file': # Image file name. Relative paths assume directory /home/lanforge. Fully + # qualified pathnames begin with a slash (eg + # /usr/lib/share/icons/icon.png).File format should be PNG, JPG or BMP. + 'lan': # IP/Mask for LAN port (192.168.2.1/24). + 'mgt ip': # DUT Management IP address. + 'model number': # DUT model number or product name + 'notes': # Notes + 'num ant radio 1': # Antenna count for DUT radio(s). + 'num ant radio 2': # Antenna count for DUT radio(s). + 'num ant radio 3': # Antenna count for DUT radio(s). + 'password-1': # WiFi Password needed to connect to DUT. + 'password-2': # WiFi Password needed to connect to DUT. + 'password-3': # WiFi Password needed to connect to DUT. + 'password-4': # WiFi Password needed to connect to DUT. + 'password-5': # WiFi Password needed to connect to DUT. + 'password-6': # WiFi Password needed to connect to DUT. + 'password-7': # WiFi Password needed to connect to DUT. + 'password-8': # WiFi Password needed to connect to DUT. + 'serial number': # DUT Identifier (serial-number, or similar) + 'serial port': # Resource and name of LANforge serial port that connects to this DUT. + # (1.1.ttyS0). Does not need to belong to lan_port or wan_port resource. + 'ssid-1': # WiFi SSID advertised by DUT. + 'ssid-2': # WiFi SSID advertised by DUT. + 'ssid-3': # WiFi SSID advertised by DUT. + 'ssid-4': # WiFi SSID advertised by DUT. + 'ssid-5': # WiFi SSID advertised by DUT. + 'ssid-6': # WiFi SSID advertised by DUT. + 'ssid-7': # WiFi SSID advertised by DUT. + 'ssid-8': # WiFi SSID advertised by DUT. + 'sw info': # DUT Software Info + 'wan': # IP/Mask for WAN port (192.168.3.2/24). + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_dut(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/dut" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="dut", + plural_key="duts") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <ENDP> type requests + + If you need to call the URL directly, + request one of these URLs: + /endp/ + /endp/$endp_id + + When requesting specific column names, they need to be URL encoded: + 1st+rx, a%2Fb, bursty, crc+fail, cwnd, cx+active, cx+estab, cx+estab%2Fs, cx+to, + delay, destination+addr, dropped, dup+pkts, eid, elapsed, entity+id, jitter, + max+pdu, max+rate, min+pdu, min+rate, mng, name, ooo+pkts, pattern, pdu%2Fs+rx, + pdu%2Fs+tx, pps+rx+ll, pps+tx+ll, rcv+buf, replays, run, rx+ber, rx+bytes, + rx+drop+%25, rx+dup+%25, rx+ooo+%25, rx+pdus, rx+pkts+ll, rx+rate, rx+rate+%281%C2%A0min%29, + rx+rate+%28last%29, rx+rate+ll, rx+wrong+dev, script, send+buf, source+addr, + tcp+mss, tcp+rtx, tx+bytes, tx+pdus, tx+pkts+ll, tx+rate, tx+rate+%281%C2%A0min%29, + tx+rate+%28last%29, tx+rate+ll # hidden columns: + drop-count-5m, latency-5m, rt-latency-5m, rx-silence-5m + Example URL: /endp?fields=1st+rx,a%2Fb + + Example py-json call (it knows the URL): + record = LFJsonGet.get_endp(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + '1st rx': # Miliseconds between starting the endpoint and receiving the first + # packet.Note that LANforge UDP connections (not including multicast) will + # wait 20msbefore sending first frame to make sure receiver has adequate + # time to start. + 'a/b': # Display side (A or B) for the endpoint. + 'bursty': # Is the transmit rate bursty or not? + 'crc fail': # Total packets received with a bad payload CRC. + 'cwnd': # Sender's TCP Current Window Size. In units of Maximum Segment Size. + 'cx active': # Total number of active connections for this endpoint. + 'cx estab': # Total times the connection between the endpoints has been established. + 'cx estab/s': # Connections established per second, averaged over the last 30 seconds. + 'cx to': # Number of TCP connection attemtps timed out by LANforge. + 'delay': # Average latency in milliseconds for packets received by this endpoint. + 'destination addr': # Destination Address (MAC, ip/port, VoIP destination). + 'dropped': # Total dropped packets, as identified by gaps in packet sequence numbers. + 'dup pkts': # Total duplicate packets received. Only an estimate, but never less than + # this value. + 'eid': # Entity ID + 'elapsed': # Amount of time (seconds) this endpoint has been running (or ran.) + 'entity id': # Entity ID + 'jitter': # Exponential decaying average jitter calculated per RFC3393(old_jitter * + # 15/16 + new_jitter * 1/16) + 'max pdu': # The maximum write size.For Ethernet protocols, this is the entire + # Ethernet frame. For UDP, it is the UDP payload size, and for TCP, it + # just means the maximum amount of data that is written per socket + # write.In all cases, the packets on the wire will not exceed theport's + # MTU + Ethernet-Header-Size (typically 1514 for Ethernet) + 'max rate': # Maximum desired transmit rate, in bits per second (bps). + 'min pdu': # The minimum write size.For Ethernet protocols, this is the entire + # Ethernet frame. For UDP, it is the UDP payload size, and for TCP, it + # just means the maximum amount of data that is written per socket + # write.In all cases, the packets on the wire will not exceed theport's + # MTU + Ethernet-Header-Size (typically 1514 for Ethernet) + 'min rate': # Minimum desired transmit rate, in bits per second (bps). + 'mng': # Is the Endpoint managed or not? + 'name': # Endpoint's Name. + 'ooo pkts': # Total out of order packets received. Only an estimate, but never less + # than this value. + 'pattern': # Pattern of bytes this endpoint transmits. + 'pdu/s rx': # Received PDU per second.This counts the protocol reads, such as UDP + # PDUs. + 'pdu/s tx': # Transmitted PDU per second.This counts the protocol writes, such as UDP + # PDUs. + 'pps rx ll': # Estimated total received packets per second (on the wire).For TCP, this + # is an estimate.UDP and Ethernet protocols should be quite accurate on + # normal networks. + 'pps tx ll': # Estimated total transmitted packets per second (on the wire).For TCP, + # this is an estimate.UDP and Ethernet protocols should be quite accurate + # on normal networks. + 'rcv buf': # Configured/Actual values for receiving buffer size (bytes). + 'replays': # Total number of files replayed. + 'run': # Is the Endpoint is Running or not. + 'rx ber': # Received bit-errors. These are only calculated in the LANforge payload + # portion starting 28 bytes into the UDP or TCP payload. In addition, the + # bit-errors are only checked when LANforge CRCis enabled and detected to + # be invalid. If the 28-byte header is corrupted, LANforge will not + # detectit, and may also give false positives for other packet errors. + # Bit-Errors are only calculated forcertain payload patterns: Increasing, + # Decreasing, Zeros, Ones, and the PRBS patterns. + 'rx bytes': # Total received bytes count. + 'rx drop %': # Percentage of packets that should have been received by Endpoint, but + # were not, as calculated by the Cross-Connect. + 'rx dup %': # Percentage of duplicate packets, as detected by sequence numbers. + 'rx ooo %': # Percentage of packets received out of order, as detected by sequence + # numbers. + 'rx pdus': # Total received PDU count.This counts the protocol reads, such as UDP + # PDUs (aka goodput). + 'rx pkts ll': # Estimated total received packet count (on the wire).For TCP, this is an + # estimate.UDP and Ethernet protocols should be quite accurate on normal + # networks. + 'rx rate': # Real receive rate (bps) for this run.This includes only the protocol + # payload (goodput). + 'rx rate (1 min)': # Real receive rate (bps) over the last minute.This includes only the + # protocol payload (goodput). + 'rx rate (last)': # Real receive rate (bps) over the last report interval.This includes only + # the protocol payload (goodput). + 'rx rate ll': # Estimated low-level receive rate (bps) over the last minute.This + # includes any Ethernet, IP, TCP, UDP or similar headers. + 'rx wrong dev': # Total packets received on the wrong device (port). + 'script': # Endpoint script state. + 'send buf': # Configured/Actual values for sending buffer size (bytes). + 'source addr': # + 'tcp mss': # Sender's TCP-MSS (max segment size) setting.This cooresponds to the + # TCP_MAXSEGS socket option,and TCP-MSS plus 54 is the maximum packet size + # on the wirefor Ethernet frames.This is a good option to efficiently + # limit TCP packet size. + 'tcp rtx': # Total packets retransmitted by the TCP stack for this connection.These + # were likely dropped or corrupted in transit. + 'tx bytes': # Total transmitted bytes count. + 'tx pdus': # Total transmitted PDU count.This counts the protocol writes, such as UDP + # PDUs (aka goodput). + 'tx pkts ll': # Estimated total transmitted packet count (on the wire).For TCP, this is + # an estimate.UDP and Ethernet protocols should be quite accurate on + # normal networks. + 'tx rate': # Real transmit rate (bps) for this run.This includes only the protocol + # payload (goodput). + 'tx rate (1 min)': # Real transmit rate (bps) over the last minute.This includes only the + # protocol payload (goodput). + 'tx rate (last)': # Real transmit rate (bps) over the last report interval.This includes + # only the protocol payload (goodput). + 'tx rate ll': # Estimated low-level transmit rate (bps) over the last minute.This + # includes any Ethernet, IP, TCP, UDP or similar headers. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_endp(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/endp" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="endpoint", + plural_key="endpoint") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <EVENTS> type requests + + If you need to call the URL directly, + request one of these URLs: + /events/ + /events/$event_id + /events/before/$event_id + /events/between/$start_event_id/$end_event_id + /events/last/$event_count + /events/since/$event_id + + When requesting specific column names, they need to be URL encoded: + eid, entity+id, event, event+description, id, name, priority, time-stamp, + type + Example URL: /events?fields=eid,entity+id + + Example py-json call (it knows the URL): + record = LFJsonGet.get_events(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'eid': # Time at which this event was created.This uses the clock on the source + # machine. + 'entity id': # Entity IdentifierExact format depends on the + # type.(shelf.resource.port.endpoint.extra) + 'event': # Event Type + 'event description': # Text description for this event. + 'id': # Unique ID for this event. + 'name': # Name of the entity associated with this event. + 'priority': # Event priority. + 'time-stamp': # Time at which this event was created.This uses the clock on the source + # machine. + 'type': # Entity type. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_events(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/events" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="alert", + plural_key="alerts") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <FILEIO> type requests + + If you need to call the URL directly, + request one of these URLs: + /fileio/ + /fileio/$endp_id + + When requesting specific column names, they need to be URL encoded: + buf-rd, buf-wr, bytes-rd, bytes-wr, crc+fail, eid, entity+id, files+%23, files-read, + files-wr, io+fail, max-file-sz, max-rd-bps, max-rw-sz, max-wr-bps, min-file-sz, + min-rd-bps, min-rw-sz, min-wr-bps, name, read-bps, rpt+timer, rx-bps-20s, + status, tx-bps-20s, type, write-bps + Example URL: /fileio?fields=buf-rd,buf-wr + + Example py-json call (it knows the URL): + record = LFJsonGet.get_fileio(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'buf-rd': # Buffer reads. When doing CRC, it takes two reads per 'packet', because + # we first read the header, then the payload. Non-CRC reads ignore the + # header. + 'buf-wr': # Buffer writes. + 'bytes-rd': # Bytes read. + 'bytes-wr': # Bytes written. + 'crc fail': # 32-bit CRC Errors detected upon READ. + 'eid': # Entity ID + 'entity id': # Entity ID + 'files #': # Number of files to write. + 'files-read': # Files read. + 'files-wr': # Files written. + 'io fail': # Amount of time in miliseconds this test has been experiencing IO + # failures. + 'max-file-sz': # Maximum configured file size (bytes). + 'max-rd-bps': # Maximum configured read rate (bps). + 'max-rw-sz': # Maximum configured size for each call to read(2) or write(2) (bytes). + 'max-wr-bps': # Maximum configured write rate (bps). + 'min-file-sz': # Minimum configured file size (bytes). + 'min-rd-bps': # Minimum configured read rate (bps). + 'min-rw-sz': # Minimum configured size for each call to read(2) or write(2) (bytes). + 'min-wr-bps': # Minimum configured write rate (bps). + 'name': # File Endpoint's Name. + 'read-bps': # File read rate for this endpoint over the duration of the test. + 'rpt timer': # Report Timer (milliseconds).This is how often the GUI will ask for + # updates from the LANforge processes.If the GUI is sluggish, increasing + # the report timers may help. + 'rx-bps-20s': # File read rate for this endpoint over the last 20 seconds. + 'status': # Current State of the connection.UninitializedHas not yet been + # started/stopped.InitializingBeing set up.StartingStarting the + # test.RunningTest is actively running.StoppedTest has been + # stopped.QuiesceTest will gracefully stop soon.HW-BYPASSTest is in + # hardware-bypass mode (WanLinks only)FTM_WAITTest wants to run, but is + # phantom, probably due to non-existent interface or resource.WAITINGWill + # restart as soon as resources are available.PHANTOMTest is stopped, and + # is phantom, probably due to non-existent interface or resource. + 'tx-bps-20s': # File write rate for this endpoint over the last 20 seconds. + 'type': # The specific type of this File Endpoint. + 'write-bps': # File write rate for this endpoint over the duration of the test. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_fileio(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/fileio" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="endpoint", + plural_key="endpoint") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <GENERIC> type requests + + If you need to call the URL directly, + request one of these URLs: + /generic/ + /generic/$endp_id + + When requesting specific column names, they need to be URL encoded: + bps+rx, bps+tx, command, dropped, eid, elapsed, entity+id, last+results, + name, pdu%2Fs+rx, pdu%2Fs+tx, rpt+timer, rpt%23, rx+bytes, rx+pkts, status, tx+bytes, + tx+pkts, type + Example URL: /generic?fields=bps+rx,bps+tx + + Example py-json call (it knows the URL): + record = LFJsonGet.get_generic(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'bps rx': # Receive rate reported by this endpoint. + 'bps tx': # Transmit rate reported by this endpoint. + 'command': # The command that this endpoint executes. + 'dropped': # Dropped PDUs reported by this endpoint. + 'eid': # Entity ID + 'elapsed': # Amount of time (seconds) this endpoint has been running (or ran.) + 'entity id': # Entity ID + 'last results': # Latest output from the Generic Endpoint. + 'name': # Endpoint's Name. + 'pdu/s rx': # Received packets-per-second reported by this endpoint. + 'pdu/s tx': # Transmitted packets-per-second reported by this endpoint. + 'rpt timer': # Report Timer (milliseconds).This is how often the GUI will ask for + # updates from the LANforge processes.If the GUI is sluggish, increasing + # the report timers may help. + 'rpt#': # The N_th report that we have received. (Some cmds will produce only one + # report, others will produce continuous reports.) + 'rx bytes': # Received bytes reported by this endpoint. + 'rx pkts': # Received PDUs reported by this endpoint. + 'status': # Current State of the connection.UninitializedHas not yet been + # started/stopped.InitializingBeing set up.StartingStarting the + # test.RunningTest is actively running.StoppedTest has been + # stopped.QuiesceTest will gracefully stop soon.HW-BYPASSTest is in + # hardware-bypass mode (WanLinks only)FTM_WAITTest wants to run, but is + # phantom, probably due to non-existent interface or resource.WAITINGWill + # restart as soon as resources are available.PHANTOMTest is stopped, and + # is phantom, probably due to non-existent interface or resource. + 'tx bytes': # Transmitted bytes reported by this endpoint. + 'tx pkts': # Transmitted PDUs reported by this endpoint. + 'type': # The specific type of this Generic Endpoint. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_generic(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/generic" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="endpoint", + plural_key="endpoints") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <GUI-CLI> type requests + + If you need to call the URL directly, + request one of these URLs: + /gui-cli/ + + + Example py-json call (it knows the URL): + record = LFJsonGet.get_gui_cli(eid_list=['1.234', '1.344'], + debug_=True) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_gui_cli(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/gui-cli" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="", + plural_key="") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <LAYER4> type requests + + If you need to call the URL directly, + request one of these URLs: + /layer4/ + /layer4/$endp_id + + When requesting specific column names, they need to be URL encoded: + %21conn, acc.+denied, bad-proto, bad-url, bytes-rd, bytes-wr, dns-avg, dns-max, + dns-min, eid, elapsed, entity+id, fb-avg, fb-max, fb-min, ftp-host, ftp-port, + ftp-stor, http-p, http-r, http-t, login-denied, name, nf+%284xx%29, other-err, + read, redir, rpt+timer, rslv-h, rslv-p, rx+rate, rx+rate+%281%C2%A0min%29, status, + timeout, total-err, total-urls, tx+rate, tx+rate+%281%C2%A0min%29, type, uc-avg, + uc-max, uc-min, urls%2Fs, write # hidden columns: + rpt-time + Example URL: /layer4?fields=%21conn,acc.+denied + + Example py-json call (it knows the URL): + record = LFJsonGet.get_layer4(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + '!conn': # Could not establish connection. + 'acc. denied': # Access Access Denied Error.This could be password, user-name, + # file-permissions or other error. + 'bad-proto': # Bad protocol. + 'bad-url': # Bad URL format. + 'bytes-rd': # Bytes read. + 'bytes-wr': # Bytes written. + 'dns-avg': # Average time in milliseconds to complete resolving the DNS lookupfor the + # last 100 requests. + 'dns-max': # Maximum time in milliseconds to complete resolving the DNS lookupfor + # requests made in the last 30 seconds. + 'dns-min': # Minimum time in milliseconds to complete resolving the DNS lookupfor + # requests made in the last 30 seconds. + 'eid': # EID + 'elapsed': # Amount of time (seconds) this endpoint has been running (or ran.) + 'entity id': # Entity ID + 'fb-avg': # Average time in milliseconds for receiving the first byte of the URLfor + # the last 100 requests. + 'fb-max': # Maximum time in milliseconds for receiving the first byte of the URLfor + # requests made in the last 30 seconds. + 'fb-min': # Minimum time in milliseconds for receiving the first byte of the URLfor + # requests made in the last 30 seconds. + 'ftp-host': # FTP HOST Error + 'ftp-port': # FTP PORT Error. + 'ftp-stor': # FTP STOR Error. + 'http-p': # HTTP Post error. + 'http-r': # HTTP RANGE error. + 'http-t': # HTTP PORT Error. + 'login-denied': # Login attempt was denied.Probable cause is user-name or password errors. + 'name': # Endpoint's Name. + 'nf (4xx)': # File not found.For HTTP, an HTTP 4XX error was returned. This is only + # counted when the endpoint has 'Enable 4XX' selected.Includes 403 + # permission denied and 404 not found errors.For other protocols, it + # should be returned any time a file is not found. + 'other-err': # Error not otherwise specified. The actual error code may be found + # inl4helper logs. Contact support if you see these errors:we would like + # to account for all possible errors. + 'read': # Error attempting to read file or URL. + 'redir': # Noticed redirect loop! + 'rpt timer': # Cross Connect's Report Timer (milliseconds).This is how often the GUI + # will ask for updates from the LANforge processes.If the GUI is sluggish, + # increasing the report timers may help. + 'rslv-h': # Couldn't resolve host. + 'rslv-p': # Couldn't resolve Proxy. + 'rx rate': # Payload receive rate (bps). + 'rx rate (1 min)': # Payload receive rate over the last minute (bps). + 'status': # Current State of the connection.UninitializedHas not yet been + # started/stopped.InitializingBeing set up.StartingStarting the + # test.RunningTest is actively running.StoppedTest has been + # stopped.QuiesceTest will gracefully stop soon.HW-BYPASSTest is in + # hardware-bypass mode (WanLinks only)FTM_WAITTest wants to run, but is + # phantom, probably due to non-existent interface or resource.WAITINGWill + # restart as soon as resources are available.PHANTOMTest is stopped, and + # is phantom, probably due to non-existent interface or resource. + 'timeout': # Operation timed out. + 'total-err': # Total Errors. + 'total-urls': # URLs processed. + 'tx rate': # Payload transmit rate (bps). + 'tx rate (1 min)': # Payload transmit rate over the last minute (bps). + 'type': # The specific type of this Layer 4-7 Endpoint. + 'uc-avg': # Average time in milliseconds to complete processing of the URLfor the + # last 100 requests. + 'uc-max': # Maximum time in milliseconds to complete processing of the URLfor + # requests made in the last 30 seconds. + 'uc-min': # Minimum time in milliseconds to complete processing of the URLfor + # requests made in the last 30 seconds. + 'urls/s': # URLs processed per second over the last minute. + 'write': # Error attempting to write file or URL. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_layer4(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/layer4" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="endpoint", + plural_key="endpoint") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <PORT> type requests + + If you need to call the URL directly, + request one of these URLs: + /port/ + /port/$shelf_id + /port/$shelf_id/$resource_id + /port/$shelf_id/$resource_id/$port_id + /portprobe/ + /portprobe/$shelf_id/$resource_id/$port_id + /ports/ + /ports/$shelf_id + /ports/$shelf_id/$resource_id + /ports/$shelf_id/$resource_id/$port_id + + When requesting specific column names, they need to be URL encoded: + 4way+time+%28us%29, activity, alias, anqp+time+%28us%29, ap, beacon, bps+rx, bps+rx+ll, + bps+tx, bps+tx+ll, bytes+rx+ll, bytes+tx+ll, channel, collisions, connections, + crypt, cx+ago, cx+time+%28us%29, device, dhcp+%28ms%29, down, entity+id, gateway+ip, + ip, ipv6+address, ipv6+gateway, key%2Fphrase, login-fail, login-ok, logout-fail, + logout-ok, mac, mask, misc, mode, mtu, no+cx+%28us%29, noise, parent+dev, phantom, + port, port+type, pps+rx, pps+tx, qlen, reset, retry+failed, rx+bytes, rx+crc, + rx+drop, rx+errors, rx+fifo, rx+frame, rx+length, rx+miss, rx+over, rx+pkts, + rx-rate, sec, signal, ssid, status, time-stamp, tx+abort, tx+bytes, tx+crr, + tx+errors, tx+fifo, tx+hb, tx+pkts, tx+wind, tx-failed+%25, tx-rate, wifi+retries, + # hidden columns: + beacon_rx_signal, port_cur_flags_h, port_cur_flags_l, port_supported_flags_h, + port_supported_flags_l, resource, rx_multicast, tx_dropped + Example URL: /port?fields=4way+time+%28us%29,activity + + Example py-json call (it knows the URL): + record = LFJsonGet.get_port(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + '4way time (us)': # TIme (in micro-seconds) it took to complete the last WiFi 4-way + # authentication. + 'activity': # Percent of the channel that is utilized over the last minute.This + # includes locally generated traffic as well as anyother systems active on + # this channel.This is a per-radio value. + 'alias': # User-specified alias for this Port. + 'anqp time (us)': # Time (in micro-seconds) it took to complete the last WiFi ANQP + # request/response session. + 'ap': # BSSID of AP for connected stations. + 'beacon': # Number of Wireless beacons from Cell or AP that have been missed. + 'bps rx': # Average bits per second received for the last 30 seconds. + 'bps rx ll': # Bits per second received, including low-level framing (Ethernet Only). + 'bps tx': # Average bits per second transmitted for the last 30 seconds. + 'bps tx ll': # Bits per second transmitted, including low-level framing (Ethernet + # Only). + 'bytes rx ll': # Bytes received, including low-level framing (Ethernet Only). + 'bytes tx ll': # Bytes transmitted, including low-level framing (Ethernet Only). + 'channel': # Channel at the device is currently on, if known. + 'collisions': # Total number of collisions reported by this Interface.For WiFi devices, + # this is number of re-transmit attempts. + 'connections': # Number of wireless connections completed. + 'crypt': # Number of Wireless packets dropped due to inability to decrypt. + 'cx ago': # How long ago was the last WiFi connection attempt started?This relates + # only to the network interface, not any higher level protocol traffic + # upon it. + 'cx time (us)': # Time (in micro-seconds) it took to complete the last WiFi connection to + # the AP. + 'device': # Ethernet device name, as seen by the kernel. + 'dhcp (ms)': # Time (in miliseconds) it took to acquire DHCP lease,or to time out while + # trying to acquire lease. + 'down': # The interface is configured DOWN. It must be configured UP to be in + # active use. + 'entity id': # Entity ID + 'gateway ip': # Default Router/Gateway IP for the Interface. + 'ip': # IP Address of the Interface. + 'ipv6 address': # IPv6 Address for this interface. If global-scope address exists, it + # will be displayed,otherwise link-local will be displayed. + 'ipv6 gateway': # IPv6 default gateway. + 'key/phrase': # WEP Key or WPA Phrase (if enabled). + 'login-fail': # The 'ifup-post' script reported failure. This is usually used for WiFi + # portallogins, but may be customized by the user for other needs. + 'login-ok': # The 'ifup-post' script reported OK. This is usually used for WiFi + # portallogins, but may be customized by the user for other needs. + 'logout-fail': # The 'ifup-post --logout' script reported failure. This is usually used + # for WiFi portallogouts, but may be customized by the user for other + # needs. + 'logout-ok': # The 'ifup-post --logout' script reported OK. This is usually used for + # WiFi portallogouts, but may be customized by the user for other needs. + 'mac': # Ethernet MAC address of the Interface. + 'mask': # IP Mask of the Interface. + 'misc': # Number of Wireless packets dropped on receive due to unspecified + # reasons. + 'mode': # Wireless radio mode (802.11a/b/g). + 'mtu': # MTU (Maximum Transmit Unit) size, in bytes. + 'no cx (us)': # How long was the WiFi disconnect duration for the last disconnection? + 'noise': # Wireless noise level. + 'parent dev': # Parent device or port of this port. Blank if this device is not a child + # of another device or port. + 'phantom': # Is the port PHANTOM (no hardware found) or not. + 'port': # Entity ID + 'port type': # Ports can be Ethernet, Radio, vAP, vSTA, Redirect, or Bridges + 'pps rx': # Average packets per second received for the last 30 seconds. + 'pps tx': # Average packets per second transmitted for the last 30 seconds. + 'qlen': # "Transmit Queue Length for this Interface. + 'reset': # Current Reset-State. + 'retry failed': # Number of Wireless packets that the interface failed to send due to + # excessive retries. + 'rx bytes': # Total number of bytes received by this Interface. + 'rx crc': # Total number of packets dropped because of a bad CRC/FCS. + 'rx drop': # Total number of dropped packets on recieve. Usually means driver/kernel + # is being over-worked. + 'rx errors': # Total number of all types of Receive Errors. + 'rx fifo': # Total number of packets dropped because driver/kernel queues are full. + 'rx frame': # Total number of packets dropped because of framing errors at the + # physical layer. + 'rx length': # Total number of packets dropped because their length was invalid. + 'rx miss': # Total number of packets dropped because of a missed interrupt. + 'rx over': # Total number of packets dropped because of framing errors at the + # physical layer. + 'rx pkts': # Total number of packets received by this Interface. + 'rx-rate': # Reported network device RX link speed. + 'sec': # Number of secondary IP addresses configured or detected. + 'signal': # Wireless signal strength (RSSI). + 'ssid': # WiFi SSID identifier.Use [BLANK] for empty SSID, which means use any + # available SSID when associating. + 'status': # Wireless link status. + 'time-stamp': # Time-Stamp + 'tx abort': # Total packets dropped on transmit because of driver abort. + 'tx bytes': # Total number of bytes sent by this Interface. + 'tx crr': # Total packets dropped on transmit because of carrier error. + 'tx errors': # Total number of all types of Transmit Errors. + 'tx fifo': # Total packets dropped on transmit because outgoing queue was full. + 'tx hb': # Total packets dropped on transmit because of transceiver heartbeat + # errors. + 'tx pkts': # Total number of packets sent by this Interface. + 'tx wind': # Total number dropped on transmit because of Out-of-Window collision. + 'tx-failed %': # Percentage of transmitted Wireless packets that were not ACKed.They + # might have succeeded on retry. + 'tx-rate': # Reported network device TX link speed. + 'wifi retries': # Number of Wireless packets that the wifi radio retried.One packet may be + # tried multiple times and each try would be counted in this stat.Not all + # radios can properly report this statistic. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_port(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/port" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="interface", + plural_key="interfaces") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <PROBE> type requests + + If you need to call the URL directly, + request one of these URLs: + /probe/ + /probe/$shelf_id/$resource_id/$port_id + + When requesting specific column names, they need to be URL encoded: + entity+id, probe+results + Example URL: /probe?fields=entity+id,probe+results + + Example py-json call (it knows the URL): + record = LFJsonGet.get_probe(eid_list=['1.234', '1.344'], + requested_col_names=['probe results'], + debug_=True) + + The record returned will have these members: + { + 'entity id': # Entity ID + 'probe results': # Probe the low level information about the port. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_probe(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/probe" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="probe-results", + plural_key="probe-results") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <QUIT> type requests + + If you need to call the URL directly, + request one of these URLs: + /quit + + + Example py-json call (it knows the URL): + record = LFJsonGet.get_quit(eid_list=['1.234', '1.344'], + debug_=True) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_quit(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/quit" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="", + plural_key="") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <RADIOSTATUS> type requests + + If you need to call the URL directly, + request one of these URLs: + /radiostatus/ + /radiostatus/$eid + /radiostatus/$shelf_id/$resource_id/$port_id + + When requesting specific column names, they need to be URL encoded: + _links, antenna, ap, capabilities, channel, country, driver, entity+id, firmware+version, + frag, frequency, max_sta, max_vap, max_vifs, monitors_down, monitors_up, + phantom, port, resource, rts, stations_down, stations_up, tx-power, vaps_down, + vaps_up, verbose+debug + Example URL: /radiostatus?fields=_links,antenna + + Example py-json call (it knows the URL): + record = LFJsonGet.get_radiostatus(eid_list=['1.234', '1.344'], + requested_col_names=['firmware version'], + debug_=True) + + The record returned will have these members: + { + '_links': # - + 'antenna': # - + 'ap': # - + 'capabilities': # - + 'channel': # - + 'country': # - + 'driver': # - + 'entity id': # - + 'firmware version': # - + 'frag': # - + 'frequency': # - + 'max_sta': # - + 'max_vap': # - + 'max_vifs': # - + 'monitors_down': # - + 'monitors_up': # - + 'phantom': # - + 'port': # - + 'resource': # - + 'rts': # - + 'stations_down': # - + 'stations_up': # - + 'tx-power': # - + 'vaps_down': # - + 'vaps_up': # - + 'verbose debug': # - + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_radiostatus(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/radiostatus" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="radio", + plural_key="radios") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <RESOURCE> type requests + + If you need to call the URL directly, + request one of these URLs: + /resource/ + /resource/$shelf_id + /resource/$shelf_id/$resource_id + + When requesting specific column names, they need to be URL encoded: + bps-rx-3s, bps-tx-3s, cli-port, cpu, ctrl-ip, ctrl-port, eid, entity+id, + free+mem, free+swap, gps, hostname, hw+version, load, max+if-up, max+staged, + mem, phantom, ports, rx+bytes, shelf, sta+up, sw+version, swap, tx+bytes, + # hidden columns: + timestamp + Example URL: /resource?fields=bps-rx-3s,bps-tx-3s + + Example py-json call (it knows the URL): + record = LFJsonGet.get_resource(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'bps-rx-3s': # Rate in bits-per-second that the manager issending management data to + # the resource, averaged over the last 3 seconds.This is TCP payload data, + # and does not count the IP and Ethernet overhead. + 'bps-tx-3s': # Rate in bits-per-second that the manager isreceiving management data + # from the resource, averaged over the last 3 seconds.This is TCP payload + # data, and does not count the IP and Ethernet overhead. + 'cli-port': # Text (telnet) interface IP Port. + 'cpu': # CPU information for the machine. + 'ctrl-ip': # IP Address of the Control Interface. + 'ctrl-port': # Binary interface IP Port. + 'eid': # Resource EID (Shelf.Resource). + 'entity id': # Entity ID + 'free mem': # Free Memory (Kbytes) in the machine. If this is too low, performance + # will be degraded. + 'free swap': # Free Swap (Kbytes) in the machine. If this is too low, performance will + # be degraded. + 'gps': # GPS Info for this machine, if GPS is attached. + 'hostname': # The name for this resource, as reported by the resource. + 'hw version': # Hardware version on the machine. + 'load': # Unix process load.. + 'max if-up': # Max number of interface-config scripts try to run at once. + 'max staged': # Max number of interfaces the system will try to bringup at once. + 'mem': # Total memory (Kbytes) on the machine. + 'phantom': # Is the resource PHANTOM (undiscovered) or not. + 'ports': # All real and phantom ports on this machine. + 'rx bytes': # Total management TCP payload bytes received from the manager process by + # this resource. + 'shelf': # Number of shelf that this resource belongs to. + 'sta up': # Max number of stations to bring up per radio per 0.25s tick. + 'sw version': # LANforge Software version running on the machine. + 'swap': # Total swap space (Kbytes) on the machine. + 'tx bytes': # Total management TCP payload bytes sent from this resource to the + # manager process. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_resource(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/resource" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="resource", + plural_key="resources") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <SCAN> type requests + + If you need to call the URL directly, + request one of these URLs: + /scan-results/ + /scan-results/$shelf_id/$resource_id/$port_id + /scan-results/$shelf_id/$resource_id/$port_id/$bssid + /scan/ + /scan/$shelf_id/$resource_id/$port_id + /scan/$shelf_id/$resource_id/$port_id/$bssid + /scanresults/ + /scanresults/$shelf_id/$resource_id/$port_id + /scanresults/$shelf_id/$resource_id/$port_id/$bssid + + + Example py-json call (it knows the URL): + record = LFJsonGet.get_scan(eid_list=['1.234', '1.344'], + debug_=True) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_scan(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/scan" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="scan-results", + plural_key="scan-results") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <STATIONS> type requests + + If you need to call the URL directly, + request one of these URLs: + /stations/ + /stations/$mac + + When requesting specific column names, they need to be URL encoded: + ap, auth-for, capabilities, entity+id, idle, roam-duration, rx+bytes, rx+pkts, + rx+rate, signal, station+bssid, tx+bytes, tx+pkts, tx+rate, tx+retries, tx-failed, + + Example URL: /stations?fields=ap,auth-for + + Example py-json call (it knows the URL): + record = LFJsonGet.get_stations(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'ap': # The Port that owns this station. + 'auth-for': # Duration in seconds this station has been authenticated. + 'capabilities': # Station's negotiated capabilities. + 'entity id': # Entity ID + 'idle': # Miliseconds since this station last received a frame from the peer. + 'roam-duration': # The difference between the authenticate-time on the new APand the last + # frame received on old AP, in milliseconds.It is not always possible to + # compute this accurately,especially if traffic is not flowing during the + # roam. + 'rx bytes': # RX Byte counter for this station. + 'rx pkts': # RX Packets counter for this station. + 'rx rate': # Station last received encoding rate. + 'signal': # Station signal quality. + 'station bssid': # Station's MAC address (BSSID). + 'tx bytes': # TX Byte counter for this station. + 'tx pkts': # TX Packets counter for this station. + 'tx rate': # Station transmit encoding rate. + 'tx retries': # TX Retries counter for this station.This counts retries at the driver + # level.Retries made by the WiFi hardware and/or firmware is not counted. + 'tx-failed': # TX Failed counter for this station.This counts TX failures at the driver + # level.The hardware and/or firmware may have made several failed attempts + # that are not included in this counter. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_stations(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/stations" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="station", + plural_key="stations") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <STATUS-MSG> type requests + + If you need to call the URL directly, + request one of these URLs: + /status-msg/ + /status-msg/$session + /status-msg/$session/$id + /status-msg/$session/$id/ws-msg,... + /status-msg/$session/all + /status-msg/$session/this + /status-msg/sessions + + + Example py-json call (it knows the URL): + record = LFJsonGet.get_status_msg(eid_list=['1.234', '1.344'], + debug_=True) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_status_msg(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/status-msg" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="sessions/messages", + plural_key="sessions/messages") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <TEST-GROUP> type requests + + If you need to call the URL directly, + request one of these URLs: + /test-group/ + /test-group/$id + /test-groups/ + /test-groups/$id + + When requesting specific column names, they need to be URL encoded: + cross+connects, entity+id, name, run, script + Example URL: /test-group?fields=cross+connects,entity+id + + Example py-json call (it knows the URL): + record = LFJsonGet.get_test_group(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'cross connects': # List of Test Manager's Cross-Connects. + 'entity id': # Entity ID + 'name': # Test Group's Name. + 'run': # Is Test Group running or not. + 'script': # Endpoint script state. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_test_group(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/test-group" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="groups", + plural_key="groups") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <TEXT> type requests + + If you need to call the URL directly, + request one of these URLs: + /text/ + /text/$group + /text/$group/$class + /text/$group/$class/$key + + When requesting specific column names, they need to be URL encoded: + eid, name, text, type + Example URL: /text?fields=eid,name + + Example py-json call (it knows the URL): + record = LFJsonGet.get_text(eid_list=['1.234', '1.344'], + requested_col_names=['text'], + debug_=True) + + The record returned will have these members: + { + 'eid': # - + 'name': # - + 'text': # - + 'type': # - + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_text(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/text" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="record", + plural_key="records") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <VOIP> type requests + + If you need to call the URL directly, + request one of these URLs: + /voip-endp/ + /voip-endp/$endp_id + /voip-ep/ + /voip-ep/$endp_id + /voip/ + /voip/$cx_id + /voip_endp/ + /voip_endp/$endp_id + /voip_ep/ + /voip_ep/$endp_id + + When requesting specific column names, they need to be URL encoded: + bps+rx+a, bps+rx+b, delay+a+%E2%86%90+b, delay+a+%E2%86%92+b, eid, endpoints+%28a%C2%A0%E2%86%94%C2%A0b%29, + entity+id, jitter+a+%E2%86%90+b, jitter+a+%E2%86%92+b, name, pkt+tx+a%C2%A0%E2%86%90%C2%A0b, + pkt+tx+a%C2%A0%E2%86%92%C2%A0b, rpt+timer, rx+drop+%25+a, rx+drop+%25+b, state, + type + Example URL: /voip?fields=bps+rx+a,bps+rx+b + + Example py-json call (it knows the URL): + record = LFJsonGet.get_voip(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'bps rx a': # Endpoint B's real transmit rate (bps).Measured at the CX Type layer. + 'bps rx b': # Endpoint A's real transmit rate (bps).Measured at the CX Type layer. + 'delay a ← b': # Average Latency in milliseconds for traffic from Endpoint B to Endpoint + # A + 'delay a → b': # Average Latency in milliseconds for traffic from Endpoint A to Endpoint + # B + 'eid': # Entity ID + 'endpoints (a ↔ b)': # Endpoints that make up this Cross Connect. + 'entity id': # Entity ID + 'jitter a ← b': # Average Jitter in milliseconds for traffic from Endpoint B to Endpoint A + 'jitter a → b': # Average Jitter in milliseconds for traffic from Endpoint A to Endpoint B + 'name': # Cross Connect's Name. + 'pkt tx a ← b': # Endpoint B's Packets Transmitted. + 'pkt tx a → b': # Endpoint A's Packets Transmitted. + 'rpt timer': # Cross Connect's Report Timer (milliseconds).This is how often the GUI + # will ask for updates from the LANforge processes.If the GUI is sluggish, + # increasing the report timers may help. + 'rx drop % a': # Endpoint A percentage packet loss.Calculated using the number of PDUs + # Endpoint B sent minus the number Endpoint A received.This number is not + # 100% correct as long as packets are in flight.After a Quiesce of the + # test, the number should be perfectly accurate. + 'rx drop % b': # Endpoint B percentage packet loss.Calculated using the number of PDUs + # Endpoint A sent minus the number Endpoint B received.This number is not + # 100% correct as long as packets are in flight.After a Quiesce of the + # test, the number should be perfectly accurate. + 'state': # Current State of the connection.UninitializedHas not yet been + # started/stopped.InitializingBeing set up.StartingStarting the + # test.RunningTest is actively running.StoppedTest has been + # stopped.QuiesceTest will gracefully stop soon.HW-BYPASSTest is in + # hardware-bypass mode (WanLinks only)FTM_WAITTest wants to run, but is + # phantom, probably due to non-existent interface or resource.WAITINGWill + # restart as soon as resources are available.PHANTOMTest is stopped, and + # is phantom, probably due to non-existent interface or resource. + 'type': # Cross-Connect type. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_voip(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/voip" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="connection", + plural_key="connections") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <VOIP-ENDP> type requests + + If you need to call the URL directly, + request one of these URLs: + /voip-endp/ + /voip-endp/$endp_id + + When requesting specific column names, they need to be URL encoded: + calls+answered, calls+attempted, calls+completed, calls+failed, cf+404, cf+408, + cf+busy, cf+canceled, delay, destination+addr, dropped, dup+pkts, eid, elapsed, + entity+id, jb+cur, jb+over, jb+silence, jb+under, jitter, mng, name, ooo+pkts, + pesq, pesq+bklg, pesq%23, reg+state, rst, rtp+rtt, run, rx+bytes, rx+pkts, + source+addr, state, tx+bytes, tx+pkts, vad+pkts + Example URL: /voip-endp?fields=calls+answered,calls+attempted + + Example py-json call (it knows the URL): + record = LFJsonGet.get_voip_endp(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'calls answered': # Number of calls that where the remote answered + 'calls attempted': # Number of calls that have been attempted + 'calls completed': # Number of calls that have been successfully completed + 'calls failed': # Number of calls that did not succeed for any reason. + 'cf 404': # Number of calls failed for '404': callee not found. + 'cf 408': # Number of calls failed for '408': callee did not answer. + 'cf busy': # Number of calls failed because callee is busy. + 'cf canceled': # Number of calls failed because they were canceled. + 'delay': # Average latency in milliseconds for packets received by this endpoint. + 'destination addr': # Destination Address (MAC, ip/port, VoIP destination). + 'dropped': # Total dropped packets, as identified by gaps in RTP sequence numbers + # (pre jitter buffer). + 'dup pkts': # Total duplicate packets, as identified by RTP sequence numbers (pre + # jitter buffer). + 'eid': # Entity ID + 'elapsed': # Amount of time (seconds) this endpoint has been running (or ran.) + 'entity id': # Entity ID + 'jb cur': # Current number of packets in the jitter buffer waiting to be played / + # Jitter Buffer Size. + 'jb over': # Total times the jitter buffer was given more packets than it could hold. + 'jb silence': # Silence is played when there is no valid voice packet, due to drop, or + # reorder/jitter/latency out of range of the jitter buffer. + 'jb under': # Total times the reader asked for a packet to play but the jitter buffer + # was empty. + 'jitter': # Average interpacket variation, calculated per RFC 1889 A.8. + 'mng': # Is the Endpoint managed or not? + 'name': # Endpoint's Name. + 'ooo pkts': # Total out-of-order packets, as identified by RTP sequence numbers (pre + # jitter buffer). + 'pesq': # PESQ Report score for the PESQ report number (PESQ#). + 'pesq bklg': # PESQ server call processing backlog. + 'pesq#': # The pesq-report-number to which the PESQ value cooresponds. + 'reg state': # Current State of the Endpoint. + 'rst': # How many times has the endpoint been restarted due to abnormal + # termination. + 'rtp rtt': # Round trip latency as reported by RTCP + 'run': # Is the Endpoint is Running or not. + 'rx bytes': # Total received bytes count. + 'rx pkts': # Total received packet count. + 'source addr': # Source Address (MAC, ip/port, VoIP source). + 'state': # Phone registration state + 'tx bytes': # Total transmitted bytes count. + 'tx pkts': # Total transmitted packet count. + 'vad pkts': # Total VAD (Silence Suppression) packets suppressed before transmit. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_voip_endp(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/voip-endp" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="endpoint", + plural_key="endpoints") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <VR> type requests + + If you need to call the URL directly, + request one of these URLs: + /vr-cx/ + /vr-cx/$shelf_id/$resource_id/$port_id + /vr/ + /vr/$shelf_id/$resource_id + /vrcx/ + /vrcx/$shelf_id/$resource_id/$port_id + + When requesting specific column names, they need to be URL encoded: + active+ipv6+router, bgp+4byte+as, bgp+damping, bgp+peers, cluster+id, collision+domain+id, + confederation+id, damping+half+life, damping+max+suppress, damping+reuse, + damping+suppress, entity+id, height, ipv6+radv, is+bgp+reflector, local+as, + multicast+routing, name, netsmith-state, notes, pad, ripv2, router+connections, + router+id, router+id, use+confederation, use+existing+cfg, use+ospf, use+rip+dft+route, + using+bgp, using+olsr, width, x, xorp+sha, y + Example URL: /vr?fields=active+ipv6+router,bgp+4byte+as + + Example py-json call (it knows the URL): + record = LFJsonGet.get_vr(eid_list=['1.234', '1.344'], + requested_col_names=['netsmith-state'], + debug_=True) + + The record returned will have these members: + { + 'active ipv6 router': # - + 'bgp 4byte as': # - + 'bgp damping': # lc_key > lc_col_name- + 'bgp peers': # - + 'cluster id': # - + 'collision domain id': # - + 'confederation id': # - + 'damping half life': # - + 'damping max suppress': # - + 'damping reuse': # - + 'damping suppress': # - + 'entity id': # Entity ID + 'height': # - + 'ipv6 radv': # - + 'is bgp reflector': # - + 'local as': # - + 'multicast routing': # - + 'name': # Name + 'netsmith-state': # - + 'notes': # - + 'pad': # - + 'ripv2': # - + 'router connections': # - + 'router id': # - + 'router id': # - + 'use confederation ': # - + 'use existing cfg': # - + 'use ospf': # - + 'use rip dft route': # - + 'using bgp': # - + 'using olsr': # - + 'width': # - + 'x': # - + 'xorp sha': # - + 'y': # - + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_vr(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/vr" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="virtual-routers", + plural_key="virtual-routers") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <VRCX> type requests + + If you need to call the URL directly, + request one of these URLs: + /vrcx/ + /vrcx/$shelf_id/$resource_id/$port_id + + When requesting specific column names, they need to be URL encoded: + entity+id, height, interface+cost, local-a, local-b, netsmith-state, remote-a, + remote-b, resource, rip+metric, vrrp+id, vrrp+interval, vrrp+ip, vrrp+ip-prefix, + vrrp+priority, wan+link, width, x, y + Example URL: /vrcx?fields=entity+id,height + + Example py-json call (it knows the URL): + record = LFJsonGet.get_vrcx(eid_list=['1.234', '1.344'], + requested_col_names=['netsmith-state'], + debug_=True) + + The record returned will have these members: + { + 'entity id': # - + 'height': # - + 'interface cost': # - + 'local-a': # - + 'local-b': # - + 'netsmith-state': # - + 'remote-a': # - + 'remote-b': # - + 'resource': # - + 'rip metric': # - + 'vrrp id': # - + 'vrrp interval': # - + 'vrrp ip': # - + 'vrrp ip-prefix': # - + 'vrrp priority': # - + 'wan link': # - + 'width': # - + 'x': # - + 'y': # - + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_vrcx(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/vrcx" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="router-connections", + plural_key="router-connections") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <WL> type requests + + If you need to call the URL directly, + request one of these URLs: + /wl-endp/ + /wl-endp/$wl_ep_id + /wl-ep/ + /wl-ep/$wl_ep_id + /wl/ + /wl/$wl_id + /wl_endp/ + /wl_endp/$wl_ep_id + /wl_ep/ + /wl_ep/$wl_ep_id + /wlendp/$wl_ep_id + + When requesting specific column names, they need to be URL encoded: + bps+rx+a, bps+rx+b, eid, endpoints+%28a%C2%A0%E2%86%94%C2%A0b%29, entity+id, k-m, + name, pkt+tx+a%C2%A0%E2%86%90%C2%A0b, pkt+tx+a%C2%A0%E2%86%92%C2%A0b, rpt+timer, + state + Example URL: /wl?fields=bps+rx+a,bps+rx+b + + Example py-json call (it knows the URL): + record = LFJsonGet.get_wl(eid_list=['1.234', '1.344'], + requested_col_names=['entity id'], + debug_=True) + + The record returned will have these members: + { + 'bps rx a': # Endpoint B's Max transmit rate (bps). + 'bps rx b': # Endpoint A's Max transmit rate (bps). + 'eid': # Entity ID + 'endpoints (a ↔ b)': # Endpoints that make up this WanLink. + 'entity id': # Entity ID + 'k-m': # Whether the WanLink is Kernel-Mode or not. + 'name': # WanLink's Name. + 'pkt tx a ← b': # Packets received on endpoint B and transmitted out endpoint A. + 'pkt tx a → b': # Packets received on endpoint A and transmitted out endpoint B. + 'rpt timer': # Cross Connect's Report Timer (milliseconds).This is how often the GUI + # will ask for updates from the LANforge processes.If the GUI is sluggish, + # increasing the report timers may help. + 'state': # Current State of the connection.UninitializedHas not yet been + # started/stopped.InitializingBeing set up.StartingStarting the + # test.RunningTest is actively running.StoppedTest has been + # stopped.QuiesceTest will gracefully stop soon.HW-BYPASSTest is in + # hardware-bypass mode (WanLinks only)FTM_WAITTest wants to run, but is + # phantom, probably due to non-existent interface or resource.WAITINGWill + # restart as soon as resources are available.PHANTOMTest is stopped, and + # is phantom, probably due to non-existent interface or resource. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_wl(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/wl" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="", + plural_key="") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <WL-ENDP> type requests + + If you need to call the URL directly, + request one of these URLs: + /wl-endp/ + /wl-endp/$wl_ep_id + + When requesting specific column names, they need to be URL encoded: + buffer, corrupt+1, corrupt+2, corrupt+3, corrupt+4, corrupt+5, corrupt+6, + delay, dropfreq+%25, dropped, dup+pkts, dupfreq+%25, eid, elapsed, extrabuf, + failed-late, jitfreq+%25, max+rate, maxjitter, maxlate, name, ooo+pkts, qdisc, + reordfrq+%25, run, rx+bytes, rx+pkts, script, serdelay, tx+bytes, tx+drop+%25, + tx+pkts, tx+rate, tx-failed, wps + Example URL: /wl-endp?fields=buffer,corrupt+1 + + Example py-json call (it knows the URL): + record = LFJsonGet.get_wl_endp(eid_list=['1.234', '1.344'], + requested_col_names=['eid'], + debug_=True) + + The record returned will have these members: + { + 'buffer': # Maximum size of receive buffer, in bytes.This is the sum of the amount + # needed for the transit buffers (delay * bandwidth)plus the WanLink + # "Backlog Buffer:" queue size which handles bursts. + 'corrupt 1': # Counters for how many times this corruption has been applied. + 'corrupt 2': # Counters for how many times this corruption has been applied. + 'corrupt 3': # Counters for how many times this corruption has been applied. + 'corrupt 4': # Counters for how many times this corruption has been applied. + 'corrupt 5': # Counters for how many times this corruption has been applied. + 'corrupt 6': # Counters for how many times this corruption has been applied. + 'delay': # Base induced latency on received packets, in microseconds. + 'dropfreq %': # Frequency out of 1,000,000 to drop a received packet.Select a preset + # value or enter your own. + 'dropped': # Total dropped packets on receive.This does not include the tx-failed + # counters. + 'dup pkts': # Total duplicate packets generated. + 'dupfreq %': # Frequency out of 1,000,000 to duplicate a received packet.Select a + # preset value or enter your own. + 'eid': # Entity ID + 'elapsed': # Amount of time (seconds) this endpoint has been running (or ran.) + 'extrabuf': # Size of "Backlog Buffer:" setting in WanLink configuration in bytes. + 'failed-late': # Total amount of received packets that could not be transmitted out the + # peer becausethe emulator was overloaded and could not transmit within + # the specified 'lateness' + 'jitfreq %': # Frequency out of 1,000,000 that packets should have jitter applied to + # them.Select a preset value or enter your own. + 'max rate': # Max transmit rate (bps) for this Endpoint. + 'maxjitter': # Maximum additional delay, in microseconds. See Jitter-Frequency as + # well. + 'maxlate': # The maximum lateness in milliseconds allowed before packets will be + # dropped on transmit.If lateness is configured to be automatic, this + # variable will change based onconfigured bandwidth and backlog buffer, + # but will not go below 10ms. + 'name': # Endpoint's Name. + 'ooo pkts': # Total out of order packets generated. + 'qdisc': # Queueing discipline (FIFO, WRR, etc). + 'reordfrq %': # Frequency out of 1,000,000 to re-order a received packet.Select a preset + # value or enter your own. + 'run': # Is the Endpoint is Running or not. + 'rx bytes': # Total received bytes count. + 'rx pkts': # Total received packet count. + 'script': # Endpoint script state. + 'serdelay': # Additional serialization delay for a 1514 byte packet at the configured + # speed (microseconds). + 'tx bytes': # Total transmitted bytes count. + 'tx drop %': # Packet drop percentage over the last 1 minute. + 'tx pkts': # Packets received on the peer interface and transmitted out this + # endpoint's interface. + 'tx rate': # The average speed over the last 30 seconds at which we are + # transmittingout the peer interface.This can be thought of as the actual + # transfer rate for packets entering the interfaceassociated with this + # Endpoint. + 'tx-failed': # Total amount of received packets that could not be transmitted out the + # peer.This includes any tx-failed-late packets. + 'wps': # Enable/Disable showing of WanPaths for individual endpoints. + } + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_wl_endp(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/wl-endp" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="endpoint", + plural_key="endpoint") + # + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <WS-MSG> type requests + + If you need to call the URL directly, + request one of these URLs: + /ws-msg/ + /ws-msg/$sessionid + + + Example py-json call (it knows the URL): + record = LFJsonGet.get_ws_msg(eid_list=['1.234', '1.344'], + debug_=True) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + def get_ws_msg(self, + eid_list=None, + requested_col_names=(), + debug_=False): + debug_ |= self.debug + url = "/ws-msg" + if (eid_list is None) or (len(eid_list) < 1): + raise ValueError("no entity id in request") + trimmed_fields = [] + if isinstance(requested_col_names, str): + if not requested_col_names.strip(): + raise ValueError("column name cannot be blank") + trimmed_fields.append(requested_col_names.strip()) + if isinstance(requested_col_names, list): + for field in requested_col_names: + if not field.strip(): + raise ValueError("column names cannot be blank") + field = field.strip() + if field.find(" ") > -1: + raise ValueError("field should be URL encoded: [%s]" % field) + trimmed_fields.append(field) + url += self.make_eid_url(eid_list=eid_list) + + if len(trimmed_fields) > 0: + url += "?fields=%s" % (",".join(trimmed_fields)) + + response = self.json_get(url, debug_=debug_) + if response is None: + return None + return self.extract_values(response=response, + singular_key="", + plural_key="") + # + + +"""----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + + These are POST requests + +----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + +class LFJsonPost(LFCliBase): + def __init__(self, lfclient_host='localhost', + lfclient_port=8080, + debug_=False, + _exit_on_error=False, + _exit_on_fail=False, + _proxy_str=None, + _capture_signal_list=()): + super().__init__(_lfjson_host=lfclient_host, + _lfjson_port=lfclient_port, + _debug=debug_, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail, + _proxy_str=_proxy_str, + _capture_signal_list=_capture_signal_list) + + @staticmethod + def set_flags(flag_class: IntFlag, starting_value: int, flag_names=None): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + :param flag_class: flag class, a subclass of IntFlag + :param starting_value: integer flag value to OR values into + :param flag_names: list of flag names to convert to integers to OR onto starting_value + + Example Usage: + value = LFJsonPost.add_flags(SetPortMumble, 0, flag_names=['bridge', 'dhcp']) + print('value now: '+value) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + if starting_value is None: + raise ValueError("starting_value should be an integer greater or equal than zero, not None") + if not flag_names: + raise ValueError("flag_names should be a name or a list of names, not None") + if type(flag_names) is list: + selected_flags = [] + for flag in flag_names: + if isinstance(flag, str): + if flag not in flag_class.__members__: + raise ValueError("%s lacks member:[%s]" % + (flag_class.__class__.__name__, flag)) + selected_flags.extend([flag_class[member].value + for member in flag_class.__members__ if member == flag]) + if isinstance(flag, IntFlag): + if flag not in flag_class: + raise ValueError("%s lacks member:[%s]" % + (flag_class.__class__.__name__, flag)) + selected_flags.extend([member.value + for member in flag_class.__members___ if member == flag]) + selected_flags.append(starting_value) + result_flags = 0 + for i in selected_flags: + result_flags |= i + return result_flags + f_name = None + if type(flag_names) is str: + f_name = flag_names + print('f_name is str %s' % f_name) + else: + print('f_name is %s' % type(flag_names)) + if f_name not in flag_class.__members__: + raise ValueError("%s lacks member:[%s]" % + (flag_class.__class__.__name__, f_name)) + return flag_class.valueof(f_name) + + @staticmethod + def clear_flags(flag_class: IntFlag, starting_value: int, flag_names=None): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + :param flag_class: flag class, a subclass of IntFlag + :param starting_value: integer flag value to OR values into + :param flag_names: list of flag names to convert to integers to OR onto starting_value + + Example Usage: + value = LFJsonPost.clear_flags(SetPortMumble, 0, flag_names=['bridge', 'dhcp']) + print('value now: '+value) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + if starting_value is None: + raise ValueError("starting_value should be an integer greater than zero and not None") + if not flag_names: + raise ValueError("flag_names should be a name or a list of names, not None") + unselected_val = None + if type(flag_names) is list: + unselected_val = starting_value + for flag in flag_names: + if isinstance(flag, str): + if flag not in flag_class.__members__: + raise ValueError("%s has no member:[%s]" % (flag_class.__class__.__name__, flag)) + if isinstance(flag, IntFlag): + if flag not in flag_class: + raise ValueError("%s has no member:[%s]" % (flag_class.__class__.__name__, flag)) + unselected_val &= ~flag.value + # print("unselected b[%s]" % (hex(unselected_val))) + return unselected_val + if isinstance(flag_names, str): + if flag_names not in flag_class.__members__: + raise ValueError("%s lacks member:[%s]" % + (flag_class.__class__.__name__, flag_names)) + unselected_val = starting_value + unselected_val &= ~flag_class.valueof(flag_names) + if isinstance(flag_names, IntFlag): + if flag_names not in flag_class: + raise ValueError("%s lacks member:[%s]" % + (flag_class.__class__.__name__, flag_names)) + unselected_val = starting_value + unselected_val &= ~flag_names.value + return unselected_val + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_ARM_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_arm_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_arm_endp(self, + alias=None, # Name of endpoint. + cpu_id=None, # Preferred CPU ID on which this endpoint should run. + mx_pkt_sz=None, # Maximum packet size, including all Ethernet headers. + pkt_sz=None, # Minimum packet size, including all Ethernet headers. + port=None, # Port number. + pps=None, # Packets per second to generate. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + tos=None, # The Type of Service, can be HEX. See set_endp_tos for details. + p_type=None, # Endpoint Type : arm_udp + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_arm_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if cpu_id is not None: + data["cpu_id"] = cpu_id + if mx_pkt_sz is not None: + data["mx_pkt_sz"] = mx_pkt_sz + if pkt_sz is not None: + data["pkt_sz"] = pkt_sz + if port is not None: + data["port"] = port + if pps is not None: + data["pps"] = pps + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if tos is not None: + data["tos"] = tos + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_arm_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_BGP_PEER> type requests + + https://www.candelatech.com/lfcli_ug.php#add_bgp_peer + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddBgpPeerFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddBgpPeerFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + ENABLE_PEER = 0x1 # Set this to zero if you don't want this peer enabled. + PEER_CLIENT = 0x2 # Sets corresponding Xorp flag in BGP Peer section. + PEER_CONFED_MEMBER = 0x4 # Sets corresponding Xorp flag in BGP Peer section. + PEER_UNICAST_V4 = 0x8 # Sets corresponding Xorp flag in BGP Peer section. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddBgpPeerFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_bgp_peer(self, + p_as=None, # BGP Peer Autonomous System number, 0-65535 + delay_open_time=None, # BGP Peer delay open time. + flags=None, # Virtual router BGP Peer flags, see above for definitions. + holdtime=None, # BGP Peer hold-time. + local_dev=None, # BGP Peer Local interface. + nexthop=None, # BGP Peer Nexthop, IPv4 Address. + nexthop6=None, # BGP Peer IPv6 Nexthop address. + peer_id=None, # BGP Peer Identifier: IPv4 Address + peer_index=None, # Peer index in this virtual router (0-7). + resource=None, # Resource number. + shelf=None, # Shelf name/id. + vr_id=None, # Name of virtual router. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_bgp_peer(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if p_as is not None: + data["as"] = p_as + if delay_open_time is not None: + data["delay_open_time"] = delay_open_time + if flags is not None: + data["flags"] = flags + if holdtime is not None: + data["holdtime"] = holdtime + if local_dev is not None: + data["local_dev"] = local_dev + if nexthop is not None: + data["nexthop"] = nexthop + if nexthop6 is not None: + data["nexthop6"] = nexthop6 + if peer_id is not None: + data["peer_id"] = peer_id + if peer_index is not None: + data["peer_index"] = peer_index + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if vr_id is not None: + data["vr_id"] = vr_id + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_bgp_peer", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_BOND> type requests + + https://www.candelatech.com/lfcli_ug.php#add_bond + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_bond(self, + network_devs=None, # Comma-separated list of network devices: eth1,eth2,eth3... + port=None, # Name of the bond device. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_bond(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if network_devs is not None: + data["network_devs"] = network_devs + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_bond", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_BR> type requests + + https://www.candelatech.com/lfcli_ug.php#add_br + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddBrBrFlags(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + none = 0 # no features + stp_enabled = 1 # Enable Spanning Tree Protocol (STP) + + def post_add_br(self, + br_aging_time=None, # MAC aging time, in seconds, 32-bit number. + br_flags=None, # Bridge flags, see above. + br_forwarding_delay=None, # How long to wait until the bridge will start forwarding packets. + br_hello_time=None, # How often does the bridge send out STP hello packets. + br_max_age=None, # How long until STP considers a non-responsive bridge dead. + br_priority=None, # Bridge priority, 16-bit number. + network_devs=None, # Comma-separated list of network devices: eth1,eth2,eth3... + port=None, # Name of the bridge device. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_br(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if br_aging_time is not None: + data["br_aging_time"] = br_aging_time + if br_flags is not None: + data["br_flags"] = br_flags + if br_forwarding_delay is not None: + data["br_forwarding_delay"] = br_forwarding_delay + if br_hello_time is not None: + data["br_hello_time"] = br_hello_time + if br_max_age is not None: + data["br_max_age"] = br_max_age + if br_priority is not None: + data["br_priority"] = br_priority + if network_devs is not None: + data["network_devs"] = network_devs + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_br", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_CD> type requests + + https://www.candelatech.com/lfcli_ug.php#add_cd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddCdFlags(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + ERR = 2 # Set to kernel mode. + RUNNING = 1 # Set to running state. + + def post_add_cd(self, + alias=None, # Name of Collision Domain. + bps=None, # Maximum speed at which this collision domain can run. + flags=None, # See above. Leave blank or use 'NA' for no default values. + report_timer=None, # How often to report stats. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + state=None, # RUNNING or STOPPED (default is RUNNING). Use this to start/stop. + p_type=None, # CD Type: WIFI, WISER_SURFACE, WISER_SURFACE_AIR, WISER_AIR_AIR, + # WISER_NCW + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_cd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if bps is not None: + data["bps"] = bps + if flags is not None: + data["flags"] = flags + if report_timer is not None: + data["report_timer"] = report_timer + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if state is not None: + data["state"] = state + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_cd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_CD_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_cd_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_cd_endp(self, + cd=None, # Name of Collision Domain. + endp=None, # Endpoint name/id. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_cd_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cd is not None: + data["cd"] = cd + if endp is not None: + data["endp"] = endp + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_cd_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_CD_VR> type requests + + https://www.candelatech.com/lfcli_ug.php#add_cd_vr + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_cd_vr(self, + cd=None, # Name of Collision Domain. + vr=None, # Virtual-Router name/ID. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_cd_vr(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cd is not None: + data["cd"] = cd + if vr is not None: + data["vr"] = vr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_cd_vr", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_CHAMBER> type requests + + https://www.candelatech.com/lfcli_ug.php#add_chamber + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddChamberChamberFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddChamberChamberFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + OPEN = 0x4 # (3) Door is open, no real isolation right now. + PHANTOM = 0x1 # (1) Chamber is not actually here right now. + VIRTUAL = 0x2 # (2) No real chamber, open-air grouping of equipment. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddChamberChamberFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_chamber(self, + chamber_type=None, # Chamber type, see above. Use 1 for Medium if uncertain. + dut_name1=None, # Name of first DUT in this chamber or NA + dut_name2=None, # Name of second DUT in this chamber or NA + dut_name3=None, # Name of third DUT in this chamber or NA + dut_name4=None, # Name of fourth DUT in this chamber or NA + flags=None, # Flag field for Chamber, see above. + flags_mask=None, # Mask of what flags to pay attention to, or NA for all. + height=None, # Height to be used when drawn in the LANforge-GUI. + isolation=None, # Estimated isolation in db for this chamber. + lanforge1=None, # EID of first LANforge Resource in this chamber or NA + lanforge2=None, # EID of second LANforge Resource in this chamber or NA + lanforge3=None, # EID of third LANforge Resource in this chamber or NA + lanforge4=None, # EID of fourth LANforge Resource in this chamber or NA + name=None, # Name of Chamber, unique identifier. + resource=None, # LANforge Resource ID for controlling turn-table via serial + # protocol. + sma_count=None, # Number of SMA connectors on this chamber, default is 16. + turntable_type=None, # Turn-Table type: see above. + width=None, # Width to be used when drawn in the LANforge-GUI. + x=None, # X coordinate to be used when drawn in the LANforge-GUI. + y=None, # Y coordinate to be used when drawn in the LANforge-GUI. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_chamber(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if chamber_type is not None: + data["chamber_type"] = chamber_type + if dut_name1 is not None: + data["dut_name1"] = dut_name1 + if dut_name2 is not None: + data["dut_name2"] = dut_name2 + if dut_name3 is not None: + data["dut_name3"] = dut_name3 + if dut_name4 is not None: + data["dut_name4"] = dut_name4 + if flags is not None: + data["flags"] = flags + if flags_mask is not None: + data["flags_mask"] = flags_mask + if height is not None: + data["height"] = height + if isolation is not None: + data["isolation"] = isolation + if lanforge1 is not None: + data["lanforge1"] = lanforge1 + if lanforge2 is not None: + data["lanforge2"] = lanforge2 + if lanforge3 is not None: + data["lanforge3"] = lanforge3 + if lanforge4 is not None: + data["lanforge4"] = lanforge4 + if name is not None: + data["name"] = name + if resource is not None: + data["resource"] = resource + if sma_count is not None: + data["sma_count"] = sma_count + if turntable_type is not None: + data["turntable_type"] = turntable_type + if width is not None: + data["width"] = width + if x is not None: + data["x"] = x + if y is not None: + data["y"] = y + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_chamber", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_CHAMBER_CX> type requests + + https://www.candelatech.com/lfcli_ug.php#add_chamber_cx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddChamberCxChamberCxFlags(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + CONNECTED = 1 # (1) Connected to something. If flag is not set, connection is open to the air + # +(maybe with antenna) + TERMINATED = 2 # (2) Connection is terminated, signal shall not pass! + + def post_add_chamber_cx(self, + a_id=None, # EidAntenna in string format for A side connection. + atten_id=None, # EID for the Attenuator module if one is inline on this + # connection. + b_id=None, # EidAntenna in string format for B side connection. + connection_idx=None, # Connection index, currently up to 32 connections supported + # (0-31) + flags=None, # Flag field for Chamber Connection, see above. + flags_mask=None, # Mask of what flags to pay attention to, or NA for all. + internal=None, # Internal (1) or not (0): Internal connections are no longer + # supported. + min_atten=None, # Specify minimum attenuation in 10ths of a db. Distance logic + # will not set atten below this. + name=None, # Name of Chamber, unique identifier. + zrssi2=None, # Specify 2.4Ghz zero-attenuation RSSI in 10ths of a db. + # Distance logic will consider this in its calculations. + zrssi5=None, # Specify 5Ghz zero-attenuation RSSI in 10ths of a db. Distance + # logic will consider this in its calculations. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_chamber_cx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if a_id is not None: + data["a_id"] = a_id + if atten_id is not None: + data["atten_id"] = atten_id + if b_id is not None: + data["b_id"] = b_id + if connection_idx is not None: + data["connection_idx"] = connection_idx + if flags is not None: + data["flags"] = flags + if flags_mask is not None: + data["flags_mask"] = flags_mask + if internal is not None: + data["internal"] = internal + if min_atten is not None: + data["min_atten"] = min_atten + if name is not None: + data["name"] = name + if zrssi2 is not None: + data["zrssi2"] = zrssi2 + if zrssi5 is not None: + data["zrssi5"] = zrssi5 + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_chamber_cx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_CHAMBER_PATH> type requests + + https://www.candelatech.com/lfcli_ug.php#add_chamber_path + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_chamber_path(self, + chamber=None, # Chamber Name. + content=None, # [BLANK] will erase all content, any other text will be appended + # to existing text. <tt escapearg='false'>Unescaped Value</tt> + path=None, # Path Name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_chamber_path(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if chamber is not None: + data["chamber"] = chamber + if content is not None: + data["content"] = content + if path is not None: + data["path"] = path + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_chamber_path", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_CHANNEL_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_channel_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddChannelGroupTypes(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + clear = "clear" # Channel(s) are bundled into a single span. No conversion or + e_m = "e&m" # Channel(s) are signalled using E&M signalling (specific + fcshdlc = "fcshdlc" # The zapdel driver performs HDLC encoding and decoding on the + fxogs = "fxogs" # Channel(s) are signalled using FXO Groundstart protocol. + fxoks = "fxoks" # Channel(s) are signalled using FXO Koolstart protocol. + fxols = "fxols" # Channel(s) are signalled using FXO Loopstart protocol. + fxsgs = "fxsgs" # Channel(s) are signalled using FXS Groundstart protocol. + fxsks = "fxsks" # Channel(s) are signalled using FXS Koolstart protocol. + fxsls = "fxsls" # Channel(s) are signalled using FXS Loopstart protocol. + indclear = "indclear" # Like 'clear' except all channels are treated individually and + nethdlc = "nethdlc" # The zaptel driver bundles the channels together into an + rawhdlc = "rawhdlc" # The zaptel driver performs HDLC encoding and decoding on the + unused = "unused" # No signalling is performed, each channel in the list remains idle + + def post_add_channel_group(self, + alias=None, # Name for this Channel Group. + channels=None, # List of channels to add to this group. + idle_flag=None, # Idle flag (byte) for this channel group, for instance: 0x7e + mtu=None, # MTU (and MRU) for this channel group. Must be a multiple of + # the number of channels if configuring a T1 WanLink. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + span_num=None, # The span number. First span is 1, second is 2... + p_type=None, # The channel-type. Use 'clear' for PPP links. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_channel_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if channels is not None: + data["channels"] = channels + if idle_flag is not None: + data["idle_flag"] = idle_flag + if mtu is not None: + data["mtu"] = mtu + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if span_num is not None: + data["span_num"] = span_num + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_channel_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_CX> type requests + + https://www.candelatech.com/lfcli_ug.php#add_cx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_cx(self, + alias=None, # Name of the Cross Connect to create. + rx_endp=None, # Name of Receiving endpoint. + test_mgr=None, # Name of test-manager to create the CX on. + tx_endp=None, # Name of Transmitting endpoint. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_cx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if rx_endp is not None: + data["rx_endp"] = rx_endp + if test_mgr is not None: + data["test_mgr"] = test_mgr + if tx_endp is not None: + data["tx_endp"] = tx_endp + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_cx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_DUT> type requests + + https://www.candelatech.com/lfcli_ug.php#add_dut + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddDutDutFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddDutDutFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + p_11r = 0x200 # Use .11r connection logic on all ssids, deprecated, see add_dut_ssid. + AP_MODE = 0x2 # (2) DUT acts as AP. + DHCPD_LAN = 0x40 # Provides DHCP server on LAN port + DHCPD_WAN = 0x80 # Provides DHCP server on WAN port + EAP_PEAP = 0x800 # Use EAP-PEAP connection logic on all ssids, deprecated, see add_dut_ssid. + EAP_TTLS = 0x400 # Use EAP-TTLS connection logic on all ssids, deprecated, see add_dut_ssid. + INACTIVE = 0x4 # (3) Ignore this in ChamberView, etc + NOT_DHCPCD = 0x1000 # Station/edge device that is NOT using DHCP. + STA_MODE = 0x1 # (1) DUT acts as Station. + WEP = 0x8 # Use WEP encryption on all ssids, deprecated, see add_dut_ssid. + WPA = 0x10 # Use WPA encryption on all ssids, deprecated, see add_dut_ssid. + WPA2 = 0x20 # Use WPA2 encryption on all ssids, deprecated, see add_dut_ssid. + WPA3 = 0x100 # Use WPA3 encryption on all ssids, deprecated, see add_dut_extras. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddDutDutFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_dut(self, + antenna_count1=None, # Antenna count for first radio. + antenna_count2=None, # Antenna count for second radio. + antenna_count3=None, # Antenna count for third radio. + api_id=None, # DUT API Identifier (none specified yet) + bssid1=None, # BSSID for first radio. + bssid2=None, # BSSID for second radio. + bssid3=None, # BSSID for third radio. + eap_id=None, # EAP Identifier, for EAP-PEAP. + flags=None, # Flag field for DUT, see above. + flags_mask=None, # Optional mask to specify what DUT flags are being set. + hw_version=None, # DUT Hardware Version information + img_file=None, # File-Name for image to represent DUT. + lan_port=None, # IP/Mask for LAN port + mgt_ip=None, # Management IP Address to access DUT + model_num=None, # DUT Model information + name=None, # Name of DUT, cannot contain '.' + passwd1=None, # WiFi Password that can be used to connect to DUT + passwd2=None, # WiFi Password that can be used to connect to DUT + passwd3=None, # WiFi Password that can be used to connect to DUT + serial_num=None, # DUT Identifier (serial-number, etc) + serial_port=None, # Resource and Serial port name on LANforge that connects to DUT + # (1.2.ttyS0). Serial port does not need to be on resource holding + # wan_port or lan_port + ssid1=None, # WiFi SSID that can be used to connect to DUT + ssid2=None, # WiFi SSID that can be used to connect to DUT + ssid3=None, # WiFi SSID that can be used to connect to DUT + sw_version=None, # DUT Software Version information + top_left_x=None, # X Location for Chamber View. + top_left_y=None, # X Location for Chamber View. + wan_port=None, # IP/Mask for WAN port + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_dut(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if antenna_count1 is not None: + data["antenna_count1"] = antenna_count1 + if antenna_count2 is not None: + data["antenna_count2"] = antenna_count2 + if antenna_count3 is not None: + data["antenna_count3"] = antenna_count3 + if api_id is not None: + data["api_id"] = api_id + if bssid1 is not None: + data["bssid1"] = bssid1 + if bssid2 is not None: + data["bssid2"] = bssid2 + if bssid3 is not None: + data["bssid3"] = bssid3 + if eap_id is not None: + data["eap_id"] = eap_id + if flags is not None: + data["flags"] = flags + if flags_mask is not None: + data["flags_mask"] = flags_mask + if hw_version is not None: + data["hw_version"] = hw_version + if img_file is not None: + data["img_file"] = img_file + if lan_port is not None: + data["lan_port"] = lan_port + if mgt_ip is not None: + data["mgt_ip"] = mgt_ip + if model_num is not None: + data["model_num"] = model_num + if name is not None: + data["name"] = name + if passwd1 is not None: + data["passwd1"] = passwd1 + if passwd2 is not None: + data["passwd2"] = passwd2 + if passwd3 is not None: + data["passwd3"] = passwd3 + if serial_num is not None: + data["serial_num"] = serial_num + if serial_port is not None: + data["serial_port"] = serial_port + if ssid1 is not None: + data["ssid1"] = ssid1 + if ssid2 is not None: + data["ssid2"] = ssid2 + if ssid3 is not None: + data["ssid3"] = ssid3 + if sw_version is not None: + data["sw_version"] = sw_version + if top_left_x is not None: + data["top_left_x"] = top_left_x + if top_left_y is not None: + data["top_left_y"] = top_left_y + if wan_port is not None: + data["wan_port"] = wan_port + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_dut", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_DUT_NOTES> type requests + + https://www.candelatech.com/lfcli_ug.php#add_dut_notes + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_dut_notes(self, + dut=None, # DUT Name. + text=None, # [BLANK] will erase all, any other text will be appended to existing + # text. <tt escapearg='false'>Unescaped Value</tt> + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_dut_notes(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if dut is not None: + data["dut"] = dut + if text is not None: + data["text"] = text + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_dut_notes", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_DUT_SSID> type requests + + https://www.candelatech.com/lfcli_ug.php#add_dut_ssid + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddDutSsidDutFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddDutSsidDutFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + p_11r = 0x200 # Use .11r connection logic + EAP_PEAP = 0x800 # Use EAP-PEAP connection logic + EAP_TTLS = 0x400 # Use EAP-TTLS connection logic + WEP = 0x8 # Use WEP encryption + WPA = 0x10 # Use WPA encryption + WPA2 = 0x20 # Use WPA2 encryption + WPA3 = 0x100 # Use WPA3 encryption + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddDutSsidDutFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_dut_ssid(self, + bssid=None, # BSSID for cooresponding SSID. + name=None, # Name of DUT, cannot contain '.' + passwd=None, # WiFi Password that can be used to connect to DUT + ssid=None, # WiFi SSID that can be used to connect to DUT + ssid_flags=None, # SSID flags, see above. + ssid_flags_mask=None, # SSID flags mask + ssid_idx=None, # Index of the SSID. Zero-based indexing: (0 - 7) + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_dut_ssid(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if bssid is not None: + data["bssid"] = bssid + if name is not None: + data["name"] = name + if passwd is not None: + data["passwd"] = passwd + if ssid is not None: + data["ssid"] = ssid + if ssid_flags is not None: + data["ssid_flags"] = ssid_flags + if ssid_flags_mask is not None: + data["ssid_flags_mask"] = ssid_flags_mask + if ssid_idx is not None: + data["ssid_idx"] = ssid_idx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_dut_ssid", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddEndpPayloadPattern(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + PRBS_11_8_10 = "PRBS_11_8_10" # PRBS (see above) + PRBS_15_0_14 = "PRBS_15_0_14" # PRBS (see above) + PRBS_4_0_3 = "PRBS_4_0_3" # Use linear feedback shift register to generate pseudo random sequence. + PRBS_7_0_6 = "PRBS_7_0_6" # PRBS (see above) + custom = "custom" # Enter your own payload with the set_endp_payload cmd. + decreasing = "decreasing" # bytes start at FF and decrease, wrapping if needed + increasing = "increasing" # bytes start at 00 and increase, wrapping if needed + ones = "ones" # payload is all ones (FF) + random = "random" # generate a new random payload each time sent + random_fixed = "random_fixed" # means generate one random payload, and send it over and over again. + zeros = "zeros" # payload is all zeros (00) + + class AddEndpType(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + custom_ether = "custom_ether" # LF frames with custom options, use with playback + custom_mc_udp = "custom_mc_udp" # LF Multicast UDP IPv4 + custom_tcp = "custom_tcp" # LF TCP IPv4 frame with custom options + custom_udp = "custom_udp" # LF UDP IPv4 frame with custom options + lf = "lf" # LF protocol + lf_sctp = "lf_sctp" # SCTP IPv4 protocol + lf_sctp6 = "lf_sctp6" # SCTP IPv6 protocol + lf_tcp = "lf_tcp" # TCP IPv4 connection + lf_tcp6 = "lf_tcp6" # TCP IPv6 connection + lf_udp = "lf_udp" # UDP IPv4 connection + lf_udp6 = "lf_udp6" # UDP IPv6 connection + mc_udp = "mc_udp" # LF Multicast IPv4 + + def post_add_endp(self, + alias=None, # Name of endpoint. + ip_port=None, # IP Port: IP port for layer three endpoints. Use -1 to let the + # LANforge server automatically configure the ip_port. Layer 2 + # endpoints will ignore + is_pkt_sz_random=None, # Yes means use random sized packets, anything else means NO. + is_rate_bursty=None, # Yes means bursty, anything else means NO. + max_pkt=None, # Maximum packet size, including all headers. 0 means 'same', -1 + # means AUTO (5.3.2+) + max_rate=None, # Maximum transmit rate (bps), used if in bursty mode. + min_pkt=None, # Minimum packet size, including all headers. -1 means AUTO + # (5.3.2+) + min_rate=None, # Minimum transmit rate (bps), or only rate if not bursty. + multi_conn=None, # If > 0, will create separate process with this many connections + # per endpoint. See AUTO_HELPER flag + payload_pattern=None, # Payload pattern, see above. + port=None, # Port/Interface name or number. + resource=None, # Resource number. + send_bad_crc_per_million=None, # If NIC supports it, will randomly send X per million packets + # with bad ethernet Frame Check Sum. + shelf=None, # Shelf name/id. + ttl=None, # Time-to-live, used by UDP Multicast Endpoints only. + p_type=None, # Endpoint Type: See above. + use_checksum=None, # Yes means checksum the payload, anything else means NO. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if ip_port is not None: + data["ip_port"] = ip_port + if is_pkt_sz_random is not None: + data["is_pkt_sz_random"] = is_pkt_sz_random + if is_rate_bursty is not None: + data["is_rate_bursty"] = is_rate_bursty + if max_pkt is not None: + data["max_pkt"] = max_pkt + if max_rate is not None: + data["max_rate"] = max_rate + if min_pkt is not None: + data["min_pkt"] = min_pkt + if min_rate is not None: + data["min_rate"] = min_rate + if multi_conn is not None: + data["multi_conn"] = multi_conn + if payload_pattern is not None: + data["payload_pattern"] = payload_pattern + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if send_bad_crc_per_million is not None: + data["send_bad_crc_per_million"] = send_bad_crc_per_million + if shelf is not None: + data["shelf"] = shelf + if ttl is not None: + data["ttl"] = ttl + if p_type is not None: + data["type"] = p_type + if use_checksum is not None: + data["use_checksum"] = use_checksum + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_EVENT> type requests + + https://www.candelatech.com/lfcli_ug.php#add_event + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_event(self, + details=None, # Event text description. Cannot include double-quote characters. + event_id=None, # Numeric ID for the event to modify, or 'new' if creating a new one. + name=None, # Event entity name. + priority=None, # See set_event_priority for available priorities. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_event(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if details is not None: + data["details"] = details + if event_id is not None: + data["event_id"] = event_id + if name is not None: + data["name"] = name + if priority is not None: + data["priority"] = priority + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_event", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_FILE_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_file_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddFileEndpFioFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddFileEndpFioFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + AUTO_MOUNT = 0x2 # (2) Attempt to mount with the provided information if not already mounted. + AUTO_UNMOUNT = 0x4 # (4) Attempt to un-mount when stopping test. + CHECK_MOUNT = 0x1 # (1) Attempt to verify NFS and SMB mounts match the configured values. + O_APPEND = 0x200 # (512) Open files for writing with O_APPEND instead + O_DIRECT = 0x8 # (8) Open file with O_DIRECT flag, disables caching. Must use block-size + # +read/write calls. + O_LARGEFILE = 0x20 # (32) Open files with O_LARGEFILE. This allows greater than 2GB files on + # +32-bit systems. + UNLINK_BW = 0x10 # (16) Unlink file before writing. This works around issues with CIFS for some + # +file-servers. + UNMOUNT_FORCE = 0x40 # (64) Use -f flag when calling umount + UNMOUNT_LAZY = 0x80 # (128) Use -l flag when calling umount + USE_FSTATFS = 0x100 # (256) Use fstatfs system call to verify file-system type when opening files. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddFileEndpFioFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class AddFileEndpPayloadPattern(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + PRBS_11_8_10 = "PRBS_11_8_10" # PRBS (see above) + PRBS_15_0_14 = "PRBS_15_0_14" # PRBS (see above) + PRBS_4_0_3 = "PRBS_4_0_3" # Use linear feedback shift register to generate pseudo random sequence. + PRBS_7_0_6 = "PRBS_7_0_6" # PRBS (see above) + custom = "custom" # Enter your own payload with the set_endp_payload cmd. + decreasing = "decreasing" # bytes start at FF and decrease, wrapping if needed. + increasing = "increasing" # bytes start at 00 and increase, wrapping if needed. + ones = "ones" # Payload is all ones (FF). + random = "random" # generate a new random payload each time sent. + random_fixed = "random_fixed" # Means generate one random payload, and send it over + zeros = "zeros" # Payload is all zeros (00). + + def post_add_file_endp(self, + alias=None, # Name of endpoint. + directory=None, # The directory to read/write in. Absolute path suggested. + fio_flags=None, # File-IO flags, see above for details. + max_read_rate=None, # Maximum read rate, bits-per-second. + max_write_rate=None, # Maximum write rate, bits-per-second. + min_read_rate=None, # Minimum read rate, bits-per-second. + min_write_rate=None, # Minimum write rate, bits-per-second. + mount_dir=None, # Directory to mount/unmount (if blank, will use 'directory'). + mount_options=None, # Optional mount options, passed to the mount command. 'NONE' + # clears. + payload_pattern=None, # Payload pattern, see above. + port=None, # Port number. + prefix=None, # The prefix of the file(s) to read/write. + resource=None, # Resource number. + retry_timer=None, # Number of miliseconds to retry errored IO calls before giving + # up. + server_mount=None, # The server to mount, ex: 192.168.100.5/exports/test1 + shelf=None, # Shelf name/id. + p_type=None, # Endpoint Type : fe_generic, fe_nfs, fe_nfs4, fe_cifs, fe_iscsi, + # fe_cifs/ip6, fe_nfs/ip6, fe_nfs4/ip6, fe_smb2, fe_smb2/ip6 + # fe_smb21 fe_smb21/ip6 fe_smb30 fe_smb30/ip6 + volume=None, # iSCSI volume to mount + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_file_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if directory is not None: + data["directory"] = directory + if fio_flags is not None: + data["fio_flags"] = fio_flags + if max_read_rate is not None: + data["max_read_rate"] = max_read_rate + if max_write_rate is not None: + data["max_write_rate"] = max_write_rate + if min_read_rate is not None: + data["min_read_rate"] = min_read_rate + if min_write_rate is not None: + data["min_write_rate"] = min_write_rate + if mount_dir is not None: + data["mount_dir"] = mount_dir + if mount_options is not None: + data["mount_options"] = mount_options + if payload_pattern is not None: + data["payload_pattern"] = payload_pattern + if port is not None: + data["port"] = port + if prefix is not None: + data["prefix"] = prefix + if resource is not None: + data["resource"] = resource + if retry_timer is not None: + data["retry_timer"] = retry_timer + if server_mount is not None: + data["server_mount"] = server_mount + if shelf is not None: + data["shelf"] = shelf + if p_type is not None: + data["type"] = p_type + if volume is not None: + data["volume"] = volume + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_file_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_GEN_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_gen_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_gen_endp(self, + alias=None, # Name of endpoint. + port=None, # Port number. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + p_type=None, # Endpoint Type : gen_generic + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_gen_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_gen_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_GRE> type requests + + https://www.candelatech.com/lfcli_ug.php#add_gre + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_gre(self, + local_lower_ip=None, # The local lower-level IP to use. + port=None, # Name of the GRE to create, suggested to start with 'gre' + remote_lower_ip=None, # The remote lower-level IP to use. + report_timer=None, # Report timer for this port, leave blank or use NA for defaults. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_gre(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if local_lower_ip is not None: + data["local_lower_ip"] = local_lower_ip + if port is not None: + data["port"] = port + if remote_lower_ip is not None: + data["remote_lower_ip"] = remote_lower_ip + if report_timer is not None: + data["report_timer"] = report_timer + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_gre", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddGroupFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddGroupFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + group_total_rates = 0x4 # Set rates as total for group. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddGroupFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_group(self, + flags=None, # Flags for this group, see above. + flags_mask=None, # Mask for flags that we care about, use 0xFFFFFFFF or leave blank for + # all. + name=None, # The name of the test group. Must be unique across all groups. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if flags is not None: + data["flags"] = flags + if flags_mask is not None: + data["flags_mask"] = flags_mask + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_L4_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_l4_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddL4EndpHttpAuthType(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddL4EndpHttpAuthType0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + BASIC = 0x1 # Basic authentication + DIGEST = 0x2 # Digest (MD5) authentication + GSSNEGOTIATE = 0x4 # GSS authentication + NTLM = 0x8 # NTLM authentication + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddL4EndpHttpAuthType has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class AddL4EndpProxyAuthType(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddL4EndpProxyAuthType0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + BASIC = 0x1 # 1 Basic authentication + BIND_DNS = 0x200 # 512 Make DNS requests go out endpoints Port. + DIGEST = 0x2 # 2 Digest (MD5) authentication + DISABLE_EPSV = 0x1000 # 4096 Disable FTP EPSV option + DISABLE_PASV = 0x800 # 2048 Disable FTP PASV option (will use PORT command) + GSSNEGOTIATE = 0x4 # 4 GSS authentication + INCLUDE_HEADERS = 0x100 # 256 especially for IMAP + NTLM = 0x8 # 8 NTLM authentication + USE_DEFLATE_COMPRESSION = 0x80 # 128 Use deflate compression + USE_GZIP_COMPRESSION = 0x40 # 64 Use gzip compression + USE_IPV6 = 0x400 # 1024 Resolve URL is IPv6. Will use IPv4 if not selected. + USE_PROXY_CACHE = 0x20 # 32 Use proxy cache + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddL4EndpProxyAuthType has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class AddL4EndpType(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddL4EndpType0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + l4_generic = 0x0 # Layer 4 type + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddL4EndpType has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_l4_endp(self, + alias=None, # Name of endpoint. + block_size=None, # TFTP Block size, in bytes. + dns_cache_timeout=None, # In seconds, how long to cache DNS lookups. 0 means no caching at + # all. + http_auth_type=None, # Bit-field for allowable http-authenticate methods. + ip_addr=None, # Local IP address, for binding to specific secondary IP. + max_speed=None, # In bits-per-second, can rate limit upload or download speed of + # the URL contents. 0 means infinite. + port=None, # Port number. + proxy_auth_type=None, # Bit-field for allowable proxy-authenticate methods. + proxy_port=None, # HTTP Proxy port if you are using a proxy. + proxy_server=None, # The name of our proxy server if using one. + proxy_userpwd=None, # The user-name and password for proxy authentication, format: + # <tt>user:passwd</tt>. + quiesce_after=None, # Quiesce test after this many URLs have been processed. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + smtp_from=None, # SMTP From address. + ssl_cert_fname=None, # Name of SSL Certs file. + timeout=None, # How long to wait for a connection, in milliseconds + p_type=None, # Endpoint Type : <tt>l4_generic</tt> + url=None, # The URL, see syntax above. Can also be a local file. + url_rate=None, # How often should we process the URL(s), per 10 + # minutes.<ul><li>600: 1/s<li>1200: 2/s<li>1800: 3/s<li>2400: + # 4/s</ul> + user_agent=None, # User-Agent string. Leave blank for default. Also SMTP-TO: + # <a@b.com><c@d.com>...<q@x.com> + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_l4_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if block_size is not None: + data["block_size"] = block_size + if dns_cache_timeout is not None: + data["dns_cache_timeout"] = dns_cache_timeout + if http_auth_type is not None: + data["http_auth_type"] = http_auth_type + if ip_addr is not None: + data["ip_addr"] = ip_addr + if max_speed is not None: + data["max_speed"] = max_speed + if port is not None: + data["port"] = port + if proxy_auth_type is not None: + data["proxy_auth_type"] = proxy_auth_type + if proxy_port is not None: + data["proxy_port"] = proxy_port + if proxy_server is not None: + data["proxy_server"] = proxy_server + if proxy_userpwd is not None: + data["proxy_userpwd"] = proxy_userpwd + if quiesce_after is not None: + data["quiesce_after"] = quiesce_after + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if smtp_from is not None: + data["smtp_from"] = smtp_from + if ssl_cert_fname is not None: + data["ssl_cert_fname"] = ssl_cert_fname + if timeout is not None: + data["timeout"] = timeout + if p_type is not None: + data["type"] = p_type + if url is not None: + data["url"] = url + if url_rate is not None: + data["url_rate"] = url_rate + if user_agent is not None: + data["user_agent"] = user_agent + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_l4_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_MONITOR> type requests + + https://www.candelatech.com/lfcli_ug.php#add_monitor + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddMonitorFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddMonitorFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + disable_ht40 = 0x800 # Disable HT-40 even if hardware and AP support it. + disable_ht80 = 0x8000000 # Disable HT80 (for AC chipset NICs only) + ht160_enable = 0x100000000 # Enable HT160 mode. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddMonitorFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_monitor(self, + aid=None, # AID, may be used when sniffing on /AX radios. + ap_name=None, # Name for this Monitor interface, for example: moni0 + bssid=None, # BSSID to use when sniffing on /AX radios, optional. + flags=None, # Flags for this monitor interface. + flags_mask=None, # Flags mask for this monitor interface. + radio=None, # Name of the physical radio interface, for example: wiphy0 + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_monitor(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aid is not None: + data["aid"] = aid + if ap_name is not None: + data["ap_name"] = ap_name + if bssid is not None: + data["bssid"] = bssid + if flags is not None: + data["flags"] = flags + if flags_mask is not None: + data["flags_mask"] = flags_mask + if radio is not None: + data["radio"] = radio + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_monitor", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_MVLAN> type requests + + https://www.candelatech.com/lfcli_ug.php#add_mvlan + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_mvlan(self, + flags=None, # 0x1: Create admin-down. + index=None, # Optional: The index of the VLAN, (the <b>4</b> in <tt>eth0#4</tt>) + mac=None, # The MAC address, can also use parent-pattern in 5.3.8 and higher: + # <tt>xx:xx:xx:*:*:xx</tt> + old_name=None, # The temporary name, used for configuring un-discovered hardware. + port=None, # Port number of an existing Ethernet interface. + report_timer=None, # Report timer for this port, leave blank or use NA for defaults. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_mvlan(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if flags is not None: + data["flags"] = flags + if index is not None: + data["index"] = index + if mac is not None: + data["mac"] = mac + if old_name is not None: + data["old_name"] = old_name + if port is not None: + data["port"] = port + if report_timer is not None: + data["report_timer"] = report_timer + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_mvlan", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_PPP_LINK> type requests + + https://www.candelatech.com/lfcli_ug.php#add_ppp_link + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_ppp_link(self, + auth=None, # YES if you want to authenticate. Default is NO. + channel_groups=None, # List of channel groups, see above. + debug=None, # YES for debug, otherwise debugging for the ppp connection is + # off. + down_time_max_ms=None, # Maximum length of downtime (ms) for PPP link between runs, or + # 0 for the link to be always up. + down_time_min_ms=None, # Minimum length of downtime (ms) for PPP link between runs, or + # 0 for the link to be always up. + dst_ip=None, # Destination IP address for this PPP connection. + extra_args=None, # Extra arguments to be passed directly to the pppd server. + holdoff=None, # Seconds between attempt to bring link back up if it dies, + # suggest 1. + lcp_echo_failure=None, # LCP echo failures before we determine links is dead, suggest + # 5. + lcp_echo_interval=None, # Seconds between LCP echos, suggest 1. + mlppp_descriptor=None, # A unique key for use with multi-link PPP connections. + persist=None, # YES if you want to persist the connection. This is suggested. + pppoe_transport_port=None, # Port number (or name) for underlying PPPoE transport. + resource=None, # Resource (machine) number. + run_time_max_ms=None, # Maximum uptime (ms) for PPP link during an experiment, or 0 + # for the link to be always up. + run_time_min_ms=None, # Minimum uptime (ms) for PPP link during an experiment, or 0 + # for the link to be always up. + shelf=None, # Shelf name/id. + src_ip=None, # Source IP address for this PPP connection. + transport_type=None, # What sort of transport this ppp link uses. + tty_transport_device=None, # TTY device for PPP links associated with TTYs. + unit=None, # Unit number for the PPP link. ie, the 7 in ppp7. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_ppp_link(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if auth is not None: + data["auth"] = auth + if channel_groups is not None: + data["channel_groups"] = channel_groups + if debug is not None: + data["debug"] = debug + if down_time_max_ms is not None: + data["down_time_max_ms"] = down_time_max_ms + if down_time_min_ms is not None: + data["down_time_min_ms"] = down_time_min_ms + if dst_ip is not None: + data["dst_ip"] = dst_ip + if extra_args is not None: + data["extra_args"] = extra_args + if holdoff is not None: + data["holdoff"] = holdoff + if lcp_echo_failure is not None: + data["lcp_echo_failure"] = lcp_echo_failure + if lcp_echo_interval is not None: + data["lcp_echo_interval"] = lcp_echo_interval + if mlppp_descriptor is not None: + data["mlppp_descriptor"] = mlppp_descriptor + if persist is not None: + data["persist"] = persist + if pppoe_transport_port is not None: + data["pppoe_transport_port"] = pppoe_transport_port + if resource is not None: + data["resource"] = resource + if run_time_max_ms is not None: + data["run_time_max_ms"] = run_time_max_ms + if run_time_min_ms is not None: + data["run_time_min_ms"] = run_time_min_ms + if shelf is not None: + data["shelf"] = shelf + if src_ip is not None: + data["src_ip"] = src_ip + if transport_type is not None: + data["transport_type"] = transport_type + if tty_transport_device is not None: + data["tty_transport_device"] = tty_transport_device + if unit is not None: + data["unit"] = unit + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_ppp_link", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_PROFILE> type requests + + https://www.candelatech.com/lfcli_ug.php#add_profile + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddProfileProfileFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddProfileProfileFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + p_11r = 0x40 # Use 802.11r roaming setup. + BSS_TRANS = 0x400 # Enable BSS Transition logic + DHCP_SERVER = 0x1 # This should provide DHCP server. + EAP_PEAP = 0x200 # Enable EAP-PEAP + EAP_TTLS = 0x80 # Use 802.1x EAP-TTLS + NAT = 0x100 # Enable NAT if this object is in a virtual router + SKIP_DHCP_ROAM = 0x10 # Ask station to not re-do DHCP on roam. + WEP = 0x2 # Use WEP encryption + WPA = 0x4 # Use WPA encryption + WPA2 = 0x8 # Use WPA2 encryption + WPA3 = 0x20 # Use WPA3 encryption + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddProfileProfileFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class AddProfileWifiMode(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + p_802_11a = "802.11a" # 802.11a + AUTO = "AUTO" # 802.11g + aAX = "aAX" # 802.11a-AX (6E disables /n and /ac) + abg = "abg" # 802.11abg + abgn = "abgn" # 802.11abgn + abgnAC = "abgnAC" # 802.11abgn-AC + abgnAX = "abgnAX" # 802.11abgn-AX + an = "an" # 802.11an + anAC = "anAC" # 802.11an-AC + anAX = "anAX" # 802.11an-AX + as_is = "as_is" # Make no changes to current configuration + b = "b" # 802.11b + bg = "bg" # 802.11bg + bgn = "bgn" # 802.11bgn + bgnAC = "bgnAC" # 802.11bgn-AC + bgnAX = "bgnAX" # 802.11bgn-AX + bond = "bond" # Bonded pair of Ethernet ports. + bridged_ap = "bridged_ap" # AP device in bridged mode. The EIDs may specify radio and bridged port. + client = "client" # Client-side non-WiFi device (Ethernet port, for instance). + g = "g" # 802.11g + mobile_sta = "mobile_sta" # Mobile station device. Expects to connect to DUT AP(s) and upstream + # +LANforge. + monitor = "monitor" # Monitor device/sniffer. The EIDs may specify which radios to use. + peer = "peer" # Edge device, client or server (Ethernet port, for instance). + rdd = "rdd" # Pair of redirect devices, typically associated with VR to act as traffic + # +endpoint + routed_ap = "routed_ap" # AP in routed mode. The EIDs may specify radio and upstream port. + sta = "sta" # Station device, most likely non mobile. The EIDs may specify radio(s) to + # +use. + uplink = "uplink" # Uplink towards rest of network (can go in virtual router and do NAT) + upstream = "upstream" # Upstream server device. The EIDs may specify which ports to use. + vlan = "vlan" # 802.1q VLAN. Specify VID with the 'freq' option. + + def post_add_profile(self, + alias_prefix=None, # Port alias prefix, aka hostname prefix. + antenna=None, # Antenna count for this profile. + bandwidth=None, # 0 (auto), 20, 40, 80 or 160 + eap_id=None, # EAP Identifier + flags_mask=None, # Specify what flags to set. + freq=None, # WiFi frequency to be used, 0 means default. + instance_count=None, # Number of devices (stations, vdevs, etc) + mac_pattern=None, # Optional MAC-Address pattern, for instance: xx:xx:xx:*:*:xx + name=None, # Profile Name. + passwd=None, # WiFi Password to be used (AP Mode), [BLANK] means no password. + profile_flags=None, # Flags for this profile, see above. + profile_type=None, # Profile type: See above. + ssid=None, # WiFi SSID to be used, [BLANK] means any. + vid=None, # Vlan-ID (only valid for vlan profiles). + wifi_mode=None, # WiFi Mode for this profile. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_profile(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias_prefix is not None: + data["alias_prefix"] = alias_prefix + if antenna is not None: + data["antenna"] = antenna + if bandwidth is not None: + data["bandwidth"] = bandwidth + if eap_id is not None: + data["eap_id"] = eap_id + if flags_mask is not None: + data["flags_mask"] = flags_mask + if freq is not None: + data["freq"] = freq + if instance_count is not None: + data["instance_count"] = instance_count + if mac_pattern is not None: + data["mac_pattern"] = mac_pattern + if name is not None: + data["name"] = name + if passwd is not None: + data["passwd"] = passwd + if profile_flags is not None: + data["profile_flags"] = profile_flags + if profile_type is not None: + data["profile_type"] = profile_type + if ssid is not None: + data["ssid"] = ssid + if vid is not None: + data["vid"] = vid + if wifi_mode is not None: + data["wifi_mode"] = wifi_mode + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_profile", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_PROFILE_NOTES> type requests + + https://www.candelatech.com/lfcli_ug.php#add_profile_notes + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_profile_notes(self, + dut=None, # Profile Name. + text=None, # [BLANK] will erase all, any other text will be appended to + # existing text. <tt escapearg='false'>Unescaped Value</tt> + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_profile_notes(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if dut is not None: + data["dut"] = dut + if text is not None: + data["text"] = text + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_profile_notes", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_RDD> type requests + + https://www.candelatech.com/lfcli_ug.php#add_rdd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_rdd(self, + peer_ifname=None, # The peer (other) RedirectDevice in this pair. + port=None, # Name of the Redirect Device to create. + report_timer=None, # Report timer for this port, leave blank or use NA for defaults. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_rdd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if peer_ifname is not None: + data["peer_ifname"] = peer_ifname + if port is not None: + data["port"] = port + if report_timer is not None: + data["report_timer"] = report_timer + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_rdd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_SEC_IP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_sec_ip + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_sec_ip(self, + ip_list=None, # IP1/prefix,IP2/prefix,...IPZ/prefix. + port=None, # Name of network device (Port) to which these IPs will be added. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_sec_ip(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if ip_list is not None: + data["ip_list"] = ip_list + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_sec_ip", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_STA> type requests + + https://www.candelatech.com/lfcli_ug.php#add_sta + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddStaFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddStaFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + p_80211r_pmska_cache = 0x4000000 # Enable oportunistic PMSKA caching for WPA2 (Related to + # +802.11r). + p_80211u_additional = 0x100000 # AP requires additional step for access (802.11u Interworking) + p_80211u_auto = 0x40000 # Enable 802.11u (Interworking) Auto-internetworking feature. + # +Always enabled currently. + p_80211u_e911 = 0x200000 # AP claims emergency services reachable (802.11u Interworking) + p_80211u_e911_unauth = 0x400000 # AP provides Unauthenticated emergency services (802.11u + # +Interworking) + p_80211u_enable = 0x20000 # Enable 802.11u (Interworking) feature. + p_80211u_gw = 0x80000 # AP Provides access to internet (802.11u Interworking) + p_8021x_radius = 0x2000000 # Use 802.1x (RADIUS for AP). + create_admin_down = 0x1000000000 # Station should be created admin-down. + custom_conf = 0x20 # Use Custom wpa_supplicant config file. + disable_ofdma = 0x200000000000 # Disable OFDMA mode + disable_twt = 0x100000000000 # Disable TWT mode + disable_fast_reauth = 0x200000000 # Disable fast_reauth option for virtual stations. + disable_gdaf = 0x1000000 # AP: Disable DGAF (used by HotSpot 2.0). + disable_ht80 = 0x8000000 # Disable HT80 (for AC chipset NICs only) + disable_roam = 0x80000000 # Disable automatic station roaming based on scan results. + disable_sgi = 0x4000 # Disable SGI (Short Guard Interval). + hs20_enable = 0x800000 # Enable Hotspot 2.0 (HS20) feature. Requires WPA-2. + ht160_enable = 0x100000000 # Enable HT160 mode. + ht40_disable = 0x800 # Disable HT-40 even if hardware and AP support it. + ibss_mode = 0x20000000 # Station should be in IBSS mode. + lf_sta_migrate = 0x8000 # OK-To-Migrate (Allow station migration between LANforge + # +radios) + mesh_mode = 0x400000000 # Station should be in MESH mode. + no_supp_op_class_ie = 0x4000000000 # Do not include supported-oper-class-IE in assoc requests. May + # +work around AP bugs. + osen_enable = 0x40000000 # Enable OSEN protocol (OSU Server-only Authentication) + passive_scan = 0x2000 # Use passive scanning (don't send probe requests). + power_save_enable = 0x800000000 # Station should enable power-save. May not work in all + # +drivers/configurations. + scan_ssid = 0x1000 # Enable SCAN-SSID flag in wpa_supplicant. + txo_enable = 0x8000000000 # Enable/disable tx-offloads, typically managed by set_wifi_txo + # +command + use_bss_transition = 0x80000000000 # Enable BSS transition. + use_wpa3 = 0x10000000000 # Enable WPA-3 (SAE Personal) mode. + verbose = 0x10000 # Verbose-Debug: Increase debug info in wpa-supplicant and + # +hostapd logs. + wds_mode = 0x2000000000 # WDS station (sort of like a lame mesh), not supported on + # +ath10k + wep_enable = 0x200 # Use wpa_supplicant configured for WEP encryption. + wpa2_enable = 0x400 # Use wpa_supplicant configured for WPA2 encryption. + wpa_enable = 0x10 # Enable WPA + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddStaFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class AddStaMode(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + p_802_11a = 1 # 802.11a + AUTO = 0 # 802.11g + aAX = 15 # 802.11a-AX (6E disables /n and /ac) + abg = 4 # 802.11abg + abgn = 5 # 802.11abgn + abgnAC = 8 # 802.11abgn-AC + abgnAX = 12 # 802.11abgn-AX + an = 10 # 802.11an + anAC = 9 # 802.11an-AC + anAX = 14 # 802.11an-AX + b = 2 # 802.11b + bg = 7 # 802.11bg + bgn = 6 # 802.11bgn + bgnAC = 11 # 802.11bgn-AC + bgnAX = 13 # 802.11bgn-AX + g = 3 # 802.11g + + class AddStaRate(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + p_a_g = "/a/g" # 6 Mbps, 9 Mbps, 12 Mbps, 18 Mbps, 24 Mbps, 36 Mbps, 48 Mbps, 54 Mbps + p_b = "/b" # 1Mbps, 2Mbps, 5.5 Mbps, 11 Mbps + DEFAULT = "DEFAULT" # Use maximum available speed + MCS0_76 = "MCS0-76" # /n rates + p_bitmap_ = "[bitmap]" # <b>'0xff 00 ...'</b> to directly specify the MCS bitmap. + + def post_add_sta(self, + ampdu_density=None, # 0-7, or 0xFF to not set. + ampdu_factor=None, # 0-3, or 0xFF to not set. + ap=None, # The Access Point BSSID this Virtual STA should be associated with + # (example: <tt>00:11:22:33:4:55</tt>, or <tt>DEFAULT</tt> for any). + flags=None, # Flags for this interface (see above.) + flags_mask=None, # If set, only these flags will be considered. + ieee80211w=None, # Management Frame Protection: 0: disabled, 1: optional, 2: Required. + key=None, # Encryption key (WEP, WPA, WPA2, WPA3, etc) for this Virtual STA. + # Prepend with 0x for ascii-hex input. + mac=None, # The MAC address, can also use parent-pattern in 5.3.8 and higher: + # <tt>xx:xx:xx:*:*:xx</tt> + max_amsdu=None, # 1 == enabled, 0 == disabled, 0xFF == do not set. + mode=None, # WiFi mode: <ul><li>0: AUTO, <li>1: 802.11a</li> <li>2: b</li> <li>3: + # g</li> <li>4: abg</li> <li>5: abgn</li> <li>6: bgn</li> <li>7: bg</li> + # <li>8: abgnAC</li> <li>9 anAC</li> <li>10 an</li><li>11 + # bgnAC</li><li>12 abgnAX</li><li>13 bgnAX</li><li>14 anAX</li><li>15 + # aAX</li></ul> + nickname=None, # Nickname for this Virtual STA. (No longer used) + radio=None, # Name of the physical radio interface, for example: wiphy0 + rate=None, # Max rate, see help above. + resource=None, # Resource number. + shelf=None, # Shelf number. + ssid=None, # SSID for this Virtual STA. Use [BLANK] for empty SSID. Start with + # <tt>0x</tt> for HEX interpretation. + sta_br_ip=None, # IP Address for station bridging. Set to 0.0.0.0 to use MAC bridging. + sta_name=None, # Name for this Virtual STA, for example: sta0 + wpa_cfg_file=None, # WPA Supplicant config file. + x_coord=None, # Floating point number. + y_coord=None, # Floating point number. + z_coord=None, # Floating point number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_sta(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if ampdu_density is not None: + data["ampdu_density"] = ampdu_density + if ampdu_factor is not None: + data["ampdu_factor"] = ampdu_factor + if ap is not None: + data["ap"] = ap + if flags is not None: + data["flags"] = flags + if flags_mask is not None: + data["flags_mask"] = flags_mask + if ieee80211w is not None: + data["ieee80211w"] = ieee80211w + if key is not None: + data["key"] = key + if mac is not None: + data["mac"] = mac + if max_amsdu is not None: + data["max_amsdu"] = max_amsdu + if mode is not None: + data["mode"] = mode + if nickname is not None: + data["nickname"] = nickname + if radio is not None: + data["radio"] = radio + if rate is not None: + data["rate"] = rate + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if ssid is not None: + data["ssid"] = ssid + if sta_br_ip is not None: + data["sta_br_ip"] = sta_br_ip + if sta_name is not None: + data["sta_name"] = sta_name + if wpa_cfg_file is not None: + data["wpa_cfg_file"] = wpa_cfg_file + if x_coord is not None: + data["x_coord"] = x_coord + if y_coord is not None: + data["y_coord"] = y_coord + if z_coord is not None: + data["z_coord"] = z_coord + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_sta", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_T1_SPAN> type requests + + https://www.candelatech.com/lfcli_ug.php#add_t1_span + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddT1SpanBuildout(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + p_15db = 6 # -15db (CSU) + p_22_5db = 7 # -22.5db (CSU) + p_7_5db = 5 # -7.5db (CSU) + p_0db = 8 # 0db (CSU) + p_133_ft = 0 # 1-133 feet + p_266_ft = 1 # 122-266 feet + p_399_ft = 2 # 266-399 feet + p_533_ft = 3 # 399-533 feet + p_655_ft = 4 # 533-655 feet + + def post_add_t1_span(self, + buildout=None, # Buildout, Integer, see above. + coding=None, # Coding: T1: ami or b8zs. E1: ami or hdb3 + cpu_id=None, # CPU identifier (A, B, etc) for multiport Sangoma resources. + first_channel=None, # The first DS0 channel for this span. + framing=None, # Framing: T1: esf or d4. E1: cas or ccs. + mtu=None, # MTU for this span (used by in-band management, if at all). + pci_bus=None, # PCI Bus number, needed for Sangoma resources. + pci_slot=None, # PCI slot number, needed for Sangoma resources. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + span_num=None, # The span number. First span is 1, second is 2... + timing=None, # Timing: 0 == do not use, 1 == primary, 2 == secondary.. + p_type=None, # Currently supported types are: Sangoma_T1, Sangoma_E1, Digium_T1 + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_t1_span(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if buildout is not None: + data["buildout"] = buildout + if coding is not None: + data["coding"] = coding + if cpu_id is not None: + data["cpu_id"] = cpu_id + if first_channel is not None: + data["first_channel"] = first_channel + if framing is not None: + data["framing"] = framing + if mtu is not None: + data["mtu"] = mtu + if pci_bus is not None: + data["pci_bus"] = pci_bus + if pci_slot is not None: + data["pci_slot"] = pci_slot + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if span_num is not None: + data["span_num"] = span_num + if timing is not None: + data["timing"] = timing + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_t1_span", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_TEXT_BLOB> type requests + + https://www.candelatech.com/lfcli_ug.php#add_text_blob + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_text_blob(self, + name=None, # Text name, for instance '2-AP-test-case' + text=None, # [BLANK] will erase all, any other text will be appended to existing + # text. <tt escapearg='false'>Unescaped Value</tt> + p_type=None, # Text type identifier stream, for instance 'cv-connectivity' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_text_blob(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if text is not None: + data["text"] = text + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_text_blob", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_TGCX> type requests + + https://www.candelatech.com/lfcli_ug.php#add_tgcx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_tgcx(self, + cxname=None, # The name of the CX. + tgname=None, # The name of the test group. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_tgcx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cxname is not None: + data["cxname"] = cxname + if tgname is not None: + data["tgname"] = tgname + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_tgcx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_THRESHOLD> type requests + + https://www.candelatech.com/lfcli_ug.php#add_threshold + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddThresholdThreshId(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + Delete_Marked = -3 # Delete any marked. + Mark_All = -2 # Mark all + + class AddThresholdThreshType(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + NO_RX_SINCE = 6 # Have not received any bytes/packets in specified time. + RX_BPS_RATE_OOR_1m = 5 # rx-bps over last 1 minute is out of range. + RX_BPS_RATE_OOR_30S = 3 # rx-bps over last 30 seconds is out of range. + RX_BPS_RATE_OOR_3S = 1 # rx-bps over last 3 seconds is out of range. + TT_RX_DROP_OOR = 8 # RX Drop percentage is out of range (per-million). + TT_RX_LAT_OOR = 7 # Latency running-average out of range. + TX_BPS_RATE_OOR_1m = 4 # tx-bps over last 1 minute is out of range. + TX_BPS_RATE_OOR_30S = 2 # tx-bps over last 30 seconds is out of range. + TX_BPS_RATE_OOR_3S = 0 # tx-bps over last 3 seconds is out of range. + + def post_add_threshold(self, + endp=None, # Endpoint name or ID. + thresh_id=None, # Threshold ID. If adding new threshold, use -1, otherwise use + # correct ID. + thresh_max=None, # Maximum acceptable value for this threshold. + thresh_min=None, # Minimum acceptable value for this threshold. + thresh_type=None, # Threshold type, integer, (see above). + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_threshold(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp is not None: + data["endp"] = endp + if thresh_id is not None: + data["thresh_id"] = thresh_id + if thresh_max is not None: + data["thresh_max"] = thresh_max + if thresh_min is not None: + data["thresh_min"] = thresh_min + if thresh_type is not None: + data["thresh_type"] = thresh_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_threshold", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_TM> type requests + + https://www.candelatech.com/lfcli_ug.php#add_tm + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_tm(self, + name=None, # The name of the test manager. Must be unique across test managers. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_tm(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_tm", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_TRAFFIC_PROFILE> type requests + + https://www.candelatech.com/lfcli_ug.php#add_traffic_profile + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddTrafficProfileTrafficProfileFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddTrafficProfileTrafficProfileFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + BI_DIRECTIONAL = 0x2 # Should we do bi-directional traffic? + IPERF_UDP = 0x4 # If Iperf, should use UDP. If not set, then will use TCP. + UP = 0x1 # Upload direction (this not set means download) + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddTrafficProfileTrafficProfileFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class AddTrafficProfileWifiMode(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + Iperf3_Client = "Iperf3-Client" # iperf3 client + Iperf3_Server = "Iperf3-Server" # iperf3 server + as_is = "as_is" # Make no changes to current configuration + http = "http" # Not yet implemented + https = "https" # Not yet implemented + tcp = "tcp" # + udp = "udp" # + + def post_add_traffic_profile(self, + instance_count=None, # Number of connections per device + max_pdu=None, # Minimum PDU size + max_speed=None, # Opposite-Direction Speed in bps. + min_pdu=None, # Minimum PDU size + min_speed=None, # Opposite-Direction Speed in bps. + name=None, # Profile Name. + tos=None, # IP Type-of-Service + traffic_profile_flags=None, # Flags for this profile, none defined at this point. + traffic_profile_flags_mask=None, # Specify what flags to set. + p_type=None, # Profile type: See above.. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_traffic_profile(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if instance_count is not None: + data["instance_count"] = instance_count + if max_pdu is not None: + data["max_pdu"] = max_pdu + if max_speed is not None: + data["max_speed"] = max_speed + if min_pdu is not None: + data["min_pdu"] = min_pdu + if min_speed is not None: + data["min_speed"] = min_speed + if name is not None: + data["name"] = name + if tos is not None: + data["tos"] = tos + if traffic_profile_flags is not None: + data["traffic_profile_flags"] = traffic_profile_flags + if traffic_profile_flags_mask is not None: + data["traffic_profile_flags_mask"] = traffic_profile_flags_mask + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_traffic_profile", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_TRAFFIC_PROFILE_NOTES> type requests + + https://www.candelatech.com/lfcli_ug.php#add_traffic_profile_notes + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_traffic_profile_notes(self, + dut=None, # Profile Name. + text=None, # [BLANK] will erase all, any other text will be appended + # to existing text. <tt escapearg='false'>Unescaped + # Value</tt> + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_traffic_profile_notes(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if dut is not None: + data["dut"] = dut + if text is not None: + data["text"] = text + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_traffic_profile_notes", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_VAP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_vap + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddVapFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddVapFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + p_80211h_enable = 0x10000000 # Enable 802.11h (needed for running on DFS channels) Requires + # +802.11d. + p_80211r_pmska_cache = 0x4000000 # Enable oportunistic PMSKA caching for WPA2 (Related to + # +802.11r). + p_80211u_additional = 0x100000 # AP requires additional step for access (802.11u Interworking) + p_80211u_auto = 0x40000 # Enable 802.11u (Interworking) Auto-internetworking feature. + # +Always enabled currently. + p_80211u_e911 = 0x200000 # AP claims emergency services reachable (802.11u Interworking) + p_80211u_e911_unauth = 0x400000 # AP provides Unauthenticated emergency services (802.11u + # +Interworking) + p_80211u_enable = 0x20000 # Enable 802.11u (Interworking) feature. + p_80211u_gw = 0x80000 # AP Provides access to internet (802.11u Interworking) + p_8021x_radius = 0x2000000 # Use 802.1x (RADIUS for AP). + create_admin_down = 0x1000000000 # Station should be created admin-down. + disable_dgaf = 0x1000000 # AP Disable DGAF (used by HotSpot 2.0). + disable_ht40 = 0x800 # Disable HT-40 (will use HT-20 if available). + disable_ht80 = 0x8000000 # Disable HT80 (for AC chipset NICs only) + enable_80211d = 0x40 # Enable 802.11D to broadcast country-code & channels in + # +VAPs + enable_wpa = 0x10 # Enable WPA + hostapd_config = 0x20 # Use Custom hostapd config file. + hs20_enable = 0x800000 # Enable Hotspot 2.0 (HS20) feature. Requires WPA-2. + ht160_enable = 0x100000000 # Enable HT160 mode. + osen_enable = 0x40000000 # Enable OSEN protocol (OSU Server-only Authentication) + pri_sec_ch_enable = 0x100 # Enable Primary/Secondary channel switch. + short_preamble = 0x80 # Allow short-preamble + use_bss_load = 0x20000000000 # Enable BSS Load IE in Beacons and Probe Responses (.11e). + use_bss_transition = 0x80000000000 # Enable BSS transition. + use_rrm_report = 0x40000000000 # Enable Radio measurements IE in beacon and probe responses. + use_wpa3 = 0x10000000000 # Enable WPA-3 (SAE Personal) mode. + verbose = 0x10000 # Verbose-Debug: Increase debug info in wpa-supplicant and + # +hostapd logs. + wep_enable = 0x200 # Enable WEP Encryption + wpa2_enable = 0x400 # Enable WPA2 Encryption + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddVapFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class AddVapMode(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + p_802_11a = 1 # 802.11a + AUTO = 0 # 802.11g + aAX = 15 # 802.11a-AX (6E disables /n and /ac) + abg = 4 # 802.11abg + abgn = 5 # 802.11abgn + abgnAC = 8 # 802.11abgn-AC + abgnAX = 12 # 802.11abgn-AX + an = 10 # 802.11an + anAC = 9 # 802.11an-AC + anAX = 14 # 802.11an-AX + b = 2 # 802.11b + bg = 7 # 802.11bg + bgn = 6 # 802.11bgn + bgnAC = 11 # 802.11bgn-AC + bgnAX = 13 # 802.11bgn-AX + g = 3 # 802.11g + + def post_add_vap(self, + ap_name=None, # Name for this Virtual AP, for example: vap0 + beacon=None, # The beacon interval, in 1kus (1.024 ms), default 100, range: 15..65535 + custom_cfg=None, # Custom hostapd config file, if you want to craft your own config. + dtim_period=None, # DTIM period, range 1..255. Default 2. + flags=None, # Flags for this interface (see above.) + flags_mask=None, # If set, only these flags will be considered. + frag_thresh=None, # UN-USED, Was Fragmentation threshold, which is now set with + # set_wifi_radio, use NA + ieee80211w=None, # Management Frame Protection: 0: disabled, 1: optional, 2: Required. + key=None, # Encryption key for this Virtual AP. Prepend with 0x for ascii-hex + # representation. + mac=None, # The MAC address, can also use parent-pattern in 5.3.8 and higher: + # <tt>xx:xx:xx:*:*:xx</tt> + max_sta=None, # Maximum number of Stations allowed to join this AP (1..2007) + mode=None, # WiFi mode: see table + radio=None, # Name of the physical radio interface, for example: wiphy0 + rate=None, # Max rate, see help for add_vsta + resource=None, # Resource number. + shelf=None, # Shelf number. + ssid=None, # SSID for this Virtual AP. + x_coord=None, # Floating point number. + y_coord=None, # Floating point number. + z_coord=None, # Floating point number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_vap(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if ap_name is not None: + data["ap_name"] = ap_name + if beacon is not None: + data["beacon"] = beacon + if custom_cfg is not None: + data["custom_cfg"] = custom_cfg + if dtim_period is not None: + data["dtim_period"] = dtim_period + if flags is not None: + data["flags"] = flags + if flags_mask is not None: + data["flags_mask"] = flags_mask + if frag_thresh is not None: + data["frag_thresh"] = frag_thresh + if ieee80211w is not None: + data["ieee80211w"] = ieee80211w + if key is not None: + data["key"] = key + if mac is not None: + data["mac"] = mac + if max_sta is not None: + data["max_sta"] = max_sta + if mode is not None: + data["mode"] = mode + if radio is not None: + data["radio"] = radio + if rate is not None: + data["rate"] = rate + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if ssid is not None: + data["ssid"] = ssid + if x_coord is not None: + data["x_coord"] = x_coord + if y_coord is not None: + data["y_coord"] = y_coord + if z_coord is not None: + data["z_coord"] = z_coord + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_vap", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_VENUE> type requests + + https://www.candelatech.com/lfcli_ug.php#add_venue + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddVenueFreq24(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddVenueFreq240, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + ALL = 0xffff # ALL + Ch_1 = 0x1 # Channel 1 + Ch_2 = 0x2 # Channel 2 + Ch_3 = 0x4 # Channel 3 + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddVenueFreq24 has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class AddVenueFreq5(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddVenueFreq50, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + Ch_100 = 0x800 # Channel 100 5500 + Ch_104 = 0x1000 # Channel 104 5520 + Ch_108 = 0x2000 # Channel 108 5540 + Ch_112 = 0x4000 # Channel 112 5560 + Ch_116 = 0x8000 # Channel 116 5580 + Ch_120 = 0x10000 # Channel 120 5600 + Ch_124 = 0x20000 # Channel 124 5620 + Ch_128 = 0x40000 # Channel 128 5640 + Ch_132 = 0x80000 # Channel 132 5660 + Ch_136 = 0x100000 # Channel 136 5680 + Ch_140 = 0x200000 # Channel 140 5700 + Ch_149 = 0x400000 # Channel 149 5745 + Ch_153 = 0x800000 # Channel 153 5765 + Ch_157 = 0x1000000 # Channel 157 5785 + Ch_161 = 0x2000000 # Channel 161 5805 + Ch_165 = 0x4000000 # Channel 165 5825 + Ch_36 = 0x1 # Channel 36 5180 + Ch_38 = 0x2 # Channel 38 5190 + Ch_40 = 0x4 # Channel 40 5200 + Ch_42 = 0x8 # Channel 42 5210 + Ch_44 = 0x10 # Channel 44 5220 + Ch_46 = 0x20 # Channel 46 5230 + Ch_48 = 0x40 # Channel 48 5240 + Ch_52 = 0x80 # Channel 52 5260 + Ch_56 = 0x100 # Channel 56 5280 + Ch_60 = 0x200 # Channel 60 5300 + Ch_64 = 0x400 # Channel 64 5320 + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddVenueFreq5 has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_venue(self, + description=None, # User-supplied description, ie: <tt>Big City Ball Park</tt>; + # 47-characters max. + freq_24=None, # Frequency list for 2.4Ghz band, see above. + freq_5=None, # Frequency list for 5Ghz band, see above. + resource=None, # Resource number. + shelf=None, # Shelf number. + venu_id=None, # Number to uniquely identify this venue on this resource. + x1=None, # Floating point coordinate for lower-left corner. + x2=None, # Floating point coordinate for upper-right corner. + y1=None, # Floating point coordinate for lower-left corner. + y2=None, # Floating point coordinate for upper-right corner. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_venue(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if description is not None: + data["description"] = description + if freq_24 is not None: + data["freq_24"] = freq_24 + if freq_5 is not None: + data["freq_5"] = freq_5 + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if venu_id is not None: + data["venu_id"] = venu_id + if x1 is not None: + data["x1"] = x1 + if x2 is not None: + data["x2"] = x2 + if y1 is not None: + data["y1"] = y1 + if y2 is not None: + data["y2"] = y2 + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_venue", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_VLAN> type requests + + https://www.candelatech.com/lfcli_ug.php#add_vlan + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_vlan(self, + old_name=None, # The temporary name, used for configuring un-discovered hardware. + port=None, # Port number of an existing Ethernet interface. + report_timer=None, # Report timer for this port, leave blank or use NA for defaults. + resource=None, # Resource number. + shelf=None, # Shelf number. + vid=None, # The VLAN-ID for this 802.1Q VLAN interface. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_vlan(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if old_name is not None: + data["old_name"] = old_name + if port is not None: + data["port"] = port + if report_timer is not None: + data["report_timer"] = report_timer + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if vid is not None: + data["vid"] = vid + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_vlan", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_VOIP_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_voip_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_voip_endp(self, + alias=None, # Name of endpoint. + auth_user_name=None, # Use this field for authentication user name. AUTO or blank mean + # use phone number. + display_name=None, # User-Name to be displayed. Use AUTO to display phone number. + gateway_port=None, # IP Port for SIP gateway (defaults to 5060). + ip_addr=None, # Use this IP for local IP address. Useful when there are + # multiple IPs on a port. + peer_phone_num=None, # Use AUTO to use phone number of peer endpoint, otherwise + # specify a number: user[@host[:port]] + phone_num=None, # Phone number for Endpoint + port=None, # Port number or name. + proxy_passwd=None, # Password to be used when registering with proxy/gateway. + resource=None, # Resource number. + rtp_port=None, # RTP port to use for send and receive. + rx_sound_file=None, # File name to save received PCM data to. Will be in WAV format, + # or AUTO + shelf=None, # Shelf name/id. + sip_gateway=None, # SIP Gateway/Proxy Name, this is who to register with, or AUTO + tx_sound_file=None, # File name containing the sound sample we will be playing. + vad_max_timer=None, # How often should we force a packet, even if VAD is on. + vad_timer=None, # How much silence (milliseconds) before VAD is enabled. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_voip_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if auth_user_name is not None: + data["auth_user_name"] = auth_user_name + if display_name is not None: + data["display_name"] = display_name + if gateway_port is not None: + data["gateway_port"] = gateway_port + if ip_addr is not None: + data["ip_addr"] = ip_addr + if peer_phone_num is not None: + data["peer_phone_num"] = peer_phone_num + if phone_num is not None: + data["phone_num"] = phone_num + if port is not None: + data["port"] = port + if proxy_passwd is not None: + data["proxy_passwd"] = proxy_passwd + if resource is not None: + data["resource"] = resource + if rtp_port is not None: + data["rtp_port"] = rtp_port + if rx_sound_file is not None: + data["rx_sound_file"] = rx_sound_file + if shelf is not None: + data["shelf"] = shelf + if sip_gateway is not None: + data["sip_gateway"] = sip_gateway + if tx_sound_file is not None: + data["tx_sound_file"] = tx_sound_file + if vad_max_timer is not None: + data["vad_max_timer"] = vad_max_timer + if vad_timer is not None: + data["vad_timer"] = vad_timer + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_voip_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_VR> type requests + + https://www.candelatech.com/lfcli_ug.php#add_vr + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddVrFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddVrFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + p_4BYTE_AS_NUMBER = 0x40 # Sets corresponding Xorp flag. + BGP_CONFED = 0x100 # Configure BGP in a confederation. + BGP_DAMPING = 0x200 # Enable BGP damping section in Xorp configuration file. + ENABLE_BGP = 0x20 # Set this to zero if you don't want BGP on this VR. + RIP_ACCEPT_DR = 0x800 # Tell RIP to accept default-routes. + ROUTE_REFLECTOR = 0x80 # Act as BGP Route Reflector. + USE_IPV6 = 0x10 # Enable IPv6 OSPF routing for this virtual router. + USE_IPV6_RADVD = 0x8 # Enable IPv6 RADV Daemon for interfaces in this virtual router. + USE_RIP = 0x400 # Enable RIP routing protocol in Xorp. + USE_XORP_MCAST = 0x2 # Enable Xorp Multicast routing (requires OSPF to be enabled currently) + USE_XORP_OLSR = 0x1000 # Enable OLSR routing protocol in Xorp. + USE_XORP_OSPF = 0x1 # Enable Xorp router daemon with OSPF (IPv4) protocol + USE_XORP_SHA = 0x4 # Enable Telcordia's Xorp SHA option (requires OSPF to be enabled) + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddVrFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_vr(self, + alias=None, # Name of virtual router. + flags=None, # Virtual router flags, see above for definitions. + height=None, # Height to be used when drawn in the LANforge-GUI. + notes=None, # Notes for this Virtual Router. Put in quotes if the notes include + # white-space. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + vr_id=None, # Leave blank, use NA or 0xFFFF unless you are certain of the value you + # want to enter. + width=None, # Width to be used when drawn in the LANforge-GUI. + x=None, # X coordinate to be used when drawn in the LANforge-GUI. + y=None, # Y coordinate to be used when drawn in the LANforge-GUI. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_vr(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if flags is not None: + data["flags"] = flags + if height is not None: + data["height"] = height + if notes is not None: + data["notes"] = notes + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if vr_id is not None: + data["vr_id"] = vr_id + if width is not None: + data["width"] = width + if x is not None: + data["x"] = x + if y is not None: + data["y"] = y + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_vr", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_VR_BGP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_vr_bgp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddVrBgpFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddVrBgpFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + p_4BYTE_AS_NUMBER = 0x40 # Sets corresponding Xorp flag. + BGP_CONFED = 0x100 # Configure BGP in a confederation. + BGP_DAMPING = 0x200 # Enable BGP damping section in Xorp configuration file. + ENABLE_BGP = 0x20 # Set this to zero if you don't want BGP on this VR. + ROUTE_REFLECTOR = 0x80 # Act as BGP Route Reflector. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddVrBgpFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_vr_bgp(self, + bgp_id=None, # BGP Identifier: IPv4 Address + cluster_id=None, # Cluster ID, IPv4 Address. Use NA if not clustering. + confed_id=None, # Confederation ID 1-65535. Use NA if not in a confederation. + flags=None, # Virtual router BGP flags, see above for definitions. + half_life=None, # Halflife in minutes for damping configuration. + local_as=None, # BGP Autonomous System number, 1-65535 + max_suppress=None, # Maximum hold down time in minutes for damping configuration. + resource=None, # Resource number. + reuse=None, # Route flag damping reuse threshold, in minutes. + shelf=None, # Shelf name/id. + suppress=None, # Route flag damping cutoff threshold, in minutes. + vr_id=None, # Name of virtual router. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_vr_bgp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if bgp_id is not None: + data["bgp_id"] = bgp_id + if cluster_id is not None: + data["cluster_id"] = cluster_id + if confed_id is not None: + data["confed_id"] = confed_id + if flags is not None: + data["flags"] = flags + if half_life is not None: + data["half_life"] = half_life + if local_as is not None: + data["local_as"] = local_as + if max_suppress is not None: + data["max_suppress"] = max_suppress + if resource is not None: + data["resource"] = resource + if reuse is not None: + data["reuse"] = reuse + if shelf is not None: + data["shelf"] = shelf + if suppress is not None: + data["suppress"] = suppress + if vr_id is not None: + data["vr_id"] = vr_id + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_vr_bgp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_VRCX> type requests + + https://www.candelatech.com/lfcli_ug.php#add_vrcx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddVrcxFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(AddVrcxFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + custom_dhcpd = 0x400 # Use custom DHCP config file + dhcpd_enabled = 0x200 # Serve IPv4 DHCP on this interface + ipv6_enabled = 0x2000 # Serve IPv6 DHCP on this interface + nat_enabled = 0x100 # This connection will NAT outgoing packets + subnet_0 = 0x1 # Specify subnet 0 + subnet_1 = 0x2 # Specify subnet 1 + subnet_2 = 0x4 # Specify subnet 2 + subnet_3 = 0x8 # Specify subnet 3 + subnet_4 = 0x10 # Specify subnet 4 + subnet_5 = 0x20 # Specify subnet 5 + subnet_6 = 0x40 # Specify subnet 6 + subnet_7 = 0x80 # Specify subnet 7 + use_multicast = 0x800 # Use this interface for multicast and-rp + use_vrrp = 0x1000 # Use this interface for VRRP + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("AddVrcxFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_add_vrcx(self, + dhcp_dns=None, # IP Address of DNS server. + dhcp_dns6=None, # IPv6 Address of DNS server. + dhcp_domain=None, # DHCP Domain name to serve. + dhcp_lease_time=None, # DHCP Lease time (in seconds) + dhcp_max=None, # Minimum IP address range to serve. + dhcp_max6=None, # Minimum IPv6 address to serve. + dhcp_min=None, # Minimum IP address range to serve. + dhcp_min6=None, # Minimum IPv6 address to serve. + flags=None, # Flags, specify if subnets 0-7 are in use, see above for others. + height=None, # Height to be used when drawn in the LANforge-GUI. + interface_cost=None, # If using OSPF, this sets the cost for this link (1-65535). + local_dev=None, # Name of port A, the local network device pair. + local_dev_b=None, # Name of port B for the local redirect device pair. + nexthop=None, # The next-hop to use when routing packets out this interface. + ospf_area=None, # If using OSPF, this sets the OSPF area for this interface. Default + # is 0.0.0.0. + remote_dev=None, # Name the remote network device. + remote_dev_b=None, # Name of port B for the remote network device. + resource=None, # Resource number. + rip_metric=None, # If using RIP, this determines the RIP metric (cost), (1-15, 15 is + # infinite). + shelf=None, # Shelf name/id. + subnets=None, # Subnets associated with this link, format: 1.1.1.1/24,1.1.2.1/16... + vr_name=None, # Virtual Router this endpoint belongs to. Use 'FREE_LIST' to add a + # stand-alone endpoint. + vrrp_id=None, # VRRP id, must be unique in this virtual router (1-255) + vrrp_interval=None, # VRRP broadcast message interval, in seconds (1-255) + vrrp_ip=None, # VRRP IPv4 address..ignored if not flagged for VRRP. + vrrp_ip_prefix=None, # Number of bits in subnet mask, ie 24 for 255.255.255.0 + vrrp_priority=None, # VRRP Priority (1-255, higher is more priority.) + wanlink=None, # The name of the WanLink that connects the two B ports. + width=None, # Width to be used when drawn in the LANforge-GUI. + x=None, # X coordinate to be used when drawn in the LANforge-GUI. + y=None, # Y coordinate to be used when drawn in the LANforge-GUI. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_vrcx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if dhcp_dns is not None: + data["dhcp_dns"] = dhcp_dns + if dhcp_dns6 is not None: + data["dhcp_dns6"] = dhcp_dns6 + if dhcp_domain is not None: + data["dhcp_domain"] = dhcp_domain + if dhcp_lease_time is not None: + data["dhcp_lease_time"] = dhcp_lease_time + if dhcp_max is not None: + data["dhcp_max"] = dhcp_max + if dhcp_max6 is not None: + data["dhcp_max6"] = dhcp_max6 + if dhcp_min is not None: + data["dhcp_min"] = dhcp_min + if dhcp_min6 is not None: + data["dhcp_min6"] = dhcp_min6 + if flags is not None: + data["flags"] = flags + if height is not None: + data["height"] = height + if interface_cost is not None: + data["interface_cost"] = interface_cost + if local_dev is not None: + data["local_dev"] = local_dev + if local_dev_b is not None: + data["local_dev_b"] = local_dev_b + if nexthop is not None: + data["nexthop"] = nexthop + if ospf_area is not None: + data["ospf_area"] = ospf_area + if remote_dev is not None: + data["remote_dev"] = remote_dev + if remote_dev_b is not None: + data["remote_dev_b"] = remote_dev_b + if resource is not None: + data["resource"] = resource + if rip_metric is not None: + data["rip_metric"] = rip_metric + if shelf is not None: + data["shelf"] = shelf + if subnets is not None: + data["subnets"] = subnets + if vr_name is not None: + data["vr_name"] = vr_name + if vrrp_id is not None: + data["vrrp_id"] = vrrp_id + if vrrp_interval is not None: + data["vrrp_interval"] = vrrp_interval + if vrrp_ip is not None: + data["vrrp_ip"] = vrrp_ip + if vrrp_ip_prefix is not None: + data["vrrp_ip_prefix"] = vrrp_ip_prefix + if vrrp_priority is not None: + data["vrrp_priority"] = vrrp_priority + if wanlink is not None: + data["wanlink"] = wanlink + if width is not None: + data["width"] = width + if x is not None: + data["x"] = x + if y is not None: + data["y"] = y + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_vrcx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_VRCX2> type requests + + https://www.candelatech.com/lfcli_ug.php#add_vrcx2 + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_add_vrcx2(self, + local_dev=None, # Name of port A for the connection. + nexthop6=None, # The IPv6 next-hop to use when routing packets out this interface. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + subnets6=None, # IPv6 Subnets associated with this link, format: + # aaaa:bbbb::0/64,cccc:dddd:eeee::0/64... + vr_name=None, # Virtual Router this endpoint belongs to. Use 'FREE_LIST' to add a + # stand-alone endpoint. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_vrcx2(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if local_dev is not None: + data["local_dev"] = local_dev + if nexthop6 is not None: + data["nexthop6"] = nexthop6 + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if subnets6 is not None: + data["subnets6"] = subnets6 + if vr_name is not None: + data["vr_name"] = vr_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_vrcx2", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADD_WL_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#add_wl_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class AddWlEndpWleFlags(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + SHOW_WP = 1 # Show WanPaths in wanlink endpoint table in GUI + + def post_add_wl_endp(self, + alias=None, # Name of WanPath. + cpu_id=None, # The CPU/thread that this process should run on (kernel-mode + # only). + description=None, # Description for this endpoint, put in single quotes if it + # contains spaces. + dest_ip=None, # Selection filter: Destination IP. + dest_ip_mask=None, # Selection filter: Destination IP MASK. + drop_every_xth_pkt=None, # YES to periodically drop every Xth pkt, NO to drop packets + # randomly. + drop_freq=None, # How often, out of 1,000,000 packets, should we purposefully + # drop a packet. + dup_every_xth_pkt=None, # YES to periodically duplicate every Xth pkt, NO to duplicate + # packets randomly. + dup_freq=None, # How often, out of 1,000,000 packets, should we purposefully + # duplicate a packet. + extra_buffer=None, # The extra amount of bytes to buffer before dropping pkts, in + # units of 1024, use -1 for AUTO. + ignore_bandwidth=None, # Should we ignore the bandwidth settings from the playback + # file? YES, NO, or NA. + ignore_dup=None, # Should we ignore the Duplicate Packet settings from the + # playback file? YES, NO, or NA. + ignore_latency=None, # Should we ignore the latency settings from the playback file? + # YES, NO, or NA. + ignore_loss=None, # Should we ignore the packet-loss settings from the playback + # file? YES, NO, or NA. + jitter_freq=None, # How often, out of 1,000,000 packets, should we apply random + # jitter. + latency=None, # The base latency added to all packets, in milliseconds (or add + # 'us' suffix for microseconds) + max_drop_amt=None, # Maximum amount of packets to drop in a row. Default is 1. + max_jitter=None, # The maximum jitter, in milliseconds (or add 'us' suffix for + # microseconds) + max_lateness=None, # Maximum amount of un-intentional delay before pkt is dropped. + # Default is AUTO + max_rate=None, # Maximum transmit rate (bps) for this WanLink. + max_reorder_amt=None, # Maximum amount of packets by which to reorder, Default is 10. + min_drop_amt=None, # Minimum amount of packets to drop in a row. Default is 1. + min_reorder_amt=None, # Minimum amount of packets by which to reorder, Default is 1. + playback_capture=None, # ON or OFF, should we play back a WAN capture file? + playback_capture_file=None, # Name of the WAN capture file to play back. + playback_loop=None, # Should we loop the playback file, YES or NO or NA. + port=None, # Port number. + reorder_every_xth_pkt=None, # YES to periodically reorder every Xth pkt, NO to reorder + # packets randomly. + reorder_freq=None, # How often, out of 1,000,000 packets, should we make a packet + # out of order. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + source_ip=None, # Selection filter: Source IP. + source_ip_mask=None, # Selection filter: Source IP MASK. + speed=None, # The maximum speed this WanLink will accept (bps). + test_mgr=None, # The name of the Test-Manager this WanPath is to use. Leave + # blank for no restrictions. + wanlink=None, # Name of WanLink to which we are adding this WanPath. + wle_flags=None, # WanLink Endpoint specific flags, see above. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_add_wl_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if cpu_id is not None: + data["cpu_id"] = cpu_id + if description is not None: + data["description"] = description + if dest_ip is not None: + data["dest_ip"] = dest_ip + if dest_ip_mask is not None: + data["dest_ip_mask"] = dest_ip_mask + if drop_every_xth_pkt is not None: + data["drop_every_xth_pkt"] = drop_every_xth_pkt + if drop_freq is not None: + data["drop_freq"] = drop_freq + if dup_every_xth_pkt is not None: + data["dup_every_xth_pkt"] = dup_every_xth_pkt + if dup_freq is not None: + data["dup_freq"] = dup_freq + if extra_buffer is not None: + data["extra_buffer"] = extra_buffer + if ignore_bandwidth is not None: + data["ignore_bandwidth"] = ignore_bandwidth + if ignore_dup is not None: + data["ignore_dup"] = ignore_dup + if ignore_latency is not None: + data["ignore_latency"] = ignore_latency + if ignore_loss is not None: + data["ignore_loss"] = ignore_loss + if jitter_freq is not None: + data["jitter_freq"] = jitter_freq + if latency is not None: + data["latency"] = latency + if max_drop_amt is not None: + data["max_drop_amt"] = max_drop_amt + if max_jitter is not None: + data["max_jitter"] = max_jitter + if max_lateness is not None: + data["max_lateness"] = max_lateness + if max_rate is not None: + data["max_rate"] = max_rate + if max_reorder_amt is not None: + data["max_reorder_amt"] = max_reorder_amt + if min_drop_amt is not None: + data["min_drop_amt"] = min_drop_amt + if min_reorder_amt is not None: + data["min_reorder_amt"] = min_reorder_amt + if playback_capture is not None: + data["playback_capture"] = playback_capture + if playback_capture_file is not None: + data["playback_capture_file"] = playback_capture_file + if playback_loop is not None: + data["playback_loop"] = playback_loop + if port is not None: + data["port"] = port + if reorder_every_xth_pkt is not None: + data["reorder_every_xth_pkt"] = reorder_every_xth_pkt + if reorder_freq is not None: + data["reorder_freq"] = reorder_freq + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if source_ip is not None: + data["source_ip"] = source_ip + if source_ip_mask is not None: + data["source_ip_mask"] = source_ip_mask + if speed is not None: + data["speed"] = speed + if test_mgr is not None: + data["test_mgr"] = test_mgr + if wanlink is not None: + data["wanlink"] = wanlink + if wle_flags is not None: + data["wle_flags"] = wle_flags + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/add_wl_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/ADMIN> type requests + + https://www.candelatech.com/lfcli_ug.php#admin + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_admin(self, + arg1=None, # Argument 1: xorp-port | scan-rslts-file | iface-name | iface-eid | + # rfgen-message | id + arg2=None, # Argument 2: scan key | message | angle | dest-radio + arg3=None, # Argument 3: noprobe | migrate-sta-mac-pattern + arg5=None, # Argument 4: table-speed + cmd=None, # Admin command: + # resync_clock|write_xorp_cfg|scan_complete|ifup_post_complete|flush_complete|req_migrate|rfgen|chamber|clean_logs + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_admin(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if arg1 is not None: + data["arg1"] = arg1 + if arg2 is not None: + data["arg2"] = arg2 + if arg3 is not None: + data["arg3"] = arg3 + if arg5 is not None: + data["arg5"] = arg5 + if cmd is not None: + data["cmd"] = cmd + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/admin", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/APPLY_VR_CFG> type requests + + https://www.candelatech.com/lfcli_ug.php#apply_vr_cfg + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_apply_vr_cfg(self, + resource=None, # The number of the resource in question, or 'ALL'. + shelf=None, # The number of the shelf in question, or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_apply_vr_cfg(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/apply_vr_cfg", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/BLINK_ATTENUATOR> type requests + + https://www.candelatech.com/lfcli_ug.php#blink_attenuator + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_blink_attenuator(self, + resource=None, # Resource number. + serno=None, # Serial number for requested Attenuator, or 'all'. + shelf=None, # Shelf number, usually 1. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_blink_attenuator(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if serno is not None: + data["serno"] = serno + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/blink_attenuator", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/C_SHOW_PORTS> type requests + + https://www.candelatech.com/lfcli_ug.php#c_show_ports + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class CShowPortsProbeFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(CShowPortsProbeFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + BRIDGE = 0x8 # 8 include bridges + EASY_IP_INFO = 0x10 # 16 Everything but gateway information, which is expensive to probe. + ETHTOOL = 0x4 # 4 include ethtool results + GW = 0x20 # 32 include gateway information + GW_FORCE_REFRESH = 0x40 # 64 Force GW (re)probe. Otherwise, cached values *might* be used. + MII = 0x2 # 2 include MII + WIFI = 0x1 # 1 include wifi stations + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("CShowPortsProbeFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_c_show_ports(self, + port=None, # Port number, or 'all'. + probe_flags=None, # See above, add them together for multiple probings. Leave blank if + # you want stats only. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_c_show_ports(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if port is not None: + data["port"] = port + if probe_flags is not None: + data["probe_flags"] = probe_flags + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/c_show_ports", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CANCEL_VR_CFG> type requests + + https://www.candelatech.com/lfcli_ug.php#cancel_vr_cfg + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_cancel_vr_cfg(self, + resource=None, # The number of the resource in question, or 'ALL'. + shelf=None, # The number of the shelf in question, or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_cancel_vr_cfg(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/cancel_vr_cfg", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CLEAR_CD_COUNTERS> type requests + + https://www.candelatech.com/lfcli_ug.php#clear_cd_counters + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_clear_cd_counters(self, + cd_name=None, # Name of Collision Domain, or 'all'. Null argument is same as + # 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_clear_cd_counters(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cd_name is not None: + data["cd_name"] = cd_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/clear_cd_counters", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CLEAR_CX_COUNTERS> type requests + + https://www.candelatech.com/lfcli_ug.php#clear_cx_counters + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_clear_cx_counters(self, + cx_name=None, # Name of Cross Connect, or 'all'. Null argument is same as + # 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_clear_cx_counters(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cx_name is not None: + data["cx_name"] = cx_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/clear_cx_counters", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CLEAR_ENDP_COUNTERS> type requests + + https://www.candelatech.com/lfcli_ug.php#clear_endp_counters + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_clear_endp_counters(self, + endp_name=None, # Name of Endpoint, or 'all'. Null argument is same as + # 'all'. + incr_seqno=None, # Enter 'YES' if you want the target to increment the + # cfg-seq-no. + just_latency=None, # Enter 'YES' if you only want to clear latency counters, + # and see above for RXGAP. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_clear_endp_counters(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if incr_seqno is not None: + data["incr_seqno"] = incr_seqno + if just_latency is not None: + data["just_latency"] = just_latency + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/clear_endp_counters", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CLEAR_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#clear_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_clear_group(self, + name=None, # The name of the test group. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_clear_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/clear_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CLEAR_PORT_COUNTERS> type requests + + https://www.candelatech.com/lfcli_ug.php#clear_port_counters + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class ClearPortCountersExtra(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + dhcp4_lease = "dhcp4_lease" # Remove dhcp lease files for IPv4 DHCP + dhcp6_lease = "dhcp6_lease" # Remove dhcp lease files for IPv6 DHCP + dhcp_leases = "dhcp_leases" # Remove dhcp lease files for IPv4 and IPv6 DHCP + + def post_clear_port_counters(self, + extra=None, # Clear something else instead: dhcp4_lease | dhcp6_lease | + # dhcp_leases + port=None, # The number of the port in question, or 'ALL'. + resource=None, # The number of the resource in question, or 'ALL'. + shelf=None, # The number of the shelf in question, or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_clear_port_counters(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if extra is not None: + data["extra"] = extra + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/clear_port_counters", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CLEAR_RESOURCE_COUNTERS> type requests + + https://www.candelatech.com/lfcli_ug.php#clear_resource_counters + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_clear_resource_counters(self, + resource=None, # The number of the resource in question, or 'ALL'. + shelf=None, # The number of the shelf in question, or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_clear_resource_counters(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/clear_resource_counters", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CLEAR_WP_COUNTERS> type requests + + https://www.candelatech.com/lfcli_ug.php#clear_wp_counters + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_clear_wp_counters(self, + endp_name=None, # Name of WanLink Endpoint. + wp_name=None, # Name of WanPath to clear. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_clear_wp_counters(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if wp_name is not None: + data["wp_name"] = wp_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/clear_wp_counters", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/CREATE_CLIENT> type requests + + https://www.candelatech.com/lfcli_ug.php#create_client + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_create_client(self, + name=None, # A single name with no white-spaces (15 characters or less) + password=None, # Can be blank or 'NA' if no password is set, otherwise must be the + # password. Use IGNORE for no change. + super_user=None, # 1 If you want this user to have Administrative powers, 0 or blank + # otherwise. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_create_client(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if password is not None: + data["password"] = password + if super_user is not None: + data["super_user"] = super_user + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/create_client", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/DIAG> type requests + + https://www.candelatech.com/lfcli_ug.php#diag + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_diag(self, + arg1=None, # Optional: Endpoint name to diag. + p_type=None, # Default (blank) is everything, options: alerts, license, counters, fds, + # clients, endpoints, shelf, iobuffer. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_diag(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if arg1 is not None: + data["arg1"] = arg1 + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/diag", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/DISCOVER> type requests + + https://www.candelatech.com/lfcli_ug.php#discover + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_discover(self, + disconnect=None, # Set to 'disconnect' to force disconnect to remote resource process. + resource=None, # Resource ID. Use if discovering Attenuators. + shelf=None, # Shelf-ID, only used if discovering Attenuators. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_discover(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if disconnect is not None: + data["disconnect"] = disconnect + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/discover", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/DO_PESQ> type requests + + https://www.candelatech.com/lfcli_ug.php#do_pesq + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_do_pesq(self, + endp_name=None, # Name of Endpoint. + result_file_name=None, # The name of the file received by the endpoint. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_do_pesq(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if result_file_name is not None: + data["result_file_name"] = result_file_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/do_pesq", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/FILE> type requests + + https://www.candelatech.com/lfcli_ug.php#file + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_file(self, + card=None, # Card ID + cmd=None, # Only 'Download' supported for now, 'Upload' reserved for future use. + filename=None, # File to transfer. + shelf=None, # Shelf ID + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_file(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if card is not None: + data["card"] = card + if cmd is not None: + data["cmd"] = cmd + if filename is not None: + data["filename"] = filename + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/file", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/FLASH_ATTENUATOR> type requests + + https://www.candelatech.com/lfcli_ug.php#flash_attenuator + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_flash_attenuator(self, + filename=None, # File to use when uploading to attenuator. + resource=None, # Resource number. + serno=None, # Serial number for requested Attenuator, or 'all'. + shelf=None, # Shelf number, usually 1. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_flash_attenuator(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if filename is not None: + data["filename"] = filename + if resource is not None: + data["resource"] = resource + if serno is not None: + data["serno"] = serno + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/flash_attenuator", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETAVGLATENCY> type requests + + https://www.candelatech.com/lfcli_ug.php#getavglatency + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getavglatency(self, + aorb=None, # For AtoB, enter 'B', for BtoA, enter 'A'. + cx=None, # Cross-connect or Test-Group name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getavglatency(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getavglatency", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETINRXBPS> type requests + + https://www.candelatech.com/lfcli_ug.php#getinrxbps + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getinrxbps(self, + aorb=None, # For endpoint a, enter 'A', for endpoint b, enter 'B'. + cx=None, # Cross-connect or Test-Group name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getinrxbps(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getinrxbps", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETINRXRATE> type requests + + https://www.candelatech.com/lfcli_ug.php#getinrxrate + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getinrxrate(self, + aorb=None, # For endpoint a, enter 'A', for endpoint b, enter 'B'. + cx=None, # Cross-connect or Test-Group name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getinrxrate(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getinrxrate", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETINTXRATE> type requests + + https://www.candelatech.com/lfcli_ug.php#getintxrate + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getintxrate(self, + aorb=None, # For endpoint a, enter 'A', for endpoint b, enter 'B'. + cx=None, # Cross-connect or Test-Group name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getintxrate(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getintxrate", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETIPADD> type requests + + https://www.candelatech.com/lfcli_ug.php#getipadd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getipadd(self, + aorb=None, # For endpoint a, enter 'A', for endpoint b, enter 'B'. + cx=None, # Cross-connect name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getipadd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getipadd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETMAC> type requests + + https://www.candelatech.com/lfcli_ug.php#getmac + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getmac(self, + aorb=None, # For endpoint a, enter 'A', for endpoint b, enter 'B'. + cx=None, # Cross-connect name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getmac(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getmac", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETMASK> type requests + + https://www.candelatech.com/lfcli_ug.php#getmask + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getmask(self, + aorb=None, # For endpoint a, enter 'A', for endpoint b, enter 'B'. + cx=None, # Cross-connect name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getmask(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getmask", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETPKTDROPS> type requests + + https://www.candelatech.com/lfcli_ug.php#getpktdrops + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getpktdrops(self, + aorb=None, # For AtoB, enter 'B', for BtoA, enter 'A'. + cx=None, # Cross-connect or Test-Group name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getpktdrops(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getpktdrops", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETRXENDPERRPKTS> type requests + + https://www.candelatech.com/lfcli_ug.php#getrxendperrpkts + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getrxendperrpkts(self, + aorb=None, # For AtoB, enter 'B', for BtoA, enter 'A'. + cx=None, # Cross-connect or Test-Group name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getrxendperrpkts(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getrxendperrpkts", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETRXPKTS> type requests + + https://www.candelatech.com/lfcli_ug.php#getrxpkts + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getrxpkts(self, + aorb=None, # For endpoint a, enter 'A', for endpoint b, enter 'B'. + cx=None, # Cross-connect or Test-Group name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getrxpkts(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getrxpkts", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETRXPORTERRPKTS> type requests + + https://www.candelatech.com/lfcli_ug.php#getrxporterrpkts + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_getrxporterrpkts(self, + aorb=None, # For AtoB, enter 'B', for BtoA, enter 'A'. + cx=None, # Cross-connect name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_getrxporterrpkts(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/getrxporterrpkts", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GETTXPKTS> type requests + + https://www.candelatech.com/lfcli_ug.php#gettxpkts + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_gettxpkts(self, + aorb=None, # For endpoint a, enter 'A', for endpoint b, enter 'B'. + cx=None, # Cross-connect or Test-Group name + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_gettxpkts(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if aorb is not None: + data["aorb"] = aorb + if cx is not None: + data["cx"] = cx + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/gettxpkts", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/GOSSIP> type requests + + https://www.candelatech.com/lfcli_ug.php#gossip + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_gossip(self, + message=None, # Message to show to others currently logged on. <tt + # escapearg='false'>Unescaped Value</tt> + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_gossip(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if message is not None: + data["message"] = message + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/gossip", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/HELP> type requests + + https://www.candelatech.com/lfcli_ug.php#help + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_help(self, + command=None, # The command to get help for. Can be 'all', or blank. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_help(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if command is not None: + data["command"] = command + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/help", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/INIT_WISER> type requests + + https://www.candelatech.com/lfcli_ug.php#init_wiser + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_init_wiser(self, + file_name=None, # The WISER file name for the desired emulation, or 'NA' for empty + # string. + node_count=None, # The number of WISER nodes for the desired emulation, or 'NA' for + # empty string. + resource=None, # The number of the resource in question. + shelf=None, # The number of the shelf in question. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_init_wiser(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if file_name is not None: + data["file_name"] = file_name + if node_count is not None: + data["node_count"] = node_count + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/init_wiser", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/LICENSES> type requests + + https://www.candelatech.com/lfcli_ug.php#licenses + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_licenses(self, + popup=None, # If 'popup', then cause a GUI popup msg, otherwise, just show text. + show_file=None, # If 'yes', then show the license file, not the parsed license + # information. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_licenses(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if popup is not None: + data["popup"] = popup + if show_file is not None: + data["show_file"] = show_file + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/licenses", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/LOAD> type requests + + https://www.candelatech.com/lfcli_ug.php#load + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_load(self, + action=None, # Should be 'append' or 'overwrite'. + clean_chambers=None, # If yes, then Chambers will be cleaned up when overwrite is selected, + # otherwise they will be kept. + clean_dut=None, # If yes, then DUT will be cleaned up when overwrite is selected, + # otherwise they will be kept. + clean_profiles=None, # If yes, then clean all profiles when overwrite is selected, otherwise + # they will be kept. + name=None, # The name of the database to load. (DFLT is the default) + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_load(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if action is not None: + data["action"] = action + if clean_chambers is not None: + data["clean_chambers"] = clean_chambers + if clean_dut is not None: + data["clean_dut"] = clean_dut + if clean_profiles is not None: + data["clean_profiles"] = clean_profiles + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/load", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/LOG_LEVEL> type requests + + https://www.candelatech.com/lfcli_ug.php#log_level + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class LogLevelLevel(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(LogLevelLevel0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + ALL = 0xffffffff # Log everything + CUST1 = 0x10000 # Cust-1, latency info (65536) + DB = 0x80 # Database related logging (128) + DBG = 0x20 # debug (32) + DBG2 = 0x1000 # very verbose logging (4096) + DIS = 0x1 # disasters (1) + ERR = 0x2 # errors (2) + INF = 0x8 # info (8) + LIO = 0x2000 # IO logging (8192) + LL_PROF = 0x8000 # Profiling information (32768) + OUT1 = 0x4000 # Some std-out logging (16384) + PARSE = 0x800 # PARSE specific (2048) + SCRIPT = 0x400 # Scripting specific stuff (1024) + SEC = 0x40 # log security violations (64) + TRC = 0x10 # function trace (16) + WRN = 0x4 # warnings (4) + XMT = 0x100 # Output going to clients (256) + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("LogLevelLevel has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_log_level(self, + level=None, # Integer corresponding to the logging flags. + target=None, # Options: 'gnu' | [file-endp-name]. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_log_level(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if level is not None: + data["level"] = level + if target is not None: + data["target"] = target + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/log_level", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/LOG_MSG> type requests + + https://www.candelatech.com/lfcli_ug.php#log_msg + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_log_msg(self, + message=None, # Message to log. <tt escapearg='false'>Unescaped Value</tt> + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_log_msg(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if message is not None: + data["message"] = message + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/log_msg", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/LOGIN> type requests + + https://www.candelatech.com/lfcli_ug.php#login + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_login(self, + name=None, # A single name with no white-spaces (15 characters or less) + password=None, # Can be blank or 'NA' if no password is set, otherwise must be the + # password. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_login(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if password is not None: + data["password"] = password + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/login", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/MOTD> type requests + + https://www.candelatech.com/lfcli_ug.php#motd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_motd(self, + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_motd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + response = self.json_post("/cli-json/motd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_CD> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_cd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_nc_show_cd(self, + collision_domain=None, # Name of the Collision Domain, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_cd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if collision_domain is not None: + data["collision_domain"] = collision_domain + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_cd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_CHANNEL_GROUPS> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_channel_groups + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_nc_show_channel_groups(self, + channel_name=None, # Name of the channel, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_channel_groups(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if channel_name is not None: + data["channel_name"] = channel_name + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_channel_groups", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_ENDPOINTS> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_endpoints + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_nc_show_endpoints(self, + endpoint=None, # Name of endpoint, or 'all'. + extra=None, # See above. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_endpoints(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endpoint is not None: + data["endpoint"] = endpoint + if extra is not None: + data["extra"] = extra + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_endpoints", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_PESQ> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_pesq + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_nc_show_pesq(self, + endpoint=None, # Name of endpoint, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_pesq(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endpoint is not None: + data["endpoint"] = endpoint + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_pesq", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_PORTS> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_ports + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class NcShowPortsProbeFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(NcShowPortsProbeFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + BRIDGE = 0x8 # 8 include bridges + EASY_IP_INFO = 0x10 # 16 Everything but gateway information, which is expensive to probe. + ETHTOOL = 0x4 # 4 include ethtool results + GW = 0x20 # 32 include gateway information + GW_FORCE_REFRESH = 0x40 # 64 Force GW (re)probe. Otherwise, cached values *might* be used. + MII = 0x2 # 2 include MII + WIFI = 0x1 # 1 include wifi stations + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("NcShowPortsProbeFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_nc_show_ports(self, + port=None, # Port number, or 'all'. + probe_flags=None, # See above, add them together for multiple probings. Leave blank + # if you want stats only. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_ports(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if port is not None: + data["port"] = port + if probe_flags is not None: + data["probe_flags"] = probe_flags + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_ports", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_PPP_LINKS> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_ppp_links + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_nc_show_ppp_links(self, + link_num=None, # Ppp-Link number of the span, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_ppp_links(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if link_num is not None: + data["link_num"] = link_num + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_ppp_links", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_SPANS> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_spans + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_nc_show_spans(self, + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + span_number=None, # Span-Number of the span, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_spans(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if span_number is not None: + data["span_number"] = span_number + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_spans", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_VR> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_vr + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_nc_show_vr(self, + resource=None, # Resource number, or 'all'. + router=None, # Name of the Virtual Router, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_vr(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if router is not None: + data["router"] = router + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_vr", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NC_SHOW_VRCX> type requests + + https://www.candelatech.com/lfcli_ug.php#nc_show_vrcx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_nc_show_vrcx(self, + cx_name=None, # Name of the Virtual Router Connection, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_nc_show_vrcx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cx_name is not None: + data["cx_name"] = cx_name + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/nc_show_vrcx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/NOTIFY_DHCP> type requests + + https://www.candelatech.com/lfcli_ug.php#notify_dhcp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_notify_dhcp(self, + cmd=None, # set/down/timeout/info: What does DHCP want us to do? + netmask=None, # New subnet mask. + new_dns=None, # New DNS server(s) for use by this interface. + new_ip=None, # New IP address. + new_ip6=None, # New Global IPv6 address: ipv6/prefix + new_mtu=None, # New MTU. + new_router=None, # One or more default routers. LANforge will only use the first one. + port=None, # Interface name. + reason=None, # DHCP reason, informational mostly. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_notify_dhcp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cmd is not None: + data["cmd"] = cmd + if netmask is not None: + data["netmask"] = netmask + if new_dns is not None: + data["new_dns"] = new_dns + if new_ip is not None: + data["new_ip"] = new_ip + if new_ip6 is not None: + data["new_ip6"] = new_ip6 + if new_mtu is not None: + data["new_mtu"] = new_mtu + if new_router is not None: + data["new_router"] = new_router + if port is not None: + data["port"] = port + if reason is not None: + data["reason"] = reason + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/notify_dhcp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/PORT_RESET_COMPLETED> type requests + + https://www.candelatech.com/lfcli_ug.php#port_reset_completed + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_port_reset_completed(self, + extra=None, # IP for SECIP, blank for others. + port=None, # The port in question. + p_type=None, # SUNOS, NORMAL, or SECIP..let us know what kind of reset + # completed. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_port_reset_completed(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if extra is not None: + data["extra"] = extra + if port is not None: + data["port"] = port + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/port_reset_completed", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/PROBE_PORT> type requests + + https://www.candelatech.com/lfcli_ug.php#probe_port + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_probe_port(self, + key=None, # Unique identifier for this request. Usually left blank.<br/> + port=None, # Port number or name + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_probe_port(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if key is not None: + data["key"] = key + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/probe_port", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/PROBE_PORTS> type requests + + https://www.candelatech.com/lfcli_ug.php#probe_ports + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_probe_ports(self, + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_probe_ports(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/probe_ports", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/QUIESCE_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#quiesce_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_quiesce_endp(self, + endp_name=None, # Name of the endpoint, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_quiesce_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/quiesce_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/QUIESCE_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#quiesce_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_quiesce_group(self, + name=None, # The name of the test group, or 'all' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_quiesce_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/quiesce_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/QUIT> type requests + + https://www.candelatech.com/lfcli_ug.php#quit + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_quit(self, + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_quit(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + response = self.json_post("/cli-json/quit", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/REBOOT_OS> type requests + + https://www.candelatech.com/lfcli_ug.php#reboot_os + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_reboot_os(self, + resource=None, # Resource number, or ALL. + shelf=None, # Shelf number, or ALL. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_reboot_os(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/reboot_os", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/REPORT> type requests + + https://www.candelatech.com/lfcli_ug.php#report + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_report(self, + reporting_on=None, # Should we globally enable/disable reporting. (YES, NO or NA) + rpt_dir=None, # Directory in which reports should be saved. + save_endps=None, # Should we save endpoint reports or not. (YES, NO or NA) + save_ports=None, # Should we save Port reports or not. (YES, NO or NA) + save_resource=None, # Should we save Resource reports or not. (YES, NO or NA) + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_report(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if reporting_on is not None: + data["reporting_on"] = reporting_on + if rpt_dir is not None: + data["rpt_dir"] = rpt_dir + if save_endps is not None: + data["save_endps"] = save_endps + if save_ports is not None: + data["save_ports"] = save_ports + if save_resource is not None: + data["save_resource"] = save_resource + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/report", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RESET_PORT> type requests + + https://www.candelatech.com/lfcli_ug.php#reset_port + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class ResetPortPreIfdown(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + P_IN = "P-IN" # Only call the portal login (do not reset drivers/supplicant/dhcp) + P_OUT = "P-OUT" # Only call the portal logout (do not reset drivers/supplicant/dhcp) + YES = "YES" # (include logout) Call portal-bot.pl ... <b>--logout</b> before going down. + + def post_reset_port(self, + port=None, # Port number to reset, or ALL. + pre_ifdown=None, # See above. Leave blank or use NA if unsure. + reset_ospf=None, # If set to 'NO' or 'NA', then OSPF will not be updated. Otherwise, it + # will be updated. + resource=None, # Resource number, or ALL. + shelf=None, # Shelf number, or ALL. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_reset_port(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if port is not None: + data["port"] = port + if pre_ifdown is not None: + data["pre_ifdown"] = pre_ifdown + if reset_ospf is not None: + data["reset_ospf"] = reset_ospf + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/reset_port", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RESET_SERIAL_SPAN> type requests + + https://www.candelatech.com/lfcli_ug.php#reset_serial_span + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_reset_serial_span(self, + resource=None, # Resource (machine) number. + shelf=None, # Shelf number + span=None, # Serial-Span number to reset. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_reset_serial_span(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if span is not None: + data["span"] = span + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/reset_serial_span", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_ATTENUATOR> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_attenuator + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_attenuator(self, + resource=None, # Resource number + serno=None, # Serial number for requested Attenuator. + shelf=None, # Shelf number, usually 1 + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_attenuator(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if serno is not None: + data["serno"] = serno + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_attenuator", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_CD> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_cd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_cd(self, + cd=None, # Name of Collision Domain. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_cd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cd is not None: + data["cd"] = cd + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_cd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_CD_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_cd_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_cd_endp(self, + cd=None, # Name of Collision Domain. + endp=None, # Endpoint name/id. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_cd_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cd is not None: + data["cd"] = cd + if endp is not None: + data["endp"] = endp + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_cd_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_CD_VR> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_cd_vr + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_cd_vr(self, + cd=None, # Name of Collision Domain. + endp=None, # Virtual-Router name/id. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_cd_vr(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cd is not None: + data["cd"] = cd + if endp is not None: + data["endp"] = endp + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_cd_vr", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_CHAMBER> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_chamber + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_chamber(self, + chamber=None, # Chamber name, or 'ALL' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_chamber(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if chamber is not None: + data["chamber"] = chamber + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_chamber", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_CHAMBER_PATH> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_chamber_path + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_chamber_path(self, + chamber=None, # Chamber Name. + path=None, # Path Name, use 'ALL' to delete all paths. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_chamber_path(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if chamber is not None: + data["chamber"] = chamber + if path is not None: + data["path"] = path + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_chamber_path", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_CHANNEL_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_channel_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_channel_group(self, + channel_name=None, # Name of the channel, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_channel_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if channel_name is not None: + data["channel_name"] = channel_name + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_channel_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_CLIENT> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_client + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_client(self, + client_name=None, # Name of the client profile you wish to remove. + client_password=None, # Client password. Not required if we are super-user. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_client(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if client_name is not None: + data["client_name"] = client_name + if client_password is not None: + data["client_password"] = client_password + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_client", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_CX> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_cx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_cx(self, + cx_name=None, # Name of the cross-connect, or 'all'. + test_mgr=None, # Name of test-mgr, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_cx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cx_name is not None: + data["cx_name"] = cx_name + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_cx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_DB> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_db + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_db(self, + db_name=None, # Name of the database to delete. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_db(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if db_name is not None: + data["db_name"] = db_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_db", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_DUT> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_dut + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_dut(self, + shelf=None, # DUT name, or 'ALL' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_dut(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_dut", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_endp(self, + endp_name=None, # Name of the endpoint, or 'YES_ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_EVENT> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_event + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_event(self, + event_id=None, # Numeric event-id, or 'all' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_event(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if event_id is not None: + data["event_id"] = event_id + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_event", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_group(self, + name=None, # The name of the test group. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_PPP_LINK> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_ppp_link + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_ppp_link(self, + resource=None, # Resource number that holds this PppLink. + shelf=None, # Name/id of the shelf. + unit_num=None, # Unit-Number for the PppLink to be deleted. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_ppp_link(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if unit_num is not None: + data["unit_num"] = unit_num + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_ppp_link", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_PROFILE> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_profile + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_profile(self, + name=None, # Profile name, or 'ALL' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_profile(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_profile", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_RESOURCE> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_resource + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_resource(self, + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_resource(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_resource", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_RFGEN> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_rfgen + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_rfgen(self, + resource=None, # Resource number + shelf=None, # Shelf number, usually 1 + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_rfgen(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_rfgen", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_SEC_IP> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_sec_ip + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_sec_ip(self, + ip_list=None, # IP1/prefix,IP2/prefix,...IPZ/prefix, or ALL + port=None, # Name of network device (Port) from which these IPs will be removed. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_sec_ip(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if ip_list is not None: + data["ip_list"] = ip_list + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_sec_ip", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_SPAN> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_span + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_span(self, + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + span_num=None, # Span-Number of the channel, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_span(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if span_num is not None: + data["span_num"] = span_num + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_span", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_TEST_MGR> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_test_mgr + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_test_mgr(self, + test_mgr=None, # Name of the test manager to be removed. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_test_mgr(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_test_mgr", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_TEXT_BLOB> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_text_blob + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_text_blob(self, + name=None, # Text Blob Name, or 'ALL' + p_type=None, # Text Blob type, or 'ALL' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_text_blob(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_text_blob", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_TGCX> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_tgcx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_tgcx(self, + cxname=None, # The name of the CX. + tgname=None, # The name of the test group. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_tgcx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cxname is not None: + data["cxname"] = cxname + if tgname is not None: + data["tgname"] = tgname + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_tgcx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_THRESHOLD> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_threshold + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_threshold(self, + endp=None, # Endpoint name or ID. + thresh_id=None, # Threshold ID to remove. Use 'all' to remove all. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_threshold(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp is not None: + data["endp"] = endp + if thresh_id is not None: + data["thresh_id"] = thresh_id + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_threshold", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_TRAFFIC_PROFILE> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_traffic_profile + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_traffic_profile(self, + name=None, # Profile name, or 'ALL' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_traffic_profile(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_traffic_profile", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_VENUE> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_venue + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_venue(self, + resource=None, # Resource number, or 'ALL' + shelf=None, # Shelf number. + venu_id=None, # Number to uniquely identify this venue on this resource, or 'ALL' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_venue(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if venu_id is not None: + data["venu_id"] = venu_id + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_venue", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_VLAN> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_vlan + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_vlan(self, + port=None, # Port number or name of the virtual interface. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_vlan(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_vlan", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_VR> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_vr + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_vr(self, + resource=None, # Resource number, or 'all'. + router_name=None, # Virtual Router name, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_vr(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if router_name is not None: + data["router_name"] = router_name + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_vr", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_VRCX> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_vrcx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_vrcx(self, + connection_name=None, # Virtual Router Connection name, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + vr_id=None, # If not removing from the free-list, then supply the virtual-router + # name/ID here. Leave blank or use NA for free-list. + vrcx_only=None, # If we should NOT delete underlying auto-created objects, enter + # 'vrcx_only' here, otherwise leave blank or use NA. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_vrcx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if connection_name is not None: + data["connection_name"] = connection_name + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if vr_id is not None: + data["vr_id"] = vr_id + if vrcx_only is not None: + data["vrcx_only"] = vrcx_only + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_vrcx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RM_WANPATH> type requests + + https://www.candelatech.com/lfcli_ug.php#rm_wanpath + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rm_wanpath(self, + endp_name=None, # Name of the endpoint. + wp_name=None, # Name of the wanpath. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rm_wanpath(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if wp_name is not None: + data["wp_name"] = wp_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rm_wanpath", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/RPT_SCRIPT> type requests + + https://www.candelatech.com/lfcli_ug.php#rpt_script + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_rpt_script(self, + endp=None, # Endpoint name or ID. + flags=None, # See above for description of the defined flags. + group_action=None, # All or Sequential. + loop_count=None, # How many times to loop before stopping (0 is infinite). + name=None, # Script name. + private=None, # Private encoding for the particular script. + p_type=None, # One of: NONE, Script2544, ScriptHunt, ScriptWL + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_rpt_script(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp is not None: + data["endp"] = endp + if flags is not None: + data["flags"] = flags + if group_action is not None: + data["group_action"] = group_action + if loop_count is not None: + data["loop_count"] = loop_count + if name is not None: + data["name"] = name + if private is not None: + data["private"] = private + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/rpt_script", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SCAN_WIFI> type requests + + https://www.candelatech.com/lfcli_ug.php#scan_wifi + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class ScanWifiExtra(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + NA = "NA" # (or left blank) the system does a full scan + dump = "dump" # then only cached values are returned + trigger_freq__freq_ = "trigger freq [freq]" # scan exactly those frequencies + + def post_scan_wifi(self, + extra=None, # Extra arguments to the scan script, see above. + key=None, # Unique identifier for this request. Usually left blank. + port=None, # Port number or name of the virtual interface. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_scan_wifi(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if extra is not None: + data["extra"] = extra + if key is not None: + data["key"] = key + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/scan_wifi", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ARM_INFO> type requests + + https://www.candelatech.com/lfcli_ug.php#set_arm_info + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetArmInfoArmFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetArmInfoArmFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + random_payload = 0x10000 # Use random payload sizes instead of linear increase + rel_tstamp = 0x400 # Use Relative Timestamps. This will increase performance + slow_start = 0x2000 # Use slow-start logic. This ramps up + udp_checksum = 0x4000 # Use UDP Checksums. + use_gw_mac = 0x1000 # Use default gateway's MAC for destination MAC. + use_tcp = 0x8000 # Use TCP instead of UDP protocol. (Note this is NOT stateful TCP!) + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetArmInfoArmFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_set_arm_info(self, + arm_flags=None, # Armageddon-related flags, see above for details. + burst=None, # Burst amount, can significantly improve throughput with some + # modern drivers, similar to 'multi_pkts', and uses the 'xmit_more' + # linux skb option. + dst_mac=None, # The destination MAC address. + dst_mac_count=None, # How many destination MACs to iterate through. + ip_dst_max=None, # Maximum destination IP address to use. + ip_dst_min=None, # Minimum destination IP address to use. + ip_src_max=None, # Maximum source IP address to use. + ip_src_min=None, # Minimum source IP address to use. + max_pkt_size=None, # Maximum packet size, including all Ethernet headers (but not + # CRC). + min_pkt_size=None, # Minimum packet size, including all Ethernet headers (but not + # CRC). + multi_pkts=None, # The number of identical packets to send before creating a new + # one. + name=None, # Name of the Endpoint we are setting. + pkts_to_send=None, # The number of packets to send. Set to zero for infinite. + src_mac=None, # The source MAC address. + src_mac_count=None, # How many source MACs to iterate through. + udp_dst_max=None, # Minimum destination UDP port. + udp_dst_min=None, # Minimum destination UDP port. + udp_src_max=None, # Maximum source UDP port. + udp_src_min=None, # Minimum source UDP port. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_arm_info(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if arm_flags is not None: + data["arm_flags"] = arm_flags + if burst is not None: + data["burst"] = burst + if dst_mac is not None: + data["dst_mac"] = dst_mac + if dst_mac_count is not None: + data["dst_mac_count"] = dst_mac_count + if ip_dst_max is not None: + data["ip_dst_max"] = ip_dst_max + if ip_dst_min is not None: + data["ip_dst_min"] = ip_dst_min + if ip_src_max is not None: + data["ip_src_max"] = ip_src_max + if ip_src_min is not None: + data["ip_src_min"] = ip_src_min + if max_pkt_size is not None: + data["max_pkt_size"] = max_pkt_size + if min_pkt_size is not None: + data["min_pkt_size"] = min_pkt_size + if multi_pkts is not None: + data["multi_pkts"] = multi_pkts + if name is not None: + data["name"] = name + if pkts_to_send is not None: + data["pkts_to_send"] = pkts_to_send + if src_mac is not None: + data["src_mac"] = src_mac + if src_mac_count is not None: + data["src_mac_count"] = src_mac_count + if udp_dst_max is not None: + data["udp_dst_max"] = udp_dst_max + if udp_dst_min is not None: + data["udp_dst_min"] = udp_dst_min + if udp_src_max is not None: + data["udp_src_max"] = udp_src_max + if udp_src_min is not None: + data["udp_src_min"] = udp_src_min + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_arm_info", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ATTENUATOR> type requests + + https://www.candelatech.com/lfcli_ug.php#set_attenuator + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_attenuator(self, + atten_idx=None, # Attenuator index, or 'all'. + mode=None, # 0 == normal attenuator, 1 == pulse mode (API Tech 4205A + # modules directly connected via USB only) + pulse_count=None, # Number of pulses (0-255) + pulse_interval_ms=None, # Time between pulses, in mili-seconds (0-60000). + pulse_time_ms=None, # Time interval between pulse groups in miliseconds (1-60000) + pulse_width_us5=None, # Pulse width in units of 1/2 micro second. So, if you want + # 1.5us, use value 3 (0-60000) + resource=None, # Resource number. + serno=None, # Serial number for requested Attenuator, or 'all'. + shelf=None, # Shelf number, usually 1. + val=None, # Requested attenution in 1/10ths of dB (ddB). + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_attenuator(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if atten_idx is not None: + data["atten_idx"] = atten_idx + if mode is not None: + data["mode"] = mode + if pulse_count is not None: + data["pulse_count"] = pulse_count + if pulse_interval_ms is not None: + data["pulse_interval_ms"] = pulse_interval_ms + if pulse_time_ms is not None: + data["pulse_time_ms"] = pulse_time_ms + if pulse_width_us5 is not None: + data["pulse_width_us5"] = pulse_width_us5 + if resource is not None: + data["resource"] = resource + if serno is not None: + data["serno"] = serno + if shelf is not None: + data["shelf"] = shelf + if val is not None: + data["val"] = val + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_attenuator", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_CHAMBER> type requests + + https://www.candelatech.com/lfcli_ug.php#set_chamber + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_chamber(self, + chamber=None, # Chamber name + cur_rotation=None, # Primarily used to store the last known rotation for turntables + # that do not report absolute position. Use NA or leave blank if + # unsure. + position=None, # Absolute position in degrees. + speed_rpm=None, # Speed in rpm (floating point number is accepted + tilt=None, # Absolute tilt in degrees. + turntable=None, # Turn-table address, for instance: 192.168.1.22:3001 + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_chamber(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if chamber is not None: + data["chamber"] = chamber + if cur_rotation is not None: + data["cur_rotation"] = cur_rotation + if position is not None: + data["position"] = position + if speed_rpm is not None: + data["speed_rpm"] = speed_rpm + if tilt is not None: + data["tilt"] = tilt + if turntable is not None: + data["turntable"] = turntable + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_chamber", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_CX_REPORT_TIMER> type requests + + https://www.candelatech.com/lfcli_ug.php#set_cx_report_timer + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_cx_report_timer(self, + cx_name=None, # Name of cross-connect, or 'all'. + cxonly=None, # If you want to set the timer for ONLY the CX, and not + milliseconds=None, # Report timer length in milliseconds. + test_mgr=None, # Name of the test manager, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_cx_report_timer(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cx_name is not None: + data["cx_name"] = cx_name + if cxonly is not None: + data["cxonly"] = cxonly + if milliseconds is not None: + data["milliseconds"] = milliseconds + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_cx_report_timer", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_CX_STATE> type requests + + https://www.candelatech.com/lfcli_ug.php#set_cx_state + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetCxStateCxState(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + DELETED = "DELETED" # Deletes the CX(s). + QUIESCE = "QUIESCE" # Stop transmitting and gracefully stop cross-connect. + RUNNING = "RUNNING" # Sets the CX(s) in the running state. + STOPPED = "STOPPED" # Sets the CX(s) in the stopped state. + SWITCH = "SWITCH" # Sets the CX(s) in the running state, stopping any conflicting tests. + + def post_set_cx_state(self, + cx_name=None, # Name of the cross-connect, or 'all'. + cx_state=None, # One of: RUNNING, SWITCH, QUIESCE, STOPPED, or DELETED. + test_mgr=None, # Name of the test-manager, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_cx_state(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cx_name is not None: + data["cx_name"] = cx_name + if cx_state is not None: + data["cx_state"] = cx_state + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_cx_state", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_ADDR> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_addr + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_endp_addr(self, + ip=None, # The IP Address. Used for TCP/IP and UDP/IP protocols. + mac=None, # The MAC address. Only needed for LANforge protocol Endpoints. + max_port=None, # The Maximum IP Port. Used for TCP/IP and UDP/IP protocols. + min_port=None, # The Minimum IP Port. Used for TCP/IP and UDP/IP protocols. + name=None, # The name of the endpoint we are configuring. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_addr(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if ip is not None: + data["ip"] = ip + if mac is not None: + data["mac"] = mac + if max_port is not None: + data["max_port"] = max_port + if min_port is not None: + data["min_port"] = min_port + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_addr", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_DETAILS> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_details + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_endp_details(self, + conn_timeout=None, # For TCP, the max time in miliseconds to wait for connection + # to establish. + dst_mac=None, # Destination MAC address, used for custom Ethernet replays. + max_conn_timer=None, # The maximum duration (in ms) this connection should run + # before re-establishing. + max_ip_port=None, # The maximum IP Port value. (The value for min ip port is + # set through the add_endp/ip_port parameter.) If greater + # than min, each connection will use a random value between + # min and max. + max_reconn_pause=None, # The maximum time between re-connects, in ms. + mcast_src_ip=None, # Multicast source address (used in SSM mode, multicast + # endpoints only) + mcast_src_port=None, # Multicast source address (used in SSM mode, multicast + # endpoints only) + min_conn_timer=None, # The minimum duration (in ms) this connection should run + # before re-establishing. + min_reconn_pause=None, # The minimum time between re-connects, in ms. + name=None, # The name of the endpoint we are configuring. + pkts_to_send=None, # Number of packets to send before stopping. 0 means + # infinite. + rcvbuf_size=None, # The receive buffer (window) size. Zero for AUTO + sndbuf_size=None, # The sending buffer (window) size. Zero for AUTO + tcp_delack_segs=None, # NA: No longer supported. + tcp_max_delack=None, # NA: No longer supported. + tcp_min_delack=None, # NA: No longer supported. + tcp_mss=None, # TCP Maximum Segment Size, affects packet size on the wire + # (88 - 32767). + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_details(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if conn_timeout is not None: + data["conn_timeout"] = conn_timeout + if dst_mac is not None: + data["dst_mac"] = dst_mac + if max_conn_timer is not None: + data["max_conn_timer"] = max_conn_timer + if max_ip_port is not None: + data["max_ip_port"] = max_ip_port + if max_reconn_pause is not None: + data["max_reconn_pause"] = max_reconn_pause + if mcast_src_ip is not None: + data["mcast_src_ip"] = mcast_src_ip + if mcast_src_port is not None: + data["mcast_src_port"] = mcast_src_port + if min_conn_timer is not None: + data["min_conn_timer"] = min_conn_timer + if min_reconn_pause is not None: + data["min_reconn_pause"] = min_reconn_pause + if name is not None: + data["name"] = name + if pkts_to_send is not None: + data["pkts_to_send"] = pkts_to_send + if rcvbuf_size is not None: + data["rcvbuf_size"] = rcvbuf_size + if sndbuf_size is not None: + data["sndbuf_size"] = sndbuf_size + if tcp_delack_segs is not None: + data["tcp_delack_segs"] = tcp_delack_segs + if tcp_max_delack is not None: + data["tcp_max_delack"] = tcp_max_delack + if tcp_min_delack is not None: + data["tcp_min_delack"] = tcp_min_delack + if tcp_mss is not None: + data["tcp_mss"] = tcp_mss + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_details", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_FILE> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_file + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_endp_file(self, + file=None, # The file name to read the playback packets from. + name=None, # The name of the endpoint we are configuring. + playback=None, # Should we playback the capture or not? ON or OFF. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_file(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if file is not None: + data["file"] = file + if name is not None: + data["name"] = name + if playback is not None: + data["playback"] = playback + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_file", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_FLAG> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_flag + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetEndpFlagFlag(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + AutoHelper = "AutoHelper" # Automatically run on helper process + ClearPortOnStart = "ClearPortOnStart" # clear stats on start + DoChecksum = "DoChecksum" # Enable checksumming + EnableConcurrentSrcIP = "EnableConcurrentSrcIP" # Concurrent source IPs? + EnableLinearSrcIP = "EnableLinearSrcIP" # linearized source IPs + EnableLinearSrcIPPort = "EnableLinearSrcIPPort" # linearized IP ports + EnableRndSrcIP = "EnableRndSrcIP" # randomize source IP + KernelMode = "KernelMode" # Enable kernel mode + QuiesceAfterDuration = "QuiesceAfterDuration" # quiesce after time period + QuiesceAfterRange = "QuiesceAfterRange" # quiesce after range of bytes + Unmanaged = "Unmanaged" # Set endpoint unmanaged + UseAutoNAT = "UseAutoNAT" # NAT friendly behavior + + def post_set_endp_flag(self, + flag=None, # The name of the flag. + name=None, # The name of the endpoint we are configuring. + val=None, # Either 1 (for on), or 0 (for off). + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_flag(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if flag is not None: + data["flag"] = flag + if name is not None: + data["name"] = name + if val is not None: + data["val"] = val + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_flag", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_PAYLOAD> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_payload + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetEndpPayloadPayloadType(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + PRBS_11_8_10 = "PRBS_11_8_10" # PRBS (see above) + PRBS_15_0_14 = "PRBS_15_0_14" # PRBS (see above) + PRBS_4_0_3 = "PRBS_4_0_3" # Use linear feedback shift register to generate pseudo random sequence. + PRBS_7_0_6 = "PRBS_7_0_6" # PRBS (see above) + custom = "custom" # Enter your own payload with the set_endp_payload + decreasing = "decreasing" # bytes start at FF and decrease, wrapping if needed. + increasing = "increasing" # bytes start at 00 and increase, wrapping if needed. + ones = "ones" # Payload is all ones (FF). + random = "random" # generate a new random payload each time sent. + random_fixed = "random_fixed" # means generate one random payload, and send it over and over again. + zeros = "zeros" # Payload is all zeros (00). + + def post_set_endp_payload(self, + name=None, # The name of the endpoint we are configuring. + payload=None, # For custom payloads, enter the payload in hex, up to 2048 + # bytes. <tt escapearg='false'>Unescaped Value</tt> + payload_type=None, # The payload type. See help for add_endp. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_payload(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if payload is not None: + data["payload"] = payload + if payload_type is not None: + data["payload_type"] = payload_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_payload", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_PLD_BOUNDS> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_pld_bounds + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_endp_pld_bounds(self, + is_random=None, # YES if random, anything else for NO. + max_pld_size=None, # The maximum payload size, in bytes. + min_pld_size=None, # The minimum payload size, in bytes. + name=None, # The name of the endpoint we are configuring. + use_checksum=None, # YES if use checksum on payload, anything else for NO. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_pld_bounds(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if is_random is not None: + data["is_random"] = is_random + if max_pld_size is not None: + data["max_pld_size"] = max_pld_size + if min_pld_size is not None: + data["min_pld_size"] = min_pld_size + if name is not None: + data["name"] = name + if use_checksum is not None: + data["use_checksum"] = use_checksum + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_pld_bounds", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_PROXY> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_proxy + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_endp_proxy(self, + enabled=None, # YES or NO to enable or disable proxying. + endp_name=None, # Name of endpoint. + proxy_ip=None, # Proxy IP Address. + proxy_ip_port=None, # Proxy IP Port. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_proxy(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if enabled is not None: + data["enabled"] = enabled + if endp_name is not None: + data["endp_name"] = endp_name + if proxy_ip is not None: + data["proxy_ip"] = proxy_ip + if proxy_ip_port is not None: + data["proxy_ip_port"] = proxy_ip_port + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_proxy", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_QUIESCE> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_quiesce + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_endp_quiesce(self, + name=None, # The name of the endpoint we are configuring. + quiesce=None, # The number of seconds to quiesce this endpoint when told to + # quiesce. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_quiesce(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if quiesce is not None: + data["quiesce"] = quiesce + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_quiesce", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_REPORT_TIMER> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_report_timer + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_endp_report_timer(self, + endp_name=None, # Name of endpoint. + milliseconds=None, # Report timer length in milliseconds. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_report_timer(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if milliseconds is not None: + data["milliseconds"] = milliseconds + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_report_timer", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_TOS> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_tos + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetEndpTosTos(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + LOWCOST = "LOWCOST" # + LOWDELAY = "LOWDELAY" # + RELIABILITY = "RELIABILITY" # + THROUGHPUT = "THROUGHPUT" # + + def post_set_endp_tos(self, + name=None, # The name of the endpoint we are configuring. + priority=None, # The socket priority, can be any positive number. + tos=None, # The Type of Service, can be HEX, see above. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_tos(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if priority is not None: + data["priority"] = priority + if tos is not None: + data["tos"] = tos + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_tos", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_ENDP_TX_BOUNDS> type requests + + https://www.candelatech.com/lfcli_ug.php#set_endp_tx_bounds + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_endp_tx_bounds(self, + is_bursty=None, # YES if bursty, anything else for NO. + max_tx_rate=None, # The maximum transmit rate, in bits per second (bps). + min_tx_rate=None, # The minimum transmit rate, in bits per second (bps). + name=None, # The name of the endpoint we are configuring. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_endp_tx_bounds(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if is_bursty is not None: + data["is_bursty"] = is_bursty + if max_tx_rate is not None: + data["max_tx_rate"] = max_tx_rate + if min_tx_rate is not None: + data["min_tx_rate"] = min_tx_rate + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_endp_tx_bounds", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_EVENT_INTEREST> type requests + + https://www.candelatech.com/lfcli_ug.php#set_event_interest + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetEventInterestEiFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetEventInterestEiFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + CLEAR = 0x0 # will clear interest + SET = 0x1 # set interest flag + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetEventInterestEiFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class SetEventInterestEvents1(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetEventInterestEvents10, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + BAD_TOS = 0x400000 # Endpoint has bad ToS values configured. + Bad_MAC = 0x100000 # Invalid MAC address configured. + Cleared = 0x2000 # Counters were cleared for some entity. + Connect = 0x100 # WiFi interface connected to AP. + Custom = 0x4 # Custom event (generated by USER in GUI or CLI). + DHCP_Fail = 0x8000 # DHCP Failed, maybe out of leases? + DHCP_Timeout = 0x10000 # Timed out talking to DHCP server. + DHCP4_Error = 0x20000 # DHCP gave out duplicated IP address. + DHCP6_Error = 0x40000 # DHCPv6 gave out duplicated IPv6 address. + Disconnect = 0x80 # WiFi interface disconnected from AP. + Endp_Started = 0x40 # Endpoint was started. + Endp_Stopped = 0x20 # Endpoint stopped for some reason. + Link_Down = 0x1 # Notify when Interface Link goes DOWN. + Link_Errors = 0x4000 # Port shows low-level link errors. + Link_Up = 0x2 # Notify when Interface Link goes UP. + Login = 0x400 # CLI/GUI user connected to LANforge. + Logout = 0x200 # CLI/GUI user disconnected from LANforge. + Migrated = 0x200000 # Port (station network interface) migrated. + NO_RX_SINCE = 0x800000 # Endpoint threshold alert. + NO_RX_SINCE_CLEARED = 0x1000000 # Endpoint threshold alert cleared. + RX_BPS_OOR_1M = 0x20000000 # Endpoint threshold alert. + RX_BPS_OOR_1M_CLEARED = 0x40000000 # Endpoint threshold alert cleared. + RX_BPS_OOR_30S = 0x8000000 # Endpoint threshold alert. + RX_BPS_OOR_30S_CLEARED = 0x10000000 # Endpoint threshold alert cleared. + RX_BPS_OOR_3S = 0x2000000 # Endpoint threshold alert. + RX_BPS_OOR_3S_CLEARED = 0x4000000 # Endpoint threshold alert cleared. + Resource_Down = 0x8 # Resource has crashed, rebooted, etc. + Resource_Up = 0x10 # Resource has connected to manager. + Start_Reports = 0x1000 # Start saving report data files (CSV). + Stop_Reports = 0x800 # Stop saving report data files (CSV). + TX_BPS_OOR_3S = 0x80000000 # Endpoint threshold alert. + WiFi_Config = 0x80000 # WiFi Configuration Error. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetEventInterestEvents1 has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class SetEventInterestEvents2(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetEventInterestEvents20, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + FW_CRASH = 0x800 # Firmware for entity has crashed. + FW_FAIL = 0x1000 # Firmware failed powerup, may require reboot. + IFDOWN_FAIL = 0x8000 # IFDOWN-PRE Script (ifup --logout) returned error code. + IFDOWN_OK = 0x10000 # IFDOWN-PRE Script (ifup --logout) completed successfully. + IFUP_FAIL = 0x2000 # IFUP-POST Script returned error code. + IFUP_OK = 0x4000 # IFUP-POST Script completed successfully. + RX_DROP_OOR_1M = 0x200 # Endpoint threshold alert. + RX_DROP_OOR_1M_CLEARED = 0x400 # Endpoint threshold alert cleared. + RX_DROP_OOR_3S = 0x80 # Endpoint threshold alert. + RX_DROP_OOR_3S_CLEARED = 0x100 # Endpoint threshold alert cleared. + RX_LAT_OOR = 0x20 # Endpoint threshold alert. + RX_LAT_OOR_CLEARED = 0x40 # Endpoint threshold alert cleared. + TX_BPS_OOR_1M = 0x8 # Endpoint threshold alert. + TX_BPS_OOR_1M_CLEARED = 0x10 # Endpoint threshold alert cleared. + TX_BPS_OOR_30S = 0x2 # Endpoint threshold alert. + TX_BPS_OOR_30S_CLEARED = 0x4 # Endpoint threshold alert cleared. + TX_BPS_OOR_3S_CLEARED = 0x1 # Endpoint threshold alert cleared. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetEventInterestEvents2 has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_set_event_interest(self, + ei_flags=None, # Event Interest flags, see above. + event_cnt=None, # Maximum number of events to store. + events1=None, # See description for possible values. + events2=None, # See description for possible values. + events3=None, # See description for possible values. + events4=None, # See description for possible values. + var1=None, # Currently un-used. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_event_interest(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if ei_flags is not None: + data["ei_flags"] = ei_flags + if event_cnt is not None: + data["event_cnt"] = event_cnt + if events1 is not None: + data["events1"] = events1 + if events2 is not None: + data["events2"] = events2 + if events3 is not None: + data["events3"] = events3 + if events4 is not None: + data["events4"] = events4 + if var1 is not None: + data["var1"] = var1 + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_event_interest", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_EVENT_PRIORITY> type requests + + https://www.candelatech.com/lfcli_ug.php#set_event_priority + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetEventPriorityEvent(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + Bad_MAC = 20 # Invalid MAC address configured. + Cleared = 13 # Counters were cleared for some entity. + Connect = 8 # WiFi interface connected to AP. + Custom = 2 # Custom event (generated by USER in GUI or CLI). + DHCP_Fail = 15 # DHCP Failed, maybe out of leases? + DHCP_Timeout = 16 # Timed out talking to DHCP server. + DHCP4_Error = 17 # DHCP gave out duplicated IP address. + DHCP6_Error = 18 # DHCPv6 gave out duplicated IPv6 address. + Disconnect = 7 # WiFi interface disconnected from AP. + Endp_Started = 6 # Endpoint was started. + Endp_Stopped = 5 # Endpoint stopped for some reason. + Link_Down = 0 # Notify when Interface Link goes UP. + Link_Errors = 14 # Port shows low-level link errors. + Link_Up = 1 # Notify when Interface Link goes DOWN. + Login = 10 # CLI/GUI user connected to LANforge. + Logout = 9 # CLI/GUI user disconnected from LANforge. + Migrated = 21 # Port (station network interface) migrated. + Resource_Down = 3 # Resource has crashed, rebooted, etc. + Resource_Up = 4 # Resource has connected to manager. + Start_Reports = 12 # Start saving report data files (CSV). + Stop_Reports = 11 # Stop saving report data files (CSV). + WiFi_Config = 19 # WiFi Configuration Error. + + class SetEventPriorityPriority(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + AUTO = "AUTO" # Let event creator decide the priority. + CRITICAL = "CRITICAL" # + DEBUG = "DEBUG" # + FATAL = "FATAL" # + INFO = "INFO" # + WARNING = "WARNING" # + + def post_set_event_priority(self, + event=None, # Number or name for the event, see above. + priority=None, # Number or name for the priority. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_event_priority(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if event is not None: + data["event"] = event + if priority is not None: + data["priority"] = priority + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_event_priority", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_FE_INFO> type requests + + https://www.candelatech.com/lfcli_ug.php#set_fe_info + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_fe_info(self, + directory=None, # The directory to read/write in. Absolute path suggested. + io_direction=None, # Should we be reading or writing: options: read, write + max_file_size=None, # The maximum file size, in bytes. + max_rw_sz=None, # Maximum read/write size, in bytes. + min_file_size=None, # The minimum file size, in bytes. + min_rw_sz=None, # Minimum read/write size, in bytes. + name=None, # The name of the file endpoint we are configuring. + num_files=None, # Number of files to create when writing. + prefix=None, # The prefix of the file(s) to read/write. + quiesce_after_files=None, # If non-zero, quiesce test after this many files have been + # read/written. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_fe_info(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if directory is not None: + data["directory"] = directory + if io_direction is not None: + data["io_direction"] = io_direction + if max_file_size is not None: + data["max_file_size"] = max_file_size + if max_rw_sz is not None: + data["max_rw_sz"] = max_rw_sz + if min_file_size is not None: + data["min_file_size"] = min_file_size + if min_rw_sz is not None: + data["min_rw_sz"] = min_rw_sz + if name is not None: + data["name"] = name + if num_files is not None: + data["num_files"] = num_files + if prefix is not None: + data["prefix"] = prefix + if quiesce_after_files is not None: + data["quiesce_after_files"] = quiesce_after_files + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_fe_info", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_FLAG> type requests + + https://www.candelatech.com/lfcli_ug.php#set_flag + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetFlagFlag(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + brief = "brief" # Request more abbreviated output to various commands. + prompt_newlines = "prompt_newlines" # Add a newline after every prompt. Can help with scripts + push_all_rpts = "push_all_rpts" # If enabled, server will send port, endpoint, and other + push_endp_rpts = "push_endp_rpts" # If enabled, server will send endpoint reports without + request_keyed_text = "request_keyed_text" # Normally most keyed-text events are only sent to the GUI + stream_events = "stream_events" # Normally the CLI will not show Events (as seen in the + # +Event + + def post_set_flag(self, + client=None, # Specify the user, if it is not the current user. Requires admin + # privileges. + flag=None, # The name of the flag. + val=None, # Either 1 (for on), or 0 (for off). + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_flag(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if client is not None: + data["client"] = client + if flag is not None: + data["flag"] = flag + if val is not None: + data["val"] = val + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_flag", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_GEN_CMD> type requests + + https://www.candelatech.com/lfcli_ug.php#set_gen_cmd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_gen_cmd(self, + command=None, # The rest of the command line arguments. <tt + # escapearg='false'>Unescaped Value</tt> + name=None, # The name of the file endpoint we are configuring. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_gen_cmd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if command is not None: + data["command"] = command + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_gen_cmd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_GPS_INFO> type requests + + https://www.candelatech.com/lfcli_ug.php#set_gps_info + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_gps_info(self, + altitude=None, # Altitude, assumes units are Meters. + ew=None, # East or west (Longitude). + lattitude=None, # The lattitude, as read from a GPS device. + longitude=None, # The longitude, as ready from a GPS device. + ns=None, # North or South (Latitude). + resource=None, # Resource number for the port to be modified. + shelf=None, # Shelf number for the port to be modified, or SELF. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_gps_info(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if altitude is not None: + data["altitude"] = altitude + if ew is not None: + data["ew"] = ew + if lattitude is not None: + data["lattitude"] = lattitude + if longitude is not None: + data["longitude"] = longitude + if ns is not None: + data["ns"] = ns + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_gps_info", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_IFUP_SCRIPT> type requests + + https://www.candelatech.com/lfcli_ug.php#set_ifup_script + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_ifup_script(self, + flags=None, # Currently un-defined, use NA + port=None, # WiFi interface name or number. + post_ifup_script=None, # Script name with optional args, will run after interface + # comes up and gets IP. <tt escapearg='false'>Unescaped + # Value</tt> + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_ifup_script(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if flags is not None: + data["flags"] = flags + if port is not None: + data["port"] = port + if post_ifup_script is not None: + data["post_ifup_script"] = post_ifup_script + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_ifup_script", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_LICENSE> type requests + + https://www.candelatech.com/lfcli_ug.php#set_license + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_license(self, + licenses=None, # License keys all appended into a single line. <tt + # escapearg='false'>Unescaped Value</tt> + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_license(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if licenses is not None: + data["licenses"] = licenses + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_license", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_MC_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#set_mc_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_mc_endp(self, + mcast_dest_port=None, # Multicast destination IP Port, for example: 55000 + mcast_group=None, # Multicast group IP, ie: 224.1.1.2 IPv6 supported as well. + name=None, # The name of the endpoint we are configuring. + rcv_mcast=None, # Should we attempt to receive? Values: Yes or No + ttl=None, # Time to live for the multicast packets generated. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_mc_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if mcast_dest_port is not None: + data["mcast_dest_port"] = mcast_dest_port + if mcast_group is not None: + data["mcast_group"] = mcast_group + if name is not None: + data["name"] = name + if rcv_mcast is not None: + data["rcv_mcast"] = rcv_mcast + if ttl is not None: + data["ttl"] = ttl + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_mc_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_PASSWORD> type requests + + https://www.candelatech.com/lfcli_ug.php#set_password + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_password(self, + client=None, # Specify the client. If left blank, will use current client. + new_password=None, # New password, or 'NA' for blank password. + old_password=None, # Old password, or 'NA' for blank password. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_password(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if client is not None: + data["client"] = client + if new_password is not None: + data["new_password"] = new_password + if old_password is not None: + data["old_password"] = old_password + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_password", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_POLL_MODE> type requests + + https://www.candelatech.com/lfcli_ug.php#set_poll_mode + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_poll_mode(self, + mode=None, # 'polling' or 'push'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_poll_mode(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if mode is not None: + data["mode"] = mode + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_poll_mode", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_PORT> type requests + + https://www.candelatech.com/lfcli_ug.php#set_port + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetPortCmdFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetPortCmdFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + abort_if_scripts = 0x400 # Forceably abort all ifup/down scripts on this Port. + force_MII_probe = 0x4 # Force MII probe + from_dhcp = 0x200 # Settings come from DHCP client. + from_user = 0x80 # from_user (Required to change Mgt Port config + new_gw_probe = 0x20 # Force new GW probe + new_gw_probe_dev = 0x40 # Force new GW probe for ONLY this interface + no_hw_probe = 0x8 # Don't probe hardware + probe_wifi = 0x10 # Probe WIFI + reset_transceiver = 0x1 # Reset transciever + restart_link_neg = 0x2 # Restart link negotiation + skip_port_bounce = 0x100 # skip-port-bounce (Don't ifdown/up + use_pre_ifdown = 0x800 # Call pre-ifdown script before bringing interface down. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetPortCmdFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class SetPortCurrentFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetPortCurrentFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + adv_100bt_fd = 0x800000 # advert-100bt-FD + adv_100bt_hd = 0x400000 # advert-100bt-HD + adv_10bt_fd = 0x200000 # advert-10bt-FD + adv_10bt_hd = 0x100000 # advert-10bt-HD + adv_10g_fd = 0x800000000 # advert-10G-FD + adv_2_5g_fd = 0x400000000 # advert-2.5G-FD + adv_5g_fd = 0x400000000000000 # Advertise 5Gbps link speed. + adv_flow_ctl = 0x8000000 # advert-flow-control + auto_neg = 0x100 # auto-negotiate + aux_mgt = 0x800000000000 # Enable Auxillary-Management flag for this port. + fixed_100bt_fd = 0x10 # Fixed-100bt-FD + fixed_100bt_hd = 0x8 # Fixed-100bt-HD + fixed_10bt_fd = 0x4 # Fixed-10bt-FD + fixed_10bt_hd = 0x2 # Fixed-10bt-HD (half duplex) + ftp_enabled = 0x400000000000 # Enable FTP (vsftpd) service for this port. + gro_enabled = 0x4000000000 # GRO-Enabled + gso_enabled = 0x10000000000 # GSO-Enabled + http_enabled = 0x200000000000 # Enable HTTP (nginx) service for this port. + if_down = 0x1 # Interface Down + ignore_dhcp = 0x2000000000000 # Don't set DHCP acquired IP on interface, + ipsec_client = 0x40000000000000 # Enable client IPSEC xfrm on this port. + ipsec_concentrator = 0x80000000000000 # Enable concentrator (upstream) IPSEC xfrm on this port. + lro_enabled = 0x2000000000 # LRO-Enabled + no_dhcp_rel = 0x80000000000 # No-DHCP-Release + no_dhcp_restart = 0x1000000000000 # Disable restart of DHCP on link connect (ie, wifi). + no_ifup_post = 0x4000000000000 # Skip ifup-post script if we can detect that we + promisc = 0x10000000 # PROMISC + radius_enabled = 0x20000000000000 # Enable RADIUS service (using hostapd as radius server) + rxfcs = 0x40000000000 # RXFCS + service_dns = 0x100000000000000 # Enable DNS (dnsmasq) service on this port. + staged_ifup = 0x100000000000 # Staged-IFUP + tso_enabled = 0x1000000000 # TSO-Enabled + ufo_enabled = 0x8000000000 # UFO-Enabled + use_dhcp = 0x80000000 # USE-DHCP + use_dhcpv6 = 0x20000000000 # USE-DHCPv6 + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetPortCurrentFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class SetPortDhcpClientId(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + NA = "NA" # Do not change from current value. + NONE = "NONE" # Do not use dhcp client ID. + p_string_ = "[string]" # Use the string for the client ID. + p__DEVNAME = "__DEVNAME" # Use the interface's name as the client ID. + p__MAC = "__MAC" # Use interface's MAC address for the client ID. + + class SetPortDhcpHostname(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + NA = "NA" # Do not change from current value. + NONE = "NONE" # Do not use dhcp Hostname + p_string_ = "[string]" # Use the string for the Hostname. + p__ALIAS__ = "__ALIAS__" # Use alias if set, or EID behaviour if alias is not set.. + p__EID__ = "__EID__" # Use hostname 'CT-[resource-id].[port-name]' + + class SetPortDhcpVendorId(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + NA = "NA" # Do not change from current value. + NONE = "NONE" # Do not use dhcp vendor ID + p_string_ = "[string]" # Use the string for the vendor ID. + + class SetPortFlags2(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetPortFlags20, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + bypass_disconnect = 0x200 # Logically disconnect the cable (link-down) + bypass_enabled = 0x10 # Enable Bypass Device + bypass_power_down = 0x80 # Should bypass be on when we shutdown or loose power? + bypass_power_on = 0x100 # Should bypass be on when we first power up? + supports_bypass = 0x2 # Support Bypass Devices + use_stp = 0x1 # Use Spanning Tree Protocol + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetPortFlags2 has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class SetPortInterest(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetPortInterest0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + alias = 0x1000 # Port alias + aux_mgt = 0x20000000 # Enable/disable Auxillary-Management for a port + bridge = 0x10000 # BRIDGE + bypass = 0x40000 # Bypass + command_flags = 0x1 # apply command flags + cpu_mask = 0x100000 # CPU Mask, useful for pinning process to CPU core + current_flags = 0x2 # apply current flags + dhcp = 0x4000 # including client-id. + dhcp_rls = 0x4000000 # DHCP release + dhcpv6 = 0x1000000 # Use DHCPv6 + gen_offload = 0x80000 # Generic offload flags, everything but LRO + ifdown = 0x800000 # Down interface + interal_use_1 = 0x800 # (INTERNAL USE) + ip_Mask = 0x8 # IP mask + ip_address = 0x4 # IP address + ip_gateway = 0x10 # IP gateway + ipv6_addrs = 0x20000 # IPv6 Address + link_speed = 0x80 # Link speed + lro_offload = 0x200000 # LRO (Must be disabled when used in Wanlink, + mac_address = 0x20 # MAC address + mtu = 0x100 # MTU + no_apply_dhcp = 0x80000000 # Enable/disable NO-APPLY-DHCP flag for a port + no_dhcp_conn = 0x40000000 # Enable/disable NO-DHCP-ON-CONNECT flag for a port + promisc_mode = 0x400 # PROMISC mode + rpt_timer = 0x8000 # Report Timer + rx_all = 0x2000 # Rx-ALL + rxfcs = 0x2000000 # RXFCS + skip_ifup_roam = 0x100000000 # Enable/disable SKIP-IFUP-ON-ROAM flag for a port + sta_br_id = 0x400000 # WiFi Bridge identifier. 0 means no bridging. + supported_flags = 0x40 # apply supported flags + svc_ftpd = 0x10000000 # Enable/disable FTP Service for a port + svc_httpd = 0x8000000 # Enable/disable HTTP Service for a port + tx_queue_length = 0x200 # TX Queue Length + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetPortInterest has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_set_port(self, + alias=None, # A user-defined name for this interface. Can be BLANK or NA. + br_aging_time=None, # MAC aging time, in seconds, 32-bit number (or peer IP for GRE). + br_forwarding_delay=None, # How long to wait until the bridge will start forwarding packets. + br_hello_time=None, # How often does the bridge send out STP hello packets. + br_max_age=None, # How long until STP considers a non-responsive bridge dead. + br_port_cost=None, # STP Port cost for a port (this applies only to NON-BRIDGE + # interfaces). + br_port_priority=None, # STP Port priority for a port (this applies only to NON-BRIDGE + # interfaces). + br_priority=None, # Bridge priority, 16-bit number. + bypass_wdt=None, # Watch Dog Timer (in seconds) for this port. Zero (0) to disable. + cmd_flags=None, # Command Flags: See above, or NA. + cpu_mask=None, # CPU Mask for CPUs that should service this interface. Zero is + # don't set (let OS make the decision). This value will be applied + # to the proper /proc/irq/[irq-num]/smp_affinity file by the + # pin_irq.pl script. + current_flags=None, # See above, or NA. + current_flags_msk=None, # This sets 'interest' for flags 'Enable RADIUS service' and higher. + # See above, or NA. + dhcp_client_id=None, # Optional string of up to 63 bytes in length to be passed to the + # dhclient process. See above. + dhcp_hostname=None, # Optional string of up to 63 bytes in length to be passed to the + # dhclient process. Option 12, see above. + dhcp_vendor_id=None, # Optional string of up to 63 bytes in length to be passed to the + # dhclient process. See above. + dns_servers=None, # DNS servers for use by traffic on this port, comma-separated list, + # BLANK means zero-length string. + flags2=None, # Bridge & other flags, see above. + gateway=None, # IP address of the gateway device - used for IP routing, or NA. + interest=None, # Which things are we really interested in setting. Can over-ride + # defaults based on the other arguments. + ip_addr=None, # IP address for the port, or NA. + ipsec_concentrator=None, # IP Address of IPSec concentrator. + ipsec_local_id=None, # Local Identifier for this IPSec tunnel. + ipsec_passwd=None, # Password for IPSec, for pubkey, use: pubkey:[pem-file-name], for + # instance: pubkey:station.pem + ipsec_remote_id=None, # Remote Identifier for this IPSec tunnel. + ipv6_addr_global=None, # Global scoped IPv6 address. + ipv6_addr_link=None, # Link scoped IPv6 address. + ipv6_dflt_gw=None, # IPv6 default gateway. + mac=None, # MAC address to set this port to, or leave blank to not set it, or + # NA. + mtu=None, # Maximum Transmit Unit (MTU) for this interface. Can be blank or + # NA. + netmask=None, # Netmask which this port should use, or NA. + port=None, # Port number for the port to be modified. + report_timer=None, # How often, in milliseconds, should we poll stats on this + # interface? + resource=None, # Resource number for the port to be modified. + shelf=None, # Shelf number for the port to be modified. + sta_br_id=None, # WiFi STAtion bridge ID. Zero means none. + tx_queue_len=None, # Transmit Queue Length for this interface. Can be blank or NA. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_port(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if br_aging_time is not None: + data["br_aging_time"] = br_aging_time + if br_forwarding_delay is not None: + data["br_forwarding_delay"] = br_forwarding_delay + if br_hello_time is not None: + data["br_hello_time"] = br_hello_time + if br_max_age is not None: + data["br_max_age"] = br_max_age + if br_port_cost is not None: + data["br_port_cost"] = br_port_cost + if br_port_priority is not None: + data["br_port_priority"] = br_port_priority + if br_priority is not None: + data["br_priority"] = br_priority + if bypass_wdt is not None: + data["bypass_wdt"] = bypass_wdt + if cmd_flags is not None: + data["cmd_flags"] = cmd_flags + if cpu_mask is not None: + data["cpu_mask"] = cpu_mask + if current_flags is not None: + data["current_flags"] = current_flags + if current_flags_msk is not None: + data["current_flags_msk"] = current_flags_msk + if dhcp_client_id is not None: + data["dhcp_client_id"] = dhcp_client_id + if dhcp_hostname is not None: + data["dhcp_hostname"] = dhcp_hostname + if dhcp_vendor_id is not None: + data["dhcp_vendor_id"] = dhcp_vendor_id + if dns_servers is not None: + data["dns_servers"] = dns_servers + if flags2 is not None: + data["flags2"] = flags2 + if gateway is not None: + data["gateway"] = gateway + if interest is not None: + data["interest"] = interest + if ip_addr is not None: + data["ip_addr"] = ip_addr + if ipsec_concentrator is not None: + data["ipsec_concentrator"] = ipsec_concentrator + if ipsec_local_id is not None: + data["ipsec_local_id"] = ipsec_local_id + if ipsec_passwd is not None: + data["ipsec_passwd"] = ipsec_passwd + if ipsec_remote_id is not None: + data["ipsec_remote_id"] = ipsec_remote_id + if ipv6_addr_global is not None: + data["ipv6_addr_global"] = ipv6_addr_global + if ipv6_addr_link is not None: + data["ipv6_addr_link"] = ipv6_addr_link + if ipv6_dflt_gw is not None: + data["ipv6_dflt_gw"] = ipv6_dflt_gw + if mac is not None: + data["mac"] = mac + if mtu is not None: + data["mtu"] = mtu + if netmask is not None: + data["netmask"] = netmask + if port is not None: + data["port"] = port + if report_timer is not None: + data["report_timer"] = report_timer + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if sta_br_id is not None: + data["sta_br_id"] = sta_br_id + if tx_queue_len is not None: + data["tx_queue_len"] = tx_queue_len + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_port", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_PORT_ALIAS> type requests + + https://www.candelatech.com/lfcli_ug.php#set_port_alias + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_port_alias(self, + alias=None, # New alias to assign to this virtual interface. + port=None, # Physical Port identifier that owns the virtual interface. + resource=None, # Resource number for the port to be modified. + shelf=None, # Shelf number for the port to be modified. + vport=None, # Virtual port identifier. MAC for MAC-VLANs, VLAN-ID for 802.1Q + # vlans. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_port_alias(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if alias is not None: + data["alias"] = alias + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if vport is not None: + data["vport"] = vport + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_port_alias", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_PPP_LINK_STATE> type requests + + https://www.candelatech.com/lfcli_ug.php#set_ppp_link_state + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_ppp_link_state(self, + link=None, # Unit Number of the PPP Link, or 'all'. + ppp_state=None, # One of: RUNNING, STOPPED, or DELETED. + resource=None, # Number of the Resource, or 'all'. + shelf=None, # Name of the Shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_ppp_link_state(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if link is not None: + data["link"] = link + if ppp_state is not None: + data["ppp_state"] = ppp_state + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_ppp_link_state", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_RESOURCE> type requests + + https://www.candelatech.com/lfcli_ug.php#set_resource + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetResourceResourceFlags(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + skip_load_db_on_start = 1 # Should we skip loading the DB on start? + + def post_set_resource(self, + device_profiles=None, # List of profiles, see above + max_helper_count=None, # Maximum number of helper traffic generation processes. 0 means + # CPU-core-count (AUTO). + max_staged_bringup=None, # Maximum amount of interfaces attempting to come up at once. + # Default is 50 + max_station_bringup=None, # Maximum amount of stations to bring up per radio per tick. + # Default is 12. + max_trying_ifup=None, # Maximum amount of interfaces running the network config 'ifup' + # logic. Default is 15 + resource=None, # Number of the Resource, or <tt>all</tt>. + resource_flags=None, # System wide flags, often requires a reboot for changes to take + # effect. + resource_flags_mask=None, # What flags to change. If unset, default is all. + shelf=None, # Name of the Shelf, or <tt>all</tt>. + top_left_x=None, # X Location for Chamber View. + top_left_y=None, # X Location for Chamber View. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_resource(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if device_profiles is not None: + data["device_profiles"] = device_profiles + if max_helper_count is not None: + data["max_helper_count"] = max_helper_count + if max_staged_bringup is not None: + data["max_staged_bringup"] = max_staged_bringup + if max_station_bringup is not None: + data["max_station_bringup"] = max_station_bringup + if max_trying_ifup is not None: + data["max_trying_ifup"] = max_trying_ifup + if resource is not None: + data["resource"] = resource + if resource_flags is not None: + data["resource_flags"] = resource_flags + if resource_flags_mask is not None: + data["resource_flags_mask"] = resource_flags_mask + if shelf is not None: + data["shelf"] = shelf + if top_left_x is not None: + data["top_left_x"] = top_left_x + if top_left_y is not None: + data["top_left_y"] = top_left_y + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_resource", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_RFGEN> type requests + + https://www.candelatech.com/lfcli_ug.php#set_rfgen + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetRfgenRfgenFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetRfgenRfgenFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + one_burst = 0x8 # Run for about 1 second and stop. Uses 5-sec sweep time for single pulse train. + running = 0x2 # Should we start the RF Generator or not? + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetRfgenRfgenFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_set_rfgen(self, + bb_gain=None, # RX Gain, 0 - 62 in 2dB steps + freq_khz=None, # Center frequency in Khz + gain=None, # Main TX/RX Amp, 0 or 14 (dB), default is 14 + p_id=None, # RF Generator ID, not used at this time, enter 'NA' or 0. + if_gain=None, # Fine-tune TX/RX Gain, 0 - 40 dB + pulse_count=None, # Number of pulses (0-255) + pulse_interval_us=None, # Time between pulses, in micro-seconds. + pulse_width_us=None, # Requested pulse width, units are in micro-seconds. + resource=None, # Resource number. + rfgen_flags=None, # RF Generator flags, see above. + rfgen_flags_mask=None, # Mask of what flags to set, see above. + shelf=None, # Shelf number, usually 1. + sweep_time_ms=None, # Time interval between pulse groups in miliseconds + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_rfgen(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if bb_gain is not None: + data["bb_gain"] = bb_gain + if freq_khz is not None: + data["freq_khz"] = freq_khz + if gain is not None: + data["gain"] = gain + if p_id is not None: + data["id"] = p_id + if if_gain is not None: + data["if_gain"] = if_gain + if pulse_count is not None: + data["pulse_count"] = pulse_count + if pulse_interval_us is not None: + data["pulse_interval_us"] = pulse_interval_us + if pulse_width_us is not None: + data["pulse_width_us"] = pulse_width_us + if resource is not None: + data["resource"] = resource + if rfgen_flags is not None: + data["rfgen_flags"] = rfgen_flags + if rfgen_flags_mask is not None: + data["rfgen_flags_mask"] = rfgen_flags_mask + if shelf is not None: + data["shelf"] = shelf + if sweep_time_ms is not None: + data["sweep_time_ms"] = sweep_time_ms + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_rfgen", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_SCRIPT> type requests + + https://www.candelatech.com/lfcli_ug.php#set_script + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetScriptFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetScriptFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + SCR_COMPLETED = 0x80 # Set automatically by LANforge. + SCR_HIDE_CONSTRAINTS = 0x2000 # Hide constraints messages. + SCR_HIDE_CSV = 0x20 # Don't print the CSV data in the report. + SCR_HIDE_HUNT = 0x800 # Hide the individual hunt steps..just show results. + SCR_HIDE_ITER_DETAILS = 0x8 # Hide iteration detail reports. + SCR_HIDE_LAT = 0x1000 # Hide latency distribution reports. + SCR_HIDE_LEGEND = 0x10 # Don't print the legend in the report. + SCR_LOOP = 0x100 # Loop script until manually stopped. + SCR_NO_KEYED_RPT = 0x2 # Script should NOT send reports to the CLI/GUI. + SCR_RUN_ON_MGR = 0x40 # Set automatically by LANforge. + SCR_SHOW_ATTENUATION = 0x4000 # Show attenuation packet stats. + SCR_SHOW_DUPS = 0x200 # Report duplicate packets. + SCR_SHOW_GOLDEN_3P = 0x20000 # Add 'golden' third-party AP graph for comparison (where available). + SCR_SHOW_GOLDEN_LF = 0x10000 # Add 'golden' LANforge graph for comparison (where available). + SCR_SHOW_OOO = 0x400 # Report out-of-order packets. + SCR_STOPPED = 0x1 # Script should NOT have any affect on the endpoint. + SCR_SYMMETRIC = 0x4 # This script should apply settings to the peer endpoing as well. + SCR_USE_MSS = 0x8000 # When setting packet size, set TCP MSS instead if endpoint supports + # +that. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetScriptFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class SetScriptType(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + NONE = "NONE" # Delete any existing script. + Script2544 = "Script2544" # For RFC 2544 type testing. + ScriptAtten = "ScriptAtten" # For Attenuators only. + ScriptHunt = "ScriptHunt" # Hunt for maximum speed with constraints. + ScriptWL = "ScriptWL" # For iterating through WanLink settings + + def post_set_script(self, + endp=None, # Endpoint, Test Group or Attenuator name or ID. + flags=None, # See above for description of the defined flags. + group_action=None, # How to handle group script operations: ALL, Sequential + loop_count=None, # How many times to loop before stopping (0 is infinite). + name=None, # Script name. + private=None, # Private encoding for the particular script. + p_type=None, # One of: NONE, Script2544, ScriptHunt, ScriptWL, ScriptAtten + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_script(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp is not None: + data["endp"] = endp + if flags is not None: + data["flags"] = flags + if group_action is not None: + data["group_action"] = group_action + if loop_count is not None: + data["loop_count"] = loop_count + if name is not None: + data["name"] = name + if private is not None: + data["private"] = private + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_script", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_SEC_IP> type requests + + https://www.candelatech.com/lfcli_ug.php#set_sec_ip + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_sec_ip(self, + ip_list=None, # IP1/prefix,IP2/prefix,...IPZ/prefix. + port=None, # Name of network device (Port) to which these IPs will be added. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_sec_ip(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if ip_list is not None: + data["ip_list"] = ip_list + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_sec_ip", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_VOIP_INFO> type requests + + https://www.candelatech.com/lfcli_ug.php#set_voip_info + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_voip_info(self, + codec=None, # Codec to use for the voice stream, supported values: G711U, + # G711A, SPEEX, g726-16, g726-24, g726-32, g726-40, g729a. + first_call_delay=None, # How long to wait before making first call, in seconds. + jitter_buffer_sz=None, # The size of the jitter buffer in packets. Default value is 8. + local_sip_port=None, # Local SIP UDP port. Default is min-rtp-port + 2. + loop_call_count=None, # How many calls to make, zero means infinite. + loop_wavefile_count=None, # How many times to play the wave file, zero means infinite. + max_call_duration=None, # How long should the call be, in seconds. + max_inter_call_gap=None, # Maximum time to wait between calls, in seconds. + messaging_protocol=None, # Messaging protocol, supported values: SIP. + min_call_duration=None, # How long should the call be, in seconds. + min_inter_call_gap=None, # Minimum time to wait between calls, in seconds. + name=None, # The name of the endpoint we are configuring. + pesq_server_ip=None, # LANforge PESQ server IP address. + pesq_server_passwd=None, # LANforge PESQ server password. Default is to use no + # authentication (blank entry). + pesq_server_port=None, # LANforge PESQ server port, default is 3998. + reg_expire_timer=None, # SIP Registration expire timer, in seconds. + ringing_timer=None, # How long (milliseconds) to wait in the ringing state before + # flagging call as no-answer. + sound_dev=None, # Which sound device should we play sound to. (see + # set_endp_flags). + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_voip_info(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if codec is not None: + data["codec"] = codec + if first_call_delay is not None: + data["first_call_delay"] = first_call_delay + if jitter_buffer_sz is not None: + data["jitter_buffer_sz"] = jitter_buffer_sz + if local_sip_port is not None: + data["local_sip_port"] = local_sip_port + if loop_call_count is not None: + data["loop_call_count"] = loop_call_count + if loop_wavefile_count is not None: + data["loop_wavefile_count"] = loop_wavefile_count + if max_call_duration is not None: + data["max_call_duration"] = max_call_duration + if max_inter_call_gap is not None: + data["max_inter_call_gap"] = max_inter_call_gap + if messaging_protocol is not None: + data["messaging_protocol"] = messaging_protocol + if min_call_duration is not None: + data["min_call_duration"] = min_call_duration + if min_inter_call_gap is not None: + data["min_inter_call_gap"] = min_inter_call_gap + if name is not None: + data["name"] = name + if pesq_server_ip is not None: + data["pesq_server_ip"] = pesq_server_ip + if pesq_server_passwd is not None: + data["pesq_server_passwd"] = pesq_server_passwd + if pesq_server_port is not None: + data["pesq_server_port"] = pesq_server_port + if reg_expire_timer is not None: + data["reg_expire_timer"] = reg_expire_timer + if ringing_timer is not None: + data["ringing_timer"] = ringing_timer + if sound_dev is not None: + data["sound_dev"] = sound_dev + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_voip_info", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_VRCX_COST> type requests + + https://www.candelatech.com/lfcli_ug.php#set_vrcx_cost + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_vrcx_cost(self, + interface_cost=None, # If using OSPF, this sets the cost for this link (1-65535). + local_dev=None, # Name of port A for the local redirect device pair. + local_dev_b=None, # Name of port B for the local redirect device pair. + remote_dev=None, # Name of port B for the remote redirect device pair. + remote_dev_b=None, # Name of port B for the remote redirect device pair. + resource=None, # Resource number. + shelf=None, # Shelf name/id. + vr_name=None, # Virtual Router this endpoint belongs to. Use 'FREE_LIST' to add + # a stand-alone endpoint. + wanlink=None, # The name of the WanLink that connects the two B ports. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_vrcx_cost(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if interface_cost is not None: + data["interface_cost"] = interface_cost + if local_dev is not None: + data["local_dev"] = local_dev + if local_dev_b is not None: + data["local_dev_b"] = local_dev_b + if remote_dev is not None: + data["remote_dev"] = remote_dev + if remote_dev_b is not None: + data["remote_dev_b"] = remote_dev_b + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if vr_name is not None: + data["vr_name"] = vr_name + if wanlink is not None: + data["wanlink"] = wanlink + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_vrcx_cost", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WANLINK_INFO> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wanlink_info + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_wanlink_info(self, + drop_freq=None, # How often, out of 1,000,000 packets, should we + # purposefully drop a packet. + dup_freq=None, # How often, out of 1,000,000 packets, should we + # purposefully duplicate a packet. + extra_buffer=None, # The extra amount of bytes to buffer before dropping pkts, + # in units of 1024. Use -1 for AUTO. + jitter_freq=None, # How often, out of 1,000,000 packets, should we apply + # jitter. + latency=None, # The base latency added to all packets, in milliseconds + # (or add 'us' suffix for microseconds + max_drop_amt=None, # Maximum amount of packets to drop in a row. Default is 1. + max_jitter=None, # The maximum jitter, in milliseconds (or ad 'us' suffix + # for microseconds) + max_lateness=None, # Maximum amount of un-intentional delay before pkt is + # dropped. Default is AUTO + max_reorder_amt=None, # Maximum amount of packets by which to reorder, Default is + # 10. + min_drop_amt=None, # Minimum amount of packets to drop in a row. Default is 1. + min_reorder_amt=None, # Minimum amount of packets by which to reorder, Default is + # 1. + name=None, # The name of the endpoint we are configuring. + playback_capture_file=None, # Name of the WAN capture file to play back. + reorder_freq=None, # How often, out of 1,000,000 packets, should we make a + # packet out of order. + speed=None, # The maximum speed of traffic this endpoint will accept + # (bps). + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wanlink_info(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if drop_freq is not None: + data["drop_freq"] = drop_freq + if dup_freq is not None: + data["dup_freq"] = dup_freq + if extra_buffer is not None: + data["extra_buffer"] = extra_buffer + if jitter_freq is not None: + data["jitter_freq"] = jitter_freq + if latency is not None: + data["latency"] = latency + if max_drop_amt is not None: + data["max_drop_amt"] = max_drop_amt + if max_jitter is not None: + data["max_jitter"] = max_jitter + if max_lateness is not None: + data["max_lateness"] = max_lateness + if max_reorder_amt is not None: + data["max_reorder_amt"] = max_reorder_amt + if min_drop_amt is not None: + data["min_drop_amt"] = min_drop_amt + if min_reorder_amt is not None: + data["min_reorder_amt"] = min_reorder_amt + if name is not None: + data["name"] = name + if playback_capture_file is not None: + data["playback_capture_file"] = playback_capture_file + if reorder_freq is not None: + data["reorder_freq"] = reorder_freq + if speed is not None: + data["speed"] = speed + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wanlink_info", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WANLINK_PCAP> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wanlink_pcap + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_wanlink_pcap(self, + capture=None, # Should we capture or not? ON or OFF. + directory=None, # The directory name in which packet capture files will be + # written. + name=None, # The name of the endpoint we are configuring. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wanlink_pcap(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if capture is not None: + data["capture"] = capture + if directory is not None: + data["directory"] = directory + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wanlink_pcap", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WANPATH_CORRUPTION> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wanpath_corruption + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetWanpathCorruptionFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetWanpathCorruptionFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + BIT_FLIP = 0x4 # Flip a random bit in a byte. + BIT_TRANSPOSE = 0x8 # Transpose two side-by-side bits in a byte. + DO_CHAIN_ON_HIT = 0x10 # Do next corruption if this corruption is applied. + OVERWRITE_FIXED = 0x2 # Write a fixed value to a byte. + OVERWRITE_RANDOM = 0x1 # Write a random value to a byte. + RECALC_CSUMS = 0x20 # Attempt to re-calculate UDP and TCP checksums. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetWanpathCorruptionFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_set_wanpath_corruption(self, + byte=None, # The byte to use for OVERWRITE_FIXED (or NA). + flags=None, # The flags for this corruption. + index=None, # The corruption to modify (0-5). + max_offset=None, # The maximum offset from start of Ethernet packet for the + # byte to be modified. + min_offset=None, # The minimum offset from start of Ethernet packet for the + # byte to be modified. + name=None, # WanLink name + path=None, # WanPath name + rate=None, # Specifies how often, per million, this corruption should + # be applied. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wanpath_corruption(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if byte is not None: + data["byte"] = byte + if flags is not None: + data["flags"] = flags + if index is not None: + data["index"] = index + if max_offset is not None: + data["max_offset"] = max_offset + if min_offset is not None: + data["min_offset"] = min_offset + if name is not None: + data["name"] = name + if path is not None: + data["path"] = path + if rate is not None: + data["rate"] = rate + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wanpath_corruption", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WANPATH_FILTER> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wanpath_filter + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_wanpath_filter(self, + defer_flush=None, # Enter 'YES' if you do NOT want this flushed to the remote. + dst_filter=None, # The destination MAC or IP/Mask, 'NA' for PCAP. + filter_type=None, # The filter type, one of: MAC, IP, PCAP. + passive=None, # Enter 'YES' if you do NOT want to use this filter currently. + reverse=None, # If you want the logic reversed, use 'ON', otherwise set to + # 'OFF' + src_filter=None, # The source MAC or IP/Mask. For PCAP, this is the only + # filter. + wl_name=None, # The name of the WanLink endpoint we are configuring. + wp_name=None, # The name of the WanPath we are configuring. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wanpath_filter(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if defer_flush is not None: + data["defer_flush"] = defer_flush + if dst_filter is not None: + data["dst_filter"] = dst_filter + if filter_type is not None: + data["filter_type"] = filter_type + if passive is not None: + data["passive"] = passive + if reverse is not None: + data["reverse"] = reverse + if src_filter is not None: + data["src_filter"] = src_filter + if wl_name is not None: + data["wl_name"] = wl_name + if wp_name is not None: + data["wp_name"] = wp_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wanpath_filter", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WANPATH_RUNNING> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wanpath_running + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetWanpathRunningRunning(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + AS_PARENT = "AS_PARENT" # then it will be started and stopped as the parent WanLink is. + RUNNING = "RUNNING" # then it will be running at all times + STOPPED = "STOPPED" # then it will not be running at any time. + + def post_set_wanpath_running(self, + running=None, # The state, one of: AS_PARENT, RUNNING, STOPPED. + wl_name=None, # The name of the WanLink endpoint we are configuring. + wp_name=None, # The name of the WanPath we are configuring. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wanpath_running(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if running is not None: + data["running"] = running + if wl_name is not None: + data["wl_name"] = wl_name + if wp_name is not None: + data["wp_name"] = wp_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wanpath_running", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WIFI_CORRUPTIONS> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wifi_corruptions + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetWifiCorruptionsCorruptFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetWifiCorruptionsCorruptFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + MSG_TYPE_DEAUTH = 0x2 # de-authentication message + MSG_TYPE_EAPOL = 0x1 # Any EAPOL message + MSG_TYPE_EAPOL_1_OF_2 = 0x40 # EAPOL message 1/2 + MSG_TYPE_EAPOL_1_OF_4 = 0x4 # EAPOL message 1/4 + MSG_TYPE_EAPOL_2_OF_2 = 0x80 # EAPOL message 2/2 + MSG_TYPE_EAPOL_2_OF_4 = 0x8 # EAPOL message 2/4 + MSG_TYPE_EAPOL_3_OF_4 = 0x10 # EAPOL message 3/4 + MSG_TYPE_EAPOL_4_OF_4 = 0x20 # EAPOL message 4/4 + MSG_TYPE_EAPOL_ASSOC = 0x200 # EAP Association + MSG_TYPE_EAPOL_KEY_REQ = 0x100 # EAP Key Request (not sure if this works properly) + MST_TYPE_EAPOL_ID_REQ = 0x400 # EAP Identity request + MST_TYPE_EAPOL_ID_RESP = 0x800 # EAP Identity response + MST_TYPE_EAPOL_OTHER_REQ = 0x1000 # EAP Requests that do not match other things. + MST_TYPE_EAPOL_OTHER_RESP = 0x2000 # EAP Responses that do not match other things. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetWifiCorruptionsCorruptFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_set_wifi_corruptions(self, + corrupt_flags=None, # Specify packet types to corrupt (see flags above). + corrupt_per_mil=None, # Per-million: Station to randomly corrupt selected + # message types by this amount. + delay_flags=None, # Specify packet types to delay (see flags above). + delay_max=None, # miliseconds: Station to randomly delay processing + # received messages, max time + delay_min=None, # miliseconds: Station to randomly delay processing + # received messages, min time + dup_flags=None, # Specify packet types to duplicate (see flags above). + dup_per_65535=None, # Percentage, represented as x per 65535 of packets we + # should duplicate. + ignore_flags=None, # Specify packet types to ignore (see flags above). + ignore_per_mil=None, # Per-million: Station to randomly ignore selected message + # types by this amount. + port=None, # WiFi interface name or number. + req_flush=None, # Set to 1 if you wish to flush changes to kernel now. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wifi_corruptions(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if corrupt_flags is not None: + data["corrupt_flags"] = corrupt_flags + if corrupt_per_mil is not None: + data["corrupt_per_mil"] = corrupt_per_mil + if delay_flags is not None: + data["delay_flags"] = delay_flags + if delay_max is not None: + data["delay_max"] = delay_max + if delay_min is not None: + data["delay_min"] = delay_min + if dup_flags is not None: + data["dup_flags"] = dup_flags + if dup_per_65535 is not None: + data["dup_per_65535"] = dup_per_65535 + if ignore_flags is not None: + data["ignore_flags"] = ignore_flags + if ignore_per_mil is not None: + data["ignore_per_mil"] = ignore_per_mil + if port is not None: + data["port"] = port + if req_flush is not None: + data["req_flush"] = req_flush + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wifi_corruptions", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WIFI_CUSTOM> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wifi_custom + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_wifi_custom(self, + port=None, # WiFi interface name or number. + resource=None, # Resource number. + shelf=None, # Shelf number. + text=None, # [BLANK] will erase all, any other text will be appended to + # existing text. <tt escapearg='false'>Unescaped Value</tt> + p_type=None, # NA for now, may specify specific locations later. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wifi_custom(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if text is not None: + data["text"] = text + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wifi_custom", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WIFI_EXTRA> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wifi_extra + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_wifi_extra(self, + anonymous_identity=None, # Anonymous identity string for EAP. + anqp_3gpp_cell_net=None, # 802.11u 3GCPP Cellular Network Info, VAP only. + ca_cert=None, # CA-CERT file name. + client_cert=None, # 802.11u Client cert file: /etc/wpa_supplicant/ca.pem + domain=None, # 802.11u domain: mytelco.com + eap=None, # EAP method: MD5, MSCHAPV2, OTP, GTC, TLS, PEAP, TTLS. + group=None, # Group cyphers: CCMP, TKIP, WEP104, WEP40, or combination. + hessid=None, # 802.11u HESSID (MAC address format) (or peer for WDS + # stations). + identity=None, # EAP Identity string. + imsi=None, # 802.11u IMSI: 310026-000000000 + ipaddr_type_avail=None, # 802.11u network type available, integer, VAP only. + key=None, # WEP key0. This should be entered in ascii-hex. Use this only + # for WEP. + key_mgmt=None, # Key management: WPA-PSK, WPA-EAP, IEEE8021X, NONE, + # WPA-PSK-SHA256, WPA-EAP-SHA256 or combo. + milenage=None, # 802.11u milenage: + # 90dca4eda45b53cf0f12d7c9c3bc6a89:cb9cccc4b9258e6dca4760379fb82 + network_auth_type=None, # 802.11u network authentication type, VAP only. + network_type=None, # 802.11u network type, integer, VAP only. + pac_file=None, # EAP-FAST PAC-File name. (For AP, this field is the RADIUS + # secret password) + pairwise=None, # Pairwise ciphers: CCMP, TKIP, NONE, or combination. + password=None, # EAP Password string. + phase1=None, # Outer-authentication, ie TLS tunnel parameters. + phase2=None, # Inner authentication with TLS tunnel. + pin=None, # EAP-SIM pin string. (For AP, this field is HS20 Operating + # Class) + pk_passwd=None, # EAP private key password. (For AP, this field is HS20 + # connection capability) + port=None, # WiFi interface name or number. + private_key=None, # EAP private key certificate file name. (For AP, this field + # is HS20 WAN Metrics) + psk=None, # WPA(2) pre-shared key. If unsure, use this field for any + # password entry. Prepend with 0x for ascii-hex + # representation. + realm=None, # 802.11u realm: mytelco.com + resource=None, # Resource number. + roaming_consortium=None, # 802.11u roaming consortium: 223344 (15 characters max) + shelf=None, # Shelf number. + venue_group=None, # 802.11u Venue Group, integer. VAP only. + venue_type=None, # 802.11u Venue Type, integer. VAP only. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wifi_extra(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if anonymous_identity is not None: + data["anonymous_identity"] = anonymous_identity + if anqp_3gpp_cell_net is not None: + data["anqp_3gpp_cell_net"] = anqp_3gpp_cell_net + if ca_cert is not None: + data["ca_cert"] = ca_cert + if client_cert is not None: + data["client_cert"] = client_cert + if domain is not None: + data["domain"] = domain + if eap is not None: + data["eap"] = eap + if group is not None: + data["group"] = group + if hessid is not None: + data["hessid"] = hessid + if identity is not None: + data["identity"] = identity + if imsi is not None: + data["imsi"] = imsi + if ipaddr_type_avail is not None: + data["ipaddr_type_avail"] = ipaddr_type_avail + if key is not None: + data["key"] = key + if key_mgmt is not None: + data["key_mgmt"] = key_mgmt + if milenage is not None: + data["milenage"] = milenage + if network_auth_type is not None: + data["network_auth_type"] = network_auth_type + if network_type is not None: + data["network_type"] = network_type + if pac_file is not None: + data["pac_file"] = pac_file + if pairwise is not None: + data["pairwise"] = pairwise + if password is not None: + data["password"] = password + if phase1 is not None: + data["phase1"] = phase1 + if phase2 is not None: + data["phase2"] = phase2 + if pin is not None: + data["pin"] = pin + if pk_passwd is not None: + data["pk_passwd"] = pk_passwd + if port is not None: + data["port"] = port + if private_key is not None: + data["private_key"] = private_key + if psk is not None: + data["psk"] = psk + if realm is not None: + data["realm"] = realm + if resource is not None: + data["resource"] = resource + if roaming_consortium is not None: + data["roaming_consortium"] = roaming_consortium + if shelf is not None: + data["shelf"] = shelf + if venue_group is not None: + data["venue_group"] = venue_group + if venue_type is not None: + data["venue_type"] = venue_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wifi_extra", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WIFI_EXTRA2> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wifi_extra2 + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_wifi_extra2(self, + corrupt_gtk_rekey_mic=None, # Per-million: AP corrupts GTK Rekey MIC. + freq_24=None, # Frequency list for 2.4Ghz band, see above. + freq_5=None, # Frequency list for 5Ghz band, see above. + ignore_assoc=None, # Per-million: AP ignore assoc request percentage. + ignore_auth=None, # Per-million: AP ignore auth request percentage. + ignore_probe=None, # Per-million: AP ignore probe percentage. + ignore_reassoc=None, # Per-million: AP ignore re-assoc request percentage. + ocsp=None, # OCSP settings: 0=disabled, 1=try, but to not require + # response, 2=require valid OCSP stapling response. + port=None, # WiFi interface name or number. + post_ifup_script=None, # Script name with optional args, will run after interface + # comes up and gets IP. + radius_ip=None, # RADIUS server IP Address (AP Only) + radius_port=None, # RADIUS server IP Port (AP Only) + req_flush=None, # Set to 1 if you wish to flush changes to kernel now. + resource=None, # Resource number. + sae_pwe=None, # Set SAE-PWE, 0 == hunting-and-pecking, 1 == + # hash-to-element, 2 allow both. + shelf=None, # Shelf number. + venue_id=None, # Venue-ID for this wifi device. VAP in same venue will + # share neigh reports as appropriate. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wifi_extra2(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if corrupt_gtk_rekey_mic is not None: + data["corrupt_gtk_rekey_mic"] = corrupt_gtk_rekey_mic + if freq_24 is not None: + data["freq_24"] = freq_24 + if freq_5 is not None: + data["freq_5"] = freq_5 + if ignore_assoc is not None: + data["ignore_assoc"] = ignore_assoc + if ignore_auth is not None: + data["ignore_auth"] = ignore_auth + if ignore_probe is not None: + data["ignore_probe"] = ignore_probe + if ignore_reassoc is not None: + data["ignore_reassoc"] = ignore_reassoc + if ocsp is not None: + data["ocsp"] = ocsp + if port is not None: + data["port"] = port + if post_ifup_script is not None: + data["post_ifup_script"] = post_ifup_script + if radius_ip is not None: + data["radius_ip"] = radius_ip + if radius_port is not None: + data["radius_port"] = radius_port + if req_flush is not None: + data["req_flush"] = req_flush + if resource is not None: + data["resource"] = resource + if sae_pwe is not None: + data["sae_pwe"] = sae_pwe + if shelf is not None: + data["shelf"] = shelf + if venue_id is not None: + data["venue_id"] = venue_id + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wifi_extra2", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WIFI_RADIO> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wifi_radio + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetWifiRadioFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetWifiRadioFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + ct_sta_mode = 0x40000 # Enable CT-STA mode if radio supports it. Efficiently replaces sw-crypt in + # +some firmware. + firmware_cfg = 0x80000 # Apply firmware config. + hw_sim = 0x1 # Create hw-sim virtual radio if radio does not already exist. + ignore_radar = 0x100000 # Ignore RADAR events reported by firmware. + no_scan_share = 0x40 # Disable sharing scan results. + no_sw_crypt = 0x20000 # Disable software-crypt for this radio. Disables some virtual-station + # +features. + use_syslog = 0x20000000 # Put supplicant logs in syslog instead of a file. + verbose = 0x10000 # Verbose-Debug: Increase debug info in wpa-supplicant and hostapd logs. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetWifiRadioFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + class SetWifiRadioMode(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + p_802_11a = 1 # 802.11a + AUTO = 0 # 802.11g + aAX = 15 # 802.11a-AX (6E disables /n and /ac) + abg = 4 # 802.11abg + abgn = 5 # 802.11abgn + abgnAC = 8 # 802.11abgn-AC + abgnAX = 12 # 802.11abgn-AX + an = 10 # 802.11an + anAC = 9 # 802.11an-AC + anAX = 14 # 802.11an-AX + b = 2 # 802.11b + bg = 7 # 802.11bg + bgn = 6 # 802.11bgn + bgnAC = 11 # 802.11bgn-AC + bgnAX = 13 # 802.11bgn-AX + g = 3 # 802.11g + + def post_set_wifi_radio(self, + active_peer_count=None, # Number of locally-cached peer objects for this radio. + ampdu_factor=None, # ax200/ax210 only, currently. Requires module reload. OS + # Default: 0xFF + antenna=None, # Antenna configuration: 0 Diversity/All, 1 Fixed-A (1x1), 4 + # AB (2x2), 7 ABC (3x3), 8 ABCD (4x4), 9 8x8 + channel=None, # Channel number for this radio device. Frequency takes + # precedence if both are set to non-default values. + # <tt>0xFFFF, AUTO or DEFAULT</tt> means ANY. + const_tx=None, # RF Pattern Generator , encoded as a single 32-bit integer. + # See above. + country=None, # Country number for this radio device. + flags=None, # Flags for this interface (see above.) + flags_mask=None, # If set, only these flags will be considered. + frag_thresh=None, # Fragmentation Threshold (256 - 2346, 2346 == disabled). + frequency=None, # Frequency for this radio. <tt>0xFFFF, AUTO or DEFAULT</tt> + # means ANY. + fwname=None, # Firmware name (for example: firmware-5.bin) + fwver=None, # Firmware API version (for example, 5 if firmware is based on + # firmware-5.bin + mac=None, # Used to identify when name cannot be trusted (2.6.34+ + # kernels). + max_amsdu=None, # Maximum number of frames per AMSDU that may be transmitted. + # See above. + mode=None, # WiFi mode, see table + peer_count=None, # Number of peer objects for this radio. + pref_ap=None, # Preferred AP BSSID for all station vdevs on this radio. + pulse2_interval_us=None, # Pause between pattern burst for RF noise generator. + pulse_interval=None, # RF Pattern generator: interval between pulses in usecs. + pulse_width=None, # RF Pattern generator: pulse width in usecs. + radio=None, # Name of the physical radio interface, for example: wiphy0 + rate=None, # No longer used, specify the rate on the virtual station(s) + # instead. + rate_ctrl_count=None, # Number of rate-ctrl objects for this radio. + resource=None, # Resource number. + rts=None, # The RTS Threshold for this radio (off, or 1-2347). + shelf=None, # Shelf number. + skid_limit=None, # Firmware hash-table Skid Limit for this radio. + stations_count=None, # Number of stations supported by this radio. + tids_count=None, # TIDs count for this radio. + tx_pulses=None, # Number of pattern pulses per burst for RF noise generator. + txdesc_count=None, # Transmit descriptor count for this radio. + txpower=None, # The transmit power setting for this radio. (AUTO for system + # defaults) + vdev_count=None, # Configure radio vdev count. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wifi_radio(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if active_peer_count is not None: + data["active_peer_count"] = active_peer_count + if ampdu_factor is not None: + data["ampdu_factor"] = ampdu_factor + if antenna is not None: + data["antenna"] = antenna + if channel is not None: + data["channel"] = channel + if const_tx is not None: + data["const_tx"] = const_tx + if country is not None: + data["country"] = country + if flags is not None: + data["flags"] = flags + if flags_mask is not None: + data["flags_mask"] = flags_mask + if frag_thresh is not None: + data["frag_thresh"] = frag_thresh + if frequency is not None: + data["frequency"] = frequency + if fwname is not None: + data["fwname"] = fwname + if fwver is not None: + data["fwver"] = fwver + if mac is not None: + data["mac"] = mac + if max_amsdu is not None: + data["max_amsdu"] = max_amsdu + if mode is not None: + data["mode"] = mode + if peer_count is not None: + data["peer_count"] = peer_count + if pref_ap is not None: + data["pref_ap"] = pref_ap + if pulse2_interval_us is not None: + data["pulse2_interval_us"] = pulse2_interval_us + if pulse_interval is not None: + data["pulse_interval"] = pulse_interval + if pulse_width is not None: + data["pulse_width"] = pulse_width + if radio is not None: + data["radio"] = radio + if rate is not None: + data["rate"] = rate + if rate_ctrl_count is not None: + data["rate_ctrl_count"] = rate_ctrl_count + if resource is not None: + data["resource"] = resource + if rts is not None: + data["rts"] = rts + if shelf is not None: + data["shelf"] = shelf + if skid_limit is not None: + data["skid_limit"] = skid_limit + if stations_count is not None: + data["stations_count"] = stations_count + if tids_count is not None: + data["tids_count"] = tids_count + if tx_pulses is not None: + data["tx_pulses"] = tx_pulses + if txdesc_count is not None: + data["txdesc_count"] = txdesc_count + if txpower is not None: + data["txpower"] = txpower + if vdev_count is not None: + data["vdev_count"] = vdev_count + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wifi_radio", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WIFI_TXO> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wifi_txo + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_set_wifi_txo(self, + port=None, # WiFi interface name or number. + resource=None, # Resource number. + shelf=None, # Shelf number. + txo_bw=None, # Configure bandwidth: 0 == 20, 1 == 40, 2 == 80, 3 == 160, 4 == + # 80+80. + txo_enable=None, # Set to 1 if you wish to enable transmit override, 0 to disable. + txo_mcs=None, # Configure the MCS (0-3 for CCK, 0-7 for OFDM, 0-7 for HT, 0-9 for + # VHT, 0-11 for HE + txo_nss=None, # Configure number of spatial streams (0 == nss1, 1 == nss2, ...). + txo_pream=None, # Select rate preamble: 0 == OFDM, 1 == CCK, 2 == HT, 3 == VHT, 4 == + # HE_SU. + txo_retries=None, # Configure number of retries. 0 or 1 means no retries). + txo_sgi=None, # Should rates be sent with short-guard-interval or not? + txo_txpower=None, # Configure TX power in db. Use 255 for system defaults. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wifi_txo(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if txo_bw is not None: + data["txo_bw"] = txo_bw + if txo_enable is not None: + data["txo_enable"] = txo_enable + if txo_mcs is not None: + data["txo_mcs"] = txo_mcs + if txo_nss is not None: + data["txo_nss"] = txo_nss + if txo_pream is not None: + data["txo_pream"] = txo_pream + if txo_retries is not None: + data["txo_retries"] = txo_retries + if txo_sgi is not None: + data["txo_sgi"] = txo_sgi + if txo_txpower is not None: + data["txo_txpower"] = txo_txpower + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wifi_txo", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WL_CORRUPTION> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wl_corruption + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetWlCorruptionFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SetWlCorruptionFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + BIT_FLIP = 0x4 # Flip a random bit in a byte. + BIT_TRANSPOSE = 0x8 # Transpose two side-by-side bits in a byte. + DO_CHAIN_ON_HIT = 0x10 # Do next corruption if this corruption is applied. + OVERWRITE_FIXED = 0x2 # Write a fixed value to a byte. + OVERWRITE_RANDOM = 0x1 # Write a random value to a byte. + RECALC_CSUMS = 0x20 # Attempt to re-calculate UDP and TCP checksums. + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SetWlCorruptionFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_set_wl_corruption(self, + byte=None, # The byte to use for OVERWRITE_FIXED (or NA). + flags=None, # The flags for this corruption. + index=None, # The corruption to modify (0-5). + max_offset=None, # The maximum offset from start of Ethernet packet for the byte + # to be modified. + min_offset=None, # The minimum offset from start of Ethernet packet for the byte + # to be modified. + name=None, # WanLink name + rate=None, # Specifies how often, per million, this corruption should be + # applied. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wl_corruption(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if byte is not None: + data["byte"] = byte + if flags is not None: + data["flags"] = flags + if index is not None: + data["index"] = index + if max_offset is not None: + data["max_offset"] = max_offset + if min_offset is not None: + data["min_offset"] = min_offset + if name is not None: + data["name"] = name + if rate is not None: + data["rate"] = rate + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wl_corruption", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SET_WL_QDISC> type requests + + https://www.candelatech.com/lfcli_ug.php#set_wl_qdisc + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SetWlQdiscQdisc(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + FIFO = "FIFO" # is the default queuing discipline, no arguments + WRR__queue_queue_____ = "WRR,[queue,queue,...]" # Weighted Round Robbin is also available + + def post_set_wl_qdisc(self, + name=None, # WanLink name + qdisc=None, # FIFO, WRR,a,b,c,d,e,f,g etc + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_set_wl_qdisc(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if qdisc is not None: + data["qdisc"] = qdisc + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/set_wl_qdisc", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_ALERTS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_alerts + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class ShowAlertsType(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + All = "All" # + CX = "CX" # + Card = "Card" # + Channel_Group = "Channel_Group" # + CollisionDomain = "CollisionDomain" # + Endp = "Endp" # + PESQ = "PESQ" # + PPP_Link = "PPP_Link" # + Port = "Port" # + Shelf = "Shelf" # + Span = "Span" # + Test_Mgr = "Test_Mgr" # + + def post_show_alerts(self, + card=None, # Alert resource filter. + endp=None, # Alert endpoint filter. + extra=None, # Extra filter, currently ignored. + port=None, # Alert port filter (can be port name or number). + shelf=None, # Alert shelf filter. + p_type=None, # Alert type filter. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_alerts(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if card is not None: + data["card"] = card + if endp is not None: + data["endp"] = endp + if extra is not None: + data["extra"] = extra + if port is not None: + data["port"] = port + if shelf is not None: + data["shelf"] = shelf + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_alerts", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_ATTENUATORS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_attenuators + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_attenuators(self, + resource=None, # Resource number, or 'all'. + serno=None, # Serial number for requested Attenuator, or 'all'. + shelf=None, # Shelf number or alias, can be 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_attenuators(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if serno is not None: + data["serno"] = serno + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_attenuators", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_CD> type requests + + https://www.candelatech.com/lfcli_ug.php#show_cd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_cd(self, + collision_domain=None, # Name of the Collision Domain, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_cd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if collision_domain is not None: + data["collision_domain"] = collision_domain + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_cd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_CHAMBER> type requests + + https://www.candelatech.com/lfcli_ug.php#show_chamber + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_chamber(self, + name=None, # Chamber Name or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_chamber(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_chamber", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_CHANNEL_GROUPS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_channel_groups + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_channel_groups(self, + channel_name=None, # Name of the channel, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_channel_groups(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if channel_name is not None: + data["channel_name"] = channel_name + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_channel_groups", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_CLIENTS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_clients + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_clients(self, + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_clients(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + response = self.json_post("/cli-json/show_clients", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_CX> type requests + + https://www.candelatech.com/lfcli_ug.php#show_cx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_cx(self, + cross_connect=None, # Specify cross-connect to act on, or 'all'. + test_mgr=None, # Specify test-mgr to act on, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_cx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cross_connect is not None: + data["cross_connect"] = cross_connect + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_cx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_CXE> type requests + + https://www.candelatech.com/lfcli_ug.php#show_cxe + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_cxe(self, + cross_connect=None, # Specify cross-connect to show, or 'all'. + test_mgr=None, # Specify test-mgr to use, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_cxe(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cross_connect is not None: + data["cross_connect"] = cross_connect + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_cxe", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_DBS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_dbs + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_dbs(self, + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_dbs(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + response = self.json_post("/cli-json/show_dbs", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_DUT> type requests + + https://www.candelatech.com/lfcli_ug.php#show_dut + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_dut(self, + name=None, # DUT Name or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_dut(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_dut", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_ENDP_PAYLOAD> type requests + + https://www.candelatech.com/lfcli_ug.php#show_endp_payload + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_endp_payload(self, + max_bytes=None, # The max number of payload bytes to print out, default is 128. + name=None, # The name of the endpoint we are configuring. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_endp_payload(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if max_bytes is not None: + data["max_bytes"] = max_bytes + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_endp_payload", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_ENDPOINTS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_endpoints + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_endpoints(self, + endpoint=None, # Name of endpoint, or 'all'. + extra=None, # See above. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_endpoints(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endpoint is not None: + data["endpoint"] = endpoint + if extra is not None: + data["extra"] = extra + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_endpoints", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_ERR> type requests + + https://www.candelatech.com/lfcli_ug.php#show_err + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_err(self, + message=None, # Message to show to others currently logged on. <tt + # escapearg='false'>Unescaped Value</tt> + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_err(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if message is not None: + data["message"] = message + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_err", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_EVENT_INTEREST> type requests + + https://www.candelatech.com/lfcli_ug.php#show_event_interest + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_event_interest(self, + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_event_interest(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + response = self.json_post("/cli-json/show_event_interest", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_EVENTS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_events + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class ShowEventsType(Enum): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + All = "All" # + CX = "CX" # + Card = "Card" # + Channel_Group = "Channel_Group" # + CollisionDomain = "CollisionDomain" # + Endp = "Endp" # + PESQ = "PESQ" # + PPP_Link = "PPP_Link" # + Port = "Port" # + Shelf = "Shelf" # + Span = "Span" # + Test_Mgr = "Test_Mgr" # + + def post_show_events(self, + card=None, # Event resource filter. + endp=None, # Event endpoint filter. + extra=None, # Extra filter, currently ignored. + port=None, # Event port filter (can be port name or number). + shelf=None, # Event shelf filter. + p_type=None, # Event type filter. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_events(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if card is not None: + data["card"] = card + if endp is not None: + data["endp"] = endp + if extra is not None: + data["extra"] = extra + if port is not None: + data["port"] = port + if shelf is not None: + data["shelf"] = shelf + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_events", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_FILES> type requests + + https://www.candelatech.com/lfcli_ug.php#show_files + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_files(self, + dir_flags=None, # Determines format of listing, see above. + directory=None, # The sub-directory in which to list. + p_filter=None, # An optional filter, as used by the 'ls' command. + key=None, # A special key, can be used for scripting. + resource=None, # The machine to search in. + shelf=None, # The virtual shelf to search in. Use 0 for manager machine. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_files(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if dir_flags is not None: + data["dir_flags"] = dir_flags + if directory is not None: + data["directory"] = directory + if p_filter is not None: + data["filter"] = p_filter + if key is not None: + data["key"] = key + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_files", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#show_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_group(self, + group=None, # Can be name of test group. Use 'all' or leave blank for all groups. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if group is not None: + data["group"] = group + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_PESQ> type requests + + https://www.candelatech.com/lfcli_ug.php#show_pesq + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_pesq(self, + endpoint=None, # Name of endpoint, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_pesq(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endpoint is not None: + data["endpoint"] = endpoint + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_pesq", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_PORTS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_ports + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_ports(self, + port=None, # Port number, or 'all'. + probe_flags=None, # See above, add them together for multiple probings. Leave blank if + # you want stats only. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_ports(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if port is not None: + data["port"] = port + if probe_flags is not None: + data["probe_flags"] = probe_flags + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_ports", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_PPP_LINKS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_ppp_links + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_ppp_links(self, + link_num=None, # Ppp-Link number of the span, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_ppp_links(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if link_num is not None: + data["link_num"] = link_num + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_ppp_links", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_PROFILE> type requests + + https://www.candelatech.com/lfcli_ug.php#show_profile + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_profile(self, + name=None, # Profile Name or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_profile(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_profile", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_RESOURCES> type requests + + https://www.candelatech.com/lfcli_ug.php#show_resources + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_resources(self, + resource=None, # Resource number, or 'all'. + shelf=None, # Shelf number or alias, can be 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_resources(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_resources", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_RFGEN> type requests + + https://www.candelatech.com/lfcli_ug.php#show_rfgen + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_rfgen(self, + resource=None, # Resource number, or 'all'. + shelf=None, # Shelf number or alias, can be 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_rfgen(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_rfgen", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_RT> type requests + + https://www.candelatech.com/lfcli_ug.php#show_rt + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_rt(self, + key=None, # Unique identifier for this request. Usually left blank. + resource=None, # Resource number. + shelf=None, # Shelf number. + virtual_router=None, # Name of the virtual router. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_rt(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if key is not None: + data["key"] = key + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if virtual_router is not None: + data["virtual_router"] = virtual_router + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_rt", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_SCRIPT_RESULTS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_script_results + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_script_results(self, + endpoint=None, # Name of endpoint, test-group, or 'all'. + key=None, # Optional 'key' to be used in keyed-text message result. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_script_results(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endpoint is not None: + data["endpoint"] = endpoint + if key is not None: + data["key"] = key + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_script_results", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_SPANS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_spans + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_spans(self, + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + span_number=None, # Span-Number of the span, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_spans(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if span_number is not None: + data["span_number"] = span_number + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_spans", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_TEXT_BLOB> type requests + + https://www.candelatech.com/lfcli_ug.php#show_text_blob + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_text_blob(self, + brief=None, # Set to 'brief' for a brief listing of all text blobs. + name=None, # Text Blob Name or 'ALL'. + p_type=None, # Text Blob type or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_text_blob(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if brief is not None: + data["brief"] = brief + if name is not None: + data["name"] = name + if p_type is not None: + data["type"] = p_type + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_text_blob", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_TM> type requests + + https://www.candelatech.com/lfcli_ug.php#show_tm + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_tm(self, + test_mgr=None, # Can be name of test manager, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_tm(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_tm", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_TRAFFIC_PROFILE> type requests + + https://www.candelatech.com/lfcli_ug.php#show_traffic_profile + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_traffic_profile(self, + name=None, # Profile Name or 'ALL'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_traffic_profile(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_traffic_profile", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_VENUE> type requests + + https://www.candelatech.com/lfcli_ug.php#show_venue + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_venue(self, + resource=None, # Resource number, or 'ALL' + shelf=None, # Shelf number. + venu_id=None, # Number to uniquely identify this venue on this resource, or 'ALL' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_venue(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if venu_id is not None: + data["venu_id"] = venu_id + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_venue", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_VR> type requests + + https://www.candelatech.com/lfcli_ug.php#show_vr + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_vr(self, + resource=None, # Resource number, or 'all'. + router=None, # Name of the Virtual Router, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_vr(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if router is not None: + data["router"] = router + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_vr", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_VRCX> type requests + + https://www.candelatech.com/lfcli_ug.php#show_vrcx + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_vrcx(self, + cx_name=None, # Name of the Virtual Router Connection, or 'all'. + resource=None, # Resource number, or 'all'. + shelf=None, # Name/id of the shelf, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_vrcx(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cx_name is not None: + data["cx_name"] = cx_name + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_vrcx", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHOW_WANPATHS> type requests + + https://www.candelatech.com/lfcli_ug.php#show_wanpaths + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_show_wanpaths(self, + endpoint=None, # Name of endpoint, or 'all'. + wanpath=None, # Name of wanpath, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_show_wanpaths(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endpoint is not None: + data["endpoint"] = endpoint + if wanpath is not None: + data["wanpath"] = wanpath + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/show_wanpaths", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHUTDOWN> type requests + + https://www.candelatech.com/lfcli_ug.php#shutdown + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_shutdown(self, + chdir=None, # Directory to cd to before dying. Only useful when using gprof to debug, + # or 'NA' to ignore. + really=None, # Must be 'YES' for command to really work. + serverctl=None, # Enter 'YES' to do a ./serverctl.bash restart to restart all LANforge + # processes. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_shutdown(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if chdir is not None: + data["chdir"] = chdir + if really is not None: + data["really"] = really + if serverctl is not None: + data["serverctl"] = serverctl + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/shutdown", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHUTDOWN_OS> type requests + + https://www.candelatech.com/lfcli_ug.php#shutdown_os + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_shutdown_os(self, + resource=None, # Resource number, or ALL. + shelf=None, # Shelf number, or ALL. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_shutdown_os(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/shutdown_os", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SHUTDOWN_RESOURCE> type requests + + https://www.candelatech.com/lfcli_ug.php#shutdown_resource + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_shutdown_resource(self, + resource=None, # Resource number, or ALL. + shelf=None, # Shelf number, or ALL. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_shutdown_resource(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/shutdown_resource", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/SNIFF_PORT> type requests + + https://www.candelatech.com/lfcli_ug.php#sniff_port + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + class SniffPortFlags(IntFlag): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + This class is stateless. It can do binary flag math, returning the integer value. + Example Usage: + int:flag_val = 0 + flag_val = LFPost.set_flags(SniffPortFlags0, flag_names=['bridge', 'dhcp']) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + + DUMPCAP = 0x2 # Use command-line dumpcap, more efficient than tshark + MATE_TERMINAL = 0x4 # Launch tshark/dumpcap in mate-terminal + MATE_XTERM = 0x8 # Launch tshark/dumpcap in xterm + TSHARK = 0x1 # Use command-line tshark instead of wireshark + + # use to get in value of flag + @classmethod + def valueof(cls, name=None): + if name is None: + return name + if name not in cls.__members__: + raise ValueError("SniffPortFlags has no member:[%s]" % name) + return (cls[member].value for member in cls.__members__ if member == name) + + def post_sniff_port(self, + display=None, # The DISPLAY option, for example: 192.168.1.5:0.0. Will guess if left + # blank. + duration=None, # Duration for doing a capture (in seconds). Default is 5 minutes for + # dumpcap/tshark, and forever for wireshark + flags=None, # Flags that control how the sniffing is done. + outfile=None, # Optional file location for saving a capture. + port=None, # The port we are trying to run the packet sniffer on. + resource=None, # Resource number. + shelf=None, # Shelf number. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_sniff_port(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if display is not None: + data["display"] = display + if duration is not None: + data["duration"] = duration + if flags is not None: + data["flags"] = flags + if outfile is not None: + data["outfile"] = outfile + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/sniff_port", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/START_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#start_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_start_endp(self, + endp_name=None, # Name of the cross-connect, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_start_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/start_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/START_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#start_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_start_group(self, + name=None, # The name of the test group. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_start_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/start_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/START_PPP_LINK> type requests + + https://www.candelatech.com/lfcli_ug.php#start_ppp_link + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_start_ppp_link(self, + resource=None, # Resource number that holds this PppLink. + shelf=None, # Name/id of the shelf. + unit_num=None, # Unit-Number for the PppLink to be started. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_start_ppp_link(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if unit_num is not None: + data["unit_num"] = unit_num + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/start_ppp_link", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/STOP_ENDP> type requests + + https://www.candelatech.com/lfcli_ug.php#stop_endp + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_stop_endp(self, + endp_name=None, # Name of the endpoint, or 'all'. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_stop_endp(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if endp_name is not None: + data["endp_name"] = endp_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/stop_endp", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/STOP_GROUP> type requests + + https://www.candelatech.com/lfcli_ug.php#stop_group + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_stop_group(self, + name=None, # The name of the test group, or 'all' + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_stop_group(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if name is not None: + data["name"] = name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/stop_group", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/STOP_PPP_LINK> type requests + + https://www.candelatech.com/lfcli_ug.php#stop_ppp_link + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_stop_ppp_link(self, + resource=None, # Resource number that holds this PppLink. + shelf=None, # Name/id of the shelf. + unit_num=None, # Unit-Number for the PppLink to be stopped. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_stop_ppp_link(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if unit_num is not None: + data["unit_num"] = unit_num + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/stop_ppp_link", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/TAIL> type requests + + https://www.candelatech.com/lfcli_ug.php#tail + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_tail(self, + cmd=None, # Command: start, stop, results + key=None, # File-name that we should be tailing. + message=None, # The contents to display (for results only) <tt escapearg='false'>Unescaped + # Value</tt> + resource=None, # Resource that holds the file. + shelf=None, # Shelf that holds the resource that holds the file. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_tail(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if cmd is not None: + data["cmd"] = cmd + if key is not None: + data["key"] = key + if message is not None: + data["message"] = message + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/tail", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/TM_REGISTER> type requests + + https://www.candelatech.com/lfcli_ug.php#tm_register + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_tm_register(self, + client_name=None, # Name of client to be registered. (dflt is current client) + test_mgr=None, # Name of test manager (can be all.) + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_tm_register(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if client_name is not None: + data["client_name"] = client_name + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/tm_register", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/TM_UNREGISTER> type requests + + https://www.candelatech.com/lfcli_ug.php#tm_unregister + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_tm_unregister(self, + client_name=None, # Name of client to be un-registered. (dflt is current client) + test_mgr=None, # Name of test manager (can be all.) + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_tm_unregister(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if client_name is not None: + data["client_name"] = client_name + if test_mgr is not None: + data["test_mgr"] = test_mgr + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/tm_unregister", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/VERSION> type requests + + https://www.candelatech.com/lfcli_ug.php#version + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_version(self, + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_version(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + response = self.json_post("/cli-json/version", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/WHO> type requests + + https://www.candelatech.com/lfcli_ug.php#who + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_who(self, + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_who(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + response = self.json_post("/cli-json/who", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/WIFI_CLI_CMD> type requests + + https://www.candelatech.com/lfcli_ug.php#wifi_cli_cmd + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_wifi_cli_cmd(self, + port=None, # Name of the WiFi station or AP interface to which this command + # will be directed. + resource=None, # Resource number. + shelf=None, # Shelf number. + wpa_cli_cmd=None, # Command to pass to wpa_cli or hostap_cli. This must be + # single-quoted. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_wifi_cli_cmd(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if port is not None: + data["port"] = port + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if wpa_cli_cmd is not None: + data["wpa_cli_cmd"] = wpa_cli_cmd + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/wifi_cli_cmd", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/WIFI_EVENT> type requests + + https://www.candelatech.com/lfcli_ug.php#wifi_event + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_wifi_event(self, + device=None, # Interface or PHY in most cases. + event=None, # What happened. + msg=None, # Entire event in human readable form. + status=None, # Status on what happened. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_wifi_event(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if device is not None: + data["device"] = device + if event is not None: + data["event"] = event + if msg is not None: + data["msg"] = msg + if status is not None: + data["status"] = status + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/wifi_event", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/WISER_RESET> type requests + + https://www.candelatech.com/lfcli_ug.php#wiser_reset + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_wiser_reset(self, + resource=None, # Resource number, or ALL. + shelf=None, # Shelf number, or ALL. + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_wiser_reset(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if resource is not None: + data["resource"] = resource + if shelf is not None: + data["shelf"] = shelf + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/wiser_reset", + data, + debug_=debug_) + return response + # + + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Notes for <CLI-JSON/WRITE> type requests + + https://www.candelatech.com/lfcli_ug.php#write + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + def post_write(self, + db_name=None, # The name the backup shall be saved as (blank means dflt) + debug_=False): + """----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + Example Usage: + result = post_write(param=value ...) + pprint.pprint( result ) + ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----""" + debug_ |= self.debug + data = {} + if db_name is not None: + data["db_name"] = db_name + if len(data) < 1: + raise ValueError(__name__+": no parameters to submit") + response = self.json_post("/cli-json/write", + data, + debug_=debug_) + return response + # diff --git a/lanforge/lanforge-scripts/py-json/LANforge/lfcli_base.py b/lanforge/lanforge-scripts/py-json/LANforge/lfcli_base.py new file mode 100644 index 000000000..dc5c8b692 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/lfcli_base.py @@ -0,0 +1,659 @@ +#!env /usr/bin/python +import sys +import os +import importlib +import traceback +# Extend this class to use common set of debug and request features for your script +import pprint +import time +import random +import string +import datetime +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") + + +class LFCliBase: + + SHOULD_RUN = 0 # indicates normal operation + SHOULD_QUIT = 1 # indicates to quit loops, close files, send SIGQUIT to threads and return + SHOULD_HALT = 2 # indicates to quit loops, send SIGABRT to threads and exit + + # do not use `super(LFCLiBase,self).__init__(self, host, port, _debug) + # that is py2 era syntax and will force self into the host variable, making you + # very confused. + def __init__(self, _lfjson_host, _lfjson_port, + _debug=False, + _exit_on_error=False, + _exit_on_fail=False, + _local_realm=None, + _proxy_str=None, + _capture_signal_list=[]): + self.fail_pref = "FAILED: " + self.pass_pref = "PASSED: " + self.lfclient_host = _lfjson_host + self.lfclient_port = _lfjson_port + self.debug = _debug + # if (_debug): + # print("LFCliBase._proxy_str: %s" % _proxy_str) + self.proxy = {} + self.adjust_proxy(_proxy_str) + + if (_local_realm is not None): + self.local_realm = _local_realm + + # if (_debug): + # print("LFCliBase._proxy_str: %s" % _proxy_str) + self.lfclient_url = "http://%s:%s" % (self.lfclient_host, self.lfclient_port) + self.test_results = [] + self.exit_on_error = _exit_on_error + self.exit_on_fail = _exit_on_fail + self.capture_signals = _capture_signal_list + # toggle using preexec_cli, preexec_method; the preexec_X parameters are useful + # when you desire the lfclient to check for existance of entities to run commands on, + # like when developing; you might toggle this with use_preexec = _debug + # Otherwise, preexec methods use more processing time because they add an extra CLI call + # into the queue, and inspect it -- typically nc_show_port + self.suppress_related_commands = None + self.finish = self.SHOULD_RUN + self.thread_map = {} + + if len(_capture_signal_list) > 0: + for zignal in _capture_signal_list: + self.captured_signal(zignal, self.my_captured_signal) + # + + def _finish(self): + """ + call this to indicate SIGQUIT + """ + self.finish = self.SHOULD_QUIT + + def _halt(self): + """ + call this to indicate SIGABRT + """ + self.finish = self.SHOULD_HALT + + def _should_finish(self): + """ + check this when in a run loop if SIGQUIT has been indicated + """ + if self.finish == self.SHOULD_RUN: + return False + if self.finish == self.SHOULD_QUIT: + return True + if self.finish == self.SHOULD_HALT: + return False + + def _should_halt(self): + """ + check this when in a run loop if SIGABRT has been indicated + """ + if self.finish == self.SHOULD_RUN: + return False + if self.finish == self.SHOULD_QUIT: + return False + if self.finish == self.SHOULD_HALT: + return True + + def track_thread(self, name, thread): + if self.thread_map is None: + self.thread_map = {} + self.thread_map[name] = thread + + def get_thread(self, name): + if self.thread_map is None: + return None + if name in self.thread_map.keys(): + return self.thread_map[name] + return None + + def remove_thread(self, name): + if self.thread_map is None: + return None + if name not in self.thread_map.keys(): + return None + thrud = self.thread_map[name] + del self.thread_map[name] + return thrud + + def send_thread_signals(self, signum, fname): + if len(self.thread_map) < 1: + print("no threads to signal") + return + for (name, thread) in self.thread_map.items(): + if self.debug: + print("sending signal %s to thread %s" % (signum, name)) + # do a thing + + def my_captured_signal(self, signum): + """ + Override me to process signals, otherwise superclass signal handler is called. + You may use _finish() or _halt() to indicate finishing soon or halting immediately. + + :return: True if we processed this signal + """ + print("my_captured_signal should be overridden") + return False + + def captured_signal(self, signum): + """ + Here is your opportunity to decide what to do on things like KeyboardInterrupt or other UNIX signals + Check that your subclass handled the signal or not. You may use _finish() or _halt() to indicate + finishing soon or halting immediately. Use signal.signal(signal.STOP) to enable this. + """ + if self.debug: + print("Captured signal %s" % signum) + if self.my_captured_signal(signum): + if self.debug: + print("subclass processed signal") + else: + if self.debug: + print("subclass ignored signal") + + def clear_test_results(self): + self.test_results.clear() + + def json_post(self, _req_url, _data, debug_=False, suppress_related_commands_=None, response_json_list_=None): + """ + send json to the LANforge client + :param _req_url: requested url + :param _data: json data to send + :param debug_: turn on debugging output + :param suppress_related_commands_: when False, override self.preexec; when True use + :param response_json_list_: array for json results in the response object, (alternative return method) + :return: http response object + """ + json_response = None + debug_ |= self.debug + try: + lf_r = LFRequest.LFRequest(url=self.lfclient_url, + uri=_req_url, + proxies_=self.proxy, + debug_=debug_, + die_on_error_=self.exit_on_error) + if suppress_related_commands_ is None: + if 'suppress_preexec_cli' in _data: + del _data['suppress_preexec_cli'] + if 'suppress_preexec_method' in _data: + del _data['suppress_preexec_method'] + if 'suppress_postexec_cli' in _data: + del _data['suppress_postexec_cli'] + if 'suppress_postexec_method' in _data: + del _data['suppress_postexec_method'] + elif suppress_related_commands_ == False: + _data['suppress_preexec_cli'] = False + _data['suppress_preexec_method'] = False + _data['suppress_postexec_cli'] = False + _data['suppress_postexec_method'] = False + elif self.suppress_related_commands or suppress_related_commands_: + _data['suppress_preexec_cli'] = False + _data['suppress_preexec_method'] = False + _data['suppress_postexec_cli'] = True + _data['suppress_postexec_method'] = True + + lf_r.addPostData(_data) + if debug_: + LFUtils.debug_printer.pprint(_data) + json_response = lf_r.json_post(show_error=debug_, + debug=debug_, + response_json_list_=response_json_list_, + die_on_error_=self.exit_on_error) + if debug_ and (response_json_list_ is not None): + pprint.pprint(response_json_list_) + except Exception as x: + if debug_ or self.exit_on_error: + print("json_post posted to %s" % _req_url) + pprint.pprint(_data) + print("Exception %s:" % x) + traceback.print_exception(Exception, x, x.__traceback__, chain=True) + if self.exit_on_error: + exit(1) + return json_response + + def json_put(self, _req_url, _data, debug_=False, response_json_list_=None): + """ + Send a PUT request. This is presently used for data sent to /status-msg for + creating a new messaging session. It is not presently used for CLI scripting + so lacks suppress_x features. + :param _req_url: url to put + :param _data: data to place at URL + :param debug_: enable debug output + :param response_json_list_: array for json results in the response object, (alternative return method) + :return: http response object + """ + debug_ |= self.debug + json_response = None + try: + lf_r = LFRequest.LFRequest(url=self.lfclient_url, + uri=_req_url, + proxies_=self.proxy, + debug_=debug_, + die_on_error_=self.exit_on_error) + lf_r.addPostData(_data) + if debug_: + LFUtils.debug_printer.pprint(_data) + json_response = lf_r.json_put(show_error=self.debug, + debug=debug_, + response_json_list_=response_json_list_, + die_on_error_=self.exit_on_error) + if debug_ and (response_json_list_ is not None): + pprint.pprint(response_json_list_) + except Exception as x: + if debug_ or self.exit_on_error: + print("json_put submitted to %s" % _req_url) + pprint.pprint(_data) + print("Exception %s:" % x) + traceback.print_exception(Exception, x, x.__traceback__, chain=True) + if self.exit_on_error: + exit(1) + return json_response + + def json_get(self, _req_url, debug_=False): + debug_ |= self.debug + # if debug_: + # print("json_get: "+_req_url) + # print("json_get: proxies:") + # pprint.pprint(self.proxy) + json_response = None + # print("----- GET ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ") + try: + lf_r = LFRequest.LFRequest(url=self.lfclient_url, + uri=_req_url, + proxies_=self.proxy, + debug_=debug_, + die_on_error_=self.exit_on_error) + json_response = lf_r.get_as_json(debug_=debug_, die_on_error_=False) + #debug_printer.pprint(json_response) + if (json_response is None): + if debug_: + if hasattr(lf_r, 'print_errors'): + lf_r.print_errors() + else: + print("LFCliBase.json_get: no entity/response, check other errors") + time.sleep(10) + return None + except ValueError as ve: + if debug_ or self.exit_on_error: + print("jsonGet asked for " + _req_url) + print("Exception %s:" % ve) + traceback.print_exception(ValueError, ve, ve.__traceback__, chain=True) + if self.exit_on_error: + sys.exit(1) + + return json_response + + def json_delete(self, _req_url, debug_=False): + debug_ |= self.debug + if debug_: + print("DELETE: "+_req_url) + json_response = None + try: + # print("----- DELETE ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ") + lf_r = LFRequest.LFRequest(url=self.lfclient_url, + uri=_req_url, + proxies_=self.proxy, + debug_=debug_, + die_on_error_=self.exit_on_error) + json_response = lf_r.json_delete(debug=debug_, die_on_error_=False) + print(json_response) + #debug_printer.pprint(json_response) + if (json_response is None) and debug_: + print("LFCliBase.json_delete: no entity/response, probabily status 404") + return None + except ValueError as ve: + if debug_ or self.exit_on_error: + print("json_delete asked for " + _req_url) + print("Exception %s:" % ve) + traceback.print_exception(ValueError, ve, ve.__traceback__, chain=True) + if self.exit_on_error: + sys.exit(1) + # print("----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ") + return json_response + + @staticmethod + def response_list_to_map(json_list, key, debug_=False): + reverse_map = {} + if (json_list is None) or (len(json_list) < 1): + if debug_: + print("response_list_to_map: no json_list provided") + raise ValueError("response_list_to_map: no json_list provided") + return reverse_map + + json_interfaces = json_list + if key in json_list: + json_interfaces = json_list[key] + + for record in json_interfaces: + if len(record.keys()) < 1: + continue + record_keys = record.keys() + k2 = "" + # we expect one key in record keys, but we can't expect [0] to be populated + json_entry = None + for k in record_keys: + k2 = k + json_entry = record[k] + # skip uninitialized port records + if k2.find("Unknown") >= 0: + continue + port_json = record[k2] + reverse_map[k2] = json_entry + + return reverse_map + + def error(self, exception): + # print("lfcli_base error: %s" % exception) + pprint.pprint(exception) + traceback.print_exception(Exception, exception, exception.__traceback__, chain=True) + + def check_connect(self): + if self.debug: + print("Checking for LANforge GUI connection: %s" % self.lfclient_url) + response = self.json_get("/", debug_=self.debug) + duration = 0 + while (response is None) and (duration < 300): + print("LANforge GUI connection not found sleeping 5 seconds, tried: %s" % self.lfclient_url) + duration += 2 + time.sleep(2) + response = self.json_get("", debug_=self.debug) + + if duration >= 300: + print("Could not connect to LANforge GUI") + sys.exit(1) + + #return ALL messages in list form + def get_result_list(self): + return self.test_results + + #return ALL fail messages in list form + def get_failed_result_list(self): + fail_list = [] + for result in self.test_results: + if not result.startswith("PASS"): + fail_list.append(result) + return fail_list + + #return ALL pass messages in list form + def get_passed_result_list(self): + pass_list = [] + for result in self.test_results: + if result.startswith("PASS"): + pass_list.append(result) + return pass_list + + def get_pass_message(self): + pass_messages = self.get_passed_result_list() + return "\n".join(pass_messages) + + def get_fail_message(self): + fail_messages = self.get_failed_result_list() + return "\n".join(fail_messages) + + def get_all_message(self): + return "\n".join(self.test_results) + + #determines if overall test passes via comparing passes vs. fails + def passes(self): + pass_counter = 0 + fail_counter = 0 + for result in self.test_results: + if result.startswith("PASS"): + pass_counter += 1 + else: + fail_counter += 1 + if (fail_counter == 0) and (pass_counter > 0): + return True + return False + + #EXIT script with a fail + def exit_fail(self, message="%d out of %d tests failed. Exiting script with script failure."): + total_len=len(self.get_result_list()) + fail_len=len(self.get_failed_result_list()) + print(message %(fail_len,total_len)) + sys.exit(1) + + # use this inside the class to log a failure result and print it if wished + def _fail(self, message, print_=False): + self.test_results.append(self.fail_pref + message) + if print_ or self.exit_on_fail: + print(self.fail_pref + message) + if self.exit_on_fail: + sys.exit(1) + + #EXIT script with a success + def exit_success(self,message="%d out of %d tests passed successfully. Exiting script with script success."): + num_total=len(self.get_result_list()) + num_passing=len(self.get_passed_result_list()) + print(message %(num_passing,num_total)) + sys.exit(0) + + def success(self,message="%d out of %d tests passed successfully."): + num_total=len(self.get_result_list()) + num_passing=len(self.get_passed_result_list()) + print(message %(num_passing,num_total)) + + # use this inside the class to log a pass result and print if wished. + def _pass(self, message, print_=False): + self.test_results.append(self.pass_pref + message) + if print_: + print(self.pass_pref + message) + + def adjust_proxy(self, proxy_str): + # if self.debug: + # print("lfclibase.adjust_proxy: %s" % proxy_str) + if (proxy_str is None) or (proxy_str == ""): + return + if self.proxy is None: + self.proxy = {} + + if proxy_str.find("http:") > -1: + self.proxy["http"] = proxy_str + if proxy_str.find("https:") > -1: + self.proxy["https"] = proxy_str + # if self.debug: + # print("lfclibase::self.proxy: ") + # pprint.pprint(self.proxy) + + + def logg2(self, level="debug", mesg=None): + if (mesg is None) or (mesg == ""): + return + print("[{level}]: {msg}".format(level=level, msg=mesg)) + + def logg(self, + level=None, + mesg=None, + filename=None, + scriptname=None): + if (mesg is None) or (mesg == "") or (level is None): + return + userhome=os.path.expanduser('~') + session = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-h-%M-m-%S-s")).replace(':','-') + if filename == None: + try: + os.mkdir("%s/report-data/%s" % (userhome, session)) + except: + pass + filename = ("%s/report-data/%s/%s.log" % (userhome,session,scriptname)) + import logging + logging.basicConfig(filename=filename, level=logging.DEBUG) + if level == "debug": + logging.debug(mesg) + elif level == "info": + logging.info(mesg) + elif level == "warning": + logging.warning(mesg) + elif level == "error": + logging.error(mesg) + + @staticmethod + def parse_time(time_string): + if isinstance(time_string, str): + pattern = re.compile("^(\d+)([dhms]$)") + td = pattern.match(time_string) + if td is not None: + dur_time = int(td.group(1)) + dur_measure = str(td.group(2)) + 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) + elif dur_measure == "ms": + duration_time = datetime.timedelta(milliseconds=dur_time) + elif dur_measure == "w": + duration_time = datetime.timedelta(weeks=dur_time) + else: + duration_time = datetime.timedelta(seconds=dur_time) + else: + raise ValueError("Cannot compute time string provided: %s" % time_string) + else: + raise ValueError("time_string must be of type str. Type %s provided" % type(time_string)) + return duration_time + + # This style of Action subclass for argparse can't do much unless we incorporate + # our argparse as a member of LFCliBase. Then we can do something like automatically + # parse our proxy string without using _init_ arguments + # class ProxyAction(argparse.Action, zelf): + # def __init__(self, outter_): + # pass + # def __call__(self, parser, namespace, values, option_string=None): + # zelf.adjust_proxy(values) + + @staticmethod + def create_bare_argparse(prog=None, + formatter_class=argparse.RawTextHelpFormatter, + epilog=None, + description=None): + if (prog is not None) or (formatter_class is not None) or (epilog is not None) or (description is not None): + parser = argparse.ArgumentParser(prog=prog, + formatter_class=formatter_class, + allow_abbrev=True, + epilog=epilog, + description=description) + else: + parser = argparse.ArgumentParser() + optional = parser.add_argument_group('optional arguments') + required = parser.add_argument_group('required arguments') + optional.add_argument('--mgr', help='hostname for where LANforge GUI is running', default='localhost') + optional.add_argument('--mgr_port', help='port LANforge GUI HTTP service is running on', default=8080) + optional.add_argument('--debug', '-d', help='Enable debugging', default=False, action="store_true") + optional.add_argument('--proxy', nargs='?', default=None, # action=ProxyAction, + help='Connection proxy like http://proxy.localnet:80 or https://user:pass@proxy.localnet:3128') + + return parser + + # Create argparse with radio, securiy, ssid and passwd required + # TODO: show example of how to add required or optional arguments from calling class + @staticmethod + def create_basic_argparse(prog=None, + formatter_class=None, + epilog=None, + description=None, + more_optional=None, + more_required=None): + if (prog is not None) or (formatter_class is not None) or (epilog is not None) or (description is not None): + parser = argparse.ArgumentParser(prog=prog, + formatter_class=formatter_class, + epilog=epilog, + description=description) + else: + parser = argparse.ArgumentParser() + optional = parser.add_argument_group('optional arguments') + required = parser.add_argument_group('required arguments') + + #Optional Args + optional.add_argument('--mgr', help='hostname for where LANforge GUI is running', default='localhost') + optional.add_argument('--mgr_port', help='port LANforge GUI HTTP service is running on', default=8080) + optional.add_argument('-u', '--upstream_port', + help='non-station port that generates traffic: <resource>.<port>, e.g: 1.eth1', + default='1.eth1') + optional.add_argument('--num_stations', help='Number of stations to create', default=0) + optional.add_argument('--test_id', help='Test ID (intended to use for ws events)', default="webconsole") + optional.add_argument('--debug', help='Enable debugging', default=False, action="store_true") + optional.add_argument('--proxy', nargs='?', default=None, + help='Connection proxy like http://proxy.localnet:80 or https://user:pass@proxy.localnet:3128') + if more_optional is not None: + for x in more_optional: + if 'default' in x.keys(): + optional.add_argument(x['name'], help=x['help'], default=x['default']) + else: + optional.add_argument(x['name'], help=x['help']) + + #Required Args + required.add_argument('--radio', help='radio EID, e.g: 1.wiphy2') + required.add_argument('--security', help='WiFi Security protocol: < open | wep | wpa | wpa2 | wpa3 >', default="open") + required.add_argument('--ssid', help='WiFi SSID for script objects to associate to') + required.add_argument('--passwd', '--password' ,'--key', help='WiFi passphrase/password/key', default="[BLANK]") + + if more_required is not None: + for x in more_required: + if 'default' in x.keys(): + required.add_argument(x['name'], help=x['help'], default=x['default']) + else: + required.add_argument(x['name'], help=x['help']) + + return parser + + # use this function to add an event You can see these events when watching websocket_client at 8081 port + def add_event(self, + message=None, + event_id="new", + name="custom", + priority=1, + debug_=False): + data = { + "event_id": event_id, + "details": message, + "priority": priority, + "name": name + } + self.json_post("/cli-json/add_event", data, debug_=debug_) + + def read_file(self, filename): + filename = open(filename, 'r') + return [line.split(',') for line in filename.readlines()] + + #Function creates random characters made of letters + def random_chars(self, size, chars=None): + if chars is None: + chars = string.ascii_letters + return ''.join(random.choice(chars) for x in range(size)) + + def get_milliseconds(self, timestamp): + return (timestamp - datetime.datetime(1970,1,1)).total_seconds()*1000 + + def get_seconds(self, timestamp): + return (timestamp - datetime.datetime(1970,1,1)).total_seconds() + + def replace_special_char(self, str): + return str.replace('+', ' ').replace('_', ' ').strip(' ') + + Help_Mode = """Station WiFi modes: use the number value below: + auto : 0, + a : 1, + b : 2, + g : 3, + abg : 4, + abgn : 5, + bgn : 6, + bg : 7, + abgnAC : 8, + anAC : 9, + an : 10, + bgnAC : 11, + abgnAX : 12, + bgnAX : 13 +""" diff --git a/lanforge/lanforge-scripts/py-json/LANforge/pandas_extensions.py b/lanforge/lanforge-scripts/py-json/LANforge/pandas_extensions.py new file mode 100644 index 000000000..935daa4b5 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/pandas_extensions.py @@ -0,0 +1,118 @@ +#!/usr/bin/env pythonn3 + +import pandas as pd + +class pandas_extensions: + + # ================ Pandas Dataframe Functions ====================================== + + # takes any dataframe and returns the specified file extension of it + def df_to_file(self, output_f=None, dataframe=None, save_path=None): + if output_f.lower() == 'hdf': + dataframe.to_hdf(save_path.replace('csv', 'h5', 1), 'table', append=True) + if output_f.lower() == 'parquet': + dataframe.to_parquet(save_path.replace('csv', 'parquet', 1), engine='pyarrow') + if output_f.lower() == 'png': + fig = dataframe.plot().get_figure() + fig.savefig(save_path.replace('csv', 'png', 1)) + if output_f.lower() == 'xlsx': + dataframe.to_excel(save_path.replace('csv', 'xlsx', 1)) + if output_f.lower() == 'json': + dataframe.to_json(save_path.replace('csv', 'json', 1)) + if output_f.lower() == 'stata': + dataframe.to_stata(save_path.replace('csv', 'dta', 1)) + if output_f.lower() == 'pickle': + dataframe.to_pickle(save_path.replace('csv', 'pkl', 1)) + if output_f.lower() == 'html': + dataframe.to_html(save_path.replace('csv', 'html', 1)) + + # takes any format of a file and returns a dataframe of it + def file_to_df(self, file_name): + if file_name.split('.')[-1] == 'csv': + return pd.read_csv(file_name) + + # only works for test_ipv4_variable_time at the moment + def compare_two_df(self, dataframe_one=None, dataframe_two=None): + # df one = current report + # df two = compared report + pd.set_option("display.max_rows", None, "display.max_columns", None) + # get all of common columns besides Timestamp, Timestamp milliseconds + common_cols = list(set(dataframe_one.columns).intersection(set(dataframe_two.columns))) + cols_to_remove = ['Timestamp milliseconds epoch', 'Timestamp', 'LANforge GUI Build: 5.4.3'] + com_cols = [i for i in common_cols if i not in cols_to_remove] + # check if dataframes have the same endpoints + if dataframe_one.name.unique().tolist().sort() == dataframe_two.name.unique().tolist().sort(): + endpoint_names = dataframe_one.name.unique().tolist() + if com_cols is not None: + dataframe_one = dataframe_one[[c for c in dataframe_one.columns if c in com_cols]] + dataframe_two = dataframe_two[[c for c in dataframe_one.columns if c in com_cols]] + dataframe_one = dataframe_one.loc[:, ~dataframe_one.columns.str.startswith('Script Name:')] + dataframe_two = dataframe_two.loc[:, ~dataframe_two.columns.str.startswith('Script Name:')] + lowest_duration = min(dataframe_one['Duration elapsed'].max(), dataframe_two['Duration elapsed'].max()) + print("The max duration in the new dataframe will be... " + str(lowest_duration)) + + compared_values_dataframe = pd.DataFrame( + columns=[col for col in com_cols if not col.startswith('Script Name:')]) + cols = compared_values_dataframe.columns.tolist() + cols = sorted(cols, key=lambda L: (L.lower(), L)) + compared_values_dataframe = compared_values_dataframe[cols] + print(compared_values_dataframe) + for duration_elapsed in range(lowest_duration): + for endpoint in endpoint_names: + # check if value has a space in it or is a str. + # if value as a space, only take value before space for calc, append that calculated value after space. + # if str. check if values match from 2 df's. if values do not match, write N/A + for_loop_df1 = dataframe_one.loc[(dataframe_one['name'] == endpoint) & ( + dataframe_one['Duration elapsed'] == duration_elapsed)] + for_loop_df2 = dataframe_two.loc[(dataframe_one['name'] == endpoint) & ( + dataframe_two['Duration elapsed'] == duration_elapsed)] + # print(for_loop_df1) + # print(for_loop_df2) + cols_to_loop = [i for i in com_cols if + i not in ['Duration elapsed', 'Name', 'Script Name: test_ipv4_variable_time']] + cols_to_loop = sorted(cols_to_loop, key=lambda L: (L.lower(), L)) + print(cols_to_loop) + row_to_append = {} + row_to_append["Duration elapsed"] = duration_elapsed + for col in cols_to_loop: + print(col) + print(for_loop_df1) + # print(for_loop_df2) + print(for_loop_df1.at[0, col]) + print(for_loop_df2.at[0, col]) + if type(for_loop_df1.at[0, col]) == str and type(for_loop_df2.at[0, col]) == str: + if (' ' in for_loop_df1.at[0, col]) == True: + # do subtraction + new_value = float(for_loop_df1.at[0, col].split(" ")[0]) - float( + for_loop_df2.at[0, col].split(" ")[0]) + # add on last half of string + new_value = str(new_value) + for_loop_df2.at[0, col].split(" ")[1] + # print(new_value) + row_to_append[col] = new_value + else: + if for_loop_df1.at[0, col] != for_loop_df2.at[0, col]: + row_to_append[col] = 'NaN' + else: + row_to_append[col] = for_loop_df1.at[0, col] + elif type(for_loop_df1.at[0, col]) == int and type(for_loop_df2.at[0, col]) == int or type( + for_loop_df1.at[0, col]) == float and type(for_loop_df2.at[0, col]) == float: + new_value = for_loop_df1.at[0, col] - for_loop_df2.at[0, col] + row_to_append[col] = new_value + compared_values_dataframe = compared_values_dataframe.append(row_to_append, ignore_index=True, ) + print(compared_values_dataframe) + # add col name to new df + print(dataframe_one) + print(dataframe_two) + print(compared_values_dataframe) + else: + ValueError("Unable to execute report comparison due to inadequate file commonalities. ") + exit(1) + else: + ValueError( + "Two files do not have the same endpoints. Please try file comparison with files that have the same endpoints.") + exit(1) + + # take those columns and separate those columns from others in DF. + + pass + # return compared_df diff --git a/lanforge/lanforge-scripts/py-json/LANforge/set_port.py b/lanforge/lanforge-scripts/py-json/LANforge/set_port.py new file mode 100644 index 000000000..f6fa7f3be --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/set_port.py @@ -0,0 +1,122 @@ +import sys +import os +import importlib + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../../"))) + +lf_json_autogen = importlib.import_module("py-json.LANforge.lf_json_autogen") +LFJsonPost = lf_json_autogen.LFJsonPost + + +if os.environ.get("LF_USE_AUTOGEN") == 1: + set_port_current_flags = LFJsonPost.SetPortCurrentFlags.__members__ + set_port_cmd_flags = LFJsonPost.SetPortCmdFlags.__members__ + set_port_interest_flags = LFJsonPost.SetPortInterest.__members__ + +else: + set_port_current_flags = { + "if_down": 0x1, # Interface Down + "fixed_10bt_hd": 0x2, # Fixed-10bt-HD (half duplex) + "fixed_10bt_fd": 0x4, # Fixed-10bt-FD + "fixed_100bt_hd": 0x8, # Fixed-100bt-HD + "fixed_100bt_fd": 0x10, # Fixed-100bt-FD + "auto_neg": 0x100, # auto-negotiate + "adv_10bt_hd": 0x100000, # advert-10bt-HD + "adv_10bt_fd": 0x200000, # advert-10bt-FD + "adv_100bt_hd": 0x400000, # advert-100bt-HD + "adv_100bt_fd": 0x800000, # advert-100bt-FD + "adv_flow_ctl": 0x8000000, # advert-flow-control + "promisc": 0x10000000, # PROMISC + "use_dhcp": 0x80000000, # USE-DHCP + "adv_10g_hd": 0x400000000, # advert-10G-HD + "adv_10g_fd": 0x800000000, # advert-10G-FD + "tso_enabled": 0x1000000000, # TSO-Enabled + "lro_enabled": 0x2000000000, # LRO-Enabled + "gro_enabled": 0x4000000000, # GRO-Enabled + "ufo_enabled": 0x8000000000, # UFO-Enabled + "gso_enabled": 0x10000000000, # GSO-Enabled + "use_dhcpv6": 0x20000000000, # USE-DHCPv6 + "rxfcs": 0x40000000000, # RXFCS + "no_dhcp_rel": 0x80000000000, # No-DHCP-Release + "staged_ifup": 0x100000000000, # Staged-IFUP + "http_enabled": 0x200000000000, # Enable HTTP (nginx) service for this port. + "ftp_enabled": 0x400000000000, # Enable FTP (vsftpd) service for this port. + "aux_mgt": 0x800000000000, # Enable Auxillary-Management flag for this port. + "no_dhcp_restart": 0x1000000000000, # Disable restart of DHCP on link connect (ie, wifi). + # This should usually be enabled when testing wifi + # roaming so that the wifi station can roam + # without having to re-acquire a DHCP lease each + # time it roams. + "ignore_dhcp": 0x2000000000000, # Don't set DHCP acquired IP on interface, + # instead print CLI text message. May be useful + # in certain wifi-bridging scenarios where external + # traffic-generator cannot directly support DHCP. + + "no_ifup_post": 0x4000000000000, # Skip ifup-post script if we can detect that we + # have roamed. Roaming is considered true if + # the IPv4 address has not changed. + + "radius_enabled": 0x20000000000000, # Enable RADIUS service (using hostapd as radius server) + "ipsec_client": 0x40000000000000, # Enable client IPSEC xfrm on this port. + "ipsec_concentrator": 0x80000000000000, # Enable concentrator (upstream) IPSEC xfrm on this port. + "service_dns": 0x100000000000000, # Enable DNS (dnsmasq) service on this port. + } + set_port_cmd_flags = { + "reset_transceiver": 0x1, # Reset transciever + "restart_link_neg": 0x2, # Restart link negotiation + "force_MII_probe": 0x4, # Force MII probe + "no_hw_probe": 0x8, # Don't probe hardware + "probe_wifi": 0x10, # Probe WIFI + "new_gw_probe": 0x20, # Force new GW probe + "new_gw_probe_dev": 0x40, # Force new GW probe for ONLY this interface + "from_user": 0x80, # from_user (Required to change Mgt Port config + # (IP, DHCP, etc) + "skip_port_bounce": 0x100, # skip-port-bounce (Don't ifdown/up + # interface if possible.) + "from_dhcp": 0x200, # Settings come from DHCP client. + "abort_if_scripts": 0x400, # Forceably abort all ifup/down scripts on this Port. + "use_pre_ifdown": 0x800, # Call pre-ifdown script before bringing interface down. + } + set_port_interest_flags = { + "command_flags" : 0x1, # apply command flags + "current_flags" : 0x2, # apply current flags + "ip_address" : 0x4, # IP address + "ip_Mask" : 0x8, # IP mask + "ip_gateway" : 0x10, # IP gateway + "mac_address" : 0x20, # MAC address + "supported_flags" : 0x40, # apply supported flags + "link_speed" : 0x80, # Link speed + "mtu" : 0x100, # MTU + "tx_queue_length" : 0x200, # TX Queue Length + "promisc_mode" : 0x400, # PROMISC mode + "interal_use_1" : 0x800, # (INTERNAL USE) + "alias" : 0x1000, # Port alias + "rx_all" : 0x2000, # Rx-ALL + "dhcp" : 0x4000, # including client-id. + "rpt_timer" : 0x8000, # Report Timer + "bridge" : 0x10000, # BRIDGE + "ipv6_addrs" : 0x20000, # IPv6 Address + "bypass" : 0x40000, # Bypass + "gen_offload" : 0x80000, # Generic offload flags, everything but LRO + "cpu_mask" : 0x100000, # CPU Mask, useful for pinning process to CPU core + "lro_offload" : 0x200000, # LRO (Must be disabled when used in Wanlink, + # and probably in routers) + + "sta_br_id" : 0x400000, # WiFi Bridge identifier. 0 means no bridging. + "ifdown" : 0x800000, # Down interface + "dhcpv6" : 0x1000000, # Use DHCPv6 + "rxfcs" : 0x2000000, # RXFCS + "dhcp_rls" : 0x4000000, # DHCP release + "svc_httpd" : 0x8000000, # Enable/disable HTTP Service for a port + "svc_ftpd" : 0x10000000, # Enable/disable FTP Service for a port + "aux_mgt" : 0x20000000, # Enable/disable Auxillary-Management for a port + "no_dhcp_conn" : 0x40000000, # Enable/disable NO-DHCP-ON-CONNECT flag for a port + "no_apply_dhcp" : 0x80000000, # Enable/disable NO-APPLY-DHCP flag for a port + "skip_ifup_roam" : 0x100000000, # Enable/disable SKIP-IFUP-ON-ROAM flag for a port + } +# diff --git a/lanforge/lanforge-scripts/py-json/LANforge/set_wifi_radio.py b/lanforge/lanforge-scripts/py-json/LANforge/set_wifi_radio.py new file mode 100644 index 000000000..f6744c8ee --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/LANforge/set_wifi_radio.py @@ -0,0 +1,17 @@ +set_radio_mode = { + "AUTO": 0, + "802.11a": 1, # 802.11a + "802.11b": 2, # 802.11b + "802.11g": 3, # 802.11g + "802.11abg": 4, # 802.11abg + "802.11abgn": 5, # 802.11abgn + "802.11bgn": 6, # 802.11bgn + "802.11bg": 7, # 802.11bg + "802.11abgnAC": 8, # 802.11abgn-AC + "802.11anAC": 9, # 802.11an-AC + "802.11an": 10, # 802.11an + "802.11bgnAC": 11, # 802.11bgn-AC + "802.11abgnAX": 12, # 802.11abgn-AX a/b/g/n/AC/AX (dual-band AX) support + "802.11bgnAX": 13, # 802.11bgn-AX + "802.11anAX": 14 # 802.11an-AX +} diff --git a/lanforge/lanforge-scripts/py-json/README.md b/lanforge/lanforge-scripts/py-json/README.md new file mode 100644 index 000000000..0f66ccd35 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/README.md @@ -0,0 +1,181 @@ +# LANforge Python JSON Scripts # + +Similar to the JSON sibling directory that provides Perl JSON adapters +with the LANforge client, this directory provides Python adapters to +the LANforge client. The LANforge client is the LANforge Java GUI +package running with the `-http` switch (by default) and possibly in the *headless* +mode using the `-daemon` switch. + +Follow our [getting started cookbook](http://www.candelatech.com/cookbook.php?vol=cli&book=Querying+the+LANforge+GUI+for+JSON+Data) +to learn more about how to operate your LANforge client. + +## Getting Started ## +The first step is to run update_deps.py which is located in py-scripts. This command will install all dependencies necessary for lanforge-scripts on your system. + +New automation tests and JSON client scripts should go in `../py-scripts`. This directory +is intended for utility and library scripts. To use this module, make sure your include path +captures this module by adding it to your `sys.path`. We recommend your scripts in `../py-scripts` +begin with these imports: + + if 'py-json' not in sys.path: + sys.path.append('../py-json') + from LANforge import LFUtils + from LANforge import lfcli_base + from LANforge.lfcli_base import LFCliBase + from LANforge.LFUtils import * + import realm + from realm import Realm + + +## These Scripts ## + + * `__init__.py`: this is a module header and it defines its relationship to sub-module LANforge, + requiring LFRequest. + * `LANforge`: this module is for our json library. Use gain access to these by using: + `import LANforge` + `from LANforge import LFUtils` + `from LANforge import LFRequest` +## create_sta.py ## +Please follow though `create_sta.py` to see how you can +utilize the JSON API provided by the LANforge client. It +is possible to use similar commands to create virtual Access points. +## create_wanlink.py ## +Example that creates a WANlink +## generic_cx.py ## +Example that creates a cross connect +## realm.py ## +Module defining the Realm class. `Realm` is a toolbox class that also serves as a facade for finer-grained methods in LFUtils and LFRequest: + + *`def __init__()`: our constructor + *`def wait_until_ports_appear()`: takes a list of ports and waits until they all appear in the list of existing stations + *`def wait_until_ports_disappear()`: takes a list of ports and waits until they all disappear from the list of existing stations + *`def rm_port()`: takes a string in eid format and attempts to remove it + *`def port_exists()`: takes a string in eid format and returns a boolean depending on if the port exists + *`def admin_up()`: takes a string in eid format attempts to set it to admin up + *`def admin_down()`: takes a string in eid format attempts to set it to admin down + *`def reset_port()`: takes a string in eid format requests a port reset + *`def rm_cx()`: takes a cross connect name as a string and attempts to remove it from LANforge + *`def rm_endp()`: takes an endpoint name as a string and attempts to remove it from LANforge + *`def set_endp_tos()`: attempts to set tos of a specified endpoint name + *`def stop_cx()`: attempts to stop a cross connect with the given name + *`def cleanup_cxe_prefix()`: attempts to remove all existing cross connects and endpoints + *`def channel_freq()`: takes a channel and returns its corresponding frequency + *`def freq_channel()`: takes a frequency and returns its corresponding channel + *`def wait_while_building()`: checks for OK or BUSY when querying cli-json/cv+is_built + *`def load()`: loads a database from the GUI + *`def cx_list()`: request json list of cross connects + *`def waitUntilEndpsAppear()`: takes a list of endpoints and waits until they all disappear from the list of existing endpoints + *deprecated method use def wait_until_endps_appear() instead* + *`def wait_until_endps_appear()`: takes a list of endpoints and waits until they all appear in the list of existing endpoints + *`def waitUntilCxsAppear()`: takes a list of cross connects and waits until they all disappear from the list of existing cross connects + *deprecated method use def wait_until_cxs_appear() instead* + *`def wait_until_cxs_appear()`: takes a list of cross connects and waits until they all disappear from the list of existing cross connects + *`def station_map()`: request a map of stations via `/port/list` and alter the list to name based map of only stations + *`def station_list()`: request a list of stations + *`def vap_list()`: request a list of virtual APs + *`def remove_vlan_by_eid()`: a way of deleting a port/station/vAP + *`def find_ports_like()`: returns a list of ports matching a string prefix, like: + * `sta\*` matches names starting with `sta` + * `sta10+` matches names with port numbers 10 or greater + * `sta[10..20]` matches a range of stations including the range sta10 -- sta20 + *`def name_to_eid()`: takes a name like `1.1.eth1` and returns it split into an array `[1, 1, "eth1"]` + *`def wait_for_ip()`: takes a list of stations and waits until they all have an ip address. Default wait time is 360 seconds, + can take -1 as timeout argument to determine timeout based on mean ip acquisition time + *`def get_curr_num_ips()`: returns the number of stations with an ip address + *`def duration_time_to_seconds()`: returns an integer for a time string converted to seconds + *`def remove_all_stations()`: attempts to remove all currently existing stations + *`def remove_all_endps()`: attempts to remove all currently existing endpoints + *`def remove_all_cxs()`: attempts to remove all currently existing cross connects + *`def new_station_profile()`: creates a blank station profile, configure station properties in this profile + and then use its `create()` method to create a series of stations + *`def new_multicast_profile()`: creates a blank multicast profile, configure it then call `create()` + *`def new_wifi_monitor_profile()`: creates a blank wifi monitor profile, configure it then call `create()` + *`def new_l3_cx_profile()`: creates a blank Layer-3 profile, configure this connection profile and + then use its `create()` method to create a series of endpoints and cross connects + *`def new_l4_cx_profile()`: creates a blank Layer-4 (http/ftp) profile, configure it then call `create()` + *`def new_generic_endp_profile()`: creates a blank Generic endpoint profile, configure it then call `create()` + *`def new_generic_cx_profile()`: creates a blank Generic connection profile (for lfping/iperf3/curl-post/speedtest.net) + then configure and call `create()` + *`def new_vap_profile()`: creates a blank VAP profile, configure it then call `create()` + *`def new_vr_profile()`: creates a blank VR profile, configure it then call `create()` + *`def new_http_profile()`: creates a blank HTTP profile, configure it then call `create()` + *`def new_fio_endp_profile()`: creates a blank FileIO profile, configure it then call `create()` + *`def new_dut_profile()`: creates a blank DUT profile, configure it then call `create()` + *`def new_mvlan_profile()`: creates a blank MACVLAN profile, configure it then call `create()` + *`def new_qvlan_profile()`: creates a blank QVLAN profile, configure it then call `create()` + *`def new_test_group_profile()`: creates a blank Test Group profile, configure it then call `create()` + *`class PacketFilter()`: This class provides filters that can be used with tshark + *`def get_filter_wlan_assoc_packets()`: This packet filter will look for wlan.fc.type_subtype<=3. It takes + two arguments: `ap_mac` and `sta_mac` + *`def get_filter_wlan_null_packets()`: This packet filter will look for wlan.fc.type_subtype==44. It takes + two arguments: `ap_mac` and `sta_mac` + *`def run_filter()`: This function will run the filter specified by the `filter` argument on the pcap + file specified by the `pcap_file` argument. It redirects this output into a txt file in /tmp + and returns the lines in that file as an array. + + + +## realm_test.py ## +Exercises realm.py +## show_ports.py ## +This simple example shows how to gather a digest of ports +## test_l4.py ## +Example of how to use LFRequest to create a L4 endpoint +## wct-example.py ## +Example of using expect on port 3990 to operate a WiFi Capacity Test +## ws-sta-monitor.py ## +Websocket 8081 client that filters interesting station events from the lfclient websocket + + + +## LANforge ## +This directory defines the LANforge module holding the following classes: + * lfcli_base.py / class **LFCliBase**: This is a base class we encourage using for creating tests and + other automation scripts. It provides a centralized manner for making uniform JSON GET and POST + calls. + * `__init__`: call this from your classes __init__ method as super().__init__(...) like below: + + class MyScript(LFCliBase): + def __init__(self, host, port, debug_=False, _exit_on_error=False, _exit_on_fail=False): + super().__init__(host, port, _debug=debug_, _exit_on_fail=_exit_on_fail) + + Those parameters provide base functionality: + * host: lfclient host running the LANforge GUI or headless LANforgeGUI -daemon + * port: lfclient HTTP port, typically 8080 + * _debug: provides verbose mode behavior + * _exit_on_fail: if a test calls _fail(), exit + + * LFRequest.py / class **LFRequest**: provides default mechanism to make API queries, use this + to create most of your API requests, but you may also use the normal + `urllib.request` library on simple GET requests if you wish. + * form_post(): post data in url-encoded format + * json_post(): post data in JSON format + * get(): GET method returns text (which could be JSON) + * get_as_json(): converts get() JSON results into python objects + * add_post_data(): provide a dictionary to this method before calling formPost() or jsonPost() + + * LFUtils.py / class **LFUtils**: defines constants and utility methods + * class PortEID: convenient handle for port objects + * sta_new_down_sta_request(): create POST data object for station down + * port_set_dhcp_down_request(): create POST data object for station down, apply `use_dhcp` flags + * port_dhcp_up_request(): apply `use_dhcp`, ask for station to come up + * port_up_request(): ask for station to come up + * port_down_request(): ask for station to go down + * generate_mac(): generate mac addresses + * port_name_series(): produce a padded-number series of port names + * generate_random_hex(): series of random octets + * portAliasesInList(): returns station aliases from `/port` listing + * find_port_eids(): returns EIDs of ports + * wait_until_ports_admin_down(): watch ports until they report admin down + * wait_until_ports_admin_up(): watch ports until they report admin up + * wait_until_ports_disappear(): use this after deleting ports + * ~~waitUntilPortsDisappear()~~: use this after deleting ports, **deprecated** + * wait_until_ports_appear(): use this after `add_sta` or `set_port` + * remove_port(): remove a port using rm_vlan command + * remove_cx(): request a list of CX names be removed + * remove_endps(): request a list of endpoint names be removed + * exec_wrap(): hair trigger method that exits when a command fails when called by os.system() + + +Have fun coding! +support@candelatech.com diff --git a/lanforge/lanforge-scripts/py-json/TODO.txt b/lanforge/lanforge-scripts/py-json/TODO.txt new file mode 100644 index 000000000..ed15eaa0d --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/TODO.txt @@ -0,0 +1,20 @@ +Able to select ports + +Create client groups / single client: +Should be able to control MAC address, + client type(b/g/n/, ac), + encryption (WPA2, WPA3), + auth -( open, 802.1x , WebAuth, WISPR) + Tx power etc + +Able to control below failures with predetermined failure rate + +eg: + +Generate auth failure +Generate assoc failure +Generate EAP failure +Generate WIPSR/WebAuth failure +Generate Radius failure +Generate DHCP failure + diff --git a/lanforge/lanforge-scripts/py-json/__init__.py b/lanforge/lanforge-scripts/py-json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lanforge/lanforge-scripts/py-json/base_profile.py b/lanforge/lanforge-scripts/py-json/base_profile.py new file mode 100644 index 000000000..c73fed0db --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/base_profile.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +import datetime +import random +import string +from pprint import pprint + +class BaseProfile: + def __init__(self, local_realm, debug=False): + self.parent_realm = local_realm + self.exit_on_error = False + self.debug = debug or local_realm.debug + self.profiles = [] + + + def json_get(self, _req_url, debug_=False): + return self.parent_realm.json_get(_req_url, debug_=False) + + def json_post(self, req_url=None, data=None, debug_=False, suppress_related_commands_=None): + return self.parent_realm.json_post(_req_url=req_url, + _data=data, + suppress_related_commands_=suppress_related_commands_, + debug_=debug_) + + def parse_time(self, time_string): + return self.parent_realm.parse_time(time_string) + + def stopping_cx(self, name): + return self.parent_realm.stop_cx(name) + + def cleanup_cxe_prefix(self, prefix): + return self.parent_realm.cleanup_cxe_prefix(prefix) + + def rm_cx(self, cx_name): + return self.parent_realm.rm_cx(cx_name) + + def rm_endp(self, ename, debug_=False, suppress_related_commands_=True): + self.parent_realm.rm_endp(ename, debug_=False, suppress_related_commands_=True) + + def name_to_eid(self, eid): + return self.parent_realm.name_to_eid(eid) + + def set_endp_tos(self, ename, _tos, debug_=False, suppress_related_commands_=True): + return self.parent_realm.set_endp_tos(ename, _tos, debug_=False, suppress_related_commands_=True) + + def wait_until_endps_appear(self, these_endp, debug=False): + return self.parent_realm.wait_until_endps_appear(these_endp, debug=False) + + def wait_until_cxs_appear(self, these_cx, debug=False): + return self.parent_realm.wait_until_cxs_appear(these_cx, debug=False) + + def logg(self, message=None, audit_list=None): + if audit_list is None: + self.parent_realm.logg(message) + for item in audit_list: + if (item is None): + continue + message += ("\n" + pprint.pformat(item, indent=4)) + self.parent_realm.logg(message) + + def replace_special_char(self, str): + return str.replace('+', ' ').replace('_', ' ').strip(' ') + + # @deprecate me + def get_milliseconds(self, timestamp): + return (timestamp - datetime.datetime(1970,1,1)).total_seconds()*1000 + + # @deprecate me + def get_seconds(self, timestamp): + return (timestamp - datetime.datetime(1970,1,1)).total_seconds() + + def read_file(self, filename): + filename = open(filename, 'r') + return [line.split(',') for line in filename.readlines()] + + #Function to create random characters made of letters + def random_chars(self, size, chars=None): + if chars is None: + chars = string.ascii_letters + return ''.join(random.choice(chars) for x in range(size)) + + + #--------------- create file path / find file path code - to be put into functions + # #Find file path to save data/csv to: + # if args.report_file is None: + # new_file_path = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-h-%M-m-%S-s")).replace(':', + # '-') + '-test_ipv4_variable_time' # create path name + # try: + # path = os.path.join('/home/lanforge/report-data/', new_file_path) + # os.mkdir(path) + # except: + # curr_dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + # path = os.path.join(curr_dir_path, new_file_path) + # os.mkdir(path) + + # if args.output_format in ['csv', 'json', 'html', 'hdf','stata', 'pickle', 'pdf', 'png', 'parquet', + # 'xlsx']: + # report_f = str(path) + '/data.' + args.output_format + # output = args.output_format + # else: + # print('Not supporting this report format or cannot find report format provided. Defaulting to csv data file output type, naming it data.csv.') + # report_f = str(path) + '/data.csv' + # output = 'csv' + + # else: + # report_f = args.report_file + # if args.output_format is None: + # output = str(args.report_file).split('.')[-1] + # else: + # output = args.output_format + # print("Saving final report data in ... " + report_f) + + # compared_rept=None + # if args.compared_report: + # compared_report_format=args.compared_report.split('.')[-1] + # #if compared_report_format not in ['csv', 'json', 'dta', 'pkl','html','xlsx','parquet','h5']: + # if compared_report_format != 'csv': + # print(ValueError("Cannot process this file type. Please select a different file and re-run script.")) + # exit(1) + # else: + # compared_rept=args.compared_report + + diff --git a/lanforge/lanforge-scripts/py-json/create_wanlink.py b/lanforge/lanforge-scripts/py-json/create_wanlink.py new file mode 100755 index 000000000..fab6c0bb3 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/create_wanlink.py @@ -0,0 +1,238 @@ +#!/usr/bin/python3 +# Create and modify WAN Links Using LANforge JSON AP : http://www.candelatech.com/cookbook.php?vol=cli&book=JSON:+Managing+WANlinks+using+JSON+and+Python +# Written by Candela Technologies Inc. +# Updated by: Erin Grimes +import sys +import urllib +import importlib +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +import os +from time import sleep +from urllib import error +import pprint + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") + +j_printer = pprint.PrettyPrinter(indent=2) +# todo: this needs to change +resource_id = 1 + + +def main(base_url, args={}): + print(base_url) + json_post = "" + json_response = "" + num_wanlinks = -1 + + # see if there are old wanlinks to remove + lf_r = LFRequest.LFRequest(base_url+"/wl/list") + print(lf_r.get_as_json()) + + # remove old wanlinks + if (num_wanlinks > 0): + lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_cx") + lf_r.addPostData({ + 'test_mgr': 'all', + 'cx_name': args['name'] + }) + lf_r.jsonPost() + sleep(0.05) + + try: + json_response = lf_r.getAsJson() + LFUtils.debug_printer.pprint(json_response) + for key, value in json_response.items(): + if (isinstance(value, dict) and "_links" in value): + num_wanlinks = 1 + except urllib.error.HTTPError as error: + print("Error code "+error.code) + + lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_endp") + lf_r.addPostData({ + 'endp_name': args['name']+"-A" + }) + lf_r.jsonPost() + sleep(0.05) + + lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_endp") + lf_r.addPostData({ + 'endp_name': args['name']+"-B" + }) + lf_r.jsonPost() + sleep(0.05) + + # create wanlink endpoint A + lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_wl_endp") + lf_r.addPostData({ + 'alias': args['name']+"-A", + 'shelf': 1, + 'resource': '1', + 'port': args['port_A'], + 'latency': args['latency_A'], + 'max_rate': args['rate_A'], + }) + lf_r.jsonPost() + sleep(0.05) + + # create wanlink endpoint B + lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_wl_endp") + lf_r.addPostData({ + 'alias': args['name']+"-B", + 'shelf': 1, + 'resource': '1', + 'port': args['port_B'], + 'latency': args['latency_B'], + 'max_rate': args['rate_B'], + }) + lf_r.jsonPost() + sleep(0.05) + + # create cx + lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_cx") + lf_r.addPostData({ + 'alias': args['name'], + 'test_mgr': 'default_tm', + 'tx_endp': args['name']+"-A", + 'rx_endp': args['name']+"-B", + }) + lf_r.jsonPost() + sleep(0.05) + + # modify wanlink endpoint A + lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_wanlink_info") + lf_r.addPostData({ + 'name': args['name']+"-A", + 'max_jitter': args['jitter_A'], + 'jitter_freq': args['jitter_freq_A'], + 'drop_freq': args['drop_A'] + }) + lf_r.jsonPost() + sleep(0.05) + + # modify wanlink endpoint B + lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_wanlink_info") + lf_r.addPostData({ + 'name': args['name']+"-B", + 'max_jitter': args['jitter_B'], + 'jitter_freq': args['jitter_freq_B'], + 'drop_freq': args['drop_B'] + }) + lf_r.jsonPost() + sleep(0.05) + + # start wanlink once we see it + seen = 0 + while (seen < 1): + sleep(1) + lf_r = LFRequest.LFRequest(base_url+"/wl/"+args['name']+"?fields=name,state,_links") + try: + json_response = lf_r.getAsJson() + if (json_response is None): + continue + LFUtils.debug_printer.pprint(json_response) + for key, value in json_response.items(): + if (isinstance(value, dict)): + if ("_links" in value): + if (value["name"] == args['name']): + seen = 1 + else: + pass + # else: + # print(" name was not wl_eg1") + # else: + # print("value lacks _links") + # else: + # print("value not a dict") + + except urllib.error.HTTPError as error: + print("Error code "+error.code) + continue + + print("starting wanlink:") + # print("the latency is {laten}".format(laten=latency)) + lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_cx_state") + lf_r.addPostData({ + 'test_mgr': 'all', + 'cx_name': args['name'], + 'cx_state': 'RUNNING' + }) + lf_r.jsonPost() + + running = 0 + while (running < 1): + sleep(1) + lf_r = LFRequest.LFRequest(base_url+"/wl/"+args['name']+"?fields=name,state,_links") + try: + json_response = lf_r.getAsJson() + if (json_response is None): + continue + for key, value in json_response.items(): + if (isinstance(value, dict)): + if ("_links" in value): + if (value["name"] == args['name']): + if (value["state"].startswith("Run")): + LFUtils.debug_printer.pprint(json_response) + running = 1 + + except urllib.error.HTTPError as error: + print("Error code "+error.code) + continue + + print("Wanlink is running") + + # stop wanlink + lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_cx_state") + lf_r.addPostData({ + 'test_mgr': 'all', + 'cx_name': args['name'], + 'cx_state': 'STOPPED' + }) + lf_r.jsonPost() + running = 1 + while (running > 0): + sleep(1) + lf_r = LFRequest.LFRequest(base_url+"/wl/"+args['name']+"?fields=name,eid,state,_links") + LFUtils.debug_printer.pprint(json_response) + try: + json_response = lf_r.getAsJson() + if (json_response is None): + continue + for key, value in json_response.items(): + if (isinstance(value, dict)): + if ("_links" in value): + if (value["name"] == args['name']): + if (value["state"].startswith("Stop")): + LFUtils.debug_printer.pprint(json_response) + running = 0 + + except urllib.error.HTTPError as error: + print("Error code "+error.code) + continue + + print("Wanlink is stopped.") + + # print("Wanlink info:") + # lf_r = LFRequest.LFRequest(base_url+"/wl/wl_eg1") + # json_response = lf_r.getAsJson() + # LFUtils.debug_printer.pprint(json_response) + + # lf_r = LFRequest.LFRequest(base_url+"/wl_ep/wl_eg1-A") + # json_response = lf_r.getAsJson() + # LFUtils.debug_printer.pprint(json_response) + + # lf_r = LFRequest.LFRequest(base_url+"/wl_ep/wl_eg1-B") + # json_response = lf_r.getAsJson() + # LFUtils.debug_printer.pprint(json_response) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-json/cv_dut_profile.py b/lanforge/lanforge-scripts/py-json/cv_dut_profile.py new file mode 100644 index 000000000..d10f97ada --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/cv_dut_profile.py @@ -0,0 +1,148 @@ +# !/usr/bin/env python3 +import sys +import os +import importlib + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +add_dut = importlib.import_module("py-json.LANforge.add_dut") +add_dut_flags = add_dut.add_dut_flags + + +class cv_dut(LFCliBase): + def __init__(self, + lfclient_host="localhost", + lfclient_port=8080, + sw_version="NA", + hw_version="NA", + serial_num="NA", + model_num="NA", + desired_dut_flags=None, + desired_dut_flags_mask=None + ): + super().__init__(_lfjson_host=lfclient_host, + _lfjson_port=lfclient_port) + self.cv_dut_name = "DUT" + self.flags = "4098" + self.sw_version = sw_version + self.hw_version = hw_version + self.model_num = model_num + self.serial_num = serial_num + self.serial_port = "[BLANK]" + self.wan_port = "[BLANK]" + self.lan_port = "[BLANK]" + self.api_id = "0" + self.flags_mask = "NA" + if desired_dut_flags is not None: + self.dut_flags = desired_dut_flags + self.dut_flags_mask = desired_dut_flags_mask + + def add_named_flags(self, desired_list, command_ref): + if desired_list is None: + raise ValueError("addNamedFlags wants a list of desired flag names") + if len(desired_list) < 1: + print("addNamedFlags: empty desired list") + return 0 + if (command_ref is None) or (len(command_ref) < 1): + raise ValueError("addNamedFlags wants a maps of flag values") + + result = 0 + for name in desired_list: + if (name is None) or (name == ""): + continue + if name not in command_ref: + if self.debug: + print(command_ref) + raise ValueError("flag %s not in map" % name) + result += command_ref[name] + + return result + + def create_dut(self, + ssid1="[BLANK]", + pass1="[BLANK]", + ssid2="[BLANK]", + pass2="[BLANK]", + ssid3="[BLANK]", + pass3="[BLANK]", + bssid1="00:00:00:00:00:00", + bssid2="00:00:00:00:00:00", + bssid3="00:00:00:00:00:00", + mgt_ip="0.0.0.0", + eap_id="[BLANK]", + top_left_x="NA", + top_left_y="NA", + ): + try: + self.flags = self.add_named_flags(self.dut_flags, add_dut_flags) + self.flags_mask = self.add_named_flags(self.dut_flags_mask, add_dut_flags) + except: + pass + response_json = [] + req_url = "/cli-json/add_dut" + data = { + "name": self.cv_dut_name, + "flags": self.flags, + "img_file": "NONE", + "sw_version": self.sw_version, + "hw_version": self.hw_version, + "model_num": self.model_num, + "serial_num": self.serial_num, + "serial_port": self.serial_port, + "wan_port": self.wan_port, + "lan_port": self.lan_port, + "ssid1": ssid1, + "passwd1": pass1, + "ssid2": ssid2, + "passwd2": pass2, + "ssid3": ssid3, + "passwd3": pass3, + "mgt_ip": mgt_ip, + "api_id": self.api_id, + "flags_mask": self.flags_mask, + "antenna_count1": "0", + "antenna_count2": "0", + "antenna_count3": "0", + "bssid1": bssid1, + "bssid2": bssid2, + "bssid3": bssid3, + "top_left_x": top_left_x, + "top_left_y": top_left_y, + "eap_id": eap_id, + } + rsp = self.json_post(req_url, data, debug_=False, response_json_list_=response_json) + return rsp + + def add_ssid(self, + dut_name="DUT", + ssid_idx=0, + ssid='[BLANK]', + passwd='[BLANK]', + bssid='00:00:00:00:00:00', + ssid_flags=0, + ssid_flags_mask=0xFFFFFFFF): + req_url = "/cli-json/add_dut_ssid" + print("name:" + dut_name, + "ssid_idx:" + ssid_idx, + "ssid:" + ssid, + "passwd:" + passwd, + "bssid:" + bssid, + "ssid_flags:" + str(ssid_flags), + "ssid_flags_mask:" + str(ssid_flags_mask)) + + self.json_post(req_url, { + "name": dut_name, + "ssid_idx": ssid_idx, + "ssid": ssid, + "passwd": passwd, + "bssid": bssid, + "ssid_flags": ssid_flags, + "ssid_flags_mask": ssid_flags_mask, + }) diff --git a/lanforge/lanforge-scripts/py-json/cv_test_manager.py b/lanforge/lanforge-scripts/py-json/cv_test_manager.py new file mode 100644 index 000000000..0c88e4eee --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/cv_test_manager.py @@ -0,0 +1,534 @@ +""" +Note: This script is working as library for chamberview tests. + It holds different commands to automate test. +""" + +import sys +import os +import importlib +import time +import json +from pprint import pprint +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +cv_test_reports = importlib.import_module("py-json.cv_test_reports") +lf_rpt = cv_test_reports.lanforge_reports +InfluxRequest = importlib.import_module("py-dashboard.InfluxRequest") +influx_add_parser_args = InfluxRequest.influx_add_parser_args +RecordInflux = InfluxRequest.RecordInflux + + +def cv_base_adjust_parser(args): + if args.test_rig != "": + # TODO: In future, can use TestRig once that GUI update has propagated + args.set.append(["Test Rig ID:", args.test_rig]) + + if args.test_tag != "": + args.set.append(["TestTag", args.test_tag]) + + if args.influx_host is not None: + if not args.pull_report: + print("Specified influx host without pull_report, will enabled pull_request.") + args.pull_report = True + + +def cv_add_base_parser(parser): + parser.add_argument("-m", "--mgr", type=str, default="localhost", + help="address of the LANforge GUI machine (localhost is default)") + parser.add_argument("-o", "--port", type=int, default=8080, + help="IP Port the LANforge GUI is listening on (8080 is default)") + parser.add_argument("--lf_user", type=str, default="lanforge", + help="LANforge username to pull reports") + parser.add_argument("--lf_password", type=str, default="lanforge", + help="LANforge Password to pull reports") + parser.add_argument("-i", "--instance_name", type=str, default="cv_dflt_inst", + help="create test instance") + parser.add_argument("-c", "--config_name", type=str, default="cv_dflt_cfg", + help="Config file name") + + parser.add_argument("-r", "--pull_report", default=False, action='store_true', + help="pull reports from lanforge (by default: False)") + parser.add_argument("--load_old_cfg", default=False, action='store_true', + help="Should we first load defaults from previous run of the capacity test? Default is False") + + parser.add_argument("--enable", action='append', nargs=1, default=[], + help="Specify options to enable (set cfg-file value to 1). See example raw text config for possible options. May be specified multiple times. Most tests are enabled by default, except: longterm") + parser.add_argument("--disable", action='append', nargs=1, default=[], + help="Specify options to disable (set value to 0). See example raw text config for possible options. May be specified multiple times.") + parser.add_argument("--set", action='append', nargs=2, default=[], + help="Specify options to set values based on their label in the GUI. Example: --set 'Basic Client Connectivity' 1 May be specified multiple times.") + parser.add_argument("--raw_line", action='append', nargs=1, default=[], + help="Specify lines of the raw config file. Example: --raw_line 'test_rig: Ferndale-01-Basic' See example raw text config for possible options. This is catch-all for any options not available to be specified elsewhere. May be specified multiple times.") + + parser.add_argument("--raw_lines_file", default="", + help="Specify a file of raw lines to apply.") + + # Reporting info + parser.add_argument("--test_rig", default="", + help="Specify the test rig info for reporting purposes, for instance: testbed-01") + parser.add_argument("--test_tag", default="", + help="Specify the test tag info for reporting purposes, for instance: testbed-01") + + influx_add_parser_args(parser) # csv_to_influx + + +class cv_test(Realm): + def __init__(self, + lfclient_host="localhost", + lfclient_port=8080, + lf_report_dir="" + ): + super().__init__(lfclient_host=lfclient_host, + lfclient_port=lfclient_port) + self.lf_report_dir = lf_report_dir + self.report_name = None + + # Add a config line to a text blob. Will create new text blob + # if none exists already. + def create_test_config(self, config_name, blob_test_name, text): + req_url = "/cli-json/add_text_blob" + data = { + "type": "Plugin-Settings", + "name": str(blob_test_name + config_name), + "text": text + } + + print("adding- " + text + " " + "to test config") + + rsp = self.json_post(req_url, data) + # time.sleep(1) + + # Tell LANforge GUI Chamber View to launch a test + def create_test(self, test_name, instance, load_old_cfg): + cmd = "cv create '{0}' '{1}' '{2}'".format(test_name, instance, load_old_cfg) + return self.run_cv_cmd(str(cmd)) + + # Tell LANforge chamber view to load a scenario. + def load_test_scenario(self, instance, scenario): + cmd = "cv load '{0}' '{1}'".format(instance, scenario) + self.run_cv_cmd(cmd) + + # load test config for a chamber view test instance. + def load_test_config(self, test_config, instance): + cmd = "cv load '{0}' '{1}'".format(instance, test_config) + self.run_cv_cmd(cmd) + + # start the test + def start_test(self, instance): + cmd = "cv click '%s' Start" % instance + return self.run_cv_cmd(cmd) + + # close test + def close_test(self, instance): + cmd = "cv click '%s' 'Close'" % instance + self.run_cv_cmd(cmd) + + # Cancel + def cancel_test(self, instance): + cmd = "cv click '%s' Cancel" % instance + self.run_cv_cmd(cmd) + + # Send chamber view commands to the LANforge GUI + def run_cv_cmd(self, command): + response_json = [] + req_url = "/gui-json/cmd" + data = { + "cmd": command + } + debug_par = "" + rsp = self.json_post("/gui-json/cmd%s" % debug_par, data, debug_=False, response_json_list_=response_json) + try: + if response_json[0]["LAST"]["warnings"].startswith("Unknown"): + print("Unknown command?\n"); + pprint(response_json) + except: + # Ignore un-handled structs at this point, let calling code deal with it. + pass + return response_json + + # For auto save report + def auto_save_report(self, instance): + cmd = "cv click %s 'Auto Save Report'" % instance + self.run_cv_cmd(cmd) + + # To get the report location + def get_report_location(self, instance): + cmd = "cv get %s 'Report Location:'" % instance + location = self.run_cv_cmd(cmd) + var = 1 + while var != 0: + try: + data = json.dumps(location[0]["LAST"]["response"]) + var = 0 + except Exception as e: + var += 1 + time.sleep(2) + if var > 5: + break + return location + + # To get if test is running or not + def get_is_running(self, instance): + cmd = "cv get %s 'StartStop'" % instance + val = self.run_cv_cmd(cmd) + # pprint(val) + return val[0]["LAST"]["response"] == 'StartStop::Stop' + + # To save to html + def save_html(self, instance): + cmd = "cv click %s 'Save HTML'" % instance + self.run_cv_cmd(cmd) + + # Check if test instance exists + def get_exists(self, instance): + cmd = "cv exists %s" % instance + val = self.run_cv_cmd(cmd) + # pprint(val) + return val[0]["LAST"]["response"] == 'YES' + + # Check if chamberview is built + def get_cv_is_built(self): + cmd = "cv is_built" + val = self.run_cv_cmd(cmd) + # pprint(val) + rv = val[0]["LAST"]["response"] == 'YES' + print("is-built: ", rv) + return rv + + # delete the test instance + def delete_instance(self, instance): + cmd = "cv delete %s" % instance + self.run_cv_cmd(cmd) + + # It can take a while, some test rebuild the old scenario upon exit, for instance. + tries = 0 + while True: + if self.get_exists(instance): + print("Waiting %i/60 for test instance: %s to be deleted." % (tries, instance)) + tries += 1 + if (tries > 60): + break + time.sleep(1) + else: + break + + # And make sure chamber-view is properly re-built + tries = 0 + while True: + if not self.get_cv_is_built(): + print("Waiting %i/60 for Chamber-View to be built." % (tries)) + tries += 1 + if (tries > 60): + break + time.sleep(1) + else: + break + + # Get port listing + def get_ports(self, url="/ports/"): + response = self.json_get(url) + return response + + def show_text_blob(self, config_name, blob_test_name, brief): + req_url = "/cli-json/show_text_blob" + response_json = [] + data = {"type": "Plugin-Settings"} + if config_name and blob_test_name: + data["name"] = "%s%s" % (blob_test_name, config_name) # config name + else: + data["name"] = "ALL" + if brief: + data["brief"] = "brief" + self.json_post(req_url, data, response_json_list_=response_json) + return response_json + + def rm_text_blob(self, config_name, blob_test_name): + req_url = "/cli-json/rm_text_blob" + data = { + "type": "Plugin-Settings", + "name": str(blob_test_name + config_name), # config name + } + rsp = self.json_post(req_url, data) + + def rm_cv_text_blob(self, type="Network-Connectivity", name=None): + req_url = "/cli-json/rm_text_blob" + data = { + "type": type, + "name": name, # config name + } + rsp = self.json_post(req_url, data) + + def apply_cfg_options(self, cfg_options, enables, disables, raw_lines, raw_lines_file): + + # Read in calibration data and whatever else. + if raw_lines_file != "": + with open(raw_lines_file) as fp: + line = fp.readline() + while line: + cfg_options.append(line) + line = fp.readline() + fp.close() + + for en in enables: + cfg_options.append("%s: 1" % (en[0])) + + for en in disables: + cfg_options.append("%s: 0" % (en[0])) + + for r in raw_lines: + cfg_options.append(r[0]) + + def build_cfg(self, config_name, blob_test, cfg_options): + for value in cfg_options: + self.create_test_config(config_name, blob_test, value) + + # Request GUI update its text blob listing. + self.show_text_blob(config_name, blob_test, False) + + # Hack, not certain if the above show returns before the action has been completed + # or not, so we sleep here until we have better idea how to query if GUI knows about + # the text blob. + time.sleep(5) + + # load_old_config is boolean + # test_name is specific to the type of test being launched (Dataplane, tr398, etc) + # ChamberViewFrame.java has list of supported test names. + # instance_name is per-test instance, it does not matter much, just use the same name + # throughout the entire run of the test. + # config_name what to call the text-blob that configures the test. Does not matter much + # since we (re)create it during the run. + # sets: Arrany of [key,value] pairs. The key is the widget name, typically the label + # before the entry field. + # pull_report: Boolean, should we download the report to current working directory. + # lf_host: LANforge machine running the GUI. + # lf_password: Password for LANforge machine running the GUI. + # cv_cmds: Array of raw chamber-view commands, such as "cv click 'button-name'" + # These (and the sets) are applied after the test is created and before it is started. + def create_and_run_test(self, load_old_cfg, test_name, instance_name, config_name, sets, + pull_report, lf_host, lf_user, lf_password, cv_cmds, local_lf_report_dir="", ssh_port=22, + graph_groups_file=None): + load_old = "false" + if load_old_cfg: + load_old = "true" + + start_try = 0 + while True: + response = self.create_test(test_name, instance_name, load_old) + if response[0]["LAST"]["response"] == "OK": + break + else: + print("Could not create test, try: %i/60:\n" % (start_try)) + pprint(response) + start_try += 1 + if start_try > 60: + print("ERROR: Could not start within 60 tries, aborting.") + exit(1) + time.sleep(1) + + self.load_test_config(config_name, instance_name) + self.auto_save_report(instance_name) + + for kv in sets: + cmd = "cv set '%s' '%s' '%s'" % (instance_name, kv[0], kv[1]) + print("Running CV set command: ", cmd) + self.run_cv_cmd(cmd) + + for cmd in cv_cmds: + print("Running CV command: ", cmd) + self.run_cv_cmd(cmd) + + response = self.start_test(instance_name) + if response[0]["LAST"]["response"].__contains__("Could not find instance:"): + print("ERROR: start_test failed: ", response[0]["LAST"]["response"], "\n"); + # pprint(response) + exit(1) + + not_running = 0 + while True: + cmd = "cv get_and_close_dialog" + dialog = self.run_cv_cmd(cmd) + try: + if dialog[0]["LAST"]["response"] != "NO-DIALOG": + print("Popup Dialog:\n") + print(dialog[0]["LAST"]["response"]) + except Exception as e: + print(e) + + check = self.get_report_location(instance_name) + location = json.dumps(check[0]["LAST"]["response"]) + + if location != '\"Report Location:::\"': + print(location) + location = location.replace('\"Report Location:::', '') + location = location.replace('\"', '') + report = lf_rpt() + print(graph_groups_file) + if graph_groups_file is not None: + filelocation = open(graph_groups_file, 'a') + if pull_report: + location2 = location.replace('/home/lanforge/html-reports/', '') + filelocation.write(location2 + '/kpi.csv\n') + else: + filelocation.write(location + '/kpi.csv\n') + filelocation.close() + print(location) + self.lf_report_dir = location + if pull_report: + try: + print(lf_host) + report.pull_reports(hostname=lf_host, username=lf_user, password=lf_password, + port=ssh_port, report_dir=local_lf_report_dir, + report_location=location) + except Exception as e: + print("SCP failed, user %s, password %s, dest %s", (lf_user, lf_password, lf_host)) + raise e # Exception("Could not find Reports") + break + + # Of if test stopped for some reason and could not generate report. + if not self.get_is_running(instance_name): + print("Detected test is not running.") + not_running += 1 + if not_running > 5: + break + + time.sleep(1) + self.report_name = self.get_report_location(instance_name) + # Ensure test is closed and cleaned up + self.delete_instance(instance_name) + + # Clean up any remaining popups. + while True: + dialog = self.run_cv_cmd(cmd); + if dialog[0]["LAST"]["response"] != "NO-DIALOG": + print("Popup Dialog:\n") + print(dialog[0]["LAST"]["response"]) + else: + break + + def a(self): + pass + + # Takes cmd-line args struct or something that looks like it. + # See csv_to_influx.py::influx_add_parser_args for options, or --help. + def check_influx_kpi(self, args): + if self.lf_report_dir == "": + # Nothing to report on. + print("Not submitting to influx, no report-dir.\n") + return + + if args.influx_host is None: + # No influx configured, return. + print("Not submitting to influx, influx_host not configured.\n") + return + + print("Creating influxdb connection, host: %s:%s org: %s token: %s bucket: %s\n" % + (args.influx_host, args.influx_port, args.influx_org, args.influx_token, args.influx_bucket)) + # lfjson_host would be if we are reading out of LANforge or some other REST + # source, which we are not. So dummy those out. + influxdb = RecordInflux(_influx_host=args.influx_host, + _influx_port=args.influx_port, + _influx_org=args.influx_org, + _influx_token=args.influx_token, + _influx_bucket=args.influx_bucket) + + # lf_wifi_capacity_test.py may be run / initiated by a remote system against a lanforge + # the local_lf_report_dir is where data is stored, if there is no local_lf_report_dir then the test is run directly on lanforge + if self.local_lf_report_dir == "": + csv_path = "%s/kpi.csv" % (self.lf_report_dir) + else: + kpi_location = self.local_lf_report_dir + "/" + os.path.basename(self.lf_report_dir) + # the local_lf_report_dir is the parent directory, need to get the directory name + csv_path = "%s/kpi.csv" % (kpi_location) + + print("Attempt to submit kpi: ", csv_path) + print("Posting to influx...\n") + influxdb.csv_to_influx(csv_path) + + print("All done posting to influx.\n") + + # ************************** chamber view ************************** + def add_text_blob_line(self, + scenario_name="Automation", + Resources="1.1", + Profile="STA-AC", + Amount="1", + DUT="DUT", + Dut_Radio="Radio-1", + Uses1="wiphy0", + Uses2="AUTO", + Traffic="http", + Freq="-1", + VLAN=""): + req_url = "/cli-json/add_text_blob" + + text_blob = "profile_link" + " " + Resources + " " + Profile + " " + Amount + " " + "\'DUT:" + " " + DUT \ + + " " + Dut_Radio + "\' " + Traffic + " " + Uses1 + "," + Uses2 + " " + Freq + " " + VLAN + + data = { + "type": "Network-Connectivity", + "name": scenario_name, + "text": text_blob + } + + rsp = self.json_post(req_url, data) + + def pass_raw_lines_to_cv(self, + scenario_name="Automation", + Rawline=""): + req_url = "/cli-json/add_text_blob" + data = { + "type": "Network-Connectivity", + "name": scenario_name, + "text": Rawline + } + rsp = self.json_post(req_url, data) + + # This is for chamber view buttons + + def apply_cv_scenario(self, cv_scenario): + cmd = "cv apply '%s'" % cv_scenario # To apply scenario + self.run_cv_cmd(cmd) + print("Applying %s scenario" % cv_scenario) + + def build_cv_scenario(self): # build chamber view scenario + cmd = "cv build" + self.run_cv_cmd(cmd) + print("Building scenario") + + def get_cv_build_status(self): # check if scenario is build + cmd = "cv is_built" + response = self.run_cv_cmd(cmd) + return self.check_reponse(response) + + def sync_cv(self): # sync + cmd = "cv sync" + print(self.run_cv_cmd(cmd)) + + def run_cv_cmd(self, command): # Send chamber view commands + response_json = [] + req_url = "/gui-json/cmd" + data = { + "cmd": command + } + rsp = self.json_post(req_url, data, debug_=False, response_json_list_=response_json) + return response_json + + def get_response_string(self, response): + return response[0]["LAST"]["response"] + + def get_popup_info_and_close(self): + cmd = "cv get_and_close_dialog" + dialog = self.run_cv_cmd(cmd); + if dialog[0]["LAST"]["response"] != "NO-DIALOG": + print("Popup Dialog:\n") + print(dialog[0]["LAST"]["response"]) diff --git a/lanforge/lanforge-scripts/py-json/cv_test_reports.py b/lanforge/lanforge-scripts/py-json/cv_test_reports.py new file mode 100644 index 000000000..325bb9d90 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/cv_test_reports.py @@ -0,0 +1,17 @@ +import paramiko +from scp import SCPClient + +class lanforge_reports: + + def pull_reports(self, hostname="localhost", port=22, username="lanforge", password="lanforge", + report_location="/home/lanforge/html-reports/", + report_dir="../../../reports/"): + ssh = paramiko.SSHClient() + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(hostname=hostname, username=username, password=password, port=port, allow_agent=False, look_for_keys=False) + + with SCPClient(ssh.get_transport()) as scp: + scp.get(remote_path=report_location, local_path=report_dir, recursive=True) + scp.close() + diff --git a/lanforge/lanforge-scripts/py-json/dataplane_test_profile.py b/lanforge/lanforge-scripts/py-json/dataplane_test_profile.py new file mode 100644 index 000000000..770044720 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/dataplane_test_profile.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +Library to Run Dataplane Test: Using lf_cv_base class + +""" +import sys +import os +import importlib + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lf_cv_base = importlib.import_module("py-json.lf_cv_base") +ChamberViewBase = lf_cv_base.ChamberViewBase + + +class DataPlaneTest(ChamberViewBase): + + def __init__(self, lfclient_host="localhost", lfclient_port=8080, debug_=False): + super().__init__(_lfjson_host=lfclient_host, _lfjson_port=lfclient_port, _debug=debug_) + self.set_config() + + def set_config(self): + blob_data = """show_events: 1 show_log: 0 port_sorting: 0 kpi_id: Dataplane Pkt-Size bg: 0xE0ECF8 test_rig: show_scan: 1 auto_helper: 0 skip_2: 0 skip_5: 0 skip_5b: 1 skip_dual: 0 skip_tri: 1 selected_dut: TIP duration: 15000 traffic_port: 1.1.136 sta00500 upstream_port: 1.1.2 eth2 path_loss: 10 speed: 85% speed2: 0Kbps min_rssi_bound: -150 max_rssi_bound: 0 channels: AUTO modes: Auto pkts: 60;142;256;512;1024;MTU spatial_streams: AUTO security_options: AUTObandw_options: AUTO traffic_types: UDP;TCP directions: DUT Transmit;DUT Receive txo_preamble: OFDM txo_mcs: 0 CCK, OFDM, HT, VHT txo_retries: No Retry txo_sgi: OFF txo_txpower: 15 attenuator: 0 attenuator2: 0 attenuator_mod: 255 attenuator_mod2: 255 attenuations: 0..+50..950 attenuations2: 0..+50..950 chamber: 0 tt_deg: 0..+45..359 cust_pkt_sz: show_bar_labels: 1 show_prcnt_tput: 0 show_3s: 0 show_ll_graphs: 1 show_gp_graphs: 1 show_1m: 1 pause_iter: 0 show_realtime: 1 operator: mconn: 1 mpkt: 1000 tos: 0 loop_iterations: 1""" + self.add_text_blobs(type="Plugin-Settings", name="dataplane-test-latest-shivam", data=blob_data) + pass + + def set_params(self): + pass + + def run_test(self): + pass + + def wait_until_test_finishes(self): + pass + + def collect_reports(self): + pass + + +def main(): + obj = DataPlaneTest(lfclient_host="localhost", lfclient_port=8080, debug_=True) + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-json/dut_profile.py b/lanforge/lanforge-scripts/py-json/dut_profile.py new file mode 100644 index 000000000..141ff9530 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/dut_profile.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +from pprint import pprint +import base64 + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +add_dut = importlib.import_module("py-json.LANforge.add_dut") + + +class DUTProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, local_realm, debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_, _local_realm=local_realm) + self.name = "NA" + self.flags = "NA" + self.img_file = "NA" + self.sw_version = "NA" + self.hw_version = "NA" + self.model_num = "NA" + self.serial_num = "NA" + self.serial_port = "NA" + self.wan_port = "NA" + self.lan_port = "NA" + self.ssid1 = "NA" + self.ssid2 = "NA" + self.ssid3 = "NA" + self.passwd1 = "NA" + self.passwd2 = "NA" + self.passwd3 = "NA" + self.mgt_ip = "NA" + self.api_id = "NA" + self.flags_mask = "NA" + self.antenna_count1 = "NA" + self.antenna_count2 = "NA" + self.antenna_count3 = "NA" + self.bssid1 = "NA" + self.bssid2 = "NA" + self.bssid3 = "NA" + self.top_left_x = "NA" + self.top_left_y = "NA" + self.eap_id = "NA" + self.flags = 0 + self.flags_mask = 0 + self.notes = [] + self.append = [] + + def set_param(self, name, value): + if (name in self.__dict__): + self.__dict__[name] = value + + def create(self, name=None, param_=None, flags=None, flags_mask=None, notes=None): + data = {} + if (name is not None) and (name != ""): + data["name"] = name + elif (self.name is not None) and (self.name != ""): + data["name"] = self.name + else: + raise ValueError("cannot create/update DUT record lacking a name") + + for param in add_dut.dut_params: + if (param.name in self.__dict__): + if (self.__dict__[param.name] is not None) \ + and (self.__dict__[param.name] != "NA"): + data[param.name] = self.__dict__[param.name] + else: + print("---------------------------------------------------------") + pprint(self.__dict__[param.name]) + print("---------------------------------------------------------") + raise ValueError("parameter %s not in dut_profile" % param) + + if (flags is not None) and (int(flags) > -1): + data["flags"] = flags + elif (self.flags is not None) and (self.flags > -1): + data["flags"] = self.flags + + if (flags_mask is not None) and (int(flags_mask) > -1): + data["flags_mask"] = flags_mask + elif (self.flags_mask is not None) and (int(self.flags_mask) > -1): + data["flags_mask"] = self.flags_mask + + url = "/cli-json/add_dut" + if self.debug: + print("---- DATA -----------------------------------------------") + pprint(data) + pprint(self.notes) + pprint(self.append) + print("---------------------------------------------------------") + self.json_post(url, data, debug_=self.debug) + + if (self.notes is not None) and (len(self.notes) > 0): + self.json_post("/cli-json/add_dut_notes", { + "dut": self.name, + "text": "[BLANK]" + }, self.debug) + notebytes = None + for line in self.notes: + notebytes = base64.b64encode(line.encode('ascii')) + if self.debug: + print("------ NOTES ---------------------------------------------------") + pprint(self.notes) + pprint(str(notebytes)) + print("---------------------------------------------------------") + self.json_post("/cli-json/add_dut_notes", { + "dut": self.name, + "text-64": notebytes.decode('ascii') + }, self.debug) + if (self.append is not None) and (len(self.append) > 0): + notebytes = None + for line in self.append: + notebytes = base64.b64encode(line.encode('ascii')) + if self.debug: + print("----- APPEND ----------------------------------------------------") + pprint(line) + pprint(str(notebytes)) + print("---------------------------------------------------------") + self.json_post("/cli-json/add_dut_notes", { + "dut": self.name, + "text-64": notebytes.decode('ascii') + }, self.debug) diff --git a/lanforge/lanforge-scripts/py-json/fio_endp_profile.py b/lanforge/lanforge-scripts/py-json/fio_endp_profile.py new file mode 100644 index 000000000..a34979232 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/fio_endp_profile.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# Class: FIOEndpProfile(LFCliBase) + +# Written by Candela Technologies Inc. +# Updated by: +import sys +import os +import importlib +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class FIOEndpProfile(LFCliBase): + """ + Very often you will create the FileIO writer profile first so that it creates the data + that a reader profile will subsequently use. + """ + + def __init__(self, lfclient_host, lfclient_port, local_realm, io_direction="write", debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_) + self.local_realm = local_realm + self.fs_type = "fe_nfsv4" + self.min_rw_size = 128 * 1024 + self.max_rw_size = 128 * 1024 + self.min_file_size = 10 * 1024 * 1024 + self.max_file_size = 10 * 1024 * 1024 + + self.min_read_rate_bps = 10 * 1000 * 1000 + self.max_read_rate_bps = 10 * 1000 * 1000 + self.min_write_rate_bps = 1000 * 1000 * 1000 + self.max_write_rate_bps = 1000 * 1000 * 1000 + + self.file_num = 10 # number of files to write + self.directory = None # directory like /mnt/lf/$endp_name + + # this refers to locally mounted directories presently used for writing + # you would set this when doing read tests simultaneously to write tests + # so like, if your endpoint names are like wo_300GB_001, your Directory value + # defaults to /mnt/lf/wo_300GB_001; but reader enpoint would be named + # /mnt/lf/ro_300GB_001, this overwrites a readers directory name to wo_300GB_001 + self.mount_dir = "AUTO" + + self.server_mount = None # like cifs://10.0.0.1/bashful or 192.168.1.1:/var/tmp + self.mount_options = None + self.iscsi_vol = None + self.retry_timer_ms = 2000 + self.io_direction = io_direction # read / write + self.quiesce_ms = 3000 + self.pattern = "increasing" + self.file_prefix = "AUTO" # defaults to endp_name + self.cx_prefix = "wo_" + + self.created_cx = {} + self.created_endp = [] + + def start_cx(self): + print("Starting CXs...") + for cx_name in self.created_cx.keys(): + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name], + "cx_state": "RUNNING" + }, debug_=self.debug) + print(".", end='') + print("") + + def stop_cx(self): + print("Stopping CXs...") + for cx_name in self.created_cx.keys(): + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name], + "cx_state": "STOPPED" + }, debug_=self.debug) + print(".", end='') + print("") + + def create_ro_profile(self): + ro_profile = self.local_realm.new_fio_endp_profile() + ro_profile.realm = self.local_realm + + ro_profile.fs_type = self.fs_type + ro_profile.min_read_rate_bps = self.min_write_rate_bps + ro_profile.max_read_rate_bps = self.max_write_rate_bps + ro_profile.min_write_rate_bps = self.min_read_rate_bps + ro_profile.max_write_rate_bps = self.max_read_rate_bps + ro_profile.file_num = self.file_num + ro_profile.directory = self.directory + ro_profile.mount_dir = self.directory + ro_profile.server_mount = self.server_mount + ro_profile.mount_options = self.mount_options + ro_profile.iscsi_vol = self.iscsi_vol + ro_profile.retry_timer_ms = self.retry_timer_ms + ro_profile.io_direction = "read" + ro_profile.quiesce_ms = self.quiesce_ms + ro_profile.pattern = self.pattern + ro_profile.file_prefix = self.file_prefix + ro_profile.cx_prefix = "ro_" + return ro_profile + + def cleanup(self): + print("Cleaning up cxs and endpoints") + if len(self.created_cx) != 0: + for cx_name in self.created_cx.keys(): + req_url = "cli-json/rm_cx" + data = { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name] + } + self.json_post(req_url, data) + # pprint(data) + req_url = "cli-json/rm_endp" + data = { + "endp_name": cx_name + } + self.json_post(req_url, data) + # pprint(data) + + def create(self, ports=[], connections_per_port=1, sleep_time=.5, debug_=False, suppress_related_commands_=None): + cx_post_data = [] + for port_name in ports: + for num_connection in range(connections_per_port): + # + if len(self.local_realm.name_to_eid(port_name)) >= 3: + shelf = self.local_realm.name_to_eid(port_name)[0] + resource = self.local_realm.name_to_eid(port_name)[1] + name = self.local_realm.name_to_eid(port_name)[2] + else: + raise ValueError("Unexpected name for port_name %s" % port_name) + if self.directory is None or self.server_mount is None or self.fs_type is None: + raise ValueError("directory [%s], server_mount [%s], and type [%s] must not be None" % ( + self.directory, self.server_mount, self.fs_type)) + endp_data = { + "alias": self.cx_prefix + name + "_" + str(num_connection) + "_fio", + "shelf": shelf, + "resource": resource, + "port": name, + "type": self.fs_type, + "min_read_rate": self.min_read_rate_bps, + "max_read_rate": self.max_read_rate_bps, + "min_write_rate": self.min_write_rate_bps, + "max_write_rate": self.max_write_rate_bps, + "directory": self.directory, + "server_mount": self.server_mount, + "mount_dir": self.mount_dir, + "prefix": self.file_prefix, + "payload_pattern": self.pattern, + + } + # Read direction is copy of write only directory + if self.io_direction == "read": + endp_data["prefix"] = "wo_" + name + "_" + str(num_connection) + "_fio" + endp_data["directory"] = "/mnt/lf/wo_" + name + "_" + str(num_connection) + "_fio" + + url = "cli-json/add_file_endp" + self.local_realm.json_post(url, endp_data, debug_=False, + suppress_related_commands_=suppress_related_commands_) + time.sleep(sleep_time) + + data = { + "name": self.cx_prefix + name + "_" + str(num_connection) + "_fio", + "io_direction": self.io_direction, + "num_files": 5 + } + self.local_realm.json_post("cli-json/set_fe_info", data, debug_=debug_, + suppress_related_commands_=suppress_related_commands_) + + self.local_realm.json_post("/cli-json/nc_show_endpoints", {"endpoint": "all"}) + for port_name in ports: + for num_connection in range(connections_per_port): + shelf = self.local_realm.name_to_eid(port_name)[0] + resource = self.local_realm.name_to_eid(port_name)[1] + name = self.local_realm.name_to_eid(port_name)[2] + + endp_data = { + "alias": "CX_" + self.cx_prefix + name + "_" + str(num_connection) + "_fio", + "test_mgr": "default_tm", + "tx_endp": self.cx_prefix + name + "_" + str(num_connection) + "_fio", + "rx_endp": "NA" + } + cx_post_data.append(endp_data) + self.created_cx[self.cx_prefix + name + "_" + str( + num_connection) + "_fio"] = "CX_" + self.cx_prefix + name + "_" + str(num_connection) + "_fio" + + for cx_data in cx_post_data: + url = "/cli-json/add_cx" + self.local_realm.json_post(url, cx_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands_) + time.sleep(sleep_time) diff --git a/lanforge/lanforge-scripts/py-json/gen_cxprofile.py b/lanforge/lanforge-scripts/py-json/gen_cxprofile.py new file mode 100644 index 000000000..f2d870c52 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/gen_cxprofile.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +from pprint import pprint +import csv +import pandas as pd +import time +import datetime +import json + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +pandas_extensions = importlib.import_module("py-json.LANforge.pandas_extensions") + + +class GenCXProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, local_realm, debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_) + self.lfclient_host = lfclient_host + self.lfclient_port = lfclient_port + self.lfclient_url = "http://%s:%s" % (lfclient_host, lfclient_port) + self.debug = debug_ + self.type = "lfping" + self.dest = "127.0.0.1" + self.interval = 1 + self.cmd = "" + self.local_realm = local_realm + self.name_prefix = "generic" + self.created_cx = [] + self.created_endp = [] + self.file_output = "/dev/null" + self.loop_count = 1 + self.speedtest_min_dl = 0 + self.speedtest_min_up = 0 + self.speedtest_max_ping = 0 + + def parse_command(self, sta_name, gen_name): + if self.type == "lfping": + if ((self.dest is not None) or (self.dest != "")) and ((self.interval is not None) or (self.interval > 0)): + self.cmd = "%s -i %s -I %s %s" % (self.type, self.interval, sta_name, self.dest) + # print(self.cmd) + else: + raise ValueError("Please ensure dest and interval have been set correctly") + elif self.type == "generic": + if self.cmd == "": + raise ValueError("Please ensure cmd has been set correctly") + elif self.type == "speedtest": + self.cmd = "vrf_exec.bash %s speedtest-cli --json --share" % (sta_name) + elif self.type == "iperf3" and self.dest is not None: + self.cmd = "iperf3 --forceflush --format k --precision 4 -c %s -t 60 --tos 0 -b 1K --bind_dev %s -i 1 " \ + "--pidfile /tmp/lf_helper_iperf3_%s.pid" % (self.dest, sta_name, gen_name) + elif self.type == "iperf3_serv" and self.dest is not None: + self.cmd = "iperf3 --forceflush --format k --precision 4 -s --bind_dev %s -i 1 " \ + "--pidfile /tmp/lf_helper_iperf3_%s.pid" % (sta_name, gen_name) + elif self.type == "lfcurl": + if self.file_output is not None: + self.cmd = "./scripts/lf_curl.sh -p %s -i AUTO -o %s -n %s -d %s" % \ + (sta_name, self.file_output, self.loop_count, self.dest) + else: + raise ValueError("Please ensure file_output has been set correctly") + else: + raise ValueError("Unknown command type") + + def start_cx(self): + print("Starting CXs...") + # print(self.created_cx) + # print(self.created_endp) + for cx_name in self.created_cx: + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": cx_name, + "cx_state": "RUNNING" + }, debug_=self.debug) + print(".", end='') + print("") + + def stop_cx(self): + print("Stopping CXs...") + for cx_name in self.created_cx: + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": cx_name, + "cx_state": "STOPPED" + }, debug_=self.debug) + print(".", end='') + print("") + + def cleanup(self): + print("Cleaning up cxs and endpoints") + for cx_name in self.created_cx: + req_url = "cli-json/rm_cx" + data = { + "test_mgr": "default_tm", + "cx_name": cx_name + } + self.json_post(req_url, data) + + for endp_name in self.created_endp: + req_url = "cli-json/rm_endp" + data = { + "endp_name": endp_name + } + self.json_post(req_url, data) + + def set_flags(self, endp_name, flag_name, val): + data = { + "name": endp_name, + "flag": flag_name, + "val": val + } + self.json_post("cli-json/set_endp_flag", data, debug_=self.debug) + + def set_cmd(self, endp_name, cmd): + data = { + "name": endp_name, + "command": cmd + } + self.json_post("cli-json/set_gen_cmd", data, debug_=self.debug) + + def parse_command_gen(self, sta_name, dest): + if self.type == "lfping": + if ((self.dest is not None) or (self.dest != "")) and ((self.interval is not None) or (self.interval > 0)): + self.cmd = "%s -i %s -I %s %s" % (self.type, self.interval, sta_name, dest) + # print(self.cmd) + else: + raise ValueError("Please ensure dest and interval have been set correctly") + elif self.type == "generic": + if self.cmd == "": + raise ValueError("Please ensure cmd has been set correctly") + elif self.type == "speedtest": + self.cmd = "vrf_exec.bash %s speedtest-cli --json --share" % (sta_name) + elif self.type == "iperf3" and self.dest is not None: + self.cmd = "iperf3 --forceflush --format k --precision 4 -c %s -t 60 --tos 0 -b 1K --bind_dev %s -i 1 " \ + "--pidfile /tmp/lf_helper_iperf3_test.pid" % (self.dest, sta_name) + elif self.type == "lfcurl": + if self.file_output is not None: + self.cmd = "./scripts/lf_curl.sh -p %s -i AUTO -o %s -n %s -d %s" % \ + (sta_name, self.file_output, self.loop_count, self.dest) + else: + raise ValueError("Please ensure file_output has been set correctly") + else: + raise ValueError("Unknown command type") + + def create_gen(self, sta_port, dest, add, sleep_time=.5, debug_=False, suppress_related_commands_=None): + if self.debug: + debug_ = True + post_data = [] + endp_tpls = [] + + if type(sta_port) == str: + if sta_port != "1.1.eth1": + count = 5 + else: + count = 40 + for i in range(0, count): + port_info = self.local_realm.name_to_eid(sta_port) + resource = port_info[1] + shelf = port_info[0] + name = port_info[2] + + gen_name_a = "%s-%s" % (self.name_prefix, name) + "_" + str(i) + add + gen_name_b = "D_%s-%s" % (self.name_prefix, name) + "_" + str(i) + add + endp_tpls.append((shelf, resource, name, gen_name_a, gen_name_b)) + + print(endp_tpls) + elif type(sta_port) == list: + for port_name in sta_port: + print("hello............", sta_port) + for i in range(0, 5): + port_info = self.local_realm.name_to_eid(port_name) + try: + resource = port_info[1] + shelf = port_info[0] + name = port_info[2] + except: + raise ValueError("Unexpected name for port_name %s" % port_name) + + # this naming convention follows what you see when you use + # lf_firemod.pl --action list_endp after creating a generic endpoint + gen_name_a = "%s-%s" % (self.name_prefix, name) + "_" + str(i) + add + gen_name_b = "D_%s-%s" % (self.name_prefix, name) + "_" + str(i) + add + endp_tpls.append((shelf, resource, name, gen_name_a, gen_name_b)) + + # exit(1) + print(endp_tpls) + + for endp_tpl in endp_tpls: + shelf = endp_tpl[0] + resource = endp_tpl[1] + name = endp_tpl[2] + gen_name_a = endp_tpl[3] + # gen_name_b = endp_tpl[3] + # (self, alias=None, shelf=1, resource=1, port=None, type=None) + + data = { + "alias": gen_name_a, + "shelf": shelf, + "resource": resource, + "port": name, + "type": "gen_generic" + } + pprint(data) + if self.debug: + pprint(data) + + self.json_post("cli-json/add_gen_endp", data, debug_=self.debug) + + self.local_realm.json_post("/cli-json/nc_show_endpoints", {"endpoint": "all"}) + time.sleep(sleep_time) + + for endp_tpl in endp_tpls: + gen_name_a = endp_tpl[3] + gen_name_b = endp_tpl[4] + self.set_flags(gen_name_a, "ClearPortOnStart", 1) + time.sleep(sleep_time) + + if type(dest) == str: + for endp_tpl in endp_tpls: + name = endp_tpl[2] + gen_name_a = endp_tpl[3] + # gen_name_b = endp_tpl[4] + self.parse_command_gen(name, dest) + self.set_cmd(gen_name_a, self.cmd) + time.sleep(sleep_time) + + elif type(dest) == list: + mm = 0 + for endp_tpl in endp_tpls: + name = endp_tpl[2] + gen_name_a = endp_tpl[3] + # gen_name_b = endp_tpl[4] + self.parse_command_gen(name, dest[mm]) + self.set_cmd(gen_name_a, self.cmd) + mm = mm + 1 + if mm == 8: + mm = 0 + time.sleep(sleep_time) + + j = 0 + for endp_tpl in endp_tpls: + name = endp_tpl[2] + gen_name_a = endp_tpl[3] + gen_name_b = endp_tpl[4] + cx_name = "CX_%s-%s" % (self.name_prefix, name) + "_" + str(j) + add + j = j + 1 + data = { + "alias": cx_name, + "test_mgr": "default_tm", + "tx_endp": gen_name_a, + "rx_endp": gen_name_b + } + post_data.append(data) + # self.created_cx = [] + self.created_cx.append(cx_name) + # self.created_endp = [] + self.created_endp.append(gen_name_a) + self.created_endp.append(gen_name_b) + time.sleep(sleep_time) + + print(self.created_cx) + + for data in post_data: + url = "/cli-json/add_cx" + pprint(data) + if self.debug: + pprint(data) + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands_) + time.sleep(2) + time.sleep(sleep_time) + for data in post_data: + self.local_realm.json_post("/cli-json/show_cx", { + "test_mgr": "default_tm", + "cross_connect": data["alias"] + }) + time.sleep(sleep_time) + + def create(self, ports=[], sleep_time=.5, debug_=False, suppress_related_commands_=None): + if self.debug: + debug_ = True + post_data = [] + endp_tpls = [] + for port_name in ports: + port_info = self.local_realm.name_to_eid(port_name) + resource = port_info[1] + shelf = port_info[0] + name = port_info[2] + + # this naming convention follows what you see when you use + # lf_firemod.pl --action list_endp after creating a generic endpoint + gen_name_a = "%s-%s" % (self.name_prefix, name) + gen_name_b = "D_%s-%s" % (self.name_prefix, name) + endp_tpls.append((shelf, resource, name, gen_name_a, gen_name_b)) + + for endp_tpl in endp_tpls: + shelf = endp_tpl[0] + resource = endp_tpl[1] + name = endp_tpl[2] + gen_name_a = endp_tpl[3] + # gen_name_b = endp_tpl[3] + # (self, alias=None, shelf=1, resource=1, port=None, type=None) + + data = { + "alias": gen_name_a, + "shelf": shelf, + "resource": resource, + "port": name, + "type": "gen_generic" + } + if self.debug: + pprint(data) + + self.json_post("cli-json/add_gen_endp", data, debug_=self.debug) + + self.local_realm.json_post("/cli-json/nc_show_endpoints", {"endpoint": "all"}) + time.sleep(sleep_time) + + for endp_tpl in endp_tpls: + gen_name_a = endp_tpl[3] + gen_name_b = endp_tpl[4] + self.set_flags(gen_name_a, "ClearPortOnStart", 1) + time.sleep(sleep_time) + + for endp_tpl in endp_tpls: + name = endp_tpl[2] + gen_name_a = endp_tpl[3] + # gen_name_b = endp_tpl[4] + self.parse_command(name, gen_name_a) + self.set_cmd(gen_name_a, self.cmd) + time.sleep(sleep_time) + + for endp_tpl in endp_tpls: + name = endp_tpl[2] + gen_name_a = endp_tpl[3] + gen_name_b = endp_tpl[4] + cx_name = "CX_%s-%s" % (self.name_prefix, name) + data = { + "alias": cx_name, + "test_mgr": "default_tm", + "tx_endp": gen_name_a, + "rx_endp": gen_name_b + } + post_data.append(data) + self.created_cx.append(cx_name) + self.created_endp.append(gen_name_a) + self.created_endp.append(gen_name_b) + + time.sleep(sleep_time) + + for data in post_data: + url = "/cli-json/add_cx" + if self.debug: + pprint(data) + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands_) + time.sleep(2) + time.sleep(sleep_time) + for data in post_data: + self.local_realm.json_post("/cli-json/show_cx", { + "test_mgr": "default_tm", + "cross_connect": data["alias"] + }) + time.sleep(sleep_time) + + def choose_ping_command(self): + gen_results = self.json_get("generic/list?fields=name,last+results", debug_=self.debug) + if self.debug: + print(gen_results) + if gen_results['endpoints'] is not None: + for name in gen_results['endpoints']: + for k, v in name.items(): + if v['name'] in self.created_endp and not v['name'].endswith('1'): + if v['last results'] != "" and "Unreachable" not in v['last results']: + return True, v['name'] + else: + return False, v['name'] + + def choose_lfcurl_command(self): + gen_results = self.json_get("generic/list?fields=name,last+results", debug_=self.debug) + if self.debug: + print(gen_results) + if gen_results['endpoints'] is not None: + for name in gen_results['endpoints']: + for k, v in name.items(): + if v['name'] != '': + results = v['last results'].split() + if 'Finished' in v['last results']: + if results[1][:-1] == results[2]: + return True, v['name'] + else: + return False, v['name'] + + def choose_iperf3_command(self): + gen_results = self.json_get("generic/list?fields=name,last+results", debug_=self.debug) + if gen_results['endpoints'] is not None: + pprint(gen_results['endpoints']) + #for name in gen_results['endpoints']: + # pprint(name.items) + #for k,v in name.items(): + exit(1) + + + def choose_speedtest_command(self): + gen_results = self.json_get("generic/list?fields=name,last+results", debug_=self.debug) + if gen_results['endpoints'] is not None: + for name in gen_results['endpoints']: + for k, v in name.items(): + if v['last results'] is not None and v['name'] in self.created_endp and v['last results'] != '': + last_results = json.loads(v['last results']) + if last_results['download'] is None and last_results['upload'] is None and last_results['ping'] is None: + return False, v['name'] + elif last_results['download'] >= self.speedtest_min_dl and \ + last_results['upload'] >= self.speedtest_min_up and \ + last_results['ping'] <= self.speedtest_max_ping: + return True, v['name'] + + def choose_generic_command(self): + gen_results = self.json_get("generic/list?fields=name,last+results", debug_=self.debug) + if (gen_results['endpoints'] is not None): + for name in gen_results['endpoints']: + for k, v in name.items(): + if v['name'] in self.created_endp and not v['name'].endswith('1'): + if v['last results'] != "" and "not known" not in v['last results']: + return True, v['name'] + else: + return False, v['name'] + + def monitor(self, + duration_sec=60, + monitor_interval_ms=1, + sta_list=None, + generic_cols=None, + port_mgr_cols=None, + created_cx=None, + monitor=True, + report_file=None, + systeminfopath=None, + output_format=None, + script_name=None, + arguments=None, + compared_report=None, + debug=False): + try: + duration_sec = self.parse_time(duration_sec).seconds + except: + if (duration_sec is None) or (duration_sec <= 1): + raise ValueError("GenCXProfile::monitor wants duration_sec > 1 second") + if (duration_sec <= monitor_interval_ms): + raise ValueError("GenCXProfile::monitor wants duration_sec > monitor_interval") + if report_file is None: + raise ValueError("Monitor requires an output file to be defined") + if systeminfopath is None: + raise ValueError("Monitor requires a system info path to be defined") + if created_cx is None: + raise ValueError("Monitor needs a list of Layer 3 connections") + if (monitor_interval_ms is None) or (monitor_interval_ms < 1): + raise ValueError("GenCXProfile::monitor wants monitor_interval >= 1 second") + if generic_cols is None: + raise ValueError("GenCXProfile::monitor wants a list of column names to monitor") + if output_format is not None: + if output_format.lower() != report_file.split('.')[-1]: + raise ValueError( + 'Filename %s has an extension that does not match output format %s .' % (report_file, output_format)) + else: + output_format = report_file.split('.')[-1] + + # default save to csv first + if report_file.split('.')[-1] != 'csv': + report_file = report_file.replace(str(output_format), 'csv', 1) + print("Saving rolling data into..." + str(report_file)) + + # ================== Step 1, set column names and header row + generic_cols = [self.replace_special_char(x) for x in generic_cols] + generic_fields = ",".join(generic_cols) + default_cols = ['Timestamp', 'Timestamp milliseconds epoch', 'Timestamp seconds epoch', 'Duration elapsed'] + default_cols.extend(generic_cols) + if port_mgr_cols is not None: + default_cols.extend(port_mgr_cols) + header_row = default_cols + + # csvwriter.writerow([systeminfo['VersionInfo']['BuildVersion'], script_name, str(arguments)]) + + if port_mgr_cols is not None: + port_mgr_cols = [self.replace_special_char(x) for x in port_mgr_cols] + port_mgr_cols_labelled = [] + for col_name in port_mgr_cols: + port_mgr_cols_labelled.append("port mgr - " + col_name) + + port_mgr_fields = ",".join(port_mgr_cols) + header_row.extend(port_mgr_cols_labelled) + # create sys info file + systeminfo = self.json_get('/') + sysinfo = [str("LANforge GUI Build: " + systeminfo['VersionInfo']['BuildVersion']), + str("Script Name: " + script_name), str("Argument input: " + str(arguments))] + with open(systeminfopath, 'w') as filehandle: + for listitem in sysinfo: + filehandle.write('%s\n' % listitem) + + # ================== Step 2, monitor columns + start_time = datetime.datetime.now() + end_time = start_time + datetime.timedelta(seconds=duration_sec) + + passes = 0 + expected_passes = 0 + + # instantiate csv file here, add specified column headers + csvfile = open(str(report_file), 'w') + csvwriter = csv.writer(csvfile, delimiter=",") + csvwriter.writerow(header_row) + + # wait 10 seconds to get proper port data + time.sleep(10) + + # for x in range(0,int(round(iterations,0))): + initial_starttime = datetime.datetime.now() + print("Starting Test...") + while datetime.datetime.now() < end_time: + + passes = 0 + expected_passes = 0 + time.sleep(15) + result = False + cur_time = datetime.datetime.now() + if self.type == "lfping": + result = self.choose_ping_command() + elif self.type == "generic": + result = self.choose_generic_command() + elif self.type == "lfcurl": + result = self.choose_lfcurl_command() + elif self.type == "speedtest": + result = self.choose_speedtest_command() + elif self.type == "iperf3": + result = self.choose_iperf3_command() + else: + continue + expected_passes += 1 + if result is not None: + if result[0]: + passes += 1 + else: + self._fail("%s Failed to ping %s " % (result[1], self.dest)) + break + time.sleep(1) + + if passes == expected_passes: + self._pass("PASS: All tests passed") + + t = datetime.datetime.now() + timestamp = t.strftime("%m/%d/%Y %I:%M:%S") + t_to_millisec_epoch = int(self.get_milliseconds(t)) + t_to_sec_epoch = int(self.get_seconds(t)) + time_elapsed = int(self.get_seconds(t)) - int(self.get_seconds(initial_starttime)) + basecolumns = [timestamp, t_to_millisec_epoch, t_to_sec_epoch, time_elapsed] + + generic_response = self.json_get("/generic/%s?fields=%s" % (created_cx, generic_fields)) + + if port_mgr_cols is not None: + port_mgr_response = self.json_get("/port/1/1/%s?fields=%s" % (sta_list, port_mgr_fields)) + # get info from port manager with list of values from cx_a_side_list + if "endpoints" not in generic_response or generic_response is None: + print(generic_response) + raise ValueError("Cannot find columns requested to be searched. Exiting script, please retry.") + if debug: + print("Json generic_response from LANforge... " + str(generic_response)) + if port_mgr_cols is not None: + if "interfaces" not in port_mgr_response or port_mgr_response is None: + print(port_mgr_response) + raise ValueError("Cannot find columns requested to be searched. Exiting script, please retry.") + if debug: + print("Json port_mgr_response from LANforge... " + str(port_mgr_response)) + + for endpoint in generic_response["endpoints"]: # each endpoint is a dictionary + endp_values = list(endpoint.values())[0] + temp_list = basecolumns + for columnname in header_row[len(basecolumns):]: + temp_list.append(endp_values[columnname]) + if port_mgr_cols is not None: + for sta_name in sta_list_edit: + if sta_name in current_sta: + for interface in port_mgr_response["interfaces"]: + if sta_name in list(interface.keys())[0]: + merge = temp_endp_values.copy() + # rename keys (separate port mgr 'rx bytes' from generic 'rx bytes') + port_mgr_values_dict = list(interface.values())[0] + renamed_port_cols = {} + for key in port_mgr_values_dict.keys(): + renamed_port_cols['port mgr - ' + key] = port_mgr_values_dict[key] + merge.update(renamed_port_cols) + for name in port_mgr_cols: + temp_list.append(merge[name]) + csvwriter.writerow(temp_list) + + time.sleep(monitor_interval_ms) + csvfile.close() + + # comparison to last report / report inputted + if compared_report is not None: + compared_df = pandas_extensions.compare_two_df(dataframe_one=pandas_extensions.file_to_df(report_file), + dataframe_two=pandas_extensions.file_to_df(compared_report)) + exit(1) + # append compared df to created one + if output_format.lower() != 'csv': + pandas_extensions.df_to_file(dataframe=pd.read_csv(report_file), output_f=output_format, save_path=report_file) + else: + if output_format.lower() != 'csv': + pandas_extensions.df_to_file(dataframe=pd.read_csv(report_file), output_f=output_format, save_path=report_file) diff --git a/lanforge/lanforge-scripts/py-json/http_profile.py b/lanforge/lanforge-scripts/py-json/http_profile.py new file mode 100644 index 000000000..9acd05d5a --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/http_profile.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +port_utils = importlib.import_module("py-json.port_utils") +PortUtils = port_utils.PortUtils +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class HTTPProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, local_realm, debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_) + self.lfclient_url = "http://%s:%s" % (lfclient_host, lfclient_port) + self.debug = debug_ + self.requests_per_ten = 600 + self.local_realm = local_realm + self.created_cx = {} + self.created_endp = [] + self.ip_map = {} + self.direction = "dl" + self.dest = "/dev/null" + self.port_util = PortUtils(self.local_realm) + self.max_speed = 0 #infinity + + def check_errors(self, debug=False): + fields_list = ["!conn", "acc.+denied", "bad-proto", "bad-url", "other-err", "total-err", "rslv-p", "rslv-h", + "timeout", "nf+(4xx)", "http-r", "http-p", "http-t", "login-denied"] + endp_list = self.json_get("layer4/list?fields=%s" % ','.join(fields_list)) + debug_info = {} + if endp_list is not None and endp_list['endpoint'] is not None: + endp_list = endp_list['endpoint'] + expected_passes = len(endp_list) + passes = len(endp_list) + for item in range(len(endp_list)): + for name, info in endp_list[item].items(): + for field in fields_list: + if info[field.replace("+", " ")] > 0: + passes -= 1 + debug_info[name] = {field: info[field.replace("+", " ")]} + if debug: + print(debug_info) + if passes == expected_passes: + return True + else: + print(list(debug_info), " Endps in this list showed errors getting to its URL") # %s") % self.url) + return False + + def start_cx(self): + print("Starting CXs...") + for cx_name in self.created_cx.keys(): + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name], + "cx_state": "RUNNING" + }, debug_=self.debug) + print(".", end='') + print("") + + def stop_cx(self): + print("Stopping CXs...") + for cx_name in self.created_cx.keys(): + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name], + "cx_state": "STOPPED" + }, debug_=self.debug) + print(".", end='') + print("") + + def cleanup(self): + print("Cleaning up cxs and endpoints") + if len(self.created_cx) != 0: + for cx_name in self.created_cx.keys(): + req_url = "cli-json/rm_cx" + data = { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name] + } + self.json_post(req_url, data) + # pprint(data) + req_url = "cli-json/rm_endp" + data = { + "endp_name": cx_name + } + self.json_post(req_url, data) + # pprint(data) + + def map_sta_ips(self, sta_list=[]): + for sta_eid in sta_list: + eid = self.local_realm.name_to_eid(sta_eid) + sta_list = self.json_get("/port/%s/%s/%s?fields=alias,ip" % + (eid[0], eid[1], eid[2])) + if sta_list['interface'] is not None: + self.ip_map[sta_list['interface']['alias']] = sta_list['interface']['ip'] + + def create(self, ports=[], sleep_time=.5, debug_=False, suppress_related_commands_=None, http=False, ftp=False, + https=False, user=None, passwd=None, source=None, ftp_ip=None, upload_name=None, http_ip=None, https_ip=None): + cx_post_data = [] + self.map_sta_ips(ports) + print("Create CXs...") + + for i in range(len(list(self.ip_map))): + url = None + if i != len(list(self.ip_map)) - 1: + port_name = list(self.ip_map)[i] + ip_addr = self.ip_map[list(self.ip_map)[i + 1]] + else: + port_name = list(self.ip_map)[i] + ip_addr = self.ip_map[list(self.ip_map)[0]] + + if (ip_addr is None) or (ip_addr == ""): + raise ValueError("HTTPProfile::create encountered blank ip/hostname") + + shelf = self.local_realm.name_to_eid(port_name)[0] + resource = self.local_realm.name_to_eid(port_name)[1] + name = self.local_realm.name_to_eid(port_name)[2] + + if upload_name != None: + name = upload_name + + if http: + if http_ip is not None: + self.port_util.set_http(port_name=name, resource=resource, on=True) + url = "%s http://%s %s" % (self.direction, http_ip, self.dest) + else: + self.port_util.set_http(port_name=name, resource=resource, on=True) + url = "%s http://%s/ %s" % (self.direction, ip_addr, self.dest) + if https: + if https_ip is not None: + self.port_util.set_http(port_name=name, resource=resource, on=True) + url = "%s https://%s %s" % (self.direction, https_ip, self.dest) + else: + self.port_util.set_http(port_name=name, resource=resource, on=True) + url = "%s https://%s/ %s" % (self.direction, ip_addr, self.dest) + + if ftp: + self.port_util.set_ftp(port_name=name, resource=resource, on=True) + if user is not None and passwd is not None and source is not None: + if ftp_ip is not None: + ip_addr=ftp_ip + url = "%s ftp://%s:%s@%s%s %s" % (self.direction, user, passwd, ip_addr, source, self.dest) + print("###### url:{}".format(url)) + else: + raise ValueError("user: %s, passwd: %s, and source: %s must all be set" % (user, passwd, source)) + if not http and not ftp and not https: + raise ValueError("Please specify ftp and/or http") + + if (url is None) or (url == ""): + raise ValueError("HTTPProfile::create: url unset") + + if upload_name ==None: + endp_data = { + "alias": name + "_l4", + "shelf": shelf, + "resource": resource, + "port": name, + "type": "l4_generic", + "timeout": 10, + "url_rate": self.requests_per_ten, + "url": url, + "proxy_auth_type": 0x200 + } + else: + endp_data = { + "alias": name + "_l4", + "shelf": shelf, + "resource": resource, + "port": ports[0], + "type": "l4_generic", + "timeout": 10, + "url_rate": self.requests_per_ten, + "url": url, + "ssl_cert_fname": "ca-bundle.crt", + "proxy_port": 0, + "max_speed": self.max_speed, + "proxy_auth_type": 0x200 + } + url = "cli-json/add_l4_endp" + self.local_realm.json_post(url, endp_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands_) + time.sleep(sleep_time) + + endp_data = { + "alias": "CX_" + name + "_l4", + "test_mgr": "default_tm", + "tx_endp": name + "_l4", + "rx_endp": "NA" + } + cx_post_data.append(endp_data) + self.created_cx[name + "_l4"] = "CX_" + name + "_l4" + + for cx_data in cx_post_data: + url = "/cli-json/add_cx" + self.local_realm.json_post(url, cx_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands_) + time.sleep(sleep_time) diff --git a/lanforge/lanforge-scripts/py-json/l3_cxprofile.py b/lanforge/lanforge-scripts/py-json/l3_cxprofile.py new file mode 100644 index 000000000..524820fb7 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/l3_cxprofile.py @@ -0,0 +1,586 @@ +# !/usr/bin/env python3 +import sys +import os +import importlib +from pprint import pprint +import csv +import pandas as pd +import time +import datetime + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +pandas_extensions = importlib.import_module("py-json.LANforge.pandas_extensions") + + +class L3CXProfile(LFCliBase): + def __init__(self, + lfclient_host, + lfclient_port, + local_realm, + side_a_min_bps=None, + side_b_min_bps=None, + side_a_max_bps=0, + side_b_max_bps=0, + side_a_min_pdu=-1, + side_b_min_pdu=-1, + side_a_max_pdu=0, + side_b_max_pdu=0, + report_timer_=3000, + name_prefix_="Unset", + number_template_="00000", + mconn=0, + debug_=False): + """ + :param lfclient_host: + :param lfclient_port: + :param local_realm: + :param side_a_min_bps: + :param side_b_min_bps: + :param side_a_max_bps: + :param side_b_max_bps: + :param side_a_min_pdu: + :param side_b_min_pdu: + :param side_a_max_pdu: + :param side_b_max_pdu: + :param name_prefix_: prefix string for connection + :param number_template_: how many zeros wide we padd, possibly a starting integer with left padding + :param mconn: Multi-conn setting for this connection. + :param debug_: + """ + super().__init__(lfclient_host, lfclient_port, _debug=debug_) + self.debug = debug_ + self.local_realm = local_realm + self.side_a_min_pdu = side_a_min_pdu + self.side_b_min_pdu = side_b_min_pdu + self.side_a_max_pdu = side_a_max_pdu + self.side_b_max_pdu = side_b_max_pdu + self.side_a_min_bps = side_a_min_bps + self.side_b_min_bps = side_b_min_bps + self.side_a_max_bps = side_a_max_bps + self.side_b_max_bps = side_b_max_bps + self.report_timer = report_timer_ + self.created_cx = {} + self.created_endp = {} + self.name_prefix = name_prefix_ + self.number_template = number_template_ + self.mconn = mconn + + def get_cx_count(self): + return len(self.created_cx.keys()) + + def get_cx_names(self): + return self.created_cx.keys() + + def get_cx_report(self): + self.data = {} + for cx_name in self.get_cx_names(): + self.data[cx_name] = self.json_get("/cx/" + cx_name).get(cx_name) + return self.data + + def __get_rx_values(self): + cx_list = self.json_get("endp?fields=name,rx+bytes") + if self.debug: + print(self.created_cx.values()) + print("==============\n", cx_list, "\n==============") + cx_rx_map = {} + for cx_name in cx_list['endpoint']: + if cx_name != 'uri' and cx_name != 'handler': + for item, value in cx_name.items(): + for value_name, value_rx in value.items(): + if value_name == 'rx bytes' and item in self.created_cx.values(): + cx_rx_map[item] = value_rx + return cx_rx_map + + def __compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + if len(old_list) == len(new_list): + for item, value in old_list.items(): + expected_passes += 1 + if new_list[item] > old_list[item]: + passes += 1 + + if passes == expected_passes: + return True + else: + return False + else: + return False + + def instantiate_file(self, file_name, file_format): + pass + + def monitor(self, + duration_sec=60, + monitor_interval_ms=1, + sta_list=None, + layer3_cols=None, + port_mgr_cols=None, + created_cx=None, + monitor=True, + report_file=None, + systeminfopath=None, + output_format=None, + script_name=None, + arguments=None, + compared_report=None, + debug=False): + try: + duration_sec = self.parse_time(duration_sec).seconds + except: + if (duration_sec is None) or (duration_sec <= 1): + raise ValueError("L3CXProfile::monitor wants duration_sec > 1 second") + if (duration_sec <= monitor_interval_ms): + raise ValueError("L3CXProfile::monitor wants duration_sec > monitor_interval") + if report_file == None: + raise ValueError("Monitor requires an output file to be defined") + if systeminfopath == None: + raise ValueError("Monitor requires a system info path to be defined") + if created_cx == None: + raise ValueError("Monitor needs a list of Layer 3 connections") + if (monitor_interval_ms is None) or (monitor_interval_ms < 1): + raise ValueError("L3CXProfile::monitor wants monitor_interval >= 1 second") + if layer3_cols is None: + raise ValueError("L3CXProfile::monitor wants a list of column names to monitor") + if output_format is not None: + if output_format.lower() != report_file.split('.')[-1]: + raise ValueError('Filename %s has an extension that does not match output format %s .' % ( + report_file, output_format)) + else: + output_format = report_file.split('.')[-1] + + # default save to csv first + if report_file.split('.')[-1] != 'csv': + report_file = report_file.replace(str(output_format), 'csv', 1) + print("Saving rolling data into..." + str(report_file)) + + # ================== Step 1, set column names and header row + layer3_cols = [self.replace_special_char(x) for x in layer3_cols] + layer3_fields = ",".join(layer3_cols) + default_cols = ['Timestamp', 'Timestamp milliseconds epoch', 'Timestamp seconds epoch', 'Duration elapsed'] + default_cols.extend(layer3_cols) + if port_mgr_cols is not None: + default_cols.extend(port_mgr_cols) + header_row = default_cols + + # csvwriter.writerow([systeminfo['VersionInfo']['BuildVersion'], script_name, str(arguments)]) + + if port_mgr_cols is not None: + port_mgr_cols = [self.replace_special_char(x) for x in port_mgr_cols] + port_mgr_cols_labelled = [] + for col_name in port_mgr_cols: + port_mgr_cols_labelled.append("port mgr - " + col_name) + + port_mgr_fields = ",".join(port_mgr_cols) + header_row.extend(port_mgr_cols_labelled) + # create sys info file + systeminfo = self.json_get('/') + sysinfo = [str("LANforge GUI Build: " + systeminfo['VersionInfo']['BuildVersion']), + str("Script Name: " + script_name), str("Argument input: " + str(arguments))] + with open(systeminfopath, 'w') as filehandle: + for listitem in sysinfo: + filehandle.write('%s\n' % listitem) + + # ================== Step 2, monitor columns + start_time = datetime.datetime.now() + end_time = start_time + datetime.timedelta(seconds=duration_sec) + + passes = 0 + expected_passes = 0 + old_cx_rx_values = self.__get_rx_values() + + # instantiate csv file here, add specified column headers + csvfile = open(str(report_file), 'w') + csvwriter = csv.writer(csvfile, delimiter=",") + csvwriter.writerow(header_row) + + # wait 10 seconds to get proper port data + time.sleep(10) + + # for x in range(0,int(round(iterations,0))): + initial_starttime = datetime.datetime.now() + while datetime.datetime.now() < end_time: + t = datetime.datetime.now() + timestamp = t.strftime("%m/%d/%Y %I:%M:%S") + t_to_millisec_epoch = int(self.get_milliseconds(t)) + t_to_sec_epoch = int(self.get_seconds(t)) + time_elapsed = int(self.get_seconds(t)) - int(self.get_seconds(initial_starttime)) + basecolumns = [timestamp, t_to_millisec_epoch, t_to_sec_epoch, time_elapsed] + layer_3_response = self.json_get("/endp/%s?fields=%s" % (created_cx, layer3_fields)) + if port_mgr_cols is not None: + port_mgr_response = self.json_get("/port/1/1/%s?fields=%s" % (sta_list, port_mgr_fields)) + # get info from port manager with list of values from cx_a_side_list + if "endpoint" not in layer_3_response or layer_3_response is None: + print(layer_3_response) + raise ValueError("Cannot find columns requested to be searched. Exiting script, please retry.") + if debug: + print("Json layer_3_response from LANforge... " + str(layer_3_response)) + if port_mgr_cols is not None: + if "interfaces" not in port_mgr_response or port_mgr_response is None: + print(port_mgr_response) + raise ValueError("Cannot find columns requested to be searched. Exiting script, please retry.") + if debug: + print("Json port_mgr_response from LANforge... " + str(port_mgr_response)) + + for endpoint in layer_3_response["endpoint"]: # each endpoint is a dictionary + endp_values = list(endpoint.values())[0] + temp_list = basecolumns + for columnname in header_row[len(basecolumns):]: + temp_list.append(endp_values[columnname]) + if port_mgr_cols is not None: + for sta_name in sta_list_edit: + if sta_name in current_sta: + for interface in port_mgr_response["interfaces"]: + if sta_name in list(interface.keys())[0]: + merge = temp_endp_values.copy() + # rename keys (separate port mgr 'rx bytes' from layer3 'rx bytes') + port_mgr_values_dict = list(interface.values())[0] + renamed_port_cols = {} + for key in port_mgr_values_dict.keys(): + renamed_port_cols['port mgr - ' + key] = port_mgr_values_dict[key] + merge.update(renamed_port_cols) + for name in port_mgr_cols: + temp_list.append(merge[name]) + csvwriter.writerow(temp_list) + + new_cx_rx_values = self.__get_rx_values() + if debug: + print(old_cx_rx_values, new_cx_rx_values) + print("\n-----------------------------------") + print(t) + print("-----------------------------------\n") + expected_passes += 1 + if self.__compare_vals(old_cx_rx_values, new_cx_rx_values): + passes += 1 + else: + self.fail("FAIL: Not all stations increased traffic") + self.exit_fail() + try: + cx_data = self.json_get("/cx/all") + cx_data.pop("handler") + cx_data.pop("uri") + + for i in self.created_cx.keys(): + endp_a_data = self.json_get("/endp/"+ cx_data[i]['endpoints'][0]) + endp_b_data = self.json_get("/endp/" + cx_data[i]['endpoints'][1]) + print("cx name:", i, "\n", + " bps tx a :", endp_a_data['endpoint']['tx rate'], " --> ", + " bps rx b : ", endp_b_data['endpoint']['rx rate'], + " rx drop % b : ", cx_data[i]['rx drop % b'], "\n" + " tx bytes a : ", endp_a_data['endpoint']['tx bytes'], " --> " + " rx bytes b", endp_b_data['endpoint']['rx bytes'], "\n" + " tx bytes b : ", endp_b_data['endpoint']['tx bytes'], " --> " + " rx bytes a", endp_a_data['endpoint']['rx bytes'], "\n" + " bps tx b :", endp_b_data['endpoint']['tx rate'], " --> " + " bps rx a : ", endp_a_data['endpoint']['rx rate'], + " rx drop % a :", cx_data[i]['rx drop % a'], "\n" + " pkt rx a :", cx_data[i]['pkt rx a'], " pkt rx b : ", cx_data[i]['pkt rx b'], + ) + print("\n\n\n") + except Exception as e: + print(e) + time.sleep(monitor_interval_ms) + csvfile.close() + + # comparison to last report / report inputted + if compared_report is not None: + compared_df = pandas_extensions.compare_two_df(dataframe_one=pandas_extensions.file_to_df(report_file), + dataframe_two=pandas_extensions.file_to_df(compared_report)) + exit(1) + # append compared df to created one + if output_format.lower() != 'csv': + pandas_extensions.df_to_file(dataframe=pd.read_csv(report_file), output_f=output_format, save_path=report_file) + else: + if output_format.lower() != 'csv': + pandas_extensions.df_to_file(dataframe=pd.read_csv(report_file), output_f=output_format, save_path=report_file) + + def refresh_cx(self): + for cx_name in self.created_cx.keys(): + self.json_post("/cli-json/show_cxe", { + "test_mgr": "ALL", + "cross_connect": cx_name + }, debug_=self.debug) + print(".", end='') + + def start_cx(self): + print("Starting CXs...") + for cx_name in self.created_cx.keys(): + if self.debug: + print("cx-name: %s" % (cx_name)) + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": cx_name, + "cx_state": "RUNNING" + }, debug_=self.debug) + if self.debug: + print(".", end='') + if self.debug: + print("") + + def stop_cx(self): + print("Stopping CXs...") + for cx_name in self.created_cx.keys(): + self.local_realm.stop_cx(cx_name) + print(".", end='') + print("") + + def cleanup_prefix(self): + self.local_realm.cleanup_cxe_prefix(self.name_prefix) + + def cleanup(self): + print("Cleaning up cxs and endpoints") + if len(self.created_cx) != 0: + for cx_name in self.created_cx.keys(): + if self.debug: + print("Cleaning cx: %s" % (cx_name)) + self.local_realm.rm_cx(cx_name) + + for side in range(len(self.created_cx[cx_name])): + ename = self.created_cx[cx_name][side] + if self.debug: + print("Cleaning endpoint: %s" % (ename)) + self.local_realm.rm_endp(self.created_cx[cx_name][side]) + + self.clean_cx_lists() + + def clean_cx_lists(self): + # Clean out our local lists, this by itself does NOT remove anything from LANforge manager. + # but, if you are trying to modify existing connections, then clearing these arrays and + # re-calling 'create' will do the trick. + self.created_cx.clear() + self.created_endp.clear() + + def create(self, endp_type, side_a, side_b, sleep_time=0.03, suppress_related_commands=None, debug_=False, + tos=None): + if self.debug: + debug_ = True + + cx_post_data = [] + timer_post_data = [] + these_endp = [] + these_cx = [] + + # print(self.side_a_min_rate, self.side_a_max_rate) + # print(self.side_b_min_rate, self.side_b_max_rate) + if (self.side_a_min_bps is None) \ + or (self.side_a_max_bps is None) \ + or (self.side_b_min_bps is None) \ + or (self.side_b_max_bps is None): + raise ValueError( + "side_a_min_bps, side_a_max_bps, side_b_min_bps, and side_b_max_bps must all be set to a value") + + if type(side_a) == list and type(side_b) != list: + side_b_info = self.local_realm.name_to_eid(side_b) + side_b_shelf = side_b_info[0] + side_b_resource = side_b_info[1] + + for port_name in side_a: + side_a_info = self.local_realm.name_to_eid(port_name, debug=debug_) + side_a_shelf = side_a_info[0] + side_a_resource = side_a_info[1] + if port_name.find('.') < 0: + port_name = "%d.%s" % (side_a_info[1], port_name) + + cx_name = "%s%s-%i" % (self.name_prefix, side_a_info[2], len(self.created_cx)) + + endp_a_name = cx_name + "-A" + endp_b_name = cx_name + "-B" + self.created_cx[cx_name] = [endp_a_name, endp_b_name] + self.created_endp[endp_a_name] = endp_a_name + self.created_endp[endp_b_name] = endp_b_name + these_cx.append(cx_name) + these_endp.append(endp_a_name) + these_endp.append(endp_b_name) + mconn_b = self.mconn + if mconn_b > 1: + mconn_b = 1 + endp_side_a = { + "alias": endp_a_name, + "shelf": side_a_shelf, + "resource": side_a_resource, + "port": side_a_info[2], + "type": endp_type, + "min_rate": self.side_a_min_bps, + "max_rate": self.side_a_max_bps, + "min_pkt": self.side_a_min_pdu, + "max_pkt": self.side_a_max_pdu, + "ip_port": -1, + "multi_conn": self.mconn, + } + endp_side_b = { + "alias": endp_b_name, + "shelf": side_b_shelf, + "resource": side_b_resource, + "port": side_b_info[2], + "type": endp_type, + "min_rate": self.side_b_min_bps, + "max_rate": self.side_b_max_bps, + "min_pkt": self.side_b_min_pdu, + "max_pkt": self.side_b_max_pdu, + "ip_port": -1, + "multi_conn": mconn_b, + } + + # print("1: endp-side-b: ", endp_side_b) + + url = "/cli-json/add_endp" + self.local_realm.json_post(url, endp_side_a, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + self.local_realm.json_post(url, endp_side_b, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + # print("napping %f sec"%sleep_time) + time.sleep(sleep_time) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_a_name, + "flag": "AutoHelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + data["name"] = endp_b_name + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + if (endp_type == "lf_udp") or (endp_type == "udp") or (endp_type == "lf_udp6") or (endp_type == "udp6"): + data["name"] = endp_a_name + data["flag"] = "UseAutoNAT" + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + data["name"] = endp_b_name + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + if tos != None: + self.local_realm.set_endp_tos(endp_a_name, tos) + self.local_realm.set_endp_tos(endp_b_name, tos) + + data = { + "alias": cx_name, + "test_mgr": "default_tm", + "tx_endp": endp_a_name, + "rx_endp": endp_b_name, + } + # pprint(data) + cx_post_data.append(data) + timer_post_data.append({ + "test_mgr": "default_tm", + "cx_name": cx_name, + "milliseconds": self.report_timer + }) + + elif type(side_b) == list and type(side_a) != list: + side_a_info = self.local_realm.name_to_eid(side_a, debug=debug_) + side_a_shelf = side_a_info[0] + side_a_resource = side_a_info[1] + # side_a_name = side_a_info[2] + + for port_name in side_b: + print(side_b) + side_b_info = self.local_realm.name_to_eid(port_name, debug=debug_) + side_b_shelf = side_b_info[0] + side_b_resource = side_b_info[1] + side_b_name = side_b_info[2] + + cx_name = "%s%s-%i" % (self.name_prefix, port_name, len(self.created_cx)) + endp_a_name = cx_name + "-A" + endp_b_name = cx_name + "-B" + self.created_cx[cx_name] = [endp_a_name, endp_b_name] + self.created_endp[endp_a_name] = endp_a_name + self.created_endp[endp_b_name] = endp_b_name + these_cx.append(cx_name) + these_endp.append(endp_a_name) + these_endp.append(endp_b_name) + mconn_b = self.mconn + if mconn_b > 1: + mconn_b = 1 + endp_side_a = { + "alias": endp_a_name, + "shelf": side_a_shelf, + "resource": side_a_resource, + "port": side_a_info[2], + "type": endp_type, + "min_rate": self.side_a_min_bps, + "max_rate": self.side_a_max_bps, + "min_pkt": self.side_a_min_pdu, + "max_pkt": self.side_a_max_pdu, + "ip_port": -1, + "multi_conn": self.mconn, + } + endp_side_b = { + "alias": endp_b_name, + "shelf": side_b_shelf, + "resource": side_b_resource, + "port": side_b_info[2], + "type": endp_type, + "min_rate": self.side_b_min_bps, + "max_rate": self.side_b_max_bps, + "min_pkt": self.side_b_min_pdu, + "max_pkt": self.side_b_max_pdu, + "ip_port": -1, + "multi_conn": mconn_b, + } + + # print("2: endp-side-b: ", endp_side_b) + + url = "/cli-json/add_endp" + self.local_realm.json_post(url, endp_side_a, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + self.local_realm.json_post(url, endp_side_b, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + # print("napping %f sec" %sleep_time ) + time.sleep(sleep_time) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_a_name, + "flag": "autohelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_b_name, + "flag": "autohelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + # print("CXNAME451: %s" % cx_name) + data = { + "alias": cx_name, + "test_mgr": "default_tm", + "tx_endp": endp_a_name, + "rx_endp": endp_b_name, + } + cx_post_data.append(data) + timer_post_data.append({ + "test_mgr": "default_tm", + "cx_name": cx_name, + "milliseconds": self.report_timer + }) + else: + raise ValueError( + "side_a or side_b must be of type list but not both: side_a is type %s side_b is type %s" % ( + type(side_a), type(side_b))) + print("wait_until_endps_appear these_endp: {} debug_ {}".format(these_endp, debug_)) + self.local_realm.wait_until_endps_appear(these_endp, debug=debug_) + + for data in cx_post_data: + url = "/cli-json/add_cx" + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + time.sleep(0.01) + + self.local_realm.wait_until_cxs_appear(these_cx, debug=debug_) + + return these_cx, these_endp + + def to_string(self): + pprint.pprint(self) diff --git a/lanforge/lanforge-scripts/py-json/l3_cxprofile2.py b/lanforge/lanforge-scripts/py-json/l3_cxprofile2.py new file mode 100644 index 000000000..1bbc3e085 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/l3_cxprofile2.py @@ -0,0 +1,678 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import csv +from pprint import pprint +import time +import datetime + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfdata = importlib.import_module("py-json.lfdata") +LFDataCollection = lfdata.LFDataCollection +base_profile = importlib.import_module("py-json.base_profile") +BaseProfile = base_profile.BaseProfile + + +class L3CXProfile2(BaseProfile): + def __init__(self, + lfclient_host, + lfclient_port, + local_realm, + side_a_min_bps=None, + side_b_min_bps=None, + side_a_max_bps=0, + side_b_max_bps=0, + side_a_min_pdu=-1, + side_b_min_pdu=-1, + side_a_max_pdu=0, + side_b_max_pdu=0, + report_timer_=3000, + name_prefix_="Unset", + number_template_="00000", + debug_=False): + """ + :param lfclient_host: + :param lfclient_port: + :param local_realm: + :param side_a_min_bps: + :param side_b_min_bps: + :param side_a_max_bps: + :param side_b_max_bps: + :param side_a_min_pdu: + :param side_b_min_pdu: + :param side_a_max_pdu: + :param side_b_max_pdu: + :param name_prefix_: prefix string for connection + :param number_template_: how many zeros wide we padd, possibly a starting integer with left padding + :param debug_: + """ + super().__init__(local_realm=local_realm, + debug=debug_) + + self.side_a_min_pdu = side_a_min_pdu + self.side_b_min_pdu = side_b_min_pdu + self.side_a_max_pdu = side_a_max_pdu + self.side_b_max_pdu = side_b_max_pdu + self.side_a_min_bps = side_a_min_bps + self.side_b_min_bps = side_b_min_bps + self.side_a_max_bps = side_a_max_bps + self.side_b_max_bps = side_b_max_bps + self.report_timer = report_timer_ + self.created_cx = {} + self.created_endp = {} + self.name_prefix = name_prefix_ + self.number_template = number_template_ + + + + def get_cx_names(self): + return self.created_cx.keys() + + def get_cx_report(self): + self.data = {} + for cx_name in self.get_cx_names(): + self.data[cx_name] = self.json_get("/cx/" + cx_name).get(cx_name) + return self.data + + + def instantiate_file(self, file_name, file_format): + pass +############################################ transfer into lfcriteria.py + #get current rx values + def __get_rx_values(self): + cx_list = self.json_get("endp?fields=name,rx+bytes") + if self.debug: + print(self.created_cx.values()) + print("==============\n", cx_list, "\n==============") + cx_rx_map = {} + for cx_name in cx_list['endpoint']: + if cx_name != 'uri' and cx_name != 'handler': + for item, value in cx_name.items(): + for value_name, value_rx in value.items(): + if value_name == 'rx bytes' and item in self.created_cx.values(): + cx_rx_map[item] = value_rx + return cx_rx_map + #compare vals + def __compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + if len(old_list) == len(new_list): + for item, value in old_list.items(): + expected_passes += 1 + if new_list[item] > old_list[item]: + passes += 1 + + if passes == expected_passes: + return True + else: + return False + else: + return False +############################################ transfer into lfcriteria.py + + + def refresh_cx(self): + for cx_name in self.created_cx.keys(): + self.json_post("/cli-json/show_cxe", { + "test_mgr": "ALL", + "cross_connect": cx_name + }, debug_=self.debug) + print(".", end='') + + def start_cx(self): + print("Starting CXs...") + for cx_name in self.created_cx.keys(): + if self.debug: + print("cx-name: %s" % (cx_name)) + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": cx_name, + "cx_state": "RUNNING" + }, debug_=self.debug) + if self.debug: + print(".", end='') + if self.debug: + print("") + + def stop_cx(self): + print("Stopping CXs...") + for cx_name in self.created_cx.keys(): + self.local_realm.stop_cx(cx_name) + print(".", end='') + print("") + + def cleanup_prefix(self): + self.local_realm.cleanup_cxe_prefix(self.name_prefix) + + def cleanup(self): + print("Cleaning up cxs and endpoints") + if len(self.created_cx) != 0: + for cx_name in self.created_cx.keys(): + if self.debug: + print("Cleaning cx: %s"%(cx_name)) + self.local_realm.rm_cx(cx_name) + + for side in range(len(self.created_cx[cx_name])): + ename = self.created_cx[cx_name][side] + if self.debug: + print("Cleaning endpoint: %s"%(ename)) + self.local_realm.rm_endp(self.created_cx[cx_name][side]) + + # added this function create_cx by taking reference from the existing function (def create()) to pass the arguments with the script requirement + def create_cx(self, endp_type, side_a, side_b, count, sleep_time=0.03, suppress_related_commands=None, debug_=False, + tos=None): + if self.debug: + debug_ = True + + cx_post_data = [] + timer_post_data = [] + these_endp = [] + these_cx = [] + + # print(self.side_a_min_rate, self.side_a_max_rate) + # print(self.side_b_min_rate, self.side_b_max_rate) + if (self.side_a_min_bps is None) \ + or (self.side_a_max_bps is None) \ + or (self.side_b_min_bps is None) \ + or (self.side_b_max_bps is None): + raise ValueError( + "side_a_min_bps, side_a_max_bps, side_b_min_bps, and side_b_max_bps must all be set to a value") + + if type(side_a) != list and type(side_b) != list: + side_b_info = self.local_realm.name_to_eid(side_b) + side_b_shelf = side_b_info[0] + side_b_resource = side_b_info[1] + + for i in range(count): + side_a_info = self.local_realm.name_to_eid(side_a, debug=debug_) + side_a_shelf = side_a_info[0] + side_a_resource = side_a_info[1] + if side_a.find('.') < 0: + port_name = "%d.%s" % (side_a_info[1], side_a) + + cx_name = "%s%s-%i" % (self.name_prefix, side_a_info[2], len(self.created_cx)) + str(i) + + endp_a_name = cx_name + "-A" + endp_b_name = cx_name + "-B" + self.created_cx[cx_name] = [endp_a_name, endp_b_name] + self.created_endp[endp_a_name] = endp_a_name + self.created_endp[endp_b_name] = endp_b_name + these_cx.append(cx_name) + these_endp.append(endp_a_name) + these_endp.append(endp_b_name) + endp_side_a = { + "alias": endp_a_name, + "shelf": side_a_shelf, + "resource": side_a_resource, + "port": side_a_info[2], + "type": endp_type, + "min_rate": self.side_a_min_bps, + "max_rate": self.side_a_max_bps, + "min_pkt": self.side_a_min_pdu, + "max_pkt": self.side_a_max_pdu, + "ip_port": -1 + } + endp_side_b = { + "alias": endp_b_name, + "shelf": side_b_shelf, + "resource": side_b_resource, + "port": side_b_info[2], + "type": endp_type, + "min_rate": self.side_b_min_bps, + "max_rate": self.side_b_max_bps, + "min_pkt": self.side_b_min_pdu, + "max_pkt": self.side_b_max_pdu, + "ip_port": -1 + } + + url = "/cli-json/add_endp" + self.local_realm.json_post(url, endp_side_a, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + self.local_realm.json_post(url, endp_side_b, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + # print("napping %f sec"%sleep_time) + time.sleep(sleep_time) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_a_name, + "flag": "AutoHelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + data["name"] = endp_b_name + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + if (endp_type == "lf_udp") or (endp_type == "udp") or (endp_type == "lf_udp6") or (endp_type == "udp6"): + data["name"] = endp_a_name + data["flag"] = "UseAutoNAT" + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + data["name"] = endp_b_name + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + if tos != None: + self.local_realm.set_endp_tos(endp_a_name, tos) + self.local_realm.set_endp_tos(endp_b_name, tos) + + data = { + "alias": cx_name, + "test_mgr": "default_tm", + "tx_endp": endp_a_name, + "rx_endp": endp_b_name, + } + # pprint(data) + cx_post_data.append(data) + timer_post_data.append({ + "test_mgr": "default_tm", + "cx_name": cx_name, + "milliseconds": self.report_timer + }) + + elif type(side_b) == list and type(side_a) != list: + side_a_info = self.local_realm.name_to_eid(side_a, debug=debug_) + side_a_shelf = side_a_info[0] + side_a_resource = side_a_info[1] + # side_a_name = side_a_info[2] + + for port_name in side_b: + for inc in range(count): + # print(side_b) + side_b_info = self.local_realm.name_to_eid(port_name, debug=debug_) + side_b_shelf = side_b_info[0] + side_b_resource = side_b_info[1] + side_b_name = side_b_info[2] + + cx_name = "%s%s-%i" % (self.name_prefix, port_name, len(self.created_cx)) + str(inc) + endp_a_name = cx_name + "-A" + endp_b_name = cx_name + "-B" + self.created_cx[cx_name] = [endp_a_name, endp_b_name] + self.created_endp[endp_a_name] = endp_a_name + self.created_endp[endp_b_name] = endp_b_name + these_cx.append(cx_name) + these_endp.append(endp_a_name) + these_endp.append(endp_b_name) + endp_side_a = { + "alias": endp_a_name, + "shelf": side_a_shelf, + "resource": side_a_resource, + "port": side_a_info[2], + "type": endp_type, + "min_rate": self.side_a_min_bps, + "max_rate": self.side_a_max_bps, + "min_pkt": self.side_a_min_pdu, + "max_pkt": self.side_a_max_pdu, + "ip_port": -1 + } + endp_side_b = { + "alias": endp_b_name, + "shelf": side_b_shelf, + "resource": side_b_resource, + "port": side_b_info[2], + "type": endp_type, + "min_rate": self.side_b_min_bps, + "max_rate": self.side_b_max_bps, + "min_pkt": self.side_b_min_pdu, + "max_pkt": self.side_b_max_pdu, + "ip_port": -1 + } + + url = "/cli-json/add_endp" + self.local_realm.json_post(url, endp_side_a, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + self.local_realm.json_post(url, endp_side_b, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + # print("napping %f sec" %sleep_time ) + time.sleep(sleep_time) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_a_name, + "flag": "autohelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_b_name, + "flag": "autohelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + # print("CXNAME451: %s" % cx_name) + data = { + "alias": cx_name, + "test_mgr": "default_tm", + "tx_endp": endp_a_name, + "rx_endp": endp_b_name, + } + cx_post_data.append(data) + timer_post_data.append({ + "test_mgr": "default_tm", + "cx_name": cx_name, + "milliseconds": self.report_timer + }) + else: + raise ValueError( + "side_a or side_b must be of type list but not both: side_a is type %s side_b is type %s" % ( + type(side_a), type(side_b))) + print("wait_until_endps_appear these_endp: {} debug_ {}".format(these_endp, debug_)) + self.local_realm.wait_until_endps_appear(these_endp, debug=debug_) + + for data in cx_post_data: + url = "/cli-json/add_cx" + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + time.sleep(0.01) + + self.local_realm.wait_until_cxs_appear(these_cx, debug=debug_) + + def create(self, endp_type, side_a, side_b, sleep_time=0.03, suppress_related_commands=None, debug_=False, + tos=None): + if self.debug: + debug_ = True + + cx_post_data = [] + timer_post_data = [] + these_endp = [] + these_cx = [] + + # print(self.side_a_min_rate, self.side_a_max_rate) + # print(self.side_b_min_rate, self.side_b_max_rate) + if (self.side_a_min_bps is None) \ + or (self.side_a_max_bps is None) \ + or (self.side_b_min_bps is None) \ + or (self.side_b_max_bps is None): + raise ValueError( + "side_a_min_bps, side_a_max_bps, side_b_min_bps, and side_b_max_bps must all be set to a value") + + if type(side_a) == list and type(side_b) != list: + side_b_info = self.local_realm.name_to_eid(side_b) + side_b_shelf = side_b_info[0] + side_b_resource = side_b_info[1] + + for port_name in side_a: + side_a_info = self.local_realm.name_to_eid(port_name,debug=debug_) + side_a_shelf = side_a_info[0] + side_a_resource = side_a_info[1] + if port_name.find('.') < 0: + port_name = "%d.%s" % (side_a_info[1], port_name) + + cx_name = "%s%s-%i" % (self.name_prefix, side_a_info[2], len(self.created_cx)) + + endp_a_name = cx_name + "-A" + endp_b_name = cx_name + "-B" + self.created_cx[cx_name] = [endp_a_name, endp_b_name] + self.created_endp[endp_a_name] = endp_a_name + self.created_endp[endp_b_name] = endp_b_name + these_cx.append(cx_name) + these_endp.append(endp_a_name) + these_endp.append(endp_b_name) + endp_side_a = { + "alias": endp_a_name, + "shelf": side_a_shelf, + "resource": side_a_resource, + "port": side_a_info[2], + "type": endp_type, + "min_rate": self.side_a_min_bps, + "max_rate": self.side_a_max_bps, + "min_pkt": self.side_a_min_pdu, + "max_pkt": self.side_a_max_pdu, + "ip_port": -1 + } + endp_side_b = { + "alias": endp_b_name, + "shelf": side_b_shelf, + "resource": side_b_resource, + "port": side_b_info[2], + "type": endp_type, + "min_rate": self.side_b_min_bps, + "max_rate": self.side_b_max_bps, + "min_pkt": self.side_b_min_pdu, + "max_pkt": self.side_b_max_pdu, + "ip_port": -1 + } + + url = "/cli-json/add_endp" + self.local_realm.json_post(url, endp_side_a, debug_=debug_, suppress_related_commands_=suppress_related_commands) + self.local_realm.json_post(url, endp_side_b, debug_=debug_, suppress_related_commands_=suppress_related_commands) + #print("napping %f sec"%sleep_time) + time.sleep(sleep_time) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_a_name, + "flag": "AutoHelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + data["name"] = endp_b_name + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + + if (endp_type == "lf_udp") or (endp_type == "udp") or (endp_type == "lf_udp6") or (endp_type == "udp6"): + data["name"] = endp_a_name + data["flag"] = "UseAutoNAT" + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + data["name"] = endp_b_name + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + + if tos != None: + self.local_realm.set_endp_tos(endp_a_name, tos) + self.local_realm.set_endp_tos(endp_b_name, tos) + + data = { + "alias": cx_name, + "test_mgr": "default_tm", + "tx_endp": endp_a_name, + "rx_endp": endp_b_name, + } + # pprint(data) + cx_post_data.append(data) + timer_post_data.append({ + "test_mgr": "default_tm", + "cx_name": cx_name, + "milliseconds": self.report_timer + }) + + elif type(side_b) == list and type(side_a) != list: + side_a_info = self.local_realm.name_to_eid(side_a,debug=debug_) + side_a_shelf = side_a_info[0] + side_a_resource = side_a_info[1] + # side_a_name = side_a_info[2] + + for port_name in side_b: + print(side_b) + side_b_info = self.local_realm.name_to_eid(port_name,debug=debug_) + side_b_shelf = side_b_info[0] + side_b_resource = side_b_info[1] + side_b_name = side_b_info[2] + + cx_name = "%s%s-%i" % (self.name_prefix, port_name, len(self.created_cx)) + endp_a_name = cx_name + "-A" + endp_b_name = cx_name + "-B" + self.created_cx[cx_name] = [endp_a_name, endp_b_name] + self.created_endp[endp_a_name] = endp_a_name + self.created_endp[endp_b_name] = endp_b_name + these_cx.append(cx_name) + these_endp.append(endp_a_name) + these_endp.append(endp_b_name) + endp_side_a = { + "alias": endp_a_name, + "shelf": side_a_shelf, + "resource": side_a_resource, + "port": side_a_info[2], + "type": endp_type, + "min_rate": self.side_a_min_bps, + "max_rate": self.side_a_max_bps, + "min_pkt": self.side_a_min_pdu, + "max_pkt": self.side_a_max_pdu, + "ip_port": -1 + } + endp_side_b = { + "alias": endp_b_name, + "shelf": side_b_shelf, + "resource": side_b_resource, + "port": side_b_info[2], + "type": endp_type, + "min_rate": self.side_b_min_bps, + "max_rate": self.side_b_max_bps, + "min_pkt": self.side_b_min_pdu, + "max_pkt": self.side_b_max_pdu, + "ip_port": -1 + } + + url = "/cli-json/add_endp" + self.local_realm.json_post(url, endp_side_a, debug_=debug_, suppress_related_commands_=suppress_related_commands) + self.local_realm.json_post(url, endp_side_b, debug_=debug_, suppress_related_commands_=suppress_related_commands) + #print("napping %f sec" %sleep_time ) + time.sleep(sleep_time) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_a_name, + "flag": "autohelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + + url = "cli-json/set_endp_flag" + data = { + "name": endp_b_name, + "flag": "autohelper", + "val": 1 + } + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + #print("CXNAME451: %s" % cx_name) + data = { + "alias": cx_name, + "test_mgr": "default_tm", + "tx_endp": endp_a_name, + "rx_endp": endp_b_name, + } + cx_post_data.append(data) + timer_post_data.append({ + "test_mgr": "default_tm", + "cx_name": cx_name, + "milliseconds": self.report_timer + }) + else: + raise ValueError( + "side_a or side_b must be of type list but not both: side_a is type %s side_b is type %s" % ( + type(side_a), type(side_b))) + print("wait_until_endps_appear these_endp: {} debug_ {}".format(these_endp,debug_)) + self.local_realm.wait_until_endps_appear(these_endp, debug=debug_) + + for data in cx_post_data: + url = "/cli-json/add_cx" + self.local_realm.json_post(url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + time.sleep(0.01) + + self.local_realm.wait_until_cxs_appear(these_cx, debug=debug_) + + def to_string(self): + pprint(self) + + # temp transfer of functions from test script to class + def build(self): + self.create(endp_type="lf_udp", side_a=self.station_profile.station_names, side_b=self.upstream, + sleep_time=0) + def start(self): + self.start_cx() + + def stop(self): + self.stop_cx() + + #to do : have the variables saved in l3cx profile, upon creation of profile , and called) + def monitor_record(self, + duration_sec=60, + monitor_interval_ms=1, + sta_list=None, + layer3_cols=None, + port_mgr_cols=None, + created_cx=None, + report_file=None, + output_format=None, + script_name=None, + arguments=None, + compared_report=None, + debug=False): + try: + duration_sec = self.parse_time(duration_sec).seconds + except: + if (duration_sec is None) or (duration_sec <= 1): + raise ValueError("L3CXProfile::monitor wants duration_sec > 1 second") + if (duration_sec <= monitor_interval_ms): + raise ValueError("L3CXProfile::monitor wants duration_sec > monitor_interval") + if report_file == None: + raise ValueError("Monitor requires an output file to be defined") + if created_cx == None: + raise ValueError("Monitor needs a list of Layer 3 connections") + if (monitor_interval_ms is None) or (monitor_interval_ms < 1): + raise ValueError("L3CXProfile::monitor wants monitor_interval >= 1 second") + if layer3_cols is None: + raise ValueError("L3CXProfile::monitor wants a list of column names to monitor") + if output_format is not None: + if output_format.lower() != report_file.split('.')[-1]: + raise ValueError('Filename %s has an extension that does not match output format %s .' % (report_file, output_format)) + else: + output_format = report_file.split('.')[-1] + + + #default save to csv first + if report_file.split('.')[-1] != 'csv': + report_file = report_file.replace(str(output_format),'csv',1) + print("Saving rolling data into..." + str(report_file)) + + #add layer3 cols to header row + layer3_cols=[self.replace_special_char(x) for x in layer3_cols] + layer3_fields = ",".join(layer3_cols) + default_cols=['Timestamp','Timestamp milliseconds epoch','Duration elapsed'] + default_cols.extend(layer3_cols) + header_row=default_cols + + #add port mgr columns to header row + if port_mgr_cols is not None: + port_mgr_cols=[self.replace_special_char(x) for x in port_mgr_cols] + port_mgr_cols_labelled =[] + for col_name in port_mgr_cols: + port_mgr_cols_labelled.append("port mgr - " + col_name) + header_row.extend(port_mgr_cols_labelled) + + #add sys info to header row + systeminfo = self.json_get('/') + header_row.extend([str("LANforge GUI Build: " + systeminfo['VersionInfo']['BuildVersion']), str("Script Name: " + script_name), str("Argument input: " + str(arguments))]) + + #cut "sta" off all "sta_names" + sta_list_edit=[] + if sta_list is not None: + for sta in sta_list: + sta_list_edit.append(sta[4:]) + sta_list=",".join(sta_list_edit) + + #instantiate csv file here, add specified column headers + csvfile=open(str(report_file),'w') + csvwriter = csv.writer(csvfile,delimiter=",") + csvwriter.writerow(header_row) + + #wait 10 seconds to get IPs + time.sleep(10) + start_time = datetime.datetime.now() + end_time = start_time + datetime.timedelta(seconds=duration_sec) + + #create lf data object + lf_data_collection = LFDataCollection(local_realm=self.local_realm,debug=self.debug) + while datetime.datetime.now() < end_time: + csvwriter.writerow(lf_data_collection.monitor_interval(start_time_=start_time,sta_list_=sta_list_edit, created_cx_=created_cx, layer3_fields_=layer3_fields,port_mgr_fields_=",".join(port_mgr_cols))) + time.sleep(monitor_interval_ms) + csvfile.close() + + def pre_cleanup(self): + self.cleanup_prefix() diff --git a/lanforge/lanforge-scripts/py-json/l4_cxprofile.py b/lanforge/lanforge-scripts/py-json/l4_cxprofile.py new file mode 100644 index 000000000..25138e722 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/l4_cxprofile.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import requests +import pandas as pd +import time +import datetime +import ast + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class L4CXProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, local_realm, debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_) + self.lfclient_url = "http://%s:%s" % (lfclient_host, lfclient_port) + self.debug = debug_ + self.url = "http://localhost/" + self.requests_per_ten = 600 + self.local_realm = local_realm + self.created_cx = {} + self.created_endp = [] + self.test_type = "urls" + self.lfclient_port = lfclient_port + self.lfclient_host = lfclient_host + + def check_errors(self, debug=False): + fields_list = ["!conn", "acc.+denied", "bad-proto", "bad-url", "other-err", "total-err", "rslv-p", "rslv-h", + "timeout", "nf+(4xx)", "http-r", "http-p", "http-t", "login-denied"] + endp_list = self.json_get("layer4/list?fields=%s" % ','.join(fields_list)) + debug_info = {} + if endp_list is not None and endp_list['endpoint'] is not None: + endp_list = endp_list['endpoint'] + expected_passes = len(endp_list) + passes = len(endp_list) + for item in range(len(endp_list)): + for name, info in endp_list[item].items(): + for field in fields_list: + if info[field.replace("+", " ")] > 0: + passes -= 1 + debug_info[name] = {field: info[field.replace("+", " ")]} + if debug: + print(debug_info) + if passes == expected_passes: + return True + else: + print(list(debug_info), " Endps in this list showed errors getting to %s " % self.url) + return False + + def start_cx(self): + print("Starting CXs...") + for cx_name in self.created_cx.keys(): + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name], + "cx_state": "RUNNING" + }, debug_=self.debug) + print(".", end='') + print("") + + def stop_cx(self): + print("Stopping CXs...") + for cx_name in self.created_cx.keys(): + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name], + "cx_state": "STOPPED" + }, debug_=self.debug) + print(".", end='') + print("") + + def compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + if len(old_list) == len(new_list): + for item, value in old_list.items(): + expected_passes += 1 + if new_list[item] > old_list[item]: + passes += 1 + if passes == expected_passes: + return True + else: + return False + else: + return False + + def get_bytes(self): + time.sleep(1) + cx_list = self.json_get("layer4/list?fields=name,%s" % self.test_type, debug_=self.debug) + # print("==============\n", cx_list, "\n==============") + cx_map = {} + for cx_name in cx_list['endpoint']: + if cx_name != 'uri' and cx_name != 'handler': + for item, value in cx_name.items(): + for value_name, value_rx in value.items(): + if item in self.created_cx.keys() and value_name == self.test_type: + cx_map[item] = value_rx + return cx_map + + def check_request_rate(self): + endp_list = self.json_get("layer4/list?fields=urls/s") + expected_passes = 0 + passes = 0 + # TODO: this might raise a nameerror lower down + # if self.target_requests_per_ten is None: + # raise NameError("check request rate: missing self.target_requests_per_ten") + if endp_list is not None and endp_list['endpoint'] is not None: + endp_list = endp_list['endpoint'] + for item in endp_list: + for name, info in item.items(): + if name in self.created_cx.keys(): + expected_passes += 1 + if info['urls/s'] * self.requests_per_ten >= self.target_requests_per_ten * .9: + # print(name, info['urls/s'], info['urls/s'] * self.requests_per_ten, self.target_requests_per_ten * .9) + passes += 1 + + return passes == expected_passes + + def cleanup(self): + print("Cleaning up cxs and endpoints") + if len(self.created_cx) != 0: + for cx_name in self.created_cx.keys(): + req_url = "cli-json/rm_cx" + data = { + "test_mgr": "default_tm", + "cx_name": self.created_cx[cx_name] + } + self.json_post(req_url, data) + # pprint(data) + req_url = "cli-json/rm_endp" + data = { + "endp_name": cx_name + } + self.json_post(req_url, data) + # pprint(data) + + def create(self, ports=[], sleep_time=.5, debug_=False, suppress_related_commands_=None): + cx_post_data = [] + for port_name in ports: + print("port_name: {} len: {} self.local_realm.name_to_eid(port_name): {}".format(port_name, len(self.local_realm.name_to_eid(port_name)), self.local_realm.name_to_eid(port_name))) + shelf = self.local_realm.name_to_eid(port_name)[0] + resource = self.local_realm.name_to_eid(port_name)[1] + name = self.local_realm.name_to_eid(port_name)[2] + endp_data = { + "alias": name + "_l4", + "shelf": shelf, + "resource": resource, + "port": name, + "type": "l4_generic", + "timeout": 10, + "url_rate": self.requests_per_ten, + "url": self.url, + "proxy_auth_type": 0x200 + } + url = "cli-json/add_l4_endp" + self.local_realm.json_post(url, endp_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands_) + time.sleep(sleep_time) + + endp_data = { + "alias": "CX_" + name + "_l4", + "test_mgr": "default_tm", + "tx_endp": name + "_l4", + "rx_endp": "NA" + } + cx_post_data.append(endp_data) + self.created_cx[name + "_l4"] = "CX_" + name + "_l4" + + for cx_data in cx_post_data: + url = "/cli-json/add_cx" + self.local_realm.json_post(url, cx_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands_) + time.sleep(sleep_time) + + def monitor(self, + duration_sec=60, + monitor_interval=1, + col_names=None, + created_cx=None, + monitor=True, + report_file=None, + output_format=None, + script_name=None, + arguments=None, + iterations=0, + debug=False): + try: + duration_sec = LFCliBase.parse_time(duration_sec).seconds + except: + if (duration_sec is None) or (duration_sec <= 1): + raise ValueError("L4CXProfile::monitor wants duration_sec > 1 second") + if (duration_sec <= monitor_interval): + raise ValueError("L4CXProfile::monitor wants duration_sec > monitor_interval") + if report_file == None: + raise ValueError("Monitor requires an output file to be defined") + if created_cx == None: + raise ValueError("Monitor needs a list of Layer 4 connections") + if (monitor_interval is None) or (monitor_interval < 1): + raise ValueError("L4CXProfile::monitor wants monitor_interval >= 1 second") + if output_format is not None: + if output_format.lower() != report_file.split('.')[-1]: + raise ValueError('Filename %s does not match output format %s' % (report_file, output_format)) + else: + output_format = report_file.split('.')[-1] + + # Step 1 - Assign column names + + if col_names is not None and len(col_names) > 0: + header_row=col_names + else: + header_row=list((list(self.json_get("/layer4/all")['endpoint'][0].values())[0].keys())) + if debug: + print(header_row) + + # Step 2 - Monitor columns + start_time = datetime.datetime.now() + end_time = start_time + datetime.timedelta(seconds=duration_sec) + sleep_interval = round(duration_sec // 5) + if debug: + print("Sleep_interval is %s ", sleep_interval) + print("Start time is %s " , start_time) + print("End time is %s " ,end_time) + value_map = dict() + passes = 0 + expected_passes = 0 + timestamps = [] + if self.test_type != 'urls': + old_rx_values = self.get_bytes() + + for test in range(1+iterations): + while datetime.datetime.now() < end_time: + if col_names is None: + response = self.json_get("/layer4/all") + else: + fields = ",".join(col_names) + response = self.json_get("/layer4/%s?fields=%s" % (created_cx, fields)) + if debug: + print(response) + if response is None: + print(response) + raise ValueError("Cannot find any endpoints") + if monitor: + if debug: + print(response) + + time.sleep(sleep_interval) + t = datetime.datetime.now() + timestamps.append(t) + value_map[t] = response + expected_passes += 1 + if self.test_type == 'urls': + if self.check_errors(self.debug): + if self.check_request_rate(): + passes += 1 + else: + self._fail("FAIL: Request rate did not exceed target rate") + break + else: + self._fail("FAIL: Errors found getting to %s " % self.url) + break + + else: + new_rx_values = self.get_bytes() + if self.compare_vals(old_rx_values, new_rx_values): + passes += 1 + else: + self._fail("FAIL: Not all stations increased traffic") + + # self.exit_fail() + time.sleep(monitor_interval) + + print(value_map) + + #[further] post-processing data, after test completion + full_test_data_list = [] + for test_timestamp, data in value_map.items(): + #reduce the endpoint data to single dictionary of dictionaries + for datum in data["endpoint"]: + for endpoint_data in datum.values(): + if debug: + print(endpoint_data) + endpoint_data["Timestamp"] = test_timestamp + full_test_data_list.append(endpoint_data) + + + header_row.append("Timestamp") + header_row.append('Timestamp milliseconds') + df = pd.DataFrame(full_test_data_list) + + df["Timestamp milliseconds"] = [self.get_milliseconds(x) for x in df["Timestamp"]] + #round entire column + df["Timestamp milliseconds"]=df["Timestamp milliseconds"].astype(int) + df["Timestamp"]=df["Timestamp"].apply(lambda x:x.strftime("%m/%d/%Y %I:%M:%S")) + df=df[["Timestamp","Timestamp milliseconds", *header_row[:-2]]] + #compare previous data to current data + + systeminfo = ast.literal_eval(requests.get('http://'+str(self.lfclient_host)+':'+str(self.lfclient_port)).text) + + if output_format == 'hdf': + df.to_hdf(report_file, 'table', append=True) + if output_format == 'parquet': + df.to_parquet(report_file, engine='pyarrow') + if output_format == 'png': + fig = df.plot().get_figure() + fig.savefig(report_file) + if output_format.lower() in ['excel', 'xlsx'] or report_file.split('.')[-1] == 'xlsx': + df.to_excel(report_file, index=False) + if output_format == 'df': + return df + supported_formats = ['csv', 'json', 'stata', 'pickle','html'] + for x in supported_formats: + if output_format.lower() == x or report_file.split('.')[-1] == x: + exec('df.to_' + x + '("'+report_file+'")') diff --git a/lanforge/lanforge-scripts/py-json/lf_attenmod.py b/lanforge/lanforge-scripts/py-json/lf_attenmod.py new file mode 100644 index 000000000..c0b5bb1c1 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/lf_attenmod.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") + + +class ATTENUATORProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, local_realm, debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_) + self.local_realm = local_realm + self.lfclient_host = lfclient_host + self.COMMANDS = ["show_attenuators", "set_attenuator"] + self.atten_serno = "" + self.atten_idx = "" + self.atten_val = "" + self.atten_data = { + "shelf": 1, + "resource": 1, + "serno": None, + "atten_idx": None, + "val": None, + "mode": None, + "pulse_width_us5": None, + "pulse_interval_ms": None, + "pulse_count": None, + "pulse_time_ms": None + } + + def set_command_param(self, command_name, param_name, param_value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + raise ValueError("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + if command_name == "set_attenuator": + self.atten_data[param_name] = param_value + + def show(self, debug=False): + print("Show Attenuators.........") + response = self.json_get("/attenuators/") + time.sleep(0.01) + if response is None: + print(response) + raise ValueError("Cannot find any endpoints") + else: + attenuator_resp = response["attenuator"] + for key, val in attenuator_resp.items(): + if key == "entity id": + serial_num = val.split(".") + print("Serial-num : %s" % serial_num[-1]) + print("%s : %s" % (key, val)) + print("\n") + + def create(self, debug=False): + if len(self.atten_serno) == 0 or len(self.atten_idx) == 0 or len(self.atten_val) == 0: + print("ERROR: Must specify atten_serno, atten_idx, and atten_val when setting attenuator.\n") + print("Setting Attenuator...") + self.set_command_param("set_attenuator", "serno", self.atten_serno) + self.set_command_param("set_attenuator", "atten_idx", self.atten_idx) + self.set_command_param("set_attenuator", "val", self.atten_val) + set_attenuators = LFRequest.LFRequest(self.lfclient_url + "/cli-json/set_attenuator", debug_=debug) + set_attenuators.addPostData(self.atten_data) + time.sleep(0.01) + json_response = set_attenuators.jsonPost(debug) + time.sleep(10) + print("\n") + diff --git a/lanforge/lanforge-scripts/py-json/lf_cv_base.py b/lanforge/lanforge-scripts/py-json/lf_cv_base.py new file mode 100644 index 000000000..4016c03c8 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/lf_cv_base.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +Base Class to be used for Chamber View Tests + +Methods: + 1.) Add a CV Profile + 2.) Remove a CV Profile + 3.) Add a DUT + 4.) Show a CV Profile +""" +import sys +import os +import importlib + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class ChamberViewBase(LFCliBase): + + def __init__(self, _lfjson_host="localhost", _lfjson_port=8080, _debug=False): + super().__init__(_lfjson_host=_lfjson_host, _lfjson_port=_lfjson_port, _debug=_debug) + + def remove_text_blobs(self): + pass + + def add_text_blobs(self, type="", name="", data="", debug=False): + data = {'type': type, + 'name': name, + "text": data + } + self.json_post("/cli-json/add_text_blob/", data, debug_=debug) + + def get_text_blob(self, type="", name="", debug=False): + data = {'type': type, + 'name': name, + } + return self.json_post("/cli-json/show_text_blob/", data, debug_=debug) + + def add_dut(self): + """ + //for DUT + + /cli-json/add_dut + + ( + { + "name": Dut name which we want to give, + "flags": "4098", + "img_file" : "NONE", + "sw_version" : "[BLANK]", + "hw_version": "[BLANK]", + "model_num":"[BLANK]", + "serial_num":"[BLANK]", + "serial_port":"[BLANK]", + "wan_port":"[BLANK]", + "lan_port": "[BLANK]", + "ssid1": SSIDname1, + "passwd1": SSIDpassword1, + "ssid2": SSIDname2, + "passwd2": SSIDpassword2, + "ssid3":"[BLANK]", + "passwd3" :"[BLANK]", + "mgt_ip" : "0.0.0.0", + "api_id": "0", + "flags_mask" : "NA", + "antenna_count1" : "0", + "antenna_count2":"0", + "antenna_count3":"0", + "bssid1" : "00:00:00:00:00:00", + "bssid2" : "00:00:00:00:00:00", + "bssid3" : "00:00:00:00:00:00", + "top_left_x": "0", + "top_left_y": "0", + "eap_id": "[BLANK]", + } + ) + """ + pass diff --git a/lanforge/lanforge-scripts/py-json/lfdata.py b/lanforge/lanforge-scripts/py-json/lfdata.py new file mode 100644 index 000000000..b0e154ad5 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/lfdata.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +import datetime + + +# LFData class actions: +# - Methods to collect data/store data (use from monitor instance) - used by Profile class. + # - file open/save + # - save row (rolling) - to CSV (standard) + # - headers + # - file to data-storage-type conversion and vice versa (e.g. dataframe (or datatable) to file type and vice versa) + # - other common util methods related to immediate data storage + # - include compression method + # - monitoring truncates every 5 mins and sends to report? --- need clarification. truncate file and rewrite to same file? + # - large data collection use NFS share to NAS. +# Websocket class actions: + #reading data from websockets + +class LFDataCollection: + def __init__(self, local_realm, debug=False): + self.parent_realm = local_realm + self.exit_on_error = False + self.debug = debug or local_realm.debug + + def json_get(self, _req_url, debug_=False): + return self.parent_realm.json_get(_req_url, debug_=False) + + def check_json_validity(self, keyword=None, json_response=None): + if json_response is None: + raise ValueError("Cannot find columns requested to be searched in port manager. Exiting script, please retry.") + if keyword is not None and keyword not in json_response: + raise ValueError("Cannot find proper information from json. Please check your json request. Exiting script, please retry.") + + + def get_milliseconds(self, + timestamp): + return (timestamp - datetime.datetime(1970,1,1)).total_seconds()*1000 + def get_seconds(self, + timestamp): + return (timestamp - datetime.datetime(1970,1,1)).total_seconds() + + + #only for ipv4_variable_time at the moment + def monitor_interval(self, header_row_= None, + start_time_= None, sta_list_= None, + created_cx_= None, layer3_fields_= None, + port_mgr_fields_= None): + + #time calculations for while loop and writing to csv + t = datetime.datetime.now() + timestamp= t.strftime("%m/%d/%Y %I:%M:%S") + t_to_millisec_epoch= int(self.get_milliseconds(t)) + time_elapsed=int(self.get_seconds(t))-int(self.get_seconds(start_time_)) + + #get responses from json + layer_3_response = self.json_get("/endp/%s?fields=%s" % (created_cx_, layer3_fields_),debug_=self.debug) + if port_mgr_fields_ is not None: + port_mgr_response=self.json_get("/port/1/1/%s?fields=%s" % (sta_list_, port_mgr_fields_), debug_=self.debug) + + #check json response validity + self.check_json_validity(keyword="endpoint",json_response=layer_3_response) + self.check_json_validity(keyword="interfaces",json_response=port_mgr_response) + + #dict manipulation + temp_list=[] + for endpoint in layer_3_response["endpoint"]: + if self.debug: + print("Current endpoint values list... ") + print(list(endpoint.values())[0]) + temp_endp_values=list(endpoint.values())[0] #dict + temp_list.extend([timestamp,t_to_millisec_epoch,time_elapsed]) + current_sta = temp_endp_values['name'] + merge={} + if port_mgr_fields_ is not None: + for sta_name in sta_list_: + if sta_name in current_sta: + for interface in port_mgr_response["interfaces"]: + if sta_name in list(interface.keys())[0]: + merge=temp_endp_values.copy() + + port_mgr_values_dict =list(interface.values())[0] + renamed_port_cols={} + for key in port_mgr_values_dict.keys(): + renamed_port_cols['port mgr - ' +key]=port_mgr_values_dict[key] + merge.update(renamed_port_cols) + for name in header_row_[3:-3]: + temp_list.append(merge[name]) + return temp_list + + +#class WebSocket(): + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-json/mac_vlan_profile.py b/lanforge/lanforge-scripts/py-json/mac_vlan_profile.py new file mode 100644 index 000000000..87636973b --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/mac_vlan_profile.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +from pprint import pprint +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +set_port = importlib.import_module("py-json.LANforge.set_port") + + +class MACVLANProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, + local_realm, + macvlan_parent="eth1", + num_macvlans=1, + admin_down=False, + dhcp=False, + debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_) + self.local_realm = local_realm + self.num_macvlans = num_macvlans + self.macvlan_parent = macvlan_parent + self.resource = 1 + self.shelf = 1 + self.desired_macvlans = [] + self.created_macvlans = [] + self.dhcp = dhcp + self.netmask = None + self.first_ip_addr = None + self.gateway = None + self.ip_list = [] + self.COMMANDS = ["set_port"] + self.desired_set_port_cmd_flags = [] + self.desired_set_port_current_flags = [] # do not default down, "if_down" + self.desired_set_port_interest_flags = ["current_flags"] # do not default down, "ifdown" + self.set_port_data = { + "shelf": 1, + "resource": 1, + "port": None, + "current_flags": 0, + "interest": 0, # (0x2 + 0x4000 + 0x800000) # current, dhcp, down, + } + + def add_named_flags(self, desired_list, command_ref): + if desired_list is None: + raise ValueError("addNamedFlags wants a list of desired flag names") + if len(desired_list) < 1: + print("addNamedFlags: empty desired list") + return 0 + if (command_ref is None) or (len(command_ref) < 1): + raise ValueError("addNamedFlags wants a maps of flag values") + + result = 0 + for name in desired_list: + if (name is None) or (name == ""): + continue + if name not in command_ref: + if self.debug: + pprint(command_ref) + raise ValueError("flag %s not in map" % name) + result += command_ref[name] + + return result + + def set_command_param(self, command_name, param_name, param_value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + raise ValueError("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + # return + if command_name == "set_port": + self.set_port_data[param_name] = param_value + + def set_command_flag(self, command_name, param_name, value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + print("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + return + + elif command_name == "set_port": + if (param_name not in set_port.set_port_current_flags) and ( + param_name not in set_port.set_port_cmd_flags) and ( + param_name not in set_port.set_port_interest_flags): + print("Parameter name [%s] not defined in set_port.py" % param_name) + if self.debug: + pprint(set_port.set_port_cmd_flags) + pprint(set_port.set_port_current_flags) + pprint(set_port.set_port_interest_flags) + return + if (param_name in set_port.set_port_cmd_flags): + if (value == 1) and (param_name not in self.desired_set_port_cmd_flags): + self.desired_set_port_cmd_flags.append(param_name) + elif value == 0: + self.desired_set_port_cmd_flags.remove(param_name) + elif (param_name in set_port.set_port_current_flags): + if (value == 1) and (param_name not in self.desired_set_port_current_flags): + self.desired_set_port_current_flags.append(param_name) + elif value == 0: + self.desired_set_port_current_flags.remove(param_name) + elif (param_name in set_port.set_port_interest_flags): + if (value == 1) and (param_name not in self.desired_set_port_interest_flags): + self.desired_set_port_interest_flags.append(param_name) + elif value == 0: + self.desired_set_port_interest_flags.remove(param_name) + else: + raise ValueError("Unknown param name: " + param_name) + + def create(self, admin_down=False, debug=False, sleep_time=1): + print("Creating MACVLANs...") + req_url = "/cli-json/add_mvlan" + + if not self.dhcp and self.first_ip_addr is not None and self.netmask is not None and self.gateway is not None: + self.desired_set_port_interest_flags.append("ip_address") + self.desired_set_port_interest_flags.append("ip_Mask") + self.desired_set_port_interest_flags.append("ip_gateway") + self.ip_list = LFUtils.gen_ip_series(ip_addr=self.first_ip_addr, netmask=self.netmask, + num_ips=self.num_macvlans) + + if self.dhcp: + print("Using DHCP") + self.desired_set_port_current_flags.append("use_dhcp") + self.desired_set_port_interest_flags.append("dhcp") + + self.set_port_data["current_flags"] = self.add_named_flags(self.desired_set_port_current_flags, + set_port.set_port_current_flags) + self.set_port_data["interest"] = self.add_named_flags(self.desired_set_port_interest_flags, + set_port.set_port_interest_flags) + set_port_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/set_port") + + for i in range(len(self.desired_macvlans)): + data = { + "shelf": self.shelf, + "resource": self.resource, + "mac": "xx:xx:xx:*:*:xx", + "port": self.local_realm.name_to_eid(self.macvlan_parent)[2], + "index": int(self.desired_macvlans[i][self.desired_macvlans[i].index('#') + 1:]), + #"dhcp": self.dhcp, + "flags": None + } + if admin_down: + data["flags"] = 1 + else: + data["flags"] = 0 + self.created_macvlans.append("%s.%s.%s#%d" % (self.shelf, self.resource, + self.macvlan_parent, int( + self.desired_macvlans[i][self.desired_macvlans[i].index('#') + 1:]))) + self.local_realm.json_post(req_url, data) + time.sleep(sleep_time) + + LFUtils.wait_until_ports_appear(base_url=self.lfclient_url, port_list=self.created_macvlans) + print(self.created_macvlans) + + time.sleep(5) + + for i in range(len(self.created_macvlans)): + eid = self.local_realm.name_to_eid(self.created_macvlans[i]) + name = eid[2] + self.set_port_data["port"] = name # for set_port calls. + if not self.dhcp and self.first_ip_addr is not None and self.netmask is not None \ + and self.gateway is not None: + self.set_port_data["ip_addr"] = self.ip_list[i] + self.set_port_data["netmask"] = self.netmask + self.set_port_data["gateway"] = self.gateway + set_port_r.addPostData(self.set_port_data) + json_response = set_port_r.jsonPost(debug) + time.sleep(sleep_time) + + def cleanup(self): + print("Cleaning up MACVLANs...") + print(self.created_macvlans) + for port_eid in self.created_macvlans: + self.local_realm.rm_port(port_eid, check_exists=True) + time.sleep(.02) + # And now see if they are gone + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.created_macvlans) + + def admin_up(self): + for macvlan in self.created_macvlans: + self.local_realm.admin_up(macvlan) + + def admin_down(self): + for macvlan in self.created_macvlans: + self.local_realm.admin_down(macvlan) diff --git a/lanforge/lanforge-scripts/py-json/multicast_profile.py b/lanforge/lanforge-scripts/py-json/multicast_profile.py new file mode 100644 index 000000000..faf43ad4b --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/multicast_profile.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +from pprint import pprint + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class MULTICASTProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, local_realm, + report_timer_=3000, name_prefix_="Unset", number_template_="00000", debug_=False): + """ + + :param lfclient_host: + :param lfclient_port: + :param local_realm: + :param name_prefix_: prefix string for connection + :param number_template_: how many zeros wide we padd, possibly a starting integer with left padding + :param debug_: + """ + super().__init__(lfclient_host, lfclient_port, debug_) + self.lfclient_url = "http://%s:%s" % (lfclient_host, lfclient_port) + self.debug = debug_ + self.local_realm = local_realm + self.report_timer = report_timer_ + self.created_mc = {} + self.name_prefix = name_prefix_ + self.number_template = number_template_ + + def clean_mc_lists(self): + # Clean out our local lists, this by itself does NOT remove anything from LANforge manager. + # but, if you are trying to modify existing connections, then clearing these arrays and + # re-calling 'create' will do the trick. + created_mc = {} + + def get_mc_names(self): + return self.created_mc.keys() + + def refresh_mc(self, debug_=False): + for endp_name in self.get_mc_names(): + self.json_post("/cli-json/show_endpoints", { + "endpoint": endp_name + }, debug_=debug_) + + def start_mc(self, suppress_related_commands=None, debug_=False): + if self.debug: + debug_ = True + + for endp_name in self.get_mc_names(): + print("Starting mcast endpoint: %s" % (endp_name)) + json_data = { + "endp_name": endp_name + } + url = "cli-json/start_endp" + self.local_realm.json_post(url, json_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + pass + + def stop_mc(self, suppress_related_commands=None, debug_=False): + if self.debug: + debug_ = True + for endp_name in self.get_mc_names(): + json_data = { + "endp_name": endp_name + } + url = "cli-json/stop_endp" + self.local_realm.json_post(url, json_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + pass + + def cleanup_prefix(self): + self.local_realm.cleanup_cxe_prefix(self.name_prefix) + + def cleanup(self, suppress_related_commands=None, debug_ = False): + if self.debug: + debug_ = True + + for endp_name in self.get_mc_names(): + self.local_realm.rm_endp(endp_name, debug_=debug_, suppress_related_commands_=suppress_related_commands) + + def create_mc_tx(self, endp_type, side_tx, mcast_group="224.9.9.9", mcast_dest_port=9999, + suppress_related_commands=None, debug_=False): + if self.debug: + debug_ = True + + side_tx_info = self.local_realm.name_to_eid(side_tx) + side_tx_shelf = side_tx_info[0] + side_tx_resource = side_tx_info[1] + side_tx_port = side_tx_info[2] + side_tx_name = "%smtx-%s-%i" % (self.name_prefix, side_tx_port, len(self.created_mc)) + + json_data = [] + + # add_endp mcast-xmit-sta 1 1 side_tx mc_udp -1 NO 4000000 0 NO 1472 0 INCREASING NO 32 0 0 + json_data = { + 'alias': side_tx_name, + 'shelf': side_tx_shelf, + 'resource': side_tx_resource, + 'port': side_tx_port, + 'type': endp_type, + 'ip_port': -1, + 'is_rate_bursty': + 'NO', 'min_rate': 256000, + 'max_rate': 0, + 'is_pkt_sz_random': 'NO', + 'min_pkt': 1472, + 'max_pkt': 0, + 'payload_pattern': 'INCREASING', + 'use_checksum': 'NO', + 'ttl': 32, + 'send_bad_crc_per_million': 0, + 'multi_conn': 0 + } + + url = "/cli-json/add_endp" + self.local_realm.json_post(url, json_data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + + json_data = { + 'name': side_tx_name, + 'ttl': 32, + 'mcast_group': mcast_group, + 'mcast_dest_port': mcast_dest_port, + 'rcv_mcast': 'No' + } + + url = "cli-json/set_mc_endp" + self.local_realm.json_post(url, json_data, debug_=debug_, suppress_related_commands_=suppress_related_commands) + + self.created_mc[side_tx_name] = side_tx_name + + these_endp = [side_tx_name] + self.local_realm.wait_until_endps_appear(these_endp, debug=debug_) + + def create_mc_rx(self, endp_type, side_rx, mcast_group="224.9.9.9", mcast_dest_port=9999, + suppress_related_commands=None, debug_=False): + if self.debug: + debug_ = True + + these_endp = [] + + for port_name in side_rx: + side_rx_info = self.local_realm.name_to_eid(port_name) + side_rx_shelf = side_rx_info[0] + side_rx_resource = side_rx_info[1] + side_rx_port = side_rx_info[2] + side_rx_name = "%smrx-%s-%i" % (self.name_prefix, side_rx_port, len(self.created_mc)) + # add_endp mcast-rcv-sta-001 1 1 sta0002 mc_udp 9999 NO 0 0 NO 1472 0 INCREASING NO 32 0 0 + json_data = { + 'alias': side_rx_name, + 'shelf': side_rx_shelf, + 'resource': side_rx_resource, + 'port': side_rx_port, + 'type': endp_type, + 'ip_port': 9999, + 'is_rate_bursty': 'NO', + 'min_rate': 0, + 'max_rate': 0, + 'is_pkt_sz_random': 'NO', + 'min_pkt': 1472, + 'max_pkt': 0, + 'payload_pattern': 'INCREASING', + 'use_checksum': 'NO', + 'ttl': 32, + 'send_bad_crc_per_million': 0, + 'multi_conn': 0 + } + + url = "cli-json/add_endp" + self.local_realm.json_post(url, json_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + json_data = { + 'name': side_rx_name, + 'ttl': 32, + 'mcast_group': mcast_group, + 'mcast_dest_port': mcast_dest_port, + 'rcv_mcast': 'Yes' + } + url = "cli-json/set_mc_endp" + self.local_realm.json_post(url, json_data, debug_=debug_, + suppress_related_commands_=suppress_related_commands) + + self.created_mc[side_rx_name] = side_rx_name + these_endp.append(side_rx_name) + + self.local_realm.wait_until_endps_appear(these_endp, debug=debug_) + + def to_string(self): + pprint.pprint(self) diff --git a/lanforge/lanforge-scripts/py-json/old-examples/create_sta.py b/lanforge/lanforge-scripts/py-json/old-examples/create_sta.py new file mode 100755 index 000000000..0cc402736 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/old-examples/create_sta.py @@ -0,0 +1,370 @@ +#!/usr/bin/python3 +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# example of how to create a LANforge station - +# - +# two examples, first using the URL-Encoded POST - +# second using JSON POST data - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +import sys +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() +import argparse +import logging +import time +from time import sleep +import pprint +import LANforge +from LANforge import LFRequest +from LANforge import LFUtils +from LANforge.LFUtils import NA + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +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() + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-json/old-examples/generic_cx.py b/lanforge/lanforge-scripts/py-json/old-examples/generic_cx.py new file mode 100755 index 000000000..1db36b8b2 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/old-examples/generic_cx.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +This script is out-dated, please see py-scripts/test_ipv4_variable_time.py +""" +import sys +import pprint +from pprint import pprint + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +from LANforge.lfcli_base import LFCliBase + +class GenericCx(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, debug_=False): + super().__init__(lfclient_host, lfclient_port, _debug=debug_) + + def createGenEndp(self, alias=None, shelf=1, resource=1, port=None, type="gen_generic"): + """ + @deprecated + + :param alias: + :param shelf: + :param resource: + :param port: + :param type: + :return: + """ + return self.create_gen_endp(alias=alias, shelf=shelf, resource=resource, port=port, type=type) + + def create_gen_endp(self, alias=None, shelf=1, resource=1, port=None, type="gen_generic"): + """ + :param alias: name of connection + :param shelf: shelf + :param resource: resource id + :param port: port + :param type: gen_generic is what firemod reports, just use this + :return: + """ + if port is None: + raise ValueError("createGenEndp: port required") + if type is None: + raise ValueError("createGenEndp: type required") + + data = { + "alias": alias, + "shelf": shelf, + "resource": resource, + "port": port, + "type": type + } + if self.debug: + pprint(data) + + self.json_post("cli-json/add_gen_endp", data, debug_=self.debug) + + def setFlags(self, endpName, flagName, val): + return self.set_flags(endpName, flagName, val) + + def set_flags(self, endpName, flagName, val): + data = { + "name": endpName, + "flag": flagName, + "val": val + } + self.json_post("cli-json/set_endp_flag", data, debug_=self.debug) + + def setCmd(self, endpName, cmd): + return self.set_cmd(endpName, cmd) + + def set_cmd(self, endpName, cmd): + data = { + "name": endpName, + "command": cmd + } + self.json_post("cli-json/set_gen_cmd", data, debug_=self.debug) diff --git a/lanforge/lanforge-scripts/py-json/old-examples/test_l4.py b/lanforge/lanforge-scripts/py-json/old-examples/test_l4.py new file mode 100755 index 000000000..1ae8ec502 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/old-examples/test_l4.py @@ -0,0 +1,58 @@ +#!/usr/bin/python3 +import sys +import urllib + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + +import time +from time import sleep +from urllib import error +import pprint +import LANforge +from LANforge import LFRequest +from LANforge import LFUtils +from LANforge.LFUtils import NA + + +j_printer = pprint.PrettyPrinter(indent=2) +# typically you're using resource 1 in stand alone realm +resource_id = 1 + +def main(): + base_url = "http://localhost:8080" + json_post = "" + json_response = "" + + # see if there are old wanlinks to remove + json_post = LFRequest.LFRequest(base_url+"/layer4/list") + try: + json_response = json_post.getAsJson() + LFUtils.debug_printer.pprint(json_response) + + except urllib.error.HTTPError as error: + j_printer.pprint(error) + + add_l4_endp_url = base_url + "/cli-json/add_l4_endp"; + json_post = LFRequest.LFRequest(add_l4_endp_url) + json_post.addPostData({ + "shelf":1, + "resource":1, + "port":"sta00500", + "type":"l4_generic", + "timeout":2000, + "url_rate":600, + # this produces an error that should be listed in headers + "URL":"dl http://10.40.0.1/ /dev/null" + }) + json_response = json_post.jsonPost(True); + j_printer.pprint(json_response) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == '__main__': + main() + +### +### \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-json/old-examples/wct-example.py b/lanforge/lanforge-scripts/py-json/old-examples/wct-example.py new file mode 100755 index 000000000..0d1e1410b --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/old-examples/wct-example.py @@ -0,0 +1,160 @@ +#!/usr/bin/python3 +''' +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# - +# Example of how to operate a WCT instance using cli-socket. - +# This script is out-dated. Please refer to py-scripts/run_cv_scenario.py - +# - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +make sure pexpect is installed: +$ sudo yum install python3-pexpect +$ sudo yum install python3-xlsxwriter + +You might need to install pexpect-serial using pip: +$ pip3 install pexpect-serial +$ pip3 install XlsxWriter + +''' + +import sys +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() +import argparse +import logging +import time +from time import sleep +import pexpect +import xlsxwriter +import pprint +import LANforge +from LANforge import LFRequest +from LANforge import LFUtils +from LANforge.LFUtils import NA + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def main(): + host = "ct524-debbie.jbr.candelatech.com" + 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-x64-3-1" + passphrase = "jedway-wpa2-x64-3-1" + clisock = 3990 + cliprompt = 'lfgui# ' + + 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/wiphy2") + 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) + + 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 + }) + 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") + 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") + data = LFUtils.portDhcpUpRequest(resource_id, sta_name) + lf_r.addPostData(data) + lf_r.jsonPost() + + LFUtils.waitUntilPortsAppear(resource_id, base_url, desired_stations) + + # Now lets do some cli-socket scripting + gui_telnet = pexpect.spawn('telnet %s %s'%(host, clisock)) + if (gui_telnet == None): + print ("Unable to telnet to %s:%s"%(host,clisock)); + exit(1) + + gui_telnet.expect('lfgui# ') + gui_telnet.sendline("cv create 'WiFi Capacity' 'wct'") + gui_telnet.expect('OK') + gui_telnet.sendline("cv load wct wct-wpa2-x64-two-loops") + gui_telnet.expect('OK') + gui_telnet.sendline("cv click wct 'Auto Save Report'") + gui_telnet.expect('OK') + gui_telnet.sendline("cv click wct Start") + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if __name__ == '__main__': + main() + + +#### +#### +#### diff --git a/lanforge/lanforge-scripts/py-json/port_utils.py b/lanforge/lanforge-scripts/py-json/port_utils.py new file mode 100644 index 000000000..7e4d49a2b --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/port_utils.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +class PortUtils(): + def __init__(self, local_realm): + self.local_realm = local_realm + + def set_ftp(self, port_name="", resource=1, on=False): + if port_name != "": + data = { + "shelf": 1, + "resource": resource, + "port": port_name, + "current_flags": 0, + "interest": 0 + } + + if on: + data["current_flags"] = 0x400000000000 + data["interest"] = 0x10000000 + else: + data["interest"] = 0x10000000 + + self.local_realm.json_post("cli-json/set_port", data) + else: + raise ValueError("Port name required") + + def set_http(self, port_name="", resource=1, on=False): + if port_name != "": + data = { + "shelf": 1, + "resource": resource, + "port": port_name, + "current_flags": 0, + "interest": 0 + } + + if on: + data["current_flags"] = 0x200000000000 + data["interest"] = 0x8000000 + else: + data["interest"] = 0x8000000 + + self.local_realm.json_post("cli-json/set_port", data) + else: + raise ValueError("Port name required") diff --git a/lanforge/lanforge-scripts/py-json/qvlan_profile.py b/lanforge/lanforge-scripts/py-json/qvlan_profile.py new file mode 100644 index 000000000..e2b8bbe66 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/qvlan_profile.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +from pprint import pprint +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") + + +class QVLANProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, + local_realm, + qvlan_parent="eth1", + num_qvlans=1, + admin_down=False, + dhcp=False, + debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_) + self.local_realm = local_realm + self.num_qvlans = num_qvlans + self.qvlan_parent = qvlan_parent + self.resource = 1 + self.shelf = 1 + self.desired_qvlans = [] + self.created_qvlans = [] + self.dhcp = dhcp + self.netmask = None + self.first_ip_addr = None + self.gateway = None + self.ip_list = [] + self.COMMANDS = ["set_port"] + self.desired_set_port_cmd_flags = [] + self.desired_set_port_current_flags = [] # do not default down, "if_down" + self.desired_set_port_interest_flags = ["current_flags"] # do not default down, "ifdown" + self.set_port_data = { + "shelf": 1, + "resource": 1, + "port": None + } + + def add_named_flags(self, desired_list, command_ref): + if desired_list is None: + raise ValueError("addNamedFlags wants a list of desired flag names") + if len(desired_list) < 1: + print("addNamedFlags: empty desired list") + return 0 + if (command_ref is None) or (len(command_ref) < 1): + raise ValueError("addNamedFlags wants a maps of flag values") + + result = 0 + for name in desired_list: + if (name is None) or (name == ""): + continue + if name not in command_ref: + if self.debug: + pprint(command_ref) + raise ValueError("flag %s not in map" % name) + result += command_ref[name] + + return result + + def set_command_param(self, command_name, param_name, param_value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + raise ValueError("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + # return + if command_name == "set_port": + self.set_port_data[param_name] = param_value + + def set_command_flag(self, command_name, param_name, value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + print("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + return + + elif command_name == "set_port": + if (param_name not in set_port.set_port_current_flags) and ( + param_name not in set_port.set_port_cmd_flags) and ( + param_name not in set_port.set_port_interest_flags): + print("Parameter name [%s] not defined in set_port.py" % param_name) + if self.debug: + pprint(set_port.set_port_cmd_flags) + pprint(set_port.set_port_current_flags) + pprint(set_port.set_port_interest_flags) + return + if (param_name in set_port.set_port_cmd_flags): + if (value == 1) and (param_name not in self.desired_set_port_cmd_flags): + self.desired_set_port_cmd_flags.append(param_name) + elif value == 0: + self.desired_set_port_cmd_flags.remove(param_name) + elif (param_name in set_port.set_port_current_flags): + if (value == 1) and (param_name not in self.desired_set_port_current_flags): + self.desired_set_port_current_flags.append(param_name) + elif value == 0: + self.desired_set_port_current_flags.remove(param_name) + elif (param_name in set_port.set_port_interest_flags): + if (value == 1) and (param_name not in self.desired_set_port_interest_flags): + self.desired_set_port_interest_flags.append(param_name) + elif value == 0: + self.desired_set_port_interest_flags.remove(param_name) + else: + raise ValueError("Unknown param name: " + param_name) + + def create(self, admin_down=False, debug=False, sleep_time=1): + print("Creating qvlans...") + req_url = "/cli-json/add_vlan" + + if not self.dhcp and self.first_ip_addr is not None and self.netmask is not None and self.gateway is not None: + self.desired_set_port_interest_flags.append("ip_address") + self.desired_set_port_interest_flags.append("ip_Mask") + self.desired_set_port_interest_flags.append("ip_gateway") + self.ip_list = LFUtils.gen_ip_series(ip_addr=self.first_ip_addr, netmask=self.netmask, + num_ips=self.num_qvlans) + + if self.dhcp: + print("Using DHCP") + self.desired_set_port_current_flags.append("use_dhcp") + self.desired_set_port_interest_flags.append("dhcp") + + self.set_port_data["current_flags"] = self.add_named_flags(self.desired_set_port_current_flags, + set_port.set_port_current_flags) + self.set_port_data["interest"] = self.add_named_flags(self.desired_set_port_interest_flags, + set_port.set_port_interest_flags) + set_port_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/set_port") + + for i in range(len(self.desired_qvlans)): + data = { + "shelf": self.shelf, + "resource": self.resource, + "port": self.local_realm.name_to_eid(self.qvlan_parent)[2], + "vid": i+1 + } + self.created_qvlans.append("%s.%s.%s#%d" % (self.shelf, self.resource, + self.qvlan_parent, int( + self.desired_qvlans[i][self.desired_qvlans[i].index('#') + 1:]))) + self.local_realm.json_post(req_url, data) + time.sleep(sleep_time) + + print(self.created_qvlans) + + def cleanup(self): + print("Cleaning up qvlans...") + print(self.created_qvlans) + for port_eid in self.created_qvlans: + self.local_realm.rm_port(port_eid, check_exists=True) + time.sleep(.02) + # And now see if they are gone + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.created_qvlans) + + def admin_up(self): + for qvlan in self.created_qvlans: + self.local_realm.admin_up(qvlan) + + def admin_down(self): + for qvlan in self.created_qvlans: + self.local_realm.admin_down(qvlan) diff --git a/lanforge/lanforge-scripts/py-json/realm.py b/lanforge/lanforge-scripts/py-json/realm.py new file mode 100755 index 000000000..e19f01be2 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/realm.py @@ -0,0 +1,1010 @@ +#!/usr/bin/env python3 +# The Realm Class is inherited by most python tests. Realm Class inherites from LFCliBase. +# The Realm Class contains the configurable components for LANforge, +# For example L3 / L4 cross connects, stations. Also contains helper methods +# http://www.candelatech.com/cookbook.php?vol=cli&book=Python_Create_Test_Scripts_With_the_Realm_Class + +# Written by Candela Technologies Inc. +# Updated by: + +import sys +import os +import importlib +import re +import time +from pprint import pprint + +# ---- ---- ---- ---- LANforge Base Imports ---- ---- ---- ---- + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LANforge = importlib.import_module("py-json.LANforge") +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + +# ---- ---- ---- ---- Profile Imports ---- ---- ---- ---- + +l3_cxprofile = importlib.import_module("py-json.l3_cxprofile") +L3CXProfile = l3_cxprofile.L3CXProfile +l3_cxprofile2 = importlib.import_module("py-json.l3_cxprofile2") +L3CXProfile2 = l3_cxprofile2.L3CXProfile2 +l4_cxprofile = importlib.import_module("py-json.l4_cxprofile") +L4CXProfile = l4_cxprofile.L4CXProfile +lf_attenmod = importlib.import_module("py-json.lf_attenmod") +ATTENUATORProfile = lf_attenmod.ATTENUATORProfile +multicast_profile = importlib.import_module("py-json.multicast_profile") +MULTICASTProfile = multicast_profile.MULTICASTProfile +http_profile = importlib.import_module("py-json.http_profile") +HTTPProfile = http_profile.HTTPProfile +station_profile = importlib.import_module("py-json.station_profile") +StationProfile = station_profile.StationProfile +fio_endp_profile = importlib.import_module("py-json.fio_endp_profile") +FIOEndpProfile = fio_endp_profile.FIOEndpProfile +test_group_profile = importlib.import_module("py-json.test_group_profile") +TestGroupProfile = test_group_profile.TestGroupProfile +dut_profile = importlib.import_module("py-json.dut_profile") +DUTProfile = dut_profile.DUTProfile +vap_profile = importlib.import_module("py-json.vap_profile") +VAPProfile = vap_profile.VAPProfile +mac_vlan_profile = importlib.import_module("py-json.mac_vlan_profile") +MACVLANProfile = mac_vlan_profile.MACVLANProfile +wifi_monitor_profile = importlib.import_module("py-json.wifi_monitor_profile") +WifiMonitor = wifi_monitor_profile.WifiMonitor +gen_cxprofile = importlib.import_module("py-json.gen_cxprofile") +GenCXProfile = gen_cxprofile.GenCXProfile +qvlan_profile = importlib.import_module("py-json.qvlan_profile") +QVLANProfile = qvlan_profile.QVLANProfile +port_utils = importlib.import_module("py-json.port_utils") +PortUtils = port_utils.PortUtils +lfdata = importlib.import_module("py-json.lfdata") +LFDataCollection = lfdata.LFDataCollection + + +def wpa_ent_list(): + return [ + "DEFAULT", + "NONE", + "WPA-PSK", + "FT-PSK", + "FT-EAP", + "FT-SAE", + "FT-EAP-SHA384", + "WPA-EAP", + "OSEN", + "IEEE8021X", + "WPA-PSK-SHA256", + "WPA-EAP-SHA256", + "WPA-PSK WPA-EAP", + "WPA-PSK-SHA256 WPA-EAP-SHA256", + "WPA-PSK WPA-EAP WPA-PSK-SHA256 WPA-EAP-SHA256" + "SAE", + "WPA-EAP-SUITE-B", + "WPA-EAP-SUITE-B-192", + "FILS-SHA256", + "FILS-SHA384", + "OWE" + ] + + +class Realm(LFCliBase): + def __init__(self, + lfclient_host="localhost", + lfclient_port=8080, + debug_=False, + _exit_on_error=False, + _exit_on_fail=False, + _proxy_str=None, + _capture_signal_list=[]): + super().__init__(_lfjson_host=lfclient_host, + _lfjson_port=lfclient_port, + _debug=debug_, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail, + _proxy_str=_proxy_str, + _capture_signal_list=_capture_signal_list) + + self.debug = debug_ + # if debug_: + # print("Realm _proxy_str: %s" % _proxy_str) + # pprint(_proxy_str) + self.check_connect() + self.chan_to_freq = {} + self.freq_to_chan = {} + freq = 0 + chan = 1 + for freq in range(2412, 2472, 5): + self.freq_to_chan[freq] = chan + self.chan_to_freq[chan] = freq + chan += 1 + + self.chan_to_freq[14] = 2484 + self.chan_to_freq[34] = 5170 + self.chan_to_freq[36] = 5180 + self.chan_to_freq[38] = 5190 + self.chan_to_freq[40] = 5200 + self.chan_to_freq[42] = 5210 + self.chan_to_freq[44] = 5220 + self.chan_to_freq[46] = 5230 + self.chan_to_freq[48] = 5240 + self.chan_to_freq[52] = 5260 + self.chan_to_freq[56] = 5280 + self.chan_to_freq[60] = 5300 + self.chan_to_freq[64] = 5320 + self.chan_to_freq[100] = 5500 + self.chan_to_freq[104] = 5520 + self.chan_to_freq[108] = 5540 + self.chan_to_freq[112] = 5560 + self.chan_to_freq[116] = 5580 + self.chan_to_freq[120] = 5600 + self.chan_to_freq[124] = 5620 + self.chan_to_freq[128] = 5640 + self.chan_to_freq[132] = 5660 + self.chan_to_freq[136] = 5680 + self.chan_to_freq[140] = 5700 + self.chan_to_freq[144] = 5720 + self.chan_to_freq[149] = 5745 + self.chan_to_freq[153] = 5765 + self.chan_to_freq[157] = 5785 + self.chan_to_freq[161] = 5805 + self.chan_to_freq[165] = 5825 + self.chan_to_freq[169] = 5845 + self.chan_to_freq[173] = 5865 + + self.freq_to_chan[2484] = 14 + self.freq_to_chan[5170] = 34 + self.freq_to_chan[5180] = 36 + self.freq_to_chan[5190] = 38 + self.freq_to_chan[5200] = 40 + self.freq_to_chan[5210] = 42 + self.freq_to_chan[5220] = 44 + self.freq_to_chan[5230] = 46 + self.freq_to_chan[5240] = 48 + self.freq_to_chan[5260] = 52 + self.freq_to_chan[5280] = 56 + self.freq_to_chan[5300] = 60 + self.freq_to_chan[5320] = 64 + self.freq_to_chan[5500] = 100 + self.freq_to_chan[5520] = 104 + self.freq_to_chan[5540] = 108 + self.freq_to_chan[5560] = 112 + self.freq_to_chan[5580] = 116 + self.freq_to_chan[5600] = 120 + self.freq_to_chan[5620] = 124 + self.freq_to_chan[5640] = 128 + self.freq_to_chan[5660] = 132 + self.freq_to_chan[5680] = 136 + self.freq_to_chan[5700] = 140 + self.freq_to_chan[5720] = 144 + self.freq_to_chan[5745] = 149 + self.freq_to_chan[5765] = 153 + self.freq_to_chan[5785] = 157 + self.freq_to_chan[5805] = 161 + self.freq_to_chan[5825] = 165 + self.freq_to_chan[5845] = 169 + self.freq_to_chan[5865] = 173 + + # 4.9Ghz police band + self.chan_to_freq[183] = 4915 + self.chan_to_freq[184] = 4920 + self.chan_to_freq[185] = 4925 + self.chan_to_freq[187] = 4935 + self.chan_to_freq[188] = 4940 + self.chan_to_freq[189] = 4945 + self.chan_to_freq[192] = 4960 + self.chan_to_freq[194] = 4970 + self.chan_to_freq[196] = 4980 + + self.freq_to_chan[4915] = 183 + self.freq_to_chan[4920] = 184 + self.freq_to_chan[4925] = 185 + self.freq_to_chan[4935] = 187 + self.freq_to_chan[4940] = 188 + self.freq_to_chan[4945] = 189 + self.freq_to_chan[4960] = 192 + self.freq_to_chan[4970] = 194 + self.freq_to_chan[4980] = 196 + + def wait_until_ports_appear(self, sta_list=None, debug_=False): + if (sta_list is None) or (len(sta_list) < 1): + print("realm.wait_until_ports_appear: no stations provided") + return + LFUtils.wait_until_ports_appear(base_url=self.lfclient_url, + port_list=sta_list, + debug=debug_) + + def wait_until_ports_disappear(self, sta_list=None, debug_=False): + if (sta_list is None) or (len(sta_list) < 1): + print("realm.wait_until_ports_appear: no stations provided") + return + + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=sta_list, + debug=debug_) + + def rm_port(self, port_eid, check_exists=True, debug_=False): + if port_eid is None: + raise ValueError("realm.rm_port: want a port eid like 1.1.eth1") + debug_ |= self.debug + req_url = "/cli-json/rm_vlan" + eid = self.name_to_eid(port_eid) + if check_exists: + if not self.port_exists(port_eid): + return False + + data = { + "shelf": eid[0], + "resource": eid[1], + "port": eid[2] + } + rsp = self.json_post(req_url, data, debug_=debug_) + return True + + def port_exists(self, port_eid): + eid = self.name_to_eid(port_eid) + current_stations = self.json_get("/port/%s/%s/%s?fields=alias" % (eid[0], eid[1], eid[2])) + if not current_stations is None: + return True + return False + + def admin_up(self, port_eid): + # print("186 admin_up port_eid: "+port_eid) + eid = self.name_to_eid(port_eid) + shelf = eid[0] + resource = eid[1] + port = eid[2] + request = LFUtils.port_up_request(resource_id=resource, port_name=port) + # print("192.admin_up request: resource: %s port_name %s"%(resource, port)) + # time.sleep(2) + self.json_post("/cli-json/set_port", request) + + def admin_down(self, port_eid): + eid = self.name_to_eid(port_eid) + shelf = eid[0] + resource = eid[1] + port = eid[2] + request = LFUtils.port_down_request(resource_id=resource, port_name=port) + self.json_post("/cli-json/set_port", request) + + def reset_port(self, port_eid): + eid = self.name_to_eid(port_eid) + shelf = eid[0] + resource = eid[1] + port = eid[2] + request = LFUtils.port_reset_request(resource_id=resource, port_name=port) + self.json_post("cli-json/reset_port", request) + + def rm_cx(self, cx_name): + req_url = "cli-json/rm_cx" + data = { + "test_mgr": "ALL", + "cx_name": cx_name + } + self.json_post(req_url, data) + + def rm_endp(self, ename, debug_=False, suppress_related_commands_=True): + req_url = "cli-json/rm_endp" + data = { + "endp_name": ename + } + self.json_post(req_url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands_) + + def set_endp_tos(self, ename, _tos, debug_=False, suppress_related_commands_=True): + req_url = "cli-json/set_endp_tos" + tos = _tos + # Convert some human readable values to numeric needed by LANforge. + if _tos == "BK": + tos = "64" + if _tos == "BE": + tos = "96" + if _tos == "VI": + tos = "128" + if _tos == "VO": + tos = "192" + data = { + "name": ename, + "tos": tos + } + self.json_post(req_url, data, debug_=debug_, suppress_related_commands_=suppress_related_commands_) + + def stop_cx(self, cx_name): + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "ALL", + "cx_name": cx_name, + "cx_state": "STOPPED" + }, debug_=self.debug) + + def cleanup_cxe_prefix(self, prefix): + cx_list = self.cx_list() + if cx_list is not None: + for cx_name in cx_list: + if cx_name.startswith(prefix): + self.rm_cx(cx_name) + + endp_list = self.json_get("/endp/list") + if endp_list is not None: + if 'endpoint' in endp_list: + endp_list = list(endp_list['endpoint']) + for idx in range(len(endp_list)): + endp_name = list(endp_list[idx])[0] + if endp_name.startswith(prefix): + self.rm_endp(endp_name) + else: + if self.debug: + print("cleanup_cxe_prefix no endpoints: endp_list{}".format(endp_list)) + + def channel_freq(self, channel_=0): + return self.chan_to_freq[channel_] + + def freq_channel(self, freq_=0): + return self.freq_to_chan[freq_] + + # checks for OK or BUSY when querying cli-json/cv+is_built + def wait_while_building(self, debug_=False): + response_json = [] + data = { + "cmd": "cv is_built" + } + last_response = "BUSY" + dbg_param = "" + if debug_: + dbg_param = "?__debug=1" + + while (last_response != "YES"): + response = self.json_post("/gui-json/cmd%s" % dbg_param, data, debug_=debug_, + response_json_list_=response_json) + # LFUtils.debug_printer.pprint(response_json) + last_response = response_json[0]["LAST"]["response"] + if (last_response != "YES"): + last_response = None + response_json = [] + time.sleep(1) + else: + return + return + + # loads a database + def load(self, name): + if (name is None) or (name == ""): + raise ValueError( + "Realm::load: wants a test scenario database name, please find one in the Status tab of the GUI") + + data = { + "name": name, + "action": "overwrite", + "clean_dut": "yes", + "clean_chambers": "yes" + } + self.json_post("/cli-json/load", _data=data, debug_=self.debug) + time.sleep(1) + + # Returns json response from webpage of all layer 3 cross connects + def cx_list(self): + response = self.json_get("/cx/list") + return response + + def waitUntilEndpsAppear(self, these_endp, debug=False): + return self.wait_until_endps_appear(these_endp, debug=debug) + + def wait_until_endps_appear(self, these_endp, debug=False): + wait_more = True + count = 0 + while wait_more: + time.sleep(1) + wait_more = False + endp_list = self.json_get("/endp/list") + found_endps = {} + if debug: + print("Waiting on endpoint endp_list {}".format(endp_list)) + if (endp_list is not None) and ("items" not in endp_list): + try: + endp_list = list(endp_list['endpoint']) + for idx in range(len(endp_list)): + name = list(endp_list[idx])[0] + found_endps[name] = name + except: + print("non-fatal exception endp_list = list(endp_list['endpoint'] did not exist, will wait some more") + + for req in these_endp: + if not req in found_endps: + if debug: + print("Waiting on endpoint: %s" % (req)) + wait_more = True + count += 1 + if (count > 100): + break + + return not wait_more + + def waitUntilCxsAppear(self, these_cx, debug=False): + return self.wait_until_cxs_appear(these_cx, debug=debug) + + def wait_until_cxs_appear(self, these_cx, debug=False): + wait_more = True + count = 0 + while wait_more: + time.sleep(1) + wait_more = False + found_cxs = {} + cx_list = self.cx_list() + not_cx = ['warnings', 'errors', 'handler', 'uri', 'items'] + if cx_list is not None: + for cx_name in cx_list: + if cx_name in not_cx: + continue + found_cxs[cx_name] = cx_name + + for req in these_cx: + if not req in found_cxs: + if debug: + print("Waiting on CX: %s" % (req)) + wait_more = True + count += 1 + if (count > 100): + break + + return not wait_more + + # Returns map of all stations with port+type == WIFI-STATION + # Key is the EID, value is the map of key/values for the port values. + def station_map(self): + response = super().json_get("/port/list?fields=port,_links,alias,device,port+type") + if (response is None) or ("interfaces" not in response): + pprint(response) + print("station_list: incomplete response, halting") + exit(1) + sta_map = {} + temp_map = LFUtils.portListToAliasMap(response) + for k, v in temp_map.items(): + if (v['port type'] == "WIFI-STA"): + sta_map[k] = v + temp_map.clear() + del temp_map + del response + return sta_map + + # Returns list of all stations with port+type == WIFI-STATION + def station_list(self): + sta_list = [] + response = super().json_get("/port/list?fields=_links,alias,device,port+type") + if (response is None) or ("interfaces" not in response): + print("station_list: incomplete response:") + pprint(response) + exit(1) + + for x in range(len(response['interfaces'])): + for k, v in response['interfaces'][x].items(): + if v['port type'] == "WIFI-STA": + sta_list.append(response['interfaces'][x]) + del response + return sta_list + + # Returns list of all ports + def port_list(self): + sta_list = [] + response = super().json_get("/port/list?fields=all") + if (response is None) or ("interfaces" not in response): + print("port_list: incomplete response:") + pprint(response) + return None + + return response['interfaces'] + + # Returns list of all VAPs with "vap" in their name + def vap_list(self): + sta_list = [] + response = super().json_get("/port/list?fields=_links,alias,device,port+type") + for x in range(len(response['interfaces'])): + for k, v in response['interfaces'][x].items(): + if "vap" in v['device']: + sta_list.append(response['interfaces'][x]) + + return sta_list + + # Returns all attenuators + def atten_list(self): + response = super().json_get("/atten/list") + return response['attenuators'] + + # EID is shelf.resource.atten-serno.atten-idx + def set_atten(self, eid, atten_ddb): + eid_toks = self.name_to_eid(eid, non_port=True) + req_url = "cli-json/set_attenuator" + data = { + "shelf": eid_toks[0], + "resource": eid_toks[1], + "serno": eid_toks[2], + "atten_idx":eid_toks[3], + "val":atten_ddb, + } + self.json_post(req_url, data) + + # removes port by eid/eidpn + def remove_vlan_by_eid(self, eid): + if (eid is None) or ("" == eid): + raise ValueError("removeVlanByEid wants eid like 1.1.sta0 but given[%s]" % eid) + hunks = self.name_to_eid(eid) + # print("- - - - - - - - - - - - - - - - -") + # pprint(hunks) + # pprint(self.lfclient_url) + # print("- - - - - - - - - - - - - - - - -") + if (len(hunks) > 3) or (len(hunks) < 2): + raise ValueError("removeVlanByEid wants eid like 1.1.sta0 but given[%s]" % eid) + elif len(hunks) == 3: + LFUtils.removePort(hunks[1], hunks[2], self.lfclient_url) + else: + LFUtils.removePort(hunks[0], hunks[1], self.lfclient_url) + + # Searches for ports that match a given pattern and returns a list of names + def find_ports_like(self, pattern="", _fields="_links,alias,device,port+type", resource=0, debug_=False): + if resource == 0: + url = "/port/1/list?fields=%s" % _fields + else: + url = "/port/1/%s/list?fields=%s" % (resource, _fields) + response = self.json_get(url) + if debug_: + print("# find_ports_like r:%s, u:%s #" % (resource, url)) + pprint(response) + alias_map = LFUtils.portListToAliasMap(response, debug_=debug_) + if debug_: + pprint(alias_map) + prelim_map = {} + matched_map = {} + for name, record in alias_map.items(): + try: + if debug_: + print("- prelim - - - - - - - - - - - - - - - - - - -") + pprint(record) + if (record["port type"] == "WIFI-STA"): + prelim_map[name] = record + + except Exception as x: + self.error(x) + + prefix = "" + try: + if pattern.find("+") > 0: + match = re.search(r"^([^+]+)[+]$", pattern) + if match.group(1): + prefix = match.group(1) + for port_eid, record in prelim_map.items(): + if debug_: + print("name:", port_eid, " Group 1: ", match.group(1)) + if port_eid.find(prefix) >= 0: + matched_map[port_eid] = record + + elif pattern.find("*") > 0: + match = re.search(r"^([^\*]+)[*]$", pattern) + if match.group(1): + prefix = match.group(1) + if debug_: + print("group 1: ", prefix) + for port_eid, record in prelim_map.items(): + if port_eid.find(prefix) >= 0: + matched_map[port_eid] = record + + elif pattern.find("[") > 0: + # TODO: regex below might have too many hack escapes + match = re.search(r"^([^\[]+)\[(\d+)\.\.(\d+)\]$", pattern) + if match.group(0): + if debug_: + print("[group1]: ", match.group(1)) + print("[group2]: ", match.group(2)) + print("[group3]: ", match.group(3)) + prefix = match.group(1) + for port_eid, record in prelim_map.items(): + if port_eid.find(prefix) >= 0: + port_suf = record["device"][len(prefix):] + if (port_suf >= match.group(2)) and (port_suf <= match.group(3)): + # print("%s: suffix[%s] between %s:%s" % (port_name, port_name, match.group(2), match.group(3)) + matched_map[port_eid] = record + except ValueError as e: + self.error(e) + + return matched_map + + def name_to_eid(self, eid, debug=False, non_port=False): + if debug: + self.logg(level="debug", mesg="name_to_eid: "+str(eid)) + if (type(eid) is list) or (type(eid) is tuple): + return eid + return LFUtils.name_to_eid(eid, non_port=non_port) + + def wait_for_ip(self, station_list=None, ipv4=True, ipv6=False, timeout_sec=360, debug=False): + if not (ipv4 or ipv6): + raise ValueError("wait_for_ip: ipv4 and/or ipv6 must be set!") + if timeout_sec >= 0: + print("Waiting for ips, timeout: %i..." % timeout_sec) + else: + print("Determining wait time based on mean station association time of stations. " + "Will not wait more that 60 seconds without single association") + stas_with_ips = {} + sec_elapsed = 0 + time_extended = False + # print(station_list) + waiting_states = ["0.0.0.0", "NA", ""] + if (station_list is None) or (len(station_list) < 1): + raise ValueError("wait_for_ip: expects non-empty list of ports") + wait_more = True + + while wait_more and (sec_elapsed <= timeout_sec or timeout_sec == -1): + wait_more = False + + if not time_extended and timeout_sec == -1: + wait_more = True + num_with_ips = len(stas_with_ips) + if sec_elapsed >= 10 and num_with_ips > 0: + time_extended = True + # print(sec_elapsed, num_with_ips, int(sec_elapsed / num_with_ips), len(station_list)) + timeout_sec = int(sec_elapsed / num_with_ips * len(station_list)) + print("New timeout is %d seconds" % timeout_sec) + elif sec_elapsed > 60 and num_with_ips == 0: + timeout_sec = 60 + wait_more = False + + for sta_eid in station_list: + if debug: + print("checking sta-eid: %s" % (sta_eid)) + eid = self.name_to_eid(sta_eid) + + response = super().json_get("/port/%s/%s/%s?fields=alias,ip,port+type,ipv6+address" % + (eid[0], eid[1], eid[2])) + # pprint(response) + + if (response is None) or ("interface" not in response): + print("station_list: incomplete response:") + pprint(response) + wait_more = True + break + + if ipv4: + v = response['interface'] + if v['ip'] in waiting_states: + wait_more = True + if debug: + print("Waiting for port %s to get IPv4 Address." % (sta_eid)) + else: + if sta_eid not in stas_with_ips: + stas_with_ips[sta_eid] = {'ipv4': v['ip']} + if debug: + print("Found IP: %s on port: %s" % (v['ip'], sta_eid)) + + if ipv6: + v = response['interface'] + # print(v) + if v['ipv6 address'] != 'DELETED' and not v['ipv6 address'].startswith('fe80') \ + and v['ipv6 address'] != 'AUTO': + if sta_eid not in stas_with_ips: + stas_with_ips[sta_eid] = {'ipv6': v['ip']} + if debug: + print("Found IPv6: %s on port: %s" % (v['ipv6 address'], sta_eid)) + else: + wait_more = True + if debug: + print("Waiting for port %s to get IPv6 Address." % (sta_eid)) + + if wait_more: + time.sleep(1) + sec_elapsed += 1 + + return not wait_more + + def get_curr_num_ips(self, num_sta_with_ips=0, station_list=None, ipv4=True, ipv6=False, debug=False): + if debug: + print("checking number of stations with ips...") + waiting_states = ["0.0.0.0", "NA", ""] + if (station_list is None) or (len(station_list) < 1): + raise ValueError("check for num curr ips expects non-empty list of ports") + for sta_eid in station_list: + if debug: + print("checking sta-eid: %s" % (sta_eid)) + eid = self.name_to_eid(sta_eid) + response = super().json_get("/port/%s/%s/%s?fields=alias,ip,port+type,ipv6+address" % + (eid[0], eid[1], eid[2])) + if debug: + pprint(response) + if (response is None) or ("interface" not in response): + print("station_list: incomplete response:") + pprint(response) + # wait_more = True + break + if ipv4: + v = response['interface'] + if (v['ip'] in waiting_states): + if debug: + print("Waiting for port %s to get IPv4 Address." % (sta_eid)) + else: + if debug: + print("Found IP: %s on port: %s" % (v['ip'], sta_eid)) + print("Incrementing stations with IP addresses found") + num_sta_with_ips += 1 + else: + num_sta_with_ips += 1 + if ipv6: + v = response['interface'] + if (v['ip'] in waiting_states): + if debug: + print("Waiting for port %s to get IPv6 Address." % (sta_eid)) + + else: + if debug: + print("Found IP: %s on port: %s" % (v['ip'], sta_eid)) + print("Incrementing stations with IP addresses found") + num_sta_with_ips += 1 + else: + num_sta_with_ips += 1 + return num_sta_with_ips + + def duration_time_to_seconds(self, time_string): + if isinstance(time_string, str): + pattern = re.compile("^(\d+)([dhms]$)") + td = pattern.match(time_string) + if td is not None: + dur_time = int(td.group(1)) + dur_measure = str(td.group(2)) + if dur_measure == "d": + duration_sec = dur_time * 24 * 60 * 60 + elif dur_measure == "h": + duration_sec = dur_time * 60 * 60 + elif dur_measure == "m": + duration_sec = dur_time * 60 + else: + duration_sec = dur_time * 1 + else: + raise ValueError("Unknown value for time_string: %s" % time_string) + else: + raise ValueError("time_string must be of type str. Type %s provided" % type(time_string)) + return duration_sec + + + def remove_all_stations(self, resource): + port_list = self.station_list() + sta_list = [] + if sta_list is not None: + print("Removing all stations") + for item in list(port_list): + if "sta" in list(item)[0]: + sta_list.append(self.name_to_eid(list(item)[0])[2]) + + for sta_name in sta_list: + req_url = "cli-json/rm_vlan" + data = { + "shelf": 1, + "resource": resource, + "port": sta_name + } + self.json_post(req_url, data) + + def remove_all_endps(self): + endp_list = self.json_get("/endp/list") + if "items" in endp_list or "empty" in endp_list: + return + if endp_list is not None or endp_list: + print("Removing all endps") + endp_list = list(endp_list['endpoint']) + for endp_name in range(len(endp_list)): + name = list(endp_list[endp_name])[0] + req_url = "cli-json/rm_endp" + data = { + "endp_name": name + } + self.json_post(req_url, data) + + def remove_all_cxs(self, remove_all_endpoints=False): + # remove cross connects + # remove endpoints + # nc show endpoints + # nc show cross connects + try: + cx_list = list(self.cx_list()) + not_cx = ['warnings', 'errors', 'handler', 'uri', 'items', 'empty'] + if cx_list is not None: + print("Removing all cxs") + for cx_name in cx_list: + if cx_name in not_cx: + continue + req_url = "cli-json/rm_cx" + data = { + "test_mgr": "default_tm", + "cx_name": cx_name + } + self.json_post(req_url, data) + except: + print("no cxs to remove") + + if remove_all_endpoints: + self.remove_all_endps() + req_url = "cli-json/nc_show_endpoints" + data = { + "endpoint": "all" + } + self.json_post(req_url, data) + req_url = "cli-json/show_cx" + data = { + "test_mgr": "all", + "cross_connect": "all" + } + + def parse_link(self, link): + link = self.lfclient_url + link + info = () + + def new_station_profile(self, ver = 1): + if ver == 1: + station_prof = StationProfile(self.lfclient_url, local_realm=self, debug_=self.debug, up=False) + #elif ver == 2: + # import station_profile2 + # station_prof = station_profile2.StationProfile2(self.lfclient_url, local_realm=self, debug_=self.debug, up=False) + return station_prof + + def new_multicast_profile(self, ver = 1): + if ver == 1: + multi_prof = MULTICASTProfile(self.lfclient_host, self.lfclient_port, + local_realm=self, debug_=self.debug, report_timer_=3000) + #elif ver == 2: + # import multicast_profile2 + # multi_prof = multicast_profile2.MULTICASTProfile2(self.lfclient_host, self.lfclient_port, + # local_realm=self, debug_=self.debug, report_timer_=3000) + return multi_prof + + def new_wifi_monitor_profile(self, resource_=1, debug_=False, up_=False, ver = 1): + if ver == 1: + wifi_mon_prof = WifiMonitor(self.lfclient_url, + local_realm=self, + resource_=resource_, + up=up_, + debug_=(self.debug or debug_)) + #elif ver == 2: + # import wifi_monitor_profile2 + # wifi_mon_prof = wifi_monitor_profile2.WifiMonitor2(self.lfclient_url, + # local_realm=self, + # resource_=resource_, + # up=up_, + # debug_=(self.debug or debug_)) + return wifi_mon_prof + + def new_l3_cx_profile(self, ver=1): + if ver == 1: + cx_prof = L3CXProfile(self.lfclient_host, + self.lfclient_port, + local_realm=self, + debug_=self.debug, + report_timer_=3000) + elif ver == 2: + cx_prof = L3CXProfile2(self.lfclient_host, + self.lfclient_port, + local_realm=self, + debug_=self.debug, + report_timer_=3000) + return cx_prof + + def new_l4_cx_profile(self, ver=1): + if ver == 1: + cx_prof = L4CXProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + #elif ver == 2: + # import l4_cxprofile2 + # cx_prof = l4_cxprofile2.L4CXProfile2(self.lfclient_host, + # self.lfclient_port, + # local_realm=self, + # debug_=self.debug, + # report_timer_=3000) + return cx_prof + def new_attenuator_profile(self, ver=1): + if ver == 1: + atten_prof = ATTENUATORProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + return atten_prof + def new_generic_endp_profile(self, ver=1): + if ver == 1 : + endp_prof = GenCXProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + #elif ver == 2: + # import gen_cxprofile2 + # endp_prof = gen_cxprofile2.GenCXProfile(self.lfclient_host, + # self.lfclient_port, + # local_realm=self, + # debug_=self.debug, + # report_timer_=3000) + return endp_prof + + def new_generic_cx_profile(self, ver=1): + """ + @deprecated + :return: new GenCXProfile + """ + if ver == 1: + cx_prof = GenCXProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + #elif ver == 2: + # import gen_cxprofile2 + # cx_prof = gen_cxprofile2.GenCXProfile(self.lfclient_host, + # self.lfclient_port, + # local_realm=self, + # debug_=self.debug, + # report_timer_=3000) + return cx_prof + + def new_vap_profile(self, ver=1): + if ver == 1: + vap_prof = VAPProfile(lfclient_host=self.lfclient_host, lfclient_port=self.lfclient_port, local_realm=self, + debug_=self.debug) + # elif ver == 2: + # import vap_profile2 + # vap_prof = vap_profile2.VAPProfile2(lfclient_host=self.lfclient_host, lfclient_port=self.lfclient_port, local_realm=self, + # debug_=self.debug) + return vap_prof + + def new_vr_profile(self, ver=2): + if ver == 2: + from vr_profile2 import VRProfile + vap_prof = VRProfile(local_realm=self, + debug=self.debug) + return vap_prof + + def new_http_profile(self, ver = 1): + if ver == 1: + http_prof = HTTPProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + # elif ver == 2: + # import http_profile2 + # http_prof = http_profile2.HTTPProfile2(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + return http_prof + + def new_fio_endp_profile(self, ver = 1): + if ver == 1: + cx_prof = FIOEndpProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + # elif ver == 2: + # import fio_endp_profile2 + # cx_prof = fio_endp_profile2.FIOEndpProfile2(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + return cx_prof + + def new_dut_profile(self, ver = 1): + if ver == 1: + dut_profile = DUTProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + # elif ver == 2: + # import dut_profile2 + # dut_profile = dut_profile2.DUTProfile2(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + return dut_profile + + def new_mvlan_profile(self, ver = 1): + if ver == 1: + mac_vlan_profile = MACVLANProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + # elif ver == 2: + # import mac_vlan_profile2 + # mac_vlan_profile = mac_vlan_profile2.MACVLANProfile2(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + return mac_vlan_profile + + def new_qvlan_profile(self): + return QVLANProfile(self.host, self.port, local_realm=self, debug_=self.debug) + + def new_test_group_profile(self, ver = 1): + if ver == 1: + test_group_profile = TestGroupProfile(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + # elif ver == 2: + # import test_group_profile2 + # test_group_profile = test_group_profile2.TestGroupProfile2(self.lfclient_host, self.lfclient_port, local_realm=self, debug_=self.debug) + return test_group_profile + + def new_lf_data_collection(self): + return LFDataCollection(local_realm=self) + +class PacketFilter(): + + def get_filter_wlan_assoc_packets(self, ap_mac, sta_mac): + filter = "-T fields -e wlan.fc.type_subtype -e wlan.addr -e wlan.fc.pwrmgt " \ + "-Y \"(wlan.addr==%s or wlan.addr==%s) and wlan.fc.type_subtype<=3\" " % (ap_mac, sta_mac) + return filter + + def get_filter_wlan_null_packets(self, ap_mac, sta_mac): + filter = "-T fields -e wlan.fc.type_subtype -e wlan.addr -e wlan.fc.pwrmgt " \ + "-Y \"(wlan.addr==%s or wlan.addr==%s) and wlan.fc.type_subtype==44\" " % (ap_mac, sta_mac) + return filter + + def run_filter(self, pcap_file, filter): + filename = "/tmp/tshark_dump.txt" + cmd = "tshark -r %s %s > %s" % (pcap_file, filter, filename) + # print("CMD: ", cmd) + os.system(cmd) + lines = [] + with open(filename) as tshark_file: + for line in tshark_file: + lines.append(line.rstrip()) + + return lines diff --git a/lanforge/lanforge-scripts/py-json/realm_test.py b/lanforge/lanforge-scripts/py-json/realm_test.py new file mode 100755 index 000000000..e3e6d913d --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/realm_test.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import time +from pprint import pprint + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") + + +localrealm = Realm("localhost", 8080, True) + +print("** Existing Stations **") +try: + sta_list = localrealm.station_list() + print("\n%s Station List:" % len(sta_list)) + print(sta_list) + del sta_list + sta_map = localrealm.station_map() + print("\n%s Station Map:" % len(sta_map)) + print(sta_map) + del sta_map + print("\n Stations like wlan+:") + print(localrealm.find_ports_like("wlan+")) + print("\n Stations like wlan0:") + print(localrealm.find_ports_like("wlan0*")) + print("\n Stations between wlan0..wlan2:") + print(localrealm.find_ports_like("wlan[0..2]")) +except Exception as x: + pprint(x) + exit(1) + +print("\n** Removing previous stations **") +station_map = localrealm.find_ports_like("sta+") +for eid,record in station_map.items(): + pprint(eid) + # a list of these objects is not super useful unless + localrealm.remove_vlan_by_eid(eid) + time.sleep(0.03) + +# convert station map to plain list +del_sta_names = [] +try: + for eid,value in station_map.items(): + #print("jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj") + #pprint(eid) + #print("rfind: %d" % ) + #print("jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj") + tname = eid[eid.rfind('.'):] + del_sta_names.append(tname) +except Exception as x: + localrealm.error(x) + +LFUtils.waitUntilPortsDisappear(resource_id=1, base_url=localrealm.lfclient_url, port_list=del_sta_names, debug=False) +print("** Creating Stations **") +profile = localrealm.new_station_profile() +profile.use_wpa2(True, "jedway-wpa2-x2048-5-1", "jedway-wpa2-x2048-5-1") +profile.set_command_flag("add_sta", "80211u_enable", 1) +profile.set_number_template("0100") +profile.create(1, "wiphy0", 5) + +try: + sta_list = localrealm.station_list() + print("%s Stations:" % {len(sta_list)}) + pprint(sta_list) + print(" Stations like wlan+:") + print(localrealm.find_ports_like("wlan+")) + print(" Stations like wlan0:") + print(localrealm.find_ports_like("wlan0*")) + print(" Stations between wlan0..wlan2:") + print(localrealm.find_ports_like("wlan[0..2]")) +except Exception as x: + pprint(x) + exit(1) + +print(" - - - - TESTING - - - - - -") +#exit(0) + +print("** Existing vAPs **") +try: + vap_list = localrealm.vap_list() + print("%s VAPs:" % len(vap_list)) + pprint(vap_list) +except Exception as x: + localrealm.error(x) + exit(1) + +print("** Existing CXs **") +try: + cx_list = localrealm.cx_list() + print("%s CXs:" % len(cx_list)) + pprint(cx_list) +except Exception as x: + localrealm.error(x) + exit(1) + +print("** Removing previous CXs **") + +print("** Creating Layer 3 CXs **") +try: + cx_profile = localrealm.new_l3_cx_profile() + # set attributes of cxProfile + cx_profile.create("lf_udp", side_a="1.1.eth1", side_b=list(localrealm.find_ports_like("sta+"))) +except Exception as x: + pprint(x) + exit(1) + +try: + cx_profile = localrealm.new_l3_cx_profile() + # set attributes of cxProfile + cx_profile.create("lf_udp", side_a=list(localrealm.find_ports_like("sta+")), side_b="1.1.eth1") +except Exception as x: + pprint(x) + exit(1) + +print("** Creating Layer 4 CXs **") +try: + cx_profile = localrealm.new_l4_cx_profile() + # set attributes of cxProfile + cx_profile.create(localrealm.find_ports_like("sta+")) +except Exception as x: + pprint(x) + exit(1) + +print("** Creating Generic CXs **") +try: + cx_profile = localrealm.new_generic_cx_profile() + # set attributes of cxProfile + cx_profile.create(localrealm.find_ports_like("sta+")) +except Exception as x: + pprint(x) + exit(1) +# +exit(0) diff --git a/lanforge/lanforge-scripts/py-json/show_ports.py b/lanforge/lanforge-scripts/py-json/show_ports.py new file mode 100644 index 000000000..05fae06a2 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/show_ports.py @@ -0,0 +1,36 @@ +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# example of how to check a LANforge json url - +# - +# the syntax of the request is /port/<shelf=1>/<resource=1>/<list|all|portid> - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +import sys +import os +import importlib +import pprint + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") + + +def main(): + url = "http://localhost:8080/port/1/1/list" + timeout = 5 # seconds + + lf_r = LFRequest.LFRequest(url) + json_response = lf_r.getAsJson(True) + j_printer = pprint.PrettyPrinter(indent=2) + j_printer.pprint(json_response) + + #for record in json_response['interfaces']: + #j_printer.pprint(record) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == "__main__": + main() +# diff --git a/lanforge/lanforge-scripts/py-json/station_profile.py b/lanforge/lanforge-scripts/py-json/station_profile.py new file mode 100644 index 000000000..673ec0eaa --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/station_profile.py @@ -0,0 +1,568 @@ +# !/usr/bin/env python3 +import sys +import os +import importlib +from pprint import pprint +import time + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +set_port = importlib.import_module("py-json.LANforge.set_port") +add_sta = importlib.import_module("py-json.LANforge.add_sta") + + +# Uncomment below to include autogen library. +# if os.environ.get("LF_USE_AUTOGEN") == 1: +# lf_json_autogen = importlib.import_module("py-json.LANforge.lf_json_autogen") +# LFJsonPost = jf_json_autogen.LFJsonPost + +# use the station profile to set the combination of features you want on your stations +# once this combination is configured, build the stations with the build(resource, radio, number) call +# build() calls will fail if the station already exists. Please survey and clean your resource +# before calling build() +# realm = importlib.import_module("py-json.realm") +# Realm = realm.Realm +# survey = Realm.findStations(resource=1) +# Realm.removeStations(survey) +# profile = Realm.newStationProfile() +# profile.set... +# profile.build(resource, radio, 64) + + +class StationProfile: + def __init__(self, lfclient_url, local_realm, + ssid="NA", + ssid_pass="NA", + security="open", + number_template_="00000", + mode=0, + up=True, + resource=1, + shelf=1, + dhcp=True, + debug_=False, + use_ht160=False): + self.debug = debug_ + self.lfclient_url = lfclient_url + self.ssid = ssid + self.ssid_pass = ssid_pass + self.mode = mode + self.up = up + self.resource = resource + self.shelf = shelf + self.dhcp = dhcp + self.security = security + self.local_realm = local_realm + self.use_ht160 = use_ht160 + self.COMMANDS = ["add_sta", "set_port"] + self.desired_add_sta_flags = ["wpa2_enable", "80211u_enable", "create_admin_down"] + self.desired_add_sta_flags_mask = ["wpa2_enable", "80211u_enable", "create_admin_down"] + self.number_template = number_template_ + self.station_names = [] # eids, these are created station names + self.add_sta_data = { + "shelf": 1, + "resource": 1, + "radio": None, + "sta_name": None, + "ssid": None, + "key": None, + "mode": 0, + "mac": "xx:xx:xx:xx:*:xx", + "flags": 0, # (0x400 + 0x20000 + 0x1000000000) # create admin down + "flags_mask": 0 + } + self.desired_set_port_cmd_flags = [] + self.desired_set_port_current_flags = ["if_down"] + self.desired_set_port_interest_flags = ["current_flags", "ifdown"] + if self.dhcp: + self.desired_set_port_current_flags.append("use_dhcp") + self.desired_set_port_interest_flags.append("dhcp") + + self.set_port_data = { + "shelf": 1, + "resource": 1, + "port": None, + "current_flags": 0, + "interest": 0, # (0x2 + 0x4000 + 0x800000) # current, dhcp, down, + } + self.wifi_extra_data_modified = False + self.wifi_extra_data = { + "shelf": 1, + "resource": 1, + "port": None, + "key_mgmt": None, + "eap": None, + "hessid": None, + "identity": None, + "password": None, + "realm": None, + "domain": None + } + self.wifi_txo_data_modified = False + self.wifi_txo_data = { + "shelf": 1, + "resource": 1, + "port": None, + "txo_enable": None, + "txo_txpower": None, + "txo_pream": None, + "txo_mcs": None, + "txo_nss": None, + "txo_bw": None, + "txo_retries": None, + "txo_sgi": None + } + + self.reset_port_extra_data = { + "shelf": 1, + "resource": 1, + "port": None, + "test_duration": 0, + "reset_port_enable": False, + "reset_port_time_min": 0, + "reset_port_time_max": 0, + "reset_port_timer_started": False, + "port_to_reset": 0, + "seconds_till_reset": 0 + } + + def set_wifi_txo(self, txo_ena=1, + tx_power=255, + pream=0, + mcs=0, + nss=0, + bw=0, + retries=1, + sgi=0): + self.wifi_txo_data_modified = True + self.wifi_txo_data["txo_enable"] = txo_ena + self.wifi_txo_data["txo_txpower"] = tx_power + self.wifi_txo_data["txo_pream"] = pream + self.wifi_txo_data["txo_mcs"] = mcs + self.wifi_txo_data["txo_nss"] = nss + self.wifi_txo_data["txo_bw"] = bw + self.wifi_txo_data["txo_retries"] = retries + self.wifi_txo_data["txo_sgi"] = sgi + + def set_wifi_extra(self, key_mgmt="WPA-EAP", + pairwise="CCMP TKIP", + group="CCMP TKIP", + psk="[BLANK]", + wep_key="[BLANK]", # wep key + ca_cert="[BLANK]", + eap="TTLS", + identity="testuser", + anonymous_identity="[BLANK]", + phase1="NA", # outter auth + phase2="NA", # inner auth + passwd="testpasswd", # eap passphrase + pin="NA", + pac_file="NA", + private_key="NA", + pk_password="NA", # priv key password + hessid="00:00:00:00:00:01", + realm="localhost.localdomain", + client_cert="NA", + imsi="NA", + milenage="NA", + domain="localhost.localdomain", + roaming_consortium="NA", + venue_group="NA", + network_type="NA", + ipaddr_type_avail="NA", + network_auth_type="NA", + anqp_3gpp_cell_net="NA" + ): + self.wifi_extra_data_modified = True + self.wifi_extra_data["key_mgmt"] = key_mgmt + self.wifi_extra_data["pairwise"] = pairwise + self.wifi_extra_data["group"] = group + self.wifi_extra_data["psk"] = psk + self.wifi_extra_data["key"] = wep_key + self.wifi_extra_data["ca_cert"] = ca_cert + self.wifi_extra_data["eap"] = eap + self.wifi_extra_data["identity"] = identity + self.wifi_extra_data["anonymous_identity"] = anonymous_identity + self.wifi_extra_data["phase1"] = phase1 + self.wifi_extra_data["phase2"] = phase2 + self.wifi_extra_data["password"] = passwd + self.wifi_extra_data["pin"] = pin + self.wifi_extra_data["pac_file"] = pac_file + self.wifi_extra_data["private_key"] = private_key + self.wifi_extra_data["pk_passwd"] = pk_password + self.wifi_extra_data["hessid"] = hessid + self.wifi_extra_data["realm"] = realm + self.wifi_extra_data["client_cert"] = client_cert + self.wifi_extra_data["imsi"] = imsi + self.wifi_extra_data["milenage"] = milenage + self.wifi_extra_data["domain"] = domain + self.wifi_extra_data["roaming_consortium"] = roaming_consortium + self.wifi_extra_data["venue_group"] = venue_group + self.wifi_extra_data["network_type"] = network_type + self.wifi_extra_data["ipaddr_type_avail"] = ipaddr_type_avail + self.wifi_extra_data["network_auth_type"] = network_auth_type + self.wifi_extra_data["anqp_3gpp_cell_net"] = anqp_3gpp_cell_net + + def set_reset_extra(self, reset_port_enable=False, test_duration=0, reset_port_min_time=0, reset_port_max_time=0, + reset_port_timer_start=False, port_to_reset=0, time_till_reset=0): + self.reset_port_extra_data["reset_port_enable"] = reset_port_enable + self.reset_port_extra_data["test_duration"] = test_duration + self.reset_port_extra_data["reset_port_time_min"] = reset_port_min_time + self.reset_port_extra_data["reset_port_time_max"] = reset_port_max_time + + def use_security(self, security_type, ssid=None, passwd=None): + types = {"wep": "wep_enable", "wpa": "wpa_enable", "wpa2": "wpa2_enable", "wpa3": "use-wpa3", "open": "[BLANK]"} + self.add_sta_data["ssid"] = ssid + if security_type in types.keys(): + if (ssid is None) or (ssid == ""): + raise ValueError("use_security: %s requires ssid" % security_type) + if (passwd is None) or (passwd == ""): + raise ValueError("use_security: %s requires passphrase or [BLANK]" % security_type) + for name in types.values(): + if name in self.desired_add_sta_flags and name in self.desired_add_sta_flags_mask: + self.desired_add_sta_flags.remove(name) + self.desired_add_sta_flags_mask.remove(name) + if security_type != "open": + self.desired_add_sta_flags.append(types[security_type]) + # self.set_command_flag("add_sta", types[security_type], 1) + self.desired_add_sta_flags_mask.append(types[security_type]) + else: + passwd = "[BLANK]" + self.set_command_param("add_sta", "ssid", ssid) + self.set_command_param("add_sta", "key", passwd) + # unset any other security flag before setting our present flags + if security_type == "wpa3": + self.set_command_param("add_sta", "ieee80211w", 2) + # self.add_sta_data["key"] = passwd + + def station_mode_to_number(self, mode): + modes = ['a', 'b', 'g', 'abg', 'an', 'abgn', 'bgn', 'bg', 'abgn-AC', 'bgn-AC', 'an-AC'] + return modes.index(mode) + 1 + + def add_security_extra(self, security): + types = {"wep": "wep_enable", "wpa": "wpa_enable", "wpa2": "wpa2_enable", "wpa3": "use-wpa3", "open": "[BLANK]"} + if self.desired_add_sta_flags.__contains__(types[security]) and \ + self.desired_add_sta_flags_mask.__contains__(types[security]): + self.desired_add_sta_flags.remove(types[security]) + self.desired_add_sta_flags_mask.remove(types[security]) + self.desired_add_sta_flags.append(types[security]) + self.desired_add_sta_flags_mask.append(types[security]) + if security == "wpa3": + self.set_command_param("add_sta", "ieee80211w", 2) + + def set_command_param(self, command_name, param_name, param_value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + raise ValueError("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + # return + if command_name == "add_sta": + self.add_sta_data[param_name] = param_value + elif command_name == "set_port": + self.set_port_data[param_name] = param_value + + def set_command_flag(self, command_name, param_name, value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + print("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + return + if command_name == "add_sta": + if (param_name not in add_sta.add_sta_flags) and (param_name not in add_sta.add_sta_modes): + print("Parameter name [%s] not defined in add_sta.py" % param_name) + if self.debug: + pprint(add_sta.add_sta_flags) + return + if (value == 1) and (param_name not in self.desired_add_sta_flags): + self.desired_add_sta_flags.append(param_name) + self.desired_add_sta_flags_mask.append(param_name) + elif value == 0: + self.desired_add_sta_flags.remove(param_name) + self.desired_add_sta_flags_mask.append(param_name) + + elif command_name == "set_port": + if (param_name not in set_port.set_port_current_flags) and ( + param_name not in set_port.set_port_cmd_flags) and ( + param_name not in set_port.set_port_interest_flags): + print("Parameter name [%s] not defined in set_port.py" % param_name) + if self.debug: + pprint(set_port.set_port_cmd_flags) + pprint(set_port.set_port_current_flags) + pprint(set_port.set_port_interest_flags) + return + if (param_name in set_port.set_port_cmd_flags): + if (value == 1) and (param_name not in self.desired_set_port_cmd_flags): + self.desired_set_port_cmd_flags.append(param_name) + elif value == 0: + self.desired_set_port_cmd_flags.remove(param_name) + elif (param_name in set_port.set_port_current_flags): + if (value == 1) and (param_name not in self.desired_set_port_current_flags): + self.desired_set_port_current_flags.append(param_name) + elif value == 0: + self.desired_set_port_current_flags.remove(param_name) + elif (param_name in set_port.set_port_interest_flags): + if (value == 1) and (param_name not in self.desired_set_port_interest_flags): + self.desired_set_port_interest_flags.append(param_name) + elif value == 0: + self.desired_set_port_interest_flags.remove(param_name) + else: + raise ValueError("Unknown param name: " + param_name) + + # use this for hinting station name; stations begin with 'sta', the + # stations created with a prefix '0100' indicate value 10100 + n with + # resulting substring(1,) applied; station 900 becomes 'sta1000' + def set_number_template(self, pref): + self.number_template = pref + + def add_named_flags(self, desired_list, command_ref): + if desired_list is None: + raise ValueError("addNamedFlags wants a list of desired flag names") + if len(desired_list) < 1: + print("addNamedFlags: empty desired list") + return 0 + if (command_ref is None) or (len(command_ref) < 1): + raise ValueError("addNamedFlags wants a maps of flag values") + + result = 0 + for name in desired_list: + if (name is None) or (name == ""): + continue + if name not in command_ref: + if self.debug: + pprint(command_ref) + raise ValueError("flag %s not in map" % name) + result += command_ref[name] + + return result + + def admin_up(self): + for eid in self.station_names: + # print("3139: admin_up sta "+eid) + # time.sleep(2) + self.local_realm.admin_up(eid) + time.sleep(0.005) + + def admin_down(self): + for sta_name in self.station_names: + self.local_realm.admin_down(sta_name) + + def cleanup(self, desired_stations=None, delay=0.03, debug_=False): + print("Cleaning up stations") + + if (desired_stations is None): + desired_stations = self.station_names + + if len(desired_stations) < 1: + print("ERROR: StationProfile cleanup, list is empty") + return + + # First, request remove on the list. + for port_eid in desired_stations: + self.local_realm.rm_port(port_eid, check_exists=True, debug_=debug_) + time.sleep(delay) + # And now see if they are gone + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=desired_stations) + + # Checks for errors in initialization values and creates specified number of stations using init parameters + def create(self, radio, + num_stations=0, + sta_names_=None, + dry_run=False, + up_=None, + debug=False, + suppress_related_commands_=True, + use_radius=False, + hs20_enable=False, + sleep_time=0.02): + if (radio is None) or (radio == ""): + raise ValueError("station_profile.create: will not create stations without radio") + radio_eid = self.local_realm.name_to_eid(radio) + radio_shelf = radio_eid[0] + radio_resource = radio_eid[1] + radio_port = radio_eid[2] + + if self.use_ht160: + self.desired_add_sta_flags.append("ht160_enable") + self.desired_add_sta_flags_mask.append("ht160_enable") + if self.mode is not None: + self.add_sta_data["mode"] = self.mode + if use_radius: + self.desired_add_sta_flags.append("8021x_radius") + self.desired_add_sta_flags_mask.append("8021x_radius") + if hs20_enable: + self.desired_add_sta_flags.append("hs20_enable") + self.desired_add_sta_flags_mask.append("hs20_enable") + if up_ is not None: + self.up = up_ + + if (sta_names_ is None) and (num_stations == 0): + raise ValueError("StationProfile.create needs either num_stations= or sta_names_= specified") + + if self.up: + if "create_admin_down" in self.desired_add_sta_flags: + del self.desired_add_sta_flags[self.desired_add_sta_flags.index("create_admin_down")] + elif "create_admin_down" not in self.desired_add_sta_flags: + self.desired_add_sta_flags.append("create_admin_down") + + # create stations down, do set_port on them, then set stations up + self.add_sta_data["flags"] = self.add_named_flags(self.desired_add_sta_flags, add_sta.add_sta_flags) + self.add_sta_data["flags_mask"] = self.add_named_flags(self.desired_add_sta_flags_mask, add_sta.add_sta_flags) + self.add_sta_data["radio"] = radio_port + + self.add_sta_data["resource"] = radio_resource + self.add_sta_data["shelf"] = radio_shelf + self.set_port_data["resource"] = radio_resource + self.set_port_data["shelf"] = radio_shelf + self.set_port_data["current_flags"] = self.add_named_flags(self.desired_set_port_current_flags, + set_port.set_port_current_flags) + self.set_port_data["interest"] = self.add_named_flags(self.desired_set_port_interest_flags, + set_port.set_port_interest_flags) + self.wifi_extra_data["resource"] = radio_resource + self.wifi_extra_data["shelf"] = radio_shelf + self.wifi_txo_data["resource"] = radio_resource + self.wifi_txo_data["shelf"] = radio_shelf + self.reset_port_extra_data["resource"] = radio_resource + self.reset_port_extra_data["shelf"] = radio_shelf + + # these are unactivated LFRequest objects that we can modify and + # re-use inside a loop, reducing the number of object creations + add_sta_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/add_sta", debug_=debug) + set_port_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/set_port", debug_=debug) + wifi_extra_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/set_wifi_extra", debug_=debug) + wifi_txo_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/set_wifi_txo", debug_=debug) + my_sta_names = [] + # add radio here + if (num_stations > 0) and (len(sta_names_) < 1): + # print("CREATING MORE STA NAMES == == == == == == == == == == == == == == == == == == == == == == == ==") + my_sta_names = LFUtils.portNameSeries("sta", 0, num_stations - 1, int("1" + self.number_template)) + # print("CREATING MORE STA NAMES == == == == == == == == == == == == == == == == == == == == == == == ==") + else: + my_sta_names = sta_names_ + + if (len(my_sta_names) >= 15) or (suppress_related_commands_ == True): + self.add_sta_data["suppress_preexec_cli"] = "yes" + self.add_sta_data["suppress_preexec_method"] = 1 + self.set_port_data["suppress_preexec_cli"] = "yes" + self.set_port_data["suppress_preexec_method"] = 1 + + num = 0 + if debug: + print("== == Created STA names == == == == == == == == == == == == == == == == == == == == == == == ==") + pprint(self.station_names) + print("== == vs Pending STA names == ==") + pprint(my_sta_names) + print("== == == == == == == == == == == == == == == == == == == == == == == == == ==") + + # track the names of stations in case we have stations added multiple times + finished_sta = [] + + for eidn in my_sta_names: + if eidn in self.station_names: + print("Station %s already created, skipping." % eidn) + continue + + # print (" EIDN "+eidn); + if eidn in finished_sta: + # pprint(my_sta_names) + # raise ValueError("************ duplicate ****************** "+eidn) + if self.debug: + print("Station %s already created" % eidn) + continue + + eid = self.local_realm.name_to_eid(eidn) + name = eid[2] + num += 1 + self.add_sta_data["shelf"] = radio_shelf + self.add_sta_data["resource"] = radio_resource + self.add_sta_data["radio"] = radio_port + self.add_sta_data["sta_name"] = name # for create station calls + self.set_port_data["port"] = name # for set_port calls. + self.set_port_data["shelf"] = radio_shelf + self.set_port_data["resource"] = radio_resource + + add_sta_r.addPostData(self.add_sta_data) + if debug: + print("- 3254 - %s- - - - - - - - - - - - - - - - - - " % eidn) + pprint(add_sta_r.requested_url) + pprint(add_sta_r.proxies) + pprint(self.add_sta_data) + print(self.set_port_data) + print("- ~3254 - - - - - - - - - - - - - - - - - - - ") + if dry_run: + print("dry run: not creating " + eidn) + continue + + # print("- 3264 - ## %s ## add_sta_r.jsonPost - - - - - - - - - - - - - - - - - - "%eidn) + json_response = add_sta_r.jsonPost(debug=self.debug) + finished_sta.append(eidn) + # print("- ~3264 - %s - add_sta_r.jsonPost - - - - - - - - - - - - - - - - - - "%eidn) + time.sleep(0.01) + set_port_r.addPostData(self.set_port_data) + # print("- 3270 -- %s -- set_port_r.jsonPost - - - - - - - - - - - - - - - - - - "%eidn) + json_response = set_port_r.jsonPost(debug) + # print("- ~3270 - %s - set_port_r.jsonPost - - - - - - - - - - - - - - - - - - "%eidn) + time.sleep(0.01) + + self.wifi_extra_data["resource"] = radio_resource + self.wifi_extra_data["port"] = name + self.wifi_txo_data["resource"] = radio_resource + self.wifi_txo_data["port"] = name + if self.wifi_extra_data_modified: + wifi_extra_r.addPostData(self.wifi_extra_data) + json_response = wifi_extra_r.jsonPost(debug) + if self.wifi_txo_data_modified: + wifi_txo_r.addPostData(self.wifi_txo_data) + json_response = wifi_txo_r.jsonPost(debug) + + # append created stations to self.station_names + self.station_names.append("%s.%s.%s" % (radio_shelf, radio_resource, name)) + time.sleep(sleep_time) + + # print("- ~3287 - waitUntilPortsAppear - - - - - - - - - - - - - - - - - - "%eidn) + LFUtils.wait_until_ports_appear(self.lfclient_url, my_sta_names) + + # and set ports up + if dry_run: + return + if (self.up): + self.admin_up() + + # for sta_name in self.station_names: + # req = LFUtils.portUpRequest(resource, sta_name, debug_on=False) + # set_port_r.addPostData(req) + # json_response = set_port_r.jsonPost(debug) + # time.sleep(0.03) + if self.debug: + print("created %s stations" % num) + + def modify(self, radio): + for station in self.station_names: + print(station) + self.add_sta_data["flags"] = self.add_named_flags(self.desired_add_sta_flags, add_sta.add_sta_flags) + self.add_sta_data["flags_mask"] = self.add_named_flags(self.desired_add_sta_flags_mask, + add_sta.add_sta_flags) + self.add_sta_data["radio"] = radio + self.add_sta_data["sta_name"] = station + self.add_sta_data["ssid"] = 'NA' + self.add_sta_data["key"] = 'NA' + self.add_sta_data['mac'] = 'NA' + self.add_sta_data['mode'] = 'NA' + self.add_sta_data['suppress_preexec_cli'] = 'NA' + self.add_sta_data['suppress_preexec_method'] = 'NA' + + add_sta_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/add_sta") + if self.debug: + print(self.lfclient_url + "/cli_json/add_sta") + print(self.add_sta_data) + add_sta_r.addPostData(self.add_sta_data) + json_response = add_sta_r.jsonPost(self.debug) diff --git a/lanforge/lanforge-scripts/py-json/test_base.py b/lanforge/lanforge-scripts/py-json/test_base.py new file mode 100644 index 000000000..aa349e667 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/test_base.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +class TestBase: + def __init__(self): + self.profiles = list() + + def pre_clean_up(self): + if self.profiles: + for profile in self.profiles: + profile.precleanup() + + def clean_up(self): + if self.profiles: + for profile in self.profiles: + profile.cleanup() + + def start(self): + if self.profiles: + for profile in self.profiles: + profile.start() + + def stop(self): + if self.profiles: + for profile in self.profiles: + profile.stop() + + def build(self): + # - create station profile + # - create 2 criteria [ex: not down, continually_receiving] object (for ex) + # - station_profile.add_criteria([not_down, continually_receiving, etc_3]) + # design - inversion of control + + if self.profiles: + for profile in self.profiles: + profile.build() + + def passes(self): + if self.profiles: + for profile in self.profiles: + profile.check_passes() + + def run_duration(self, monitor_enabled= False): + #here check if monitor is enabled or not, then run loop accordingly + self.check_for_halt() + if self.profiles: + if monitor_enabled: + for profile in self.profiles: + profile.monitor_record() #check for halt in monitor record? + for profile in self.profiles: + profile.grade() + if self.exit_on_fail: + if self.fails(): + self.exit_fail() + self.check_for_quit() + + def report(self, enabled= False): + #here check if monitor is enabled or not, then run loop accordingly with lfreporting + pass + + def begin(self): + self.pre_clean_up() + self.build() + self.start() + self.run_duration() + self.stop() + self.report() + self.clean_up() + diff --git a/lanforge/lanforge-scripts/py-json/test_group_profile.py b/lanforge/lanforge-scripts/py-json/test_group_profile.py new file mode 100644 index 000000000..b1723c4a8 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/test_group_profile.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class TestGroupProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, local_realm, test_group_name=None, debug_=False): + super().__init__(lfclient_host, lfclient_port, debug_) + self.local_realm = local_realm + self.group_name = test_group_name + self.cx_list = [] + + def start_group(self): + if self.group_name is not None: + self.local_realm.json_post("/cli-json/start_group", {"name": self.group_name}) + else: + raise ValueError("test_group name must be set.") + + def quiesce_group(self): + if self.group_name is not None: + self.local_realm.json_post("/cli-json/quiesce_group", {"name": self.group_name}) + else: + raise ValueError("test_group name must be set.") + + def stop_group(self): + if self.group_name is not None: + self.local_realm.json_post("/cli-json/stop_group", {"name": self.group_name}) + else: + raise ValueError("test_group name must be set.") + + def create_group(self): + if self.group_name is not None: + self.local_realm.json_post("/cli-json/add_group", {"name": self.group_name}) + else: + raise ValueError("test_group name must be set.") + + def rm_group(self): + if self.group_name is not None: + self.local_realm.json_post("/cli-json/rm_group", {"name": self.group_name}) + else: + raise ValueError("test_group name must be set.") + + def add_cx(self, cx_name): + self.local_realm.json_post("/cli-json/add_tgcx", {"tgname": self.group_name, "cxname": cx_name}) + + def rm_cx(self, cx_name): + self.local_realm.json_post("/cli-json/rm_tgcx", {"tgname": self.group_name, "cxname": cx_name}) + + def check_group_exists(self): + test_groups = self.local_realm.json_get("/testgroups/all") + if test_groups is not None and "groups" in test_groups: + test_groups = test_groups["groups"] + for group in test_groups: + for k, v in group.items(): + if v['name'] == self.group_name: + return True + else: + return False + + def list_groups(self): + test_groups = self.local_realm.json_get("/testgroups/all") + tg_list = [] + if test_groups is not None: + test_groups = test_groups["groups"] + for group in test_groups: + for k, v in group.items(): + tg_list.append(v['name']) + return tg_list + + def list_cxs(self): + test_groups = self.local_realm.json_get("/testgroups/all") + if test_groups is not None: + test_groups = test_groups["groups"] + for group in test_groups: + for k, v in group.items(): + if v['name'] == self.group_name: + return v['cross connects'] + + else: + return [] diff --git a/lanforge/lanforge-scripts/py-json/test_histogram.py b/lanforge/lanforge-scripts/py-json/test_histogram.py new file mode 100755 index 000000000..aefd2ab6b --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/test_histogram.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +""" ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + internal test driving LFUtils.expand_endp_histogram +----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- """ +import sys +import os +import importlib + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") + + +distrib_load = { + "histo_category_width" : 3, + "histogram" : [ + 221, + 113, + 266, + 615, + 16309, + 56853, + 7954, + 1894, + 29246, + 118, + 12, + 2, + 0, + 0, + 0, + 0 + ], + "time window ms" : 300000, + "window avg" : 210.285, + "window max" : 228, + "window min" : 193 +} + +if __name__ == '__main__': + LFUtils.expand_endp_histogram(distrib_load) + + diff --git a/lanforge/lanforge-scripts/py-json/test_utility.py b/lanforge/lanforge-scripts/py-json/test_utility.py new file mode 100644 index 000000000..edc6462a0 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/test_utility.py @@ -0,0 +1,327 @@ +""" +Candela Technologies Inc. + +Info : Standard Script for Webconsole Test Utility +Date : + +""" +import sys +import os +import importlib +import time +import matplotlib.pyplot as plt +import threading +import re +import json + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +PortUtils = realm.PortUtils + +webconsole_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.getcwd()))) +updates_path = webconsole_dir + "/web_json/updates.js" + + +class ClientVisualization(LFCliBase, threading.Thread): + def __init__(self, lfclient_host="localhost", lfclient_port=8080, num_clients= 64, max_data= 120, thread_id=None, _debug_on=False, _exit_on_error=False, _exit_on_fail=False): + super().__init__(lfclient_host, lfclient_port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + threading.Thread.__init__(self) + self.num_clients = num_clients + self.max_data = max_data + self._stop_event = threading.Event() + self.client_data = {"down":[], "phantom":[], "ip":[], "scanning":[]} + + def stop(self): + self._stop_event.set() + + def stopped(self): + return self._stop_event.is_set() + + def run(self): + self.start_thread() + + def start_thread(self): + while True: + self.scanning = 0 + self.ip = 0 + self.down = 0 + self.phantom = 0 + for i in self.json_get("/port/list?fields=port,alias,parent%20dev,down,phantom,ip,port%20type")['interfaces']: + + for j in i: + print(i[j]['port type']) + if i[j]['port type'] == "WIFI-STA" and i[j]['parent dev'] == "wiphy1" and i[j]['alias'] != 'wlan1': + #print(j) + if i[j]['down'] == False and i[j]['phantom'] == False and i[j]['ip'] == '0.0.0.0': + self.scanning += 1 + elif i[j]['down'] == False and i[j]['phantom'] == True: + self.phantom += 1 + elif i[j]['down'] == True and i[j]['phantom'] == True: + self.phantom += 1 + self.client_data['phantom'].append(self.phantom) + elif i[j]['down'] == True and i[j]['phantom'] == False: + self.down += 1 + elif i[j]['ip'] != "0.0.0.0": + self.ip += 1 + else: + continue + self.client_data['scanning'].append(self.scanning) + self.client_data['phantom'].append(self.phantom) + self.client_data['down'].append(self.down) + self.client_data['ip'].append(self.ip) + + + for i in self.client_data: + if len(self.client_data[i]) >= self.max_data: + self.client_data[i].pop(0) + time.sleep(1) + if self.stopped(): + break + + +class CreateHTML(): + def __init__(self, path="", test_name="", time_snap="", dut_ssid="", test_conf_data={}, objective="", test_results={}, chart_data={}, chart_params={}): + self.head = """ + <html> + <head> + <title>"""+test_name+""" + + +
    + +
    +
    +

    """+test_name+"""

    +

    """+time_snap+"""

    +
    +
    +
    + """ + self.test_conf = """ + + + + + + + + +
    + Test Setup Information +
    + Device Under Test + + + + + + + """ + + for i in test_conf_data: + self.test_conf = self.test_conf + """ + + + + """ + + self.test_conf = self.test_conf + """
    + SSID + """+dut_ssid+""" +
    """+str(i)+""" + """+test_conf_data[i]+""" +
    +
    + """ + + self.objective = """ +

    Objective

    + """+objective+""" +

    +
    + """ + + if str(test_results['summary']).__contains__("PASS"): + self.summary_results =""" +
    + + + + + + + +
    + Summary Results +
    + """ + test_results['summary'] + """ +
    +
    + """ + else: + self.summary_results = """ +
    + + + + + + + +
    + Summary Results +
    + """ + test_results['summary'] + """ +
    +
    + """ + chart_d =[] + chart_label =[] + for i in chart_data: + chart_label.append(i) + chart_d.append(chart_data[i]) + + + + + self.detail_result = """ + +
    Detailed Results
    + + """ + for index in test_results['detail']['keys']: + self.detail_result = self.detail_result+"" + self.detail_result = self.detail_result +"" + + for data in test_results['detail']['data']: + self.detail_result = self.detail_result + "" + print("shivam") + print(data) + for i in data: + print(data[i]) + if str(data[i]).__contains__("PASS"): + self.detail_result = self.detail_result + "" + elif str(data[i]).__contains__("FAIL"): + self.detail_result = self.detail_result + "" + else: + self.detail_result = self.detail_result + "" + self.detail_result = self.detail_result +"" + + self.chart_data = chart_data + chart_values = [] + for i in self.chart_data: + chart_values.append(self.chart_data[i]) + plt.bar(list(self.chart_data.keys()), chart_values, tick_label=list(self.chart_data.keys())) + + plt.xlabel(chart_params['xlabel']) + # naming the y-axis + plt.ylabel(chart_params['ylabel']) + # plot title + plt.title(chart_params['chart_head']) + plt.xticks(rotation=90, fontsize=8) + plt.tight_layout() + # function to show the plot + plt.savefig(fname=path + "plot.png") + plt.close() + + self.chart = """""" + + + self.end = """
    "+index+"
    " + str(data[i]) + "" + str(data[i]) + "" + str(data[i]) + "
    + + + + """ + self.report = self.head + self.test_conf + self.objective + self.summary_results + self.chart +self.detail_result + self.end + + + +class RuntimeUpdates(): + def __init__(self, session_id, init_data): + self.session_id = session_id + self.init_data = init_data + f = open(updates_path, 'r+') + data = f.read() + f.close() + obj = data[data.find('{'): data.rfind('}') + 1] + obj = re.sub('[\']', '"', obj) + data = json.loads(obj) + print(data) + data["web_updates"].append({"ID": self.session_id, "data": self.init_data}) + print(data) + f = open(updates_path, 'r+') + f.seek(0) + f.truncate() + f.write("var updates = " + str(data) + ";") + f.close() + + def send_update(self, update_data): + f = open(updates_path, 'r+') + data = f.read() + f.close() + obj = data[data.find('{'): data.rfind('}') + 1] + obj = re.sub('[\']', '"', obj) + data = json.loads(obj) + + for update in data["web_updates"]: + if update["ID"] == self.session_id: + update["data"] = update_data + print(data) + f = open(updates_path, 'r+') + f.seek(0) + f.truncate() + f.write("var updates = " + str(data) + ";") + f.close() + + +class StatusSession(LFCliBase): + def __init__(self, lfclient_host="localhost", lfclient_port=8080, + _deep_clean=False, + session_id="0", + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(lfclient_host, lfclient_port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.deep_clean = _deep_clean + self.session_id = session_id + self.json_put("/status-msg/" + self.session_id, {}) + + def update(self, key, message): + """ + Method to add new Message into a session + """ + self.json_post("/status-msg/" + self.session_id, { + "key": key, + "content-type": "text/plain", + "message": message + }) + + def read(self): + """ + Method to read all the messages for a particular session + """ + keys = [] + for i in self.json_get("/status-msg/"+self.session_id)['messages']: + keys.append(i['key']) + json_uri = "/status-msg/"+self.session_id + "/" + for i in keys: + json_uri = json_uri + i + "," + return self.json_get(json_uri)['messages'] + + + + + + + + +if __name__ == "__main__": + obj = StatusMsg(lfclient_host="localhost", lfclient_port=8090, session_id="01_18_21_20_04_20") + print(obj.read()) + + diff --git a/lanforge/lanforge-scripts/py-json/vap_profile.py b/lanforge/lanforge-scripts/py-json/vap_profile.py new file mode 100644 index 000000000..cace9bb30 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/vap_profile.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +from pprint import pprint +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFRequest = importlib.import_module("py-json.LANforge.LFRequest") +add_vap = importlib.import_module("py-json.LANforge.add_vap") +set_port = importlib.import_module("py-json.LANforge.set_port") +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") + + +class VAPProfile(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, local_realm, + vap_name="", + ssid="NA", + ssid_pass="NA", + mode=0, + debug_=False): + super().__init__(_lfjson_host=lfclient_host, _lfjson_port=lfclient_port, _debug=debug_) + self.debug = debug_ + # self.lfclient_url = lfclient_url # done in super() + self.ssid = ssid + self.ssid_pass = ssid_pass + self.mode = mode + self.local_realm = local_realm + self.vap_name = vap_name + self.COMMANDS = ["add_vap", "set_port"] + self.desired_add_vap_flags = ["wpa2_enable", "80211u_enable", "create_admin_down"] + self.desired_add_vap_flags_mask = ["wpa2_enable", "80211u_enable", "create_admin_down"] + + self.add_vap_data = { + "shelf": 1, + "resource": 1, + "radio": None, + "ap_name": None, + "flags": 0, + "flags_mask": 0, + "mode": 0, + "ssid": None, + "key": None, + "mac": "xx:xx:xx:xx:*:xx" + } + + self.desired_set_port_cmd_flags = [] + self.desired_set_port_current_flags = ["if_down"] + self.desired_set_port_interest_flags = ["current_flags", "ifdown"] + self.set_port_data = { + "shelf": 1, + "resource": 1, + "port": None, + "current_flags": 0, + "interest": 0, # (0x2 + 0x4000 + 0x800000) # current, dhcp, down + } + self.wifi_extra_data_modified = False + self.wifi_extra_data = { + "shelf": 1, + "resource": 1, + "port": None, + "key_mgmt": None, + "eap": None, + "hessid": None, + "identity": None, + "password": None, + "realm": None, + "domain": None + } + + def set_wifi_extra(self, + key_mgmt="WPA-EAP", + pairwise="DEFAULT", + group="DEFAULT", + psk="[BLANK]", + eap="TTLS", + identity="testuser", + passwd="testpasswd", + realm="localhost.localdomain", + domain="localhost.localdomain", + hessid="00:00:00:00:00:01"): + self.wifi_extra_data_modified = True + self.wifi_extra_data["key_mgmt"] = key_mgmt + self.wifi_extra_data["eap"] = eap + self.wifi_extra_data["identity"] = identity + self.wifi_extra_data["password"] = passwd + self.wifi_extra_data["realm"] = realm + self.wifi_extra_data["domain"] = domain + self.wifi_extra_data["hessid"] = hessid + + def admin_up(self, resource): + set_port_r = LFRequest.LFRequest(self.lfclient_url, "/cli-json/set_port", debug_=self.debug) + req_json = LFUtils.portUpRequest(resource, None, debug_on=self.debug) + req_json["port"] = self.vap_name + set_port_r.addPostData(req_json) + json_response = set_port_r.jsonPost(self.debug) + time.sleep(0.03) + + def admin_down(self, resource): + set_port_r = LFRequest.LFRequest(self.lfclient_url, "/cli-json/set_port", debug_=self.debug) + req_json = LFUtils.port_down_request(resource, None, debug_on=self.debug) + req_json["port"] = self.vap_name + set_port_r.addPostData(req_json) + json_response = set_port_r.jsonPost(self.debug) + time.sleep(0.03) + + def use_security(self, security_type, ssid=None, passwd=None): + types = {"wep": "wep_enable", "wpa": "wpa_enable", "wpa2": "wpa2_enable", "wpa3": "use-wpa3", "open": "[BLANK]"} + self.add_vap_data["ssid"] = ssid + if security_type in types.keys(): + if (ssid is None) or (ssid == ""): + raise ValueError("use_security: %s requires ssid" % security_type) + if (passwd is None) or (passwd == ""): + raise ValueError("use_security: %s requires passphrase or [BLANK]" % security_type) + for name in types.values(): + if name in self.desired_add_vap_flags and name in self.desired_add_vap_flags_mask: + self.desired_add_vap_flags.remove(name) + self.desired_add_vap_flags_mask.remove(name) + if security_type != "open": + self.desired_add_vap_flags.append(types[security_type]) + self.desired_add_vap_flags_mask.append(types[security_type]) + else: + passwd = "[BLANK]" + self.set_command_param("add_vap", "ssid", ssid) + self.set_command_param("add_vap", "key", passwd) + # unset any other security flag before setting our present flags + if security_type == "wpa3": + self.set_command_param("add_vap", "ieee80211w", 2) + + def set_command_flag(self, command_name, param_name, value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + print("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + return + if command_name == "add_vap": + if (param_name not in add_vap.add_vap_flags): + print("Parameter name [%s] not defined in add_vap.py" % param_name) + if self.debug: + pprint(add_vap.add_vap_flags) + return + if (value == 1) and (param_name not in self.desired_add_vap_flags): + self.desired_add_vap_flags.append(param_name) + self.desired_add_vap_flags_mask.append(param_name) + elif value == 0: + self.desired_add_vap_flags.remove(param_name) + self.desired_add_vap_flags_mask.append(param_name) + + elif command_name == "set_port": + if (param_name not in set_port.set_port_current_flags) and ( + param_name not in set_port.set_port_cmd_flags) and ( + param_name not in set_port.set_port_interest_flags): + print("Parameter name [%s] not defined in set_port.py" % param_name) + if self.debug: + pprint(set_port.set_port_cmd_flags) + pprint(set_port.set_port_current_flags) + pprint(set_port.set_port_interest_flags) + return + if param_name in set_port.set_port_cmd_flags: + if (value == 1) and (param_name not in self.desired_set_port_cmd_flags): + self.desired_set_port_cmd_flags.append(param_name) + elif value == 0: + self.desired_set_port_cmd_flags.remove(param_name) + elif param_name in set_port.set_port_current_flags: + if (value == 1) and (param_name not in self.desired_set_port_current_flags): + self.desired_set_port_current_flags.append(param_name) + elif value == 0: + self.desired_set_port_current_flags.remove(param_name) + elif param_name in set_port.set_port_interest_flags: + if (value == 1) and (param_name not in self.desired_set_port_interest_flags): + self.desired_set_port_interest_flags.append(param_name) + elif value == 0: + self.desired_set_port_interest_flags.remove(param_name) + else: + raise ValueError("Unknown param name: " + param_name) + + def set_command_param(self, command_name, param_name, param_value): + # we have to check what the param name is + if (command_name is None) or (command_name == ""): + return + if (param_name is None) or (param_name == ""): + return + if command_name not in self.COMMANDS: + self.error("Command name name [%s] not defined in %s" % (command_name, self.COMMANDS)) + return + if command_name == "add_vap": + self.add_vap_data[param_name] = param_value + elif command_name == "set_port": + self.set_port_data[param_name] = param_value + + def add_named_flags(self, desired_list, command_ref): + if desired_list is None: + raise ValueError("addNamedFlags wants a list of desired flag names") + if len(desired_list) < 1: + print("addNamedFlags: empty desired list") + return 0 + if (command_ref is None) or (len(command_ref) < 1): + raise ValueError("addNamedFlags wants a maps of flag values") + + result = 0 + for name in desired_list: + if (name is None) or (name == ""): + continue + if name not in command_ref: + if self.debug: + pprint(command_ref) + raise ValueError("flag %s not in map" % name) + # print("add-named-flags: %s %i"%(name, command_ref[name])) + result |= command_ref[name] + + return result + + def create(self, resource, radio, channel=None, up_=None, debug=False, use_ht40=True, use_ht80=True, + use_ht160=False, + suppress_related_commands_=True, use_radius=False, hs20_enable=False, bridge=True): + port_list = self.local_realm.json_get("port/1/1/list") + if port_list is not None: + port_list = port_list['interfaces'] + for port in port_list: + for k, v in port.items(): + if v['alias'] == self.vap_name: + self.local_realm.rm_port(k, check_exists=True) + if use_ht160: + self.desired_add_vap_flags.append("enable_80211d") + self.desired_add_vap_flags_mask.append("enable_80211d") + self.desired_add_vap_flags.append("80211h_enable") + self.desired_add_vap_flags_mask.append("80211h_enable") + self.desired_add_vap_flags.append("ht160_enable") + self.desired_add_vap_flags_mask.append("ht160_enable") + if not use_ht40: + self.desired_add_vap_flags.append("disable_ht40") + self.desired_add_vap_flags_mask.append("disable_ht40") + if not use_ht80: + self.desired_add_vap_flags.append("disable_ht80") + self.desired_add_vap_flags_mask.append("disable_ht80") + if use_radius: + self.desired_add_vap_flags.append("8021x_radius") + self.desired_add_vap_flags_mask.append("8021x_radius") + if hs20_enable: + self.desired_add_vap_flags.append("hs20_enable") + self.desired_add_vap_flags_mask.append("hs20_enable") + + # print("MODE ========= ", self.mode) + + jr = self.local_realm.json_get("/radiostatus/1/%s/%s?fields=channel,frequency,country" % (resource, radio), + debug_=self.debug) + if jr is None: + raise ValueError("No radio %s.%s found" % (resource, radio)) + + eid = "1.%s.%s" % (resource, radio) + frequency = 0 + country = 0 + if eid in jr: + country = jr[eid]["country"] + + data = { + "shelf": 1, + "resource": resource, + "radio": radio, + "mode": self.mode, # "NA", #0 for AUTO or "NA" + "channel": channel, + "country": country, + "frequency": self.local_realm.channel_freq(channel_=channel) + } + self.local_realm.json_post("/cli-json/set_wifi_radio", _data=data) + if up_ is not None: + self.up = up_ + if self.up: + if "create_admin_down" in self.desired_add_vap_flags: + del self.desired_add_vap_flags[self.desired_add_vap_flags.index("create_admin_down")] + elif "create_admin_down" not in self.desired_add_vap_flags: + self.desired_add_vap_flags.append("create_admin_down") + + # create vaps down, do set_port on them, then set vaps up + self.add_vap_data["mode"] = self.mode + self.add_vap_data["flags"] = self.add_named_flags(self.desired_add_vap_flags, add_vap.add_vap_flags) + self.add_vap_data["flags_mask"] = self.add_named_flags(self.desired_add_vap_flags_mask, add_vap.add_vap_flags) + self.add_vap_data["radio"] = radio + + self.add_vap_data["resource"] = resource + self.set_port_data["current_flags"] = self.add_named_flags(self.desired_set_port_current_flags, + set_port.set_port_current_flags) + self.set_port_data["interest"] = self.add_named_flags(self.desired_set_port_interest_flags, + set_port.set_port_interest_flags) + # these are unactivated LFRequest objects that we can modify and + # re-use inside a loop, reducing the number of object creations + add_vap_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/add_vap") + set_port_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/set_port") + wifi_extra_r = LFRequest.LFRequest(self.lfclient_url + "/cli-json/set_wifi_extra") + if suppress_related_commands_: + self.add_vap_data["suppress_preexec_cli"] = "yes" + self.add_vap_data["suppress_preexec_method"] = 1 + self.set_port_data["suppress_preexec_cli"] = "yes" + self.set_port_data["suppress_preexec_method"] = 1 + + # pprint(self.station_names) + # exit(1) + self.set_port_data["port"] = self.vap_name + self.add_vap_data["ap_name"] = self.vap_name + add_vap_r.addPostData(self.add_vap_data) + if debug: + print("- 1502 - %s- - - - - - - - - - - - - - - - - - " % self.vap_name) + pprint(self.add_vap_data) + pprint(self.set_port_data) + pprint(add_vap_r) + print("- ~1502 - - - - - - - - - - - - - - - - - - - ") + + json_response = add_vap_r.jsonPost(debug) + # time.sleep(0.03) + time.sleep(2) + set_port_r.addPostData(self.set_port_data) + json_response = set_port_r.jsonPost(debug) + time.sleep(0.03) + + self.wifi_extra_data["resource"] = resource + self.wifi_extra_data["port"] = self.vap_name + if self.wifi_extra_data_modified: + wifi_extra_r.addPostData(self.wifi_extra_data) + json_response = wifi_extra_r.jsonPost(debug) + + port_list = self.local_realm.json_get("port/1/1/list") + if port_list is not None: + port_list = port_list['interfaces'] + for port in port_list: + for k, v in port.items(): + if v['alias'] == 'br0': + self.local_realm.rm_port(k, check_exists=True) + time.sleep(5) + + # create bridge + if bridge : + print("creating bridge") + data = { + "shelf": 1, + "resource": resource, + "port": "br0", + "network_devs": "eth1,%s" % self.vap_name + } + self.local_realm.json_post("cli-json/add_br", data) + + bridge_set_port = { + "shelf": 1, + "resource": resource, + "port": "br0", + "current_flags": 0x80000000, + "interest": 0x4000 # (0x2 + 0x4000 + 0x800000) # current, dhcp, down + } + self.local_realm.json_post("cli-json/set_port", bridge_set_port) + + if (self.up): + self.admin_up(resource) + + def cleanup(self, resource, delay=0.03): + print("Cleaning up VAPs") + desired_ports = ["1.%s.%s" % (resource, self.vap_name), "1.%s.br0" % resource] + + del_count = len(desired_ports) + + # First, request remove on the list. + for port_eid in desired_ports: + self.local_realm.rm_port(port_eid, check_exists=True) + + # And now see if they are gone + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=desired_ports) + diff --git a/lanforge/lanforge-scripts/py-json/vr_profile2.py b/lanforge/lanforge-scripts/py-json/vr_profile2.py new file mode 100644 index 000000000..7e994ddf5 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/vr_profile2.py @@ -0,0 +1,772 @@ +#!/usr/bin/env python3 + +import sys +import os +import importlib +import time +from pprint import pprint +from random import randint + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +base_profile = importlib.import_module("py-json.base_profile") +BaseProfile = base_profile.BaseProfile + + +class VRProfile(BaseProfile): + Default_Margin = 15 # margin between routers and router connections + Default_VR_height = 250 + Default_VR_width = 50 + + """ + Virtual Router profile + """ + def __init__(self, + local_realm, + debug=False): + super().__init__(local_realm=local_realm, + debug=debug) + self.vr_eid = None + self.vr_name = None + # self.created_rdds = [] + self.cached_vrcx = {} + self.cached_routers = {} + + # self.vrcx_data = { + # 'shelf': 1, + # 'resource': 1, + # 'vr-name': None, + # 'local_dev': None, # outer rdd + # 'remote_dev': None, # inner rdd + # "x": 200+ran, + # "y": 0, + # "width": 10, + # "height": 10, + # 'flags': 0, + # "subnets": None, + # "nexthop": None, + # "vrrp_ip": "0.0.0.0" + # } + # + # self.set_port_data = { + # "shelf": 1, + # "resource": 1, + # "port": None, + # "ip_addr": None, + # "netmask": None, + # "gateway": None + # } + + """ + https://unihd-cag.github.io/simple-geometry/reference/rect.html + """ + + def get_netsmith_bounds(self, resource=None, debug=False): + if (resource is None) or (resource < 1): + raise ValueError("get_netsmith_bounds wants resource id") + debug |= self.debug + + occupied_area = self.get_occupied_area(resource=resource, debug=debug) + return Rect(x=0, y=0, height=occupied_area.height, width=occupied_area.width) + + + def vr_eid_to_url(self, eid_str=None, debug=False): + debug |= self.debug + if (eid_str is None) or ("" == eid_str) or (eid_str.index(".") < 1): + raise ValueError("vr_eid_to_url cannot read eid[%s]" % eid_str) + hunks = eid_str.split(".") + if len(hunks) > 3: + return "/vr/1/%s/%s" % (hunks[1], hunks[2]) + if len(hunks) > 2: + return "/vr/1/%s/%s" % (hunks[1], hunks[2]) + return "/vr/1/%s/%s" % (hunks[0], hunks[1]) # probably a short eid + + + def vr_to_rect(self, vr_dict=None, debug=False): + debug |= self.debug + if vr_dict is None: + raise ValueError(__name__+": vr_dict should not be none") + if debug: + pprint(("vr_dict: ", vr_dict)) + if "x" not in vr_dict: + if "eid" not in vr_dict: + raise ValueError("vr_to_rect: Unable to determine eid of rectangle to query") + router_url = self.vr_eid_to_url(vr_dict["eid"]) + expanded_router_j = self.json_get(router_url, debug_=debug) + if expanded_router_j is None: + raise ValueError("vr_to_rect: unable to determine vr using url [%s]"%router_url) + vr_dict = expanded_router_j + return self.to_rect(x=int(vr_dict["x"]), + y=int(vr_dict["y"]), + width=int(vr_dict["width"]), + height=int(vr_dict["height"])) + + def to_rect(self, x=0, y=0, width=10, height=10): + rect = Rect(x=int(x), y=int(y), width=int(width), height=int(height)) + return rect + + def get_occupied_area(self, + resource=1, + debug=False): + debug |= self.debug + if (resource is None) or (resource == 0) or ("" == resource): + raise ValueError("resource needs to be a number greater than 1") + + router_map = self.router_list(resource=resource, debug=debug) + vrcx_map = self.vrcx_list(resource=resource, debug=debug) + + rect_list = [] + for eid,item in router_map.items(): + rect_list.append(self.vr_to_rect(item)) + for eid,item in vrcx_map.items(): + rect_list.append(self.vr_to_rect(item)) + if len(rect_list) < 1: + return None + bounding_group = Group() + for item in rect_list: + #if debug: + # pprint(("item:", item)) + bounding_group.append(item) + bounding_group.update() + if debug: + pprint(("get_occupied_area: bounding_group:", bounding_group)) + time.sleep(5) + + return Rect(x=bounding_group.x, + y=bounding_group.y, + width=bounding_group.width, + height=bounding_group.height) + + def vrcx_list(self, resource=None, + do_sync=False, + fields=["eid","x","y","height","width"], + debug=False): + """ + + :param resource: + :param do_sync: + :param debug: + :return: + """ + debug |= self.debug + if (resource is None) or (resource == ""): + raise ValueError(__name__+ ": resource cannot be blank") + if do_sync or (self.cached_vrcx is None) or (len(self.cached_vrcx) < 1): + self.sync_netsmith(resource=resource, debug=debug) + fields_str = ",".join(fields) + if debug: + pprint([ + ("vrcx_list: fields", fields_str), + ("fields_str", fields_str) + ]) + time.sleep(5) + list_of_vrcx = self.json_get("/vrcx/1/%s/list?fields=%s" % (resource, fields_str), + debug_=debug) + mapped_vrcx = LFUtils.list_to_alias_map(json_list=list_of_vrcx, + from_element="router-connections", + debug_=debug) + self.cached_vrcx = mapped_vrcx + return self.cached_vrcx + + def router_list(self, + resource=None, + do_refresh=True, + fields=("eid", "x", "y", "height", "width"), + debug=False): + """ + Provides an updated list of routers, and caches the results to self.cached_routers. + Call this method again to update the cached list. + :param resource: + :param debug: + :return: list of routers provided by /vr/1/{resource}?fields=eid,x,y,height,width + """ + debug |= self.debug + fields_str = ",".join(fields) + if (resource is None) or (resource == ""): + raise ValueError(__name__+"; router_list needs valid resource parameter") + if do_refresh or (self.cached_routers is None) or (len(self.cached_routers) < 1): + list_of_routers = self.json_get("/vr/1/%s/list?%s" % (resource, fields_str), + debug_=debug) + mapped_routers = LFUtils.list_to_alias_map(json_list=list_of_routers, + from_element="virtual-routers", + debug_=debug) + self.cached_routers = mapped_routers + if debug: + pprint(("cached_routers: ", self.cached_routers)) + return self.cached_routers + + def create_rdd(self, + resource=1, + ip_addr=None, + netmask=None, + gateway=None, + suppress_related_commands_=True, + debug_=False): + rdd_data = { + "shelf": 1, + "resource": resource, + "port": "rdd0", + "peer_ifname": "rdd1" + } + # print("creating rdd0") + self.json_post("/cli-json/add_rdd", + rdd_data, + ) + + rdd_data = { + "shelf": 1, + "resource": resource, + "port": "rdd1", + "peer_ifname": "rdd0" + } + # print("creating rdd1") + # self.json_post("/cli-json/add_rdd", + # rdd_data, + # suppress_related_commands_=suppress_related_commands_, + # debug_=debug_) + # + # self.set_port_data["port"] = "rdd0" + # self.set_port_data["ip_addr"] = gateway + # self.set_port_data["netmask"] = netmask + # self.set_port_data["gateway"] = gateway + # self.json_post("/cli-json/set_port", + # self.set_port_data, + # suppress_related_commands_=suppress_related_commands_, + # debug_=debug_) + # + # self.set_port_data["port"] = "rdd1" + # self.set_port_data["ip_addr"] = ip_addr + # self.set_port_data["netmask"] = netmask + # self.set_port_data["gateway"] = gateway + # self.json_post("/cli-json/set_port", + # self.set_port_data, + # suppress_related_commands_=suppress_related_commands_, + # debug_=debug_) + # + # self.created_rdds.append("rdd0") + # self.created_rdds.append("rdd1") + + def create_vrcx(self, + resource=1, + local_dev=None, + remote_dev=None, + subnets=None, + nexthop=None, + flags=0, + suppress_related_commands_=True, + debug_=False): + if self.vr_name is None: + raise ValueError("vr_name must be set. Current name: %s" % self.vr_name) + + vrcx_data = {} + vrcx_data["resource"] = resource + vrcx_data["vr_name"] = self.vr_name + vrcx_data["local_dev"] = local_dev + vrcx_data["remote_dev"] = remote_dev + vrcx_data["subnets"] = subnets + vrcx_data["nexthop"] = nexthop + vrcx_data["flags"] = flags + self.json_post("/cli-json/add_vrcx", + vrcx_data, + suppress_related_commands_=suppress_related_commands_, + debug_=debug_) + + def find_position(self, eid=None, target_group=None, debug=False): + debug |= self.debug + """ + get rectangular coordinates of VR or VRCX + :param eid: + :param target_group: + :return: + """ + pass + + def next_available_area(self, + go_right=True, + go_down=False, + debug=False, + height=Default_VR_height, + width=Default_VR_width): + """ + Returns a coordinate adjacent to the right or bottom of the presently occupied area with a 15px margin. + :param go_right: look to right + :param go_down: look to bottom + :param debug: + :return: rectangle that that next next VR could occupy + """ + debug |= self.debug + + # pprint(("used_vrcx_area:", used_vrcx_area)) + # print("used x %s, y %s" % (used_vrcx_area.right+15, used_vrcx_area.top+15 )) + + if not (go_right or go_down): + raise ValueError("Either go right or go down") + + used_vrcx_area = self.get_occupied_area(resource=self.vr_eid[1], debug=debug) + next_area = None + if (go_right): + next_area = Rect(x=used_vrcx_area.right+15, + y=15, + width=50, + height=250) + elif (go_down): + next_area = Rect(x=15, + y=used_vrcx_area.bottom+15, + width=50, + height=250) + else: + raise ValueError("Unexpected positioning") + + # pprint(("next_rh_area", next_area)) + # print("next_rh_area: right %s, top %s" % (next_area.right, next_area.top )) + # print("next_rh_area: x %s, y %s" % (next_area.x, next_area.y )) + return next_area + + def is_inside_virtual_router(self, resource=None, vrcx_rect=None, vr_eid=None, debug=False): + """ + + :param resource: resource id + :param vrcx_rect: port rectangle, probably 10px x 10px + :param vr_eid: 'all' or router_eid, None is not acceptable + :param debug: + :return: True if area is inside listed virtual router(s) + """ + debug |= self.debug + if (resource is None) or (resource == 0) or ("" == resource): + raise ValueError("resource needs to be a number greater than 1") + if (vrcx_rect is None) or type(vrcx_rect ) or ("" == resource): + raise ValueError("resource needs to be a number greater than 1") + router_list = self.router_list(resource=resource, debug=debug) + #router_list = self.json_get("/vr/1/%s/%s?fields=eid,x,y,height,width") + if (router_list is None) or (len(router_list) < 1): + return False + + for router in router_list: + rect = self.vr_to_rect(router) + if (vr_eid == "all"): + if (vrcx_rect.is_inside_of(rect)): + return True + else: + if (vr_eid == router["eid"]) and (vrcx_rect.is_inside_of(rect)): + return True + return False + + def find_cached_router(self, resource=0, router_name=None, debug=False): + debug |= self.debug + if (resource is None) or (resource == 0): + raise ValueError(__name__+": find_cached_router needs resource_id") + if (router_name is None) or (router_name == ""): + raise ValueError(__name__+": find_cached_router needs router_name") + + temp_eid_str = "1.%s.1.65535.%s" % (resource, router_name) + if temp_eid_str in self.cached_routers.keys(): + return self.cached_routers[temp_eid_str] + + temp_eid_str = "1.%s." % resource + for router in self.cached_routers.keys(): + if debug: + pprint(("cached_router: ", router)) + if router.startswith(temp_eid_str) and router.endswith(router_name): + return self.cached_routers[router] + if self.exit_on_error: + raise ValueError("Unable to find cached router %s"%temp_eid_str) + # exit(1) + return None + + def add_vrcx_to_router(self, vrcx_name=None, vr_eid=None, debug=False): + """ + This is the Java psuedocode: + def moveConnection: + found_router = findRouter(x, y) + + if connection.getRouter() is None: + if found_router.addConnection(): + free_vrxc.remove(connection) + connection.setPosition(x, y) + return + + if found_router is not None: + router.remove(connection) + free_vrcx.add(connection) + else: + if found_router != router: + router.remove(connection) + found_router.add(connection) + + connection.setPosition(x, y) + + :param vrcx_name: + :param vr_eid: + :param debug: + :return: new coordinates tuple + """ + debug |= self.debug + if debug: + pprint([("move_vrcx: vr_eid:", vr_eid), + ("vrcx_name:", vrcx_name), + ("self.cached_routers, check vr_eid:", self.cached_routers)]) + time.sleep(5) + if (vrcx_name is None) or (vrcx_name == ""): + raise ValueError(__name__+"empty vrcx_name") + if (vr_eid is None) or (vr_eid == ""): + raise ValueError(__name__+"empty vr_eid") + my_vrcx_name = vrcx_name + if (vrcx_name.index(".") > 0): + hunks = vrcx_name.split(".") + my_vrcx_name = hunks[-1] + if debug: + pprint([("move_vrcx: vr_eid:", vr_eid), + ("vrcx_name:", my_vrcx_name), + ("self.cached_routers, check vr_eid:", self.cached_routers)]) + router_val = self.find_cached_router(resource=vr_eid[1], router_name=vr_eid[2]) + if router_val is None: + self.router_list(resource=vr_eid[1], debug=debug) + router_val = self.find_cached_router(resource=vr_eid[1], router_name=vr_eid[2]) + if router_val is None: + raise ValueError(__name__+": move_vrcx: No router matches %s"%vr_eid) + new_bounds = self.vr_to_rect(vr_dict=router_val, debug=self.debug) + new_location = self.vrcx_landing_spot(bounds=new_bounds, debug=debug) + self.json_post("/cli-json/add_vrcx", { + "shelf": 1, + "resource": vr_eid[1], + "vr_name": vr_eid[2], + "local_dev": my_vrcx_name, + "x": new_location[0], + "y": new_location[1], + }, debug_=debug) + if debug: + pprint([ + ("router_val", router_val), + ("new_bounds", new_bounds), + ("new_location", new_location), + ("my_vrcx_name",my_vrcx_name), + ("router_val",router_val) + ]) + return new_location + + def move_vr(self, eid=None, go_right=True, go_down=False, upper_left_x=None, upper_left_y=None, debug=False): + """ + + :param eid: virtual router EID + :param go_right: select next area to the right of things + :param go_down: select next area below all things + :param upper_left_x: integer value for specific x + :param upper_left_y: integer value for specific y + :return: + """ + debug |= self.debug + used_vrcx_area = self.get_occupied_area(resource=self.vr_eid[1], debug=debug) + + def sync_netsmith(self, resource=0, delay=0.1, debug=False): + """ + This syncs the netsmith window. Doing a sync could destroy any move changes you just did. + :param resource: + :param delay: + :param debug: + :return: + """ + debug |= self.debug + if (resource is None) or (resource < 1): + raise ValueError("sync_netsmith: resource must be > 0") + + self.json_post("/vr/1/%s/0" % resource, { "action": "sync" }, debug_=True) + time.sleep(delay) + + def apply_netsmith(self, resource=0, delay=2, timeout=30, debug=False): + debug |= self.debug + if resource is None or resource < 1: + raise ValueError("refresh_netsmith: resource must be > 0") + + self.json_post("/vr/1/%s/0" % resource, { "action":"apply" }, debug_=debug) + # now poll vrcx to check state + state = "UNSET" + cur_time = int(time.time()) + end_time = int(time.time()) + (1000 * timeout) + while (cur_time < end_time) and (state != "OK"): + time.sleep(delay) + state = "UNSET" + connection_list = self.vrcx_list(resource=resource, + do_sync=True, + fields=["eid", "netsmith-state"], + debug=debug) + vrcx_list_keys = list(connection_list.keys()) + if debug: + pprint([ + ("vrcx_list", connection_list), + ("keys", vrcx_list_keys)]) + time.sleep(5) + if (connection_list is not None) and (len(vrcx_list_keys) > 0): + if (vrcx_list_keys[0] is not None) and ("netsmith-state" in connection_list[vrcx_list_keys[0]]): + item = connection_list[vrcx_list_keys[0]] + if debug: + pprint(("item zero", item)) + state = item["netsmith-state"] + else: + self.logg("apply_netsmith: no vrcx list?") + + if (state != "UNSET"): + continue + + vr_list = self.router_list(resource=resource, + fields=("eid", "netsmith-state"), + debug=debug) + if (vr_list is not None) or (len(vr_list) > 0): + if (vr_list[0] is not None) and ("netsmith-state" in vr_list[0]): + state = vr_list[0]["netsmith-state"] + else: + self.logg("apply_netsmith: no vr_list?") + + return state + + + def refresh_netsmith(self, resource=0, delay=0.03, debug=False): + """ + This does not do a netsmith->Apply. + This does not do a netsmith sync. Doing a sync could destroy any move changes you just did. + This is VirtualRouterPanel.privDoUpdate: + for vr in virtual_routers: + vr.ensurePortsCreated() + for connection in free_router_connections: + connection.ensurePortsCreated() + for vr in virtual_routers: + ... remove connections that are unbound + for vr in virtual_routers: + remove vr that cannot be found + for connections in vrcx: + remove connection not found or remove endpoint from free list + for router in virtual_routers: + update vr + for connection in free_connections: + update connection + apply_vr_cfg + show_card + show_vr + show_vrcx + + :param resource: + :param delay: + :param debug: + :return: + """ + debug |= self.debug + if resource is None or resource < 1: + raise ValueError("refresh_netsmith: resource must be > 0") + + self.json_post("/cli-json/apply_vr_cfg", { + "shelf": 1, + "resource": resource + }, debug_=debug, suppress_related_commands_=True) + self.json_post("/cli-json/show_resources", { + "shelf": 1, + "resource": resource + }, debug_=debug) + time.sleep(delay) + self.json_post("/cli-json/show_vr", { + "shelf": 1, + "resource": resource, + "router": "all" + }, debug_=debug) + self.json_post("/cli-json/show_vrcx", { + "shelf": 1, + "resource": resource, + "cx_name": "all" + }, debug_=debug) + time.sleep(delay * 2) + + def create(self, + vr_name=None, + debug=False, + suppress_related_commands=True): + # Create vr + debug |= self.debug + + if vr_name is None: + raise ValueError("vr_name must be set. Current name: %s" % vr_name) + + self.vr_eid = self.parent_realm.name_to_eid(vr_name) + if debug: + pprint(("self.vr_eid:", self.vr_eid)) + + # determine a free area to place a router + next_area = self.next_available_area(go_right=True, debug=debug) + self.add_vr_data = { + "alias": self.vr_eid[2], + "shelf": 1, + "resource": self.vr_eid[1], + "x": int(next_area.x), + "y": 15, + "width": 50, + "height": 250, + "flags": 0 + } + self.json_post("/cli-json/add_vr", + self.add_vr_data, + suppress_related_commands_=suppress_related_commands, + debug_=debug) + self.json_post("/cli-json/apply_vr_cfg", { + "shelf": 1, + "resource": self.vr_eid[1] + }, debug_=debug, suppress_related_commands_=suppress_related_commands) + time.sleep(1) + self.apply_netsmith(resource=self.vr_eid[1], debug=debug) + + def wait_until_vrcx_appear(self, resource=0, name_list=None, timeout_sec=120, debug=False): + debug |= self.debug + if (name_list is None) or (len(name_list) < 1): + raise ValueError("wait_until_vrcx_appear wants a non-empty name list") + num_expected = len(name_list) + num_found = 0 + import time + cur_time = int(time.time()) + end_time = cur_time + timeout_sec + sync_time = 10 + while (num_found < num_expected) and (cur_time <= end_time): + time.sleep(1) + cur_time = int(time.time()) + num_found = 0 + response = self.json_get("/vrcx/1/%s/list" % resource) + if (response is None) or ("router-connections" not in response): + raise ValueError("unable to find router-connections for /vrcx/1/%s/list" % resource) + + vrcx_list = LFUtils.list_to_alias_map(json_list=response, from_element='router-connections', debug_=debug) + num_found = len(vrcx_list) + if (num_found < 1): + self.logg("wait_until_vrcx_appear: zero vrcx in vrcx_list") + raise ValueError("zero router-connections for /vrcx/1/%s/list" % resource) + num_found = 0 + for name in name_list: + name = "1.%s.%s" % (resource, name) + if name in vrcx_list: + num_found += 1 + if num_found == len(name_list): + return True + # this is should not be done yet + # self.refresh_netsmith(resource=resource, debug=debug) + if ((end_time - cur_time) % sync_time) == 0: + self.sync_netsmith(resource=resource, debug=debug) + time.sleep(1) + if (num_found > 0) and (num_found < num_expected): + self.refresh_netsmith(resource=resource, debug=debug) + if debug: + pprint([("response", response), + ("list", vrcx_list), + ("num_found", num_found), + ("num_expected", num_expected) + ]) + self.logg("wait_until_vrcx_appear: timeout waiting for router-connections to appear") + return False + + def remove_vr(self, eid=None, + refresh=True, + debug=False, + delay=0.05, + die_on_error=False, + suppress_related_commands=True): + + if (eid is None) or (eid[1] is None) or (eid[2] is None): + self.logg("remove_vr: invalid eid: ", audit_list=[eid]) + if (die_on_error): + raise ValueError("remove_vr: invalid eid") + data = { + "shelf": 1, + "resource": eid[1], + "router_name": eid[2] + } + self.json_post("/cli-json/rm_vr", data, debug_=debug, suppress_related_commands_=suppress_related_commands) + time.sleep(delay) + if (refresh): + self.json_post("/cli-json/nc_show_vr", { + "shelf": 1, + "resource": eid[1], + "router": "all" + }, debug_=debug, suppress_related_commands_=suppress_related_commands) + self.json_post("/cli-json/nc_show_vrcx", { + "shelf": 1, + "resource": eid[1], + "cx_name": "all" + }, debug_=debug, suppress_related_commands_=suppress_related_commands) + + def cleanup(self, resource=0, vr_id=0, delay=0.3, debug=False): + debug |= self.debug + if self.vr_eid is None: + return + if resource == 0: + resource = self.vr_eid[1] + if vr_id == 0: + vr_id = self.vr_eid[2] + + data = { + "shelf": 1, + "resource": resource, + "router_name": vr_id + } + self.json_post("/cli-json/rm_vr", data, debug_=debug, suppress_related_commands_=True) + time.sleep(delay) + self.refresh_netsmith(resource=resource, debug=debug) + + def add_vrcx(self, vr_eid=None, connection_name_list=None, debug=False): + if (vr_eid is None) or (vr_eid == ""): + raise ValueError(__name__+": add_vrcx wants router EID") + existing_list = self.vrcx_list(resource=vr_eid[1], do_sync=True) + if debug: + pprint([ + ("vr_eid", vr_eid), + ("connect_names", connection_name_list), + ("existing_list", existing_list) + ]) + time.sleep(10) + edited_connection_list = [] + if type(connection_name_list) == str: + edited_connection_list.append(connection_name_list) + else: + edited_connection_list = connection_name_list + if debug: + pprint(("my_list was:", edited_connection_list)) + time.sleep(1) + # for vrcx_name in my_list: + edited_connection_list[:] = ["1.%s.%s"%(vr_eid[1], x) if (not x.startswith("1.")) else None for x in edited_connection_list] + + if debug: + pprint(("my list is now:", edited_connection_list)) + + # at this point move the vrcx into the vr + for vrcx_name in edited_connection_list: + print ("Looking for old coordinates of %s"%vrcx_name) + if debug: + pprint([("vrcx_name:", vrcx_name), + ("existing_list", existing_list.get(vrcx_name))]) + if existing_list.get(vrcx_name) is None: + if debug: + pprint(("existing_list:", existing_list)) + raise ValueError("Is vrcx mis-named?") + old_coords = self.vr_to_rect( existing_list.get(vrcx_name)) + if old_coords is None: + raise ValueError("old coordinates for vrcx disappeared") + new_coords = self.add_vrcx_to_router(vrcx_name=vrcx_name, vr_eid=vr_eid, debug=debug) + if debug: + print("coordinates were %s and will become %s "%(old_coords, new_coords)) + + def vrcx_landing_spot(self, bounds=None, debug=False): + """ + + :param bounds: Rect we will select position within a 15px margin inside + :param debug: + :return: tuple (new_x, new_y) within bounds + """ + if (bounds is None): + raise ValueError(__name__+": missing bounds to land vrcx") + if not isinstance(bounds, Rect): + raise ValueError(__name__+": bounds not of type Rect") + pprint([("bounds.x", bounds.x), + ("bounds.y", bounds.y), + ("bounds.width", bounds.x+bounds.width), + ("bounds.height", bounds.y+bounds.height) + ]) + new_x = randint(bounds.x+15, bounds.x+bounds.width-15) + new_y = randint(bounds.y+15, bounds.y+bounds.height-15) + return (new_x, new_y) +### +### +### diff --git a/lanforge/lanforge-scripts/py-json/wifi_monitor_profile.py b/lanforge/lanforge-scripts/py-json/wifi_monitor_profile.py new file mode 100644 index 000000000..ac001b8f8 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/wifi_monitor_profile.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 + +import sys +import os +import importlib +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +add_monitor = importlib.import_module("py-json.LANforge.add_monitor") +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +set_wifi_radio = importlib.import_module("py-json.LANforge.set_wifi_radio") +set_radio_mode = set_wifi_radio.set_radio_mode + + +class WifiMonitor: + def __init__(self, lfclient_url, local_realm, up=True, debug_=False, resource_=1): + self.debug = debug_ + self.lfclient_url = lfclient_url + self.up = up + self.local_realm = local_realm + self.monitor_name = None + self.resource = resource_ + self.flag_names = [] + self.flag_mask_names = [] + self.flags_mask = add_monitor.default_flags_mask + self.aid = "NA" # used when sniffing /ax radios + self.bsssid = "00:00:00:00:00:00" # used when sniffing on /ax radios + + def create(self, resource_=1, channel=None, mode="AUTO", radio_="wiphy0", name_="moni0"): + print("Creating monitor " + name_) + self.monitor_name = name_ + computed_flags = 0 + for flag_n in self.flag_names: + computed_flags += add_monitor.flags[flag_n] + + # we want to query the existing country code of the radio + # there's no reason to change it but we get hollering from server + # if we don't provide a value for the parameter + jr = self.local_realm.json_get("/radiostatus/1/%s/%s?fields=channel,frequency,country" % (resource_, radio_), + debug_=self.debug) + if jr is None: + raise ValueError("No radio %s.%s found" % (resource_, radio_)) + + eid = "1.%s.%s" % (resource_, radio_) + #frequency = 0 + country = 0 + if eid in jr: + country = jr[eid]["country"] + + data = { + "shelf": 1, + "resource": resource_, + "radio": radio_, + "mode": set_radio_mode[mode], # "NA", #0 for AUTO or "NA" + "channel": channel, + "country": country, + "frequency": self.local_realm.channel_freq(channel_=channel) + + } + self.local_realm.json_post("/cli-json/set_wifi_radio", _data=data) + time.sleep(1) + self.local_realm.json_post("/cli-json/add_monitor", { + "shelf": 1, + "resource": resource_, + "radio": radio_, + "ap_name": self.monitor_name, + "flags": computed_flags, + "flags_mask": self.flags_mask + }) + + def set_flag(self, param_name, value): + if (param_name not in add_monitor.flags): + raise ValueError("Flag '%s' does not exist for add_monitor, consult add_monitor.py" % param_name) + if (value == 1) and (param_name not in self.flag_names): + self.flag_names.append(param_name) + elif (value == 0) and (param_name in self.flag_names): + del self.flag_names[param_name] + self.flags_mask |= add_monitor.flags[param_name] + + def cleanup(self, resource_=1, desired_ports=None): + print("Cleaning up monitors") + if (desired_ports is None) or (len(desired_ports) < 1): + if (self.monitor_name is None) or (self.monitor_name == ""): + print("No monitor name set to delete") + return + LFUtils.removePort(resource=resource_, + port_name=self.monitor_name, + baseurl=self.lfclient_url, + debug=self.debug) + else: + names = ",".join(desired_ports) + existing_ports = self.local_realm.json_get("/port/1/%d/%s?fields=alias" % (resource_, names), debug_=False) + if (existing_ports is None) or ("interfaces" not in existing_ports) or ("interface" not in existing_ports): + print("No monitor names found to delete") + return + if ("interfaces" in existing_ports): + for eid, info in existing_ports["interfaces"].items(): + LFUtils.removePort(resource=resource_, + port_name=info["alias"], + baseurl=self.lfclient_url, + debug=self.debug) + if ("interface" in existing_ports): + for eid, info in existing_ports["interface"].items(): + LFUtils.removePort(resource=resource_, + port_name=info["alias"], + baseurl=self.lfclient_url, + debug=self.debug) + + def admin_up(self): + up_request = LFUtils.port_up_request(resource_id=self.resource, port_name=self.monitor_name) + self.local_realm.json_post("/cli-json/set_port", up_request) + self.local_realm.json_post("/cli-json/set_port", up_request) + + def admin_down(self): + down_request = LFUtils.portDownRequest(resource_id=self.resource, port_name=self.monitor_name) + self.local_realm.json_post("/cli-json/set_port", down_request) + + def start_sniff(self, capname=None, duration_sec=60): + if capname is None: + raise ValueError("Need a capture file name") + data = { + "shelf": 1, + "resource": 1, + "port": self.monitor_name, + "display": "NA", + "flags": 0x2, + "outfile": capname, + "duration": duration_sec + } + self.local_realm.json_post("/cli-json/sniff_port", _data=data) + diff --git a/lanforge/lanforge-scripts/py-json/wlan_theoretical_sta.py b/lanforge/lanforge-scripts/py-json/wlan_theoretical_sta.py new file mode 100755 index 000000000..261c23c2a --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/wlan_theoretical_sta.py @@ -0,0 +1,2231 @@ +#!/usr/bin/env python3 + +''' + +Candela Technologies Inc. +Info : Standard Script for WLAN Capaity Calculator +Date : +Author : Anjali Rahamatkar + +This Script has three classes : + 1. abg11_calculator : It will take all the user input of 802.11a/b/g station,calculate Intermediate values and Theoretical values. + 2. n11_calculator : It will take all the user input of 802.11n station,calculate Intermediate values and Theoretical values. + 3. ac11_calculator : It will take all the user input of 802.11ac station,calculate Intermediate values and Theoretical values. +All classes have different functions: input_parameter() that calculates intermediate values and generate theroretical data + +''' + +import argparse +import json + + +# Class to take all user input (802.11a/b/g Standard) + + + +class abg11_calculator(): + + def __init__(self, Traffic_Type, PHY_Bit_Rate, Encryption, QoS, MAC_Frame_802_11, Basic_Rate_Set, Preamble, + slot_name, Codec_Type, RTS_CTS_Handshake, CTS_to_self): + self.Traffic_Type = Traffic_Type + self.PHY_Bit_Rate = PHY_Bit_Rate + self.Encryption = Encryption + self.QoS = QoS + self.MAC_Frame_802_11 = MAC_Frame_802_11 + self.Basic_Rate_Set = Basic_Rate_Set + self.Preamble = Preamble + self.slot_name = slot_name + self.Codec_Type = Codec_Type + self.RTS_CTS_Handshake = RTS_CTS_Handshake + self.CTS_to_self = CTS_to_self + + + + + # This function is for calculate intermediate values and Theoretical values + + @staticmethod + def create_argparse(prog=None, formatter_class=None, epilog=None, description=None): + if (prog is not None) or (formatter_class is not None) or (epilog is not None) or (description is not None): + ap = argparse.ArgumentParser(prog=prog, + formatter_class=formatter_class, + allow_abbrev=True, + epilog=epilog, + description=description) + else: + ap = argparse.ArgumentParser() + + # Station : 11abg + + ap.add_argument("-sta", "--station", help="Enter Station Name : [11abg,11n,11ac](by Default 11abg)") + ap.add_argument("-t", "--traffic", help="Enter the Traffic Type : [Data,Voice](by Default Data)") + ap.add_argument("-p", "--phy", + help="Enter the PHY Bit Rate of Data Flow : [1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54](by Default 54)") + ap.add_argument("-e", "--encryption", + help="Enter the Encryption : [None, WEP , TKIP, CCMP](by Default None)") + ap.add_argument("-q", "--qos", help="Enter the QoS = : [No, Yes](by Default [No for 11abg] and [Yes for 11n])") + ap.add_argument("-m", "--mac", + help="Enter the 802.11 MAC Frame : [Any Value](by Default [106 for 11abg] and [1538 for 11n])") + ap.add_argument("-b", "--basic", nargs='+', + help="Enter the Basic Rate Set : [1,2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54]" + " (by Default [1 2 5.5 11 6 12] for 11abg, [6 12 24] for 11n/11ac])") + ap.add_argument("-pre", "--preamble", help="Enter Preamble value : [ Short, Long, N/A](by Default Short)") + ap.add_argument("-s", "--slot", help="Enter the Slot Time : [Short, Long, N/A](by Default Short)") + ap.add_argument("-co", "--codec", help="Enter the Codec Type (Voice Traffic): {[ G.711 , G.723 , G.729]" + "by Default G.723 for 11abg, G.711 for 11n} and" + "{['Mixed','Greenfield'] by Default Mixed for 11ac}") + ap.add_argument("-r", "--rts", help="Enter the RTS/CTS Handshake : [No, Yes](by Default No)") + ap.add_argument("-c", "--cts", help="Enter the CTS-to-self (protection) : [No, Yes](by Default No)") + + # Station : 11n and 11ac + + ap.add_argument("-d", "--data", + help="Enter the Data/Voice MCS Index : ['0','1','2','3','4','5','6','7','8','9','10'," + "'11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26'," + "'27','28','29','30','31']by Default 7") + ap.add_argument("-ch", "--channel", + help="Enter the Channel Bandwidth = : ['20','40'] by Default 40 for 11n and " + "['20','40','80'] by Default 80 for 11ac") + ap.add_argument("-gu", "--guard", help="Enter the Guard Interval = : ['400','800'] (by Default 400)") + ap.add_argument("-high", "--highest", + help="Enter the Highest Basic MCS = : ['0','1','2','3','4','5','6','7','8','9'," + "'10','11','12','13','14','15','16','17','18','19','20','21','22','23','24'," + "'25','26','27','28','29','30','31'](by Default 1)") + ap.add_argument("-pl", "--plcp", + help="Enter the PLCP Configuration = : ['Mixed','Greenfield'] (by Default Mixed) for 11n") + ap.add_argument("-ip", "--ip", + help="Enter the IP Packets per A-MSDU = : ['0','1','2','3','4','5','6','7','8','9'," + "'10','11','12','13','14','15','16','17','18','19','20'] (by Default 0)") + ap.add_argument("-mc", "--mc", + help="Enter the MAC Frames per A-MPDU = : ['0','1','2','3','4','5','6','7','8'," + "'9','10','11','12','13','14','15','16','17','18','19','20','21','22','23'," + "'24','25','26','27','28','29','30','31','32','33','34','35','36','37','38'," + "'39','40','41','42','43','44','45','46','47','48','49','50','51','52','53'," + "'54','55','56','57','58','59','60','61','62','63','64'](by Default [42 for 11n] and [64 for 11ac])") + ap.add_argument("-cw", "--cwin", + help="Enter the CWmin (leave alone for default) = : [Any Value] (by Default 15)") + ap.add_argument("-spa", "--spatial", help="Enter the Spatial Streams = [1,2,3,4] (by Default 4)") + ap.add_argument("-rc", "--rtscts", help="Enter the RTS/CTS Handshake and CTS-to-self " + " = ['No','Yes'] (by Default No for 11ac)") + return ap + + def calculate(self): + + PHY_Bit_Rate_float = float(self.PHY_Bit_Rate) + PHY_Bit_Rate_int = int(PHY_Bit_Rate_float) + if PHY_Bit_Rate_int < 12: + if PHY_Bit_Rate_int < 5: + yellow_cell = PHY_Bit_Rate_int + else: + if PHY_Bit_Rate_int == 5: + yellow_cell = 5.5 + else: + if PHY_Bit_Rate_int == 11: + yellow_cell = 11 + else: + if PHY_Bit_Rate_int == 6: + yellow_cell = 6 + else: + if PHY_Bit_Rate_int == 9: + yellow_cell = 9 + else: + if PHY_Bit_Rate_int == 12: + yellow_cell = 12 + else: + if PHY_Bit_Rate_int == 18: + yellow_cell = 18 + else: + if PHY_Bit_Rate_int == 24: + yellow_cell = 24 + else: + if PHY_Bit_Rate_int == 36: + yellow_cell = 36 + else: + if PHY_Bit_Rate_int == 48: + yellow_cell = 48 + else: + yellow_cell = 54 + + # (IP Packet) + + if "None" in self.Encryption: + Enc_value = 0 + else: + if "WEP" in self.Encryption: + Enc_value = 8 + else: + if "TKIP" in self.Encryption: + Enc_value = 20 + else: + Enc_value = 16 + if "Yes" in self.QoS: + Qos_value = 2 + else: + Qos_value = 0 + ip_packet_1 = int(self.MAC_Frame_802_11) - 36 - float(Enc_value) - float(Qos_value) + ip_packet = int(ip_packet_1) + if ip_packet < 20: + get_ip_packet = "N/A" + ip = 0 + else: + get_ip_packet = ip_packet + ip = 1 + + # (Ethernet MAC Frame) + + encrpt = float(self.MAC_Frame_802_11) - 24 - 8 + 14 - float(Enc_value) - float(Qos_value) + Ethernet_MAC_Frame_int = max(encrpt, 64) + Ethernet_value = int(Ethernet_MAC_Frame_int) + get_ethernet = Ethernet_value + + # Usable Basic Rates + + if "1" in self.Basic_Rate_Set: + if "1" in self.Basic_Rate_Set and yellow_cell >= 1: + Usable_Basic_Rates_1 = 1 + else: + Usable_Basic_Rates_1 = 0 + else: + Usable_Basic_Rates_1 = 0 + + if "2" in self.Basic_Rate_Set: + if "2" in self.Basic_Rate_Set and yellow_cell >= 2: + Usable_Basic_Rates_2 = 2 + else: + Usable_Basic_Rates_2 = 0 + else: + Usable_Basic_Rates_2 = 0 + + if "5.5" in self.Basic_Rate_Set: + if "5.5" in self.Basic_Rate_Set and yellow_cell >= 5: + Usable_Basic_Rates_5 = 5.5 + else: + Usable_Basic_Rates_5 = 0 + else: + Usable_Basic_Rates_5 = 0 + + if "11" in self.Basic_Rate_Set: + if "11" in self.Basic_Rate_Set and yellow_cell >= 11: + Usable_Basic_Rates_11 = 11 + else: + Usable_Basic_Rates_11 = 0 + else: + Usable_Basic_Rates_11 = 0 + + if "6" in self.Basic_Rate_Set: + if "6" in self.Basic_Rate_Set and yellow_cell >= 6: + Usable_Basic_Rates_6 = 6 + else: + Usable_Basic_Rates_6 = 0 + else: + Usable_Basic_Rates_6 = 0 + + if "9" in self.Basic_Rate_Set: + if "9" in self.Basic_Rate_Set and yellow_cell >= 9: + Usable_Basic_Rates_9 = 9 + else: + Usable_Basic_Rates_9 = 0 + else: + Usable_Basic_Rates_9 = 0 + + if "12" in self.Basic_Rate_Set: + if "12" in self.Basic_Rate_Set and yellow_cell >= 12: + Usable_Basic_Rates_12 = 12 + else: + Usable_Basic_Rates_12 = 0 + else: + Usable_Basic_Rates_12 = 0 + + if "18" in self.Basic_Rate_Set: + if "18" in self.Basic_Rate_Set and yellow_cell >= 18: + Usable_Basic_Rates_18 = 18 + else: + Usable_Basic_Rates_18 = 0 + else: + Usable_Basic_Rates_18 = 0 + + if "24" in self.Basic_Rate_Set: + if "24" in self.Basic_Rate_Set and yellow_cell >= 24: + Usable_Basic_Rates_24 = 24 + else: + Usable_Basic_Rates_24 = 0 + else: + Usable_Basic_Rates_24 = 0 + + if "36" in self.Basic_Rate_Set: + if "36" in self.Basic_Rate_Set and yellow_cell >= 36: + Usable_Basic_Rates_36 = 36 + else: + Usable_Basic_Rates_36 = 0 + else: + Usable_Basic_Rates_36 = 0 + + if "48" in self.Basic_Rate_Set: + if "48" in self.Basic_Rate_Set and yellow_cell >= 48: + Usable_Basic_Rates_48 = 48 + else: + Usable_Basic_Rates_48 = 0 + else: + Usable_Basic_Rates_48 = 0 + + if "54" in self.Basic_Rate_Set: + if "54" in self.Basic_Rate_Set and yellow_cell >= 54: + Usable_Basic_Rates_54 = 54 + else: + Usable_Basic_Rates_54 = 0 + else: + Usable_Basic_Rates_54 = 0 + + # Usable Mandatory Rates + + if (PHY_Bit_Rate_int <= 11) and yellow_cell >= 1: + Mandatory_1 = 1 + else: + Mandatory_1 = 0 + if (PHY_Bit_Rate_int <= 11) and yellow_cell >= 2: + Mandatory_2 = 2 + else: + Mandatory_2 = 0 + if (PHY_Bit_Rate_int <= 11) and yellow_cell >= 5: + Mandatory_5 = 5.5 + else: + Mandatory_5 = 0 + if (PHY_Bit_Rate_int <= 11) and yellow_cell >= 11: + Mandatory_11 = 11 + else: + Mandatory_11 = 0 + if (PHY_Bit_Rate_int >= 6) and yellow_cell >= 6: + Mandatory_6 = 6 + else: + Mandatory_6 = 0 + if (PHY_Bit_Rate_int >= 6) and yellow_cell >= 9: + Mandatory_9 = 6 + else: + Mandatory_9 = 0 + if (PHY_Bit_Rate_int >= 6) and yellow_cell >= 12: + Mandatory_12 = 12 + else: + Mandatory_12 = 0 + if (PHY_Bit_Rate_int >= 6) and yellow_cell >= 18: + Mandatory_18 = 12 + else: + Mandatory_18 = 0 + if (PHY_Bit_Rate_int >= 6) and yellow_cell >= 24: + Mandatory_24 = 24 + else: + Mandatory_24 = 0 + if (PHY_Bit_Rate_int >= 6) and yellow_cell >= 36: + Mandatory_36 = 24 + else: + Mandatory_36 = 0 + if (PHY_Bit_Rate_int >= 6) and yellow_cell >= 48: + Mandatory_48 = 24 + else: + Mandatory_48 = 0 + if (PHY_Bit_Rate_int >= 6) and yellow_cell >= 54: + Mandatory_54 = 24 + else: + Mandatory_54 = 0 + + # CWmin_str (leave alone for default) + + if (PHY_Bit_Rate_int == 1 or PHY_Bit_Rate_int == 2 or PHY_Bit_Rate_int == 5 or PHY_Bit_Rate_int == 11): + CWmin_str = 31 + else: + if ( + "1" in self.Basic_Rate_Set or "2" in self.Basic_Rate_Set or "5.5" in self.Basic_Rate_Set or "11" in self.Basic_Rate_Set) \ + and (not ("6" in self.Basic_Rate_Set or "9" in self.Basic_Rate_Set or "12" in self.Basic_Rate_Set or + "18" in self.Basic_Rate_Set or "24" in self.Basic_Rate_Set or "36" in self.Basic_Rate_Set or "48" in self.Basic_Rate_Set or "54" in self.Basic_Rate_Set)): + CWmin_str = 31 + else: + CWmin_str = 15 + + # MAC MPDU Size + + if "G.711" in self.Codec_Type: + Codec_IP_Packet_Size = 200 + Codec_Frame_rate = 100 + else: + if "G.723" in self.Codec_Type: + Codec_IP_Packet_Size = 60 + Codec_Frame_rate = 67 + elif "G.729" in self.Codec_Type: + Codec_IP_Packet_Size = 60 + Codec_Frame_rate = 100 + else: + Codec_IP_Packet_Size = 0 + Codec_Frame_rate = 0 + + if "Data" in self.Traffic_Type: + MAC_MPDU_Size = int(self.MAC_Frame_802_11) + else: + if "None" in self.Encryption: + result = Codec_IP_Packet_Size + 28 + 0 + elif "WEP" in self.Encryption: + result = Codec_IP_Packet_Size + 28 + 8 + elif "TKIP" in self.Encryption: + result = Codec_IP_Packet_Size + 28 + 20 + else: + result = Codec_IP_Packet_Size + 28 + 16 + if "Yes" in self.QoS: + final_result = result + 2 + else: + final_result = result + 0 + + MAC_MPDU_Size = final_result + 8 + + # PHY Bit Rate of Control Frames + + if (PHY_Bit_Rate_int == 1 or PHY_Bit_Rate_int == 2 or PHY_Bit_Rate_int == 5 or PHY_Bit_Rate_int == 11): + data = 1 + else: + data = 6 + + if len(self.Basic_Rate_Set) == 0: + present_BasicRate = 0 + else: + present_BasicRate = 1 + + if present_BasicRate == 1: + PHY_Bit = max(Usable_Basic_Rates_1, Usable_Basic_Rates_2, Usable_Basic_Rates_5, + Usable_Basic_Rates_11, Usable_Basic_Rates_6, + Usable_Basic_Rates_9, Usable_Basic_Rates_12, Usable_Basic_Rates_18, + Usable_Basic_Rates_24, Usable_Basic_Rates_36, Usable_Basic_Rates_48, + Usable_Basic_Rates_54, data) + + + else: + PHY_Bit = max(Mandatory_1, Mandatory_2, Mandatory_5, Mandatory_11, Mandatory_6, Mandatory_9, + Mandatory_12, Mandatory_18, Mandatory_24, Mandatory_36, Mandatory_48, Mandatory_54) + + # Ttxframe (ACK) + + if "Short" in self.Preamble: + Preamble_1 = float(96) + else: + Preamble_1 = float(192) + + if (PHY_Bit == 1) or (PHY_Bit == 2) or (PHY_Bit == 5.5) or (PHY_Bit == 11): + Ttxframe = (14 * 8) / PHY_Bit + (Preamble_1) + + Ttxframe_new = format(Ttxframe, '.2f') + else: + Ttxframe = int((14 * 8 + 22 + PHY_Bit * 4 - 1) / (PHY_Bit * 4)) * 4 + 20 + Ttxframe_new = format(Ttxframe, '.2f') + + # RTS/CTS Handshake Overhead + + if (PHY_Bit_Rate_int == 1 or PHY_Bit_Rate_int == 2 or PHY_Bit_Rate_int == 5 or PHY_Bit_Rate_int == 11): + SIFS_value = float(10) + else: + SIFS_value = float(16) + + if "No" in self.RTS_CTS_Handshake: + RTS_CTS_Handshake_Overhead = 0 + + elif "Yes" in self.RTS_CTS_Handshake: + if (PHY_Bit == 1) or (PHY_Bit == 2) or (PHY_Bit == 5.5) or (PHY_Bit == 11): + RTS_CTS_Handshake = ((20 + 14) * 8) / PHY_Bit + (Preamble_1) + + else: + RTS_CTS_Handshake = int(((20 + 14) * 8 + 22 + PHY_Bit * 4 - 1) / (PHY_Bit * 4)) * 4 + 2 * 20 + + RTS_CTS_Handshake_Overhead = RTS_CTS_Handshake + (2 * SIFS_value) + + # c22 CTS-to-self Handshake Overhead + + if ("Yes" in self.CTS_to_self) and ("Yes" in self.RTS_CTS_Handshake): + CTS_to_self_Handshake = 0 + else: + if ("No" in self.CTS_to_self) or ("Yes" in self.RTS_CTS_Handshake): + CTS_to_self_Handshake = 0 + else: + if (PHY_Bit == 1) or (PHY_Bit == 2) or (PHY_Bit == 5.5) or (PHY_Bit == 11): + CTS_to_self_Handshake = (14 * 8) / PHY_Bit + (Preamble_1) + SIFS_value + else: + CTS_to_self_Handshake = int( + (14 * 8 + 22 + PHY_Bit * 4 - 1) / (PHY_Bit * 4)) * 4 + 20 + SIFS_value + + # DIFS calulation + + if (PHY_Bit_Rate_int == 1 or PHY_Bit_Rate_int == 2 or PHY_Bit_Rate_int == 5 or PHY_Bit_Rate_int == 11): + DIFS_value = 50 + elif ("Short" in self.slot_name): + DIFS_value = 34 + else: + DIFS_value = 50 + + # MeanBackoff calculation + + if (PHY_Bit_Rate_int == 1 or PHY_Bit_Rate_int == 2 or PHY_Bit_Rate_int == 5 or PHY_Bit_Rate_int == 11): + c4 = (CWmin_str * 20 / 2) + MeanBackoff_value = float(c4) + elif ("Short" in self.slot_name): + d2 = (CWmin_str * 9 / 2) + MeanBackoff_value = float(d2) + else: + d3 = (CWmin_str * 20 / 2) + MeanBackoff_value = float(d3) + + # c27 Ndbps, MAC data bits per symbol + Ndbps_value = yellow_cell * 4 + + # Nbits, bits in MAC frame + Nbits_value = (MAC_MPDU_Size * 8) + + # Tmac, time for MAC frame and Tplcp, time for MAC PLCP + if (PHY_Bit_Rate_int == 1 or PHY_Bit_Rate_int == 2 or PHY_Bit_Rate_int == 5 or PHY_Bit_Rate_int == 11): + Tmac_value = Nbits_value / yellow_cell + if "Short" in self.Preamble: + Tplcp = float(96) + else: + Tplcp = float(192) + else: + Tmac_value = int((Nbits_value + 22 + Ndbps_value) / Ndbps_value) * 4 + Tplcp = float(20) + + # Ttxframe (DATA) + + Ttxframe_data = Tmac_value + Tplcp + + Client_1 = Ttxframe_data + SIFS_value + Ttxframe + DIFS_value + RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake + MeanBackoff_value + Client_2 = Ttxframe_data + SIFS_value + Ttxframe + DIFS_value + RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake + MeanBackoff_value / 2 + Client_3 = Ttxframe_data + SIFS_value + Ttxframe + DIFS_value + RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake + MeanBackoff_value / 5 + Client_4 = Ttxframe_data + SIFS_value + Ttxframe + DIFS_value + RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake + MeanBackoff_value / 10 + Client_5 = Ttxframe_data + SIFS_value + Ttxframe + DIFS_value + RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake + MeanBackoff_value / 20 + Client_6 = Ttxframe_data + SIFS_value + Ttxframe + DIFS_value + RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake + MeanBackoff_value / 50 + Client_7 = Ttxframe_data + SIFS_value + Ttxframe + DIFS_value + RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake + MeanBackoff_value / 100 + self.Client_1_new = format(Client_1, '.2f') + Client_2_new = format(Client_2, '.4f') + Client_3_new = format(Client_3, '.4f') + Client_4_new = format(Client_4, '.4f') + Client_5_new = format(Client_5, '.4f') + Client_6_new = format(Client_6, '.4f') + Client_7_new = round(Client_7) + + # Max Frame Rate + + Max_Frame_Rate_C1 = 1000000 / Client_1 + self.Max_Frame_Rate_C1_round = round(Max_Frame_Rate_C1) + Max_Frame_Rate_C2 = 1000000 / Client_2 + Max_Frame_Rate_C2_round = round(Max_Frame_Rate_C2) + Max_Frame_Rate_C3 = 1000000 / Client_3 + Max_Frame_Rate_C3_round = round(Max_Frame_Rate_C3) + Max_Frame_Rate_C4 = 1000000 / Client_4 + Max_Frame_Rate_C4_round = round(Max_Frame_Rate_C4) + Max_Frame_Rate_C5 = 1000000 / Client_5 + Max_Frame_Rate_C5_round = round(Max_Frame_Rate_C5) + Max_Frame_Rate_C6 = 1000000 / Client_6 + Max_Frame_Rate_C6_round = round(Max_Frame_Rate_C6) + Max_Frame_Rate_C7 = 1000000 / Client_7 + Max_Frame_Rate_C7_round = round(Max_Frame_Rate_C7) + + # Max. Offered Load (802.11) + + Max_Offered_Load_C1 = Max_Frame_Rate_C1 * Nbits_value / 1000000 + self.Max_Offered_Load_C1_new = format(Max_Offered_Load_C1, '.3f') + Max_Offered_Load_C2 = Max_Frame_Rate_C2 * Nbits_value / 1000000 + Max_Offered_Load_C2_new = format(Max_Offered_Load_C2, '.3f') + Max_Offered_Load_C3 = Max_Frame_Rate_C3 * Nbits_value / 1000000 + Max_Offered_Load_C3_new = format(Max_Offered_Load_C3, '.3f') + Max_Offered_Load_C4 = Max_Frame_Rate_C4 * Nbits_value / 1000000 + Max_Offered_Load_C4_new = format(Max_Offered_Load_C4, '.3f') + Max_Offered_Load_C5 = Max_Frame_Rate_C5 * Nbits_value / 1000000 + Max_Offered_Load_C5_new = format(Max_Offered_Load_C5, '.3f') + Max_Offered_Load_C6 = Max_Frame_Rate_C6 * Nbits_value / 1000000 + Max_Offered_Load_C6_new = format(Max_Offered_Load_C6, '.3f') + Max_Offered_Load_C7 = Max_Frame_Rate_C7 * Nbits_value / 1000000 + Max_Offered_Load_C7_new = format(Max_Offered_Load_C7, '.3f') + + # Offered Load Per 802.11 Client + + Offered_Load_Per_Client1 = Max_Offered_Load_C1 / 1 + self.Offered_Load_Per_Client1_new = format(Offered_Load_Per_Client1, '.3f') + Offered_Load_Per_Client2 = Max_Offered_Load_C2 / 2 + Offered_Load_Per_Client2_new = format(Offered_Load_Per_Client2, '.3f') + Offered_Load_Per_Client3 = Max_Offered_Load_C3 / 5 + Offered_Load_Per_Client3_new = format(Offered_Load_Per_Client3, '.3f') + Offered_Load_Per_Client4 = Max_Offered_Load_C4 / 10 + Offered_Load_Per_Client4_new = format(Offered_Load_Per_Client4, '.3f') + Offered_Load_Per_Client5 = Max_Offered_Load_C5 / 20 + Offered_Load_Per_Client5_new = format(Offered_Load_Per_Client5, '.3f') + Offered_Load_Per_Client6 = Max_Offered_Load_C6 / 50 + Offered_Load_Per_Client6_new = format(Offered_Load_Per_Client6, '.3f') + Offered_Load_Per_Client7 = Max_Offered_Load_C7 / 100 + Offered_Load_Per_Client7_new = format(Offered_Load_Per_Client7, '.3f') + + # Offered Load (802.3 Side) + + Offered_Load_C1 = Max_Frame_Rate_C1 * Ethernet_MAC_Frame_int * 8 / 1000000 + self.Offered_Load_C1_new = format(Offered_Load_C1, '.3f') + Offered_Load_C2 = Max_Frame_Rate_C2 * Ethernet_MAC_Frame_int * 8 / 1000000 + Offered_Load_C2_new = format(Offered_Load_C2, '.3f') + Offered_Load_C3 = Max_Frame_Rate_C3 * Ethernet_MAC_Frame_int * 8 / 1000000 + Offered_Load_C3_new = format(Offered_Load_C3, '.3f') + Offered_Load_C4 = Max_Frame_Rate_C4 * Ethernet_MAC_Frame_int * 8 / 1000000 + Offered_Load_C4_new = format(Offered_Load_C4, '.3f') + Offered_Load_C5 = Max_Frame_Rate_C5 * Ethernet_MAC_Frame_int * 8 / 1000000 + Offered_Load_C5_new = format(Offered_Load_C5, '.3f') + Offered_Load_C6 = Max_Frame_Rate_C6 * Ethernet_MAC_Frame_int * 8 / 1000000 + Offered_Load_C6_new = format(Offered_Load_C6, '.3f') + Offered_Load_C7 = Max_Frame_Rate_C7 * Ethernet_MAC_Frame_int * 8 / 1000000 + Offered_Load_C7_new = format(Offered_Load_C7, '.3f') + + # IP Throughput (802.11 -> 802.3) + + if ip == 1: + IP_Throughput_C1 = Max_Frame_Rate_C1 * ip_packet * 8 / 1000000 + self.IP_Throughput_C1_new = format(IP_Throughput_C1, '.3f') + IP_Throughput_C2 = Max_Frame_Rate_C2 * ip_packet * 8 / 1000000 + IP_Throughput_C2_new = format(IP_Throughput_C2, '.3f') + IP_Throughput_C3 = Max_Frame_Rate_C3 * ip_packet * 8 / 1000000 + IP_Throughput_C3_new = format(IP_Throughput_C3, '.3f') + IP_Throughput_C4 = Max_Frame_Rate_C4 * ip_packet * 8 / 1000000 + IP_Throughput_C4_new = format(IP_Throughput_C4, '.3f') + IP_Throughput_C5 = Max_Frame_Rate_C5 * ip_packet * 8 / 1000000 + IP_Throughput_C5_new = format(IP_Throughput_C5, '.3f') + IP_Throughput_C6 = Max_Frame_Rate_C6 * ip_packet * 8 / 1000000 + IP_Throughput_C6_new = format(IP_Throughput_C6, '.3f') + IP_Throughput_C7 = Max_Frame_Rate_C7 * ip_packet * 8 / 1000000 + IP_Throughput_C7_new = format(IP_Throughput_C7, '.3f') + else: + self.IP_Throughput_C1_new = "N/A" + IP_Throughput_C2_new = "N/A" + IP_Throughput_C3_new = "N/A" + IP_Throughput_C4_new = "N/A" + IP_Throughput_C5_new = "N/A" + IP_Throughput_C6_new = "N/A" + IP_Throughput_C7_new = "N/A" + + + + Voice_Call = Max_Frame_Rate_C1 / Codec_Frame_rate + Voice_Call_value = round(Voice_Call) + + if "Data" in self.Traffic_Type: + self.Maximum_Theoretical_R_value = "N/A" + else: + if "G.711" in self.Codec_Type: + self.Maximum_Theoretical_R_value = 85.9 + else: + if "G.723" in self.Codec_Type: + self.Maximum_Theoretical_R_value = 72.9 + else: + if "G.729" in self.Codec_Type: + self.Maximum_Theoretical_R_value = 81.7 + else: + self.Maximum_Theoretical_R_value = 93.2 + + if "Data" in self.Traffic_Type: + self.Estimated_MOS_Score = "N/A" + self.Maximum_Bidirectional_Voice_Calls = "N/A" + else: + if (Voice_Call_value <= 1): + Maximum_Bidirectional_Voice_Calls1 = self.Max_Frame_Rate_C1_round / Codec_Frame_rate + elif (Voice_Call_value <= 2): + Maximum_Bidirectional_Voice_Calls1 = Max_Frame_Rate_C2_round / Codec_Frame_rate + elif (Voice_Call_value <= 5): + Maximum_Bidirectional_Voice_Calls1 = Max_Frame_Rate_C3_round / Codec_Frame_rate + + elif (Voice_Call_value <= 10): + Maximum_Bidirectional_Voice_Calls1 = Max_Frame_Rate_C4_round / Codec_Frame_rate + elif (Voice_Call_value <= 20): + Maximum_Bidirectional_Voice_Calls1 = Max_Frame_Rate_C5_round / Codec_Frame_rate + elif (Voice_Call_value <= 50): + Maximum_Bidirectional_Voice_Calls1 = Max_Frame_Rate_C6_round / Codec_Frame_rate + else: + Maximum_Bidirectional_Voice_Calls1 = Max_Frame_Rate_C7_round / Codec_Frame_rate + self.Maximum_Bidirectional_Voice_Calls = round(Maximum_Bidirectional_Voice_Calls1, 2) + if self.Maximum_Theoretical_R_value < 0: + self.Estimated_MOS_Score = 1 + else: + if self.Maximum_Theoretical_R_value > 100: + self.Estimated_MOS_Score = 4.5 + + else: + Estimated_MOS_Score_1 = 1 + 0.035 * self.Maximum_Theoretical_R_value + self.Maximum_Theoretical_R_value * ( + self.Maximum_Theoretical_R_value - 60) * ( + 100 - self.Maximum_Theoretical_R_value) * 7 * 0.000001 + self.Estimated_MOS_Score = round(Estimated_MOS_Score_1, 2) + + + + def get_result(self): + + print("\n" + "******************Station : 11abgCalculator*****************************" + "\n") + print("Theoretical Maximum Offered Load" + "\n") + print("1 Client:") + All_theoretical_output = {'Packet Interval(usec)': self.Client_1_new, 'Max Frame Rate(fps)': self.Max_Frame_Rate_C1_round, + 'Max. Offered Load (802.11)(Mb/s)': self.Max_Offered_Load_C1_new, + 'Offered Load Per 802.11 Client(Mb/s)': self.Offered_Load_Per_Client1_new, + 'Offered Load (802.3 Side)(Mb/s)': self.Offered_Load_C1_new, + 'IP Throughput (802.11 -> 802.3)(Mb/s)': self.IP_Throughput_C1_new} + print(json.dumps(All_theoretical_output, indent=4)) + + print("\n" + "Theroretical Voice Call Capacity" + "\n") + + All_theoretical_voice = {'Maximum Theoretical R-value': self.Maximum_Theoretical_R_value, + 'Estimated MOS Score': self.Estimated_MOS_Score, + 'Maximum Bidirectional Voice Calls(calls)': self.Maximum_Bidirectional_Voice_Calls} + print(json.dumps(All_theoretical_voice, indent=4)) + + +##Class to take all user input (802.11n Standard) + +class n11_calculator(abg11_calculator): + + def __init__(self, Traffic_Type, Data_Voice_MCS, Channel_Bandwidth, Guard_Interval_value, Highest_Basic_str, + Encryption, QoS, + IP_Packets_MSDU_str, MAC_Frames_per_A_MPDU_str, BSS_Basic_Rate, MAC_MPDU_Size_Data_Traffic, + Codec_Type, PLCP, CWmin, RTS_CTS_Handshake, CTS_to_self,PHY_Bit_Rate=None,MAC_Frame_802_11=None,Basic_Rate_Set=None,Preamble=None,slot_name=None): + super().__init__(Traffic_Type, PHY_Bit_Rate, Encryption, QoS, MAC_Frame_802_11, Basic_Rate_Set, Preamble, + slot_name, Codec_Type, RTS_CTS_Handshake, CTS_to_self) + self.Data_Voice_MCS = Data_Voice_MCS + self.Channel_Bandwidth = Channel_Bandwidth + self.Guard_Interval_value = Guard_Interval_value + self.Highest_Basic_str = Highest_Basic_str + self.IP_Packets_MSDU_str = IP_Packets_MSDU_str + self.MAC_Frames_per_A_MPDU_str = MAC_Frames_per_A_MPDU_str + self.BSS_Basic_Rate = BSS_Basic_Rate + self.MAC_MPDU_Size_Data_Traffic = MAC_MPDU_Size_Data_Traffic + self.PLCP = PLCP + self.CWmin = CWmin + + + # This function is for calculate intermediate values and Theoretical values + + def calculate(self): + global HT_data_temp + global temp_value + SIFS = 16.00 + DIFS = 34.00 + Slot_Time = 9.00 + Tsymbol_Control = 4.00 + # *********************************************Auxilliary Data*********************************************** + Non_HT_Ref = ['6', '12', '18', '24', '36', '48', '54', '54', '6', '12', '18', '24', '36', '48', '54', '54', '6', + '12', '18', '24', '36', '48', '54', '54', '6', '12', '18', '24', '36', '48', '54', '54'] + HT_LTFs = ['0', '1', '3', '3'] + Ndbps_20MHz = ['26', '52', '78', '104', '156', '208', '234', '260'] + Ndbps_40MHz = ['54', '108', '162', '216', '324', '432', '486', '540'] + Nes = ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', + '2', '2', '2', '1', '1', '1', '1', '2', '2', '2', '2', ] + + Data_Voice_MCS_int = int(self.Data_Voice_MCS) + IP_Packets_MSDU = int(self.IP_Packets_MSDU_str) + IP_Packets_MSDU_loc = IP_Packets_MSDU + 1 + Highest_Basic = int(self.Highest_Basic_str) + + i = 0 + while True: + if Data_Voice_MCS_int == i: + Non_HT = Non_HT_Ref[i] + break + i = i + 1 + + Non_HT_value = int(Non_HT) + + if Non_HT_value >= 6: + if "6" in self.BSS_Basic_Rate: + Allowed_control6 = 6 + else: + if (not ( + "6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate or "18" in self.BSS_Basic_Rate or + "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + Allowed_control6 = 6 + else: + Allowed_control6 = 0 + else: + Allowed_control6 = 0 + + if Non_HT_value >= 9: + if "9" in self.BSS_Basic_Rate: + Allowed_control9 = 9 + else: + if (not ( + "6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate or "18" in self.BSS_Basic_Rate or + "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + Allowed_control9 = 6 + else: + Allowed_control9 = 0 + else: + Allowed_control9 = 0 + + if Non_HT_value >= 12: + if "12" in self.BSS_Basic_Rate: + Allowed_control12 = 12 + else: + if (not ( + "6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate or "18" in self.BSS_Basic_Rate or + "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + Allowed_control12 = 12 + else: + Allowed_control12 = 0 + else: + Allowed_control12 = 0 + + if Non_HT_value >= 18: + if "18" in self.BSS_Basic_Rate: + Allowed_control18 = 18 + else: + if (not ( + "6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate or "18" in self.BSS_Basic_Rate or + "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + Allowed_control18 = 12 + else: + Allowed_control18 = 0 + else: + Allowed_control18 = 0 + + if Non_HT_value >= 24: + if "24" in self.BSS_Basic_Rate: + Allowed_control24 = 24 + else: + if (not ( + "6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate or "18" in self.BSS_Basic_Rate or + "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + Allowed_control24 = 24 + else: + Allowed_control24 = 0 + else: + Allowed_control24 = 0 + + if Non_HT_value >= 36: + if "36" in self.BSS_Basic_Rate: + Allowed_control36 = 36 + else: + if (not ( + "6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate or "18" in self.BSS_Basic_Rate or + "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + Allowed_control36 = 24 + else: + Allowed_control36 = 0 + else: + Allowed_control36 = 0 + + if Non_HT_value >= 48: + if "48" in self.BSS_Basic_Rate: + Allowed_control48 = 48 + else: + if (not ( + "6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate or "18" in self.BSS_Basic_Rate or + "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + Allowed_control48 = 24 + else: + Allowed_control48 = 0 + else: + Allowed_control48 = 0 + + if Non_HT_value >= 54: + if "54" in self.BSS_Basic_Rate: + Allowed_control54 = 54 + else: + if (not ( + "6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate or "18" in self.BSS_Basic_Rate or + "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + Allowed_control54 = 24 + else: + Allowed_control54 = 0 + else: + Allowed_control54 = 0 + + # g24 QoS Hdr + if "Yes" in self.QoS: + QoS_1 = 1 + elif "No" in self.QoS: + QoS_1 = 0 + if (QoS_1 == 1) or (IP_Packets_MSDU > 1): + QoS_Hdr = 2 + else: + QoS_Hdr = 0 + + # g23 Encrypt Hdr + + if "None" in self.Encryption: + Encrypt_Hdr = 0 + else: + if "WEP" in self.Encryption: + Encrypt_Hdr = 8 + elif "TKIP" in self.Encryption: + Encrypt_Hdr = 20 + else: + Encrypt_Hdr = 16 + # c36 Codec IP Packet Size + + if "G.711" in self.Codec_Type: + Codec_IP_Packet_Size = 200 + Codec_Frame_Rate = 100 + + else: + if "G.723" in self.Codec_Type: + Codec_IP_Packet_Size = 60 + Codec_Frame_Rate = 67 + + else: + if "G.729" in self.Codec_Type: + Codec_IP_Packet_Size = 60 + Codec_Frame_Rate = 100 + + else: + Codec_IP_Packet_Size = 0 + Codec_Frame_Rate = 0 + + # c17 MAC MPDU Size + + if "Data" in self.Traffic_Type: + MAC_MPDU_Size = int(self.MAC_MPDU_Size_Data_Traffic) + + else: + if ((IP_Packets_MSDU == 0)): + MAC_MPDU_Size = (Codec_IP_Packet_Size + 28 + QoS_Hdr + Encrypt_Hdr + 8) + + else: + MAC_MPDU_Size_1 = (Codec_IP_Packet_Size + 28 + QoS_Hdr + Encrypt_Hdr + 8 + (IP_Packets_MSDU_loc - 1) * ( + 14 + 3)) + MAC_MPDU_Size = int(MAC_MPDU_Size_1 / (IP_Packets_MSDU_loc - 1)) + + # MSDU Size + + if IP_Packets_MSDU == 0: + MSDU_final = MAC_MPDU_Size - 28 - QoS_Hdr - Encrypt_Hdr + + + else: + MSDU_1 = (MAC_MPDU_Size - 28 - QoS_Hdr - Encrypt_Hdr - (IP_Packets_MSDU) * (14 + 3)) + MSDU_final = MSDU_1 / IP_Packets_MSDU + + if MSDU_final < 0: + msdu_str = str(MSDU_final) + x = msdu_str.split(".") + sec = len(x[0]) + if sec > 2: + MSDU = int(MSDU_final) - 1 + else: + MSDU = int(MSDU_final) + else: + MSDU = int(MSDU_final) + + if (MSDU - 8) < 20: + IP_Packet_value = "N/A" + ip_value = 0 + else: + IP_Packet_value = MSDU - 8 + ip_value = 1 + + if ip_value == 0: + Ethernet_value = "N/A" + eth_value = 0 + else: + ip_1 = int(IP_Packet_value) + Ethernet_value = max(ip_1 + 18, 64) + eth_value = 1 + + # HT-LTFs + + temp_value = int(Data_Voice_MCS_int / 8) + + def HT_value_func(): + if temp_value == 0: + HT_data_temp = HT_LTFs[0] + elif temp_value == 1: + HT_data_temp = HT_LTFs[1] + elif temp_value == 2: + HT_data_temp = HT_LTFs[2] + elif temp_value == 3: + HT_data_temp = HT_LTFs[3] + return HT_data_temp + + HT_data = HT_value_func() + HT_data_1 = int(HT_data) + + # c20 Tppdu_fixed (HT Data Frames) + + if "Mixed" in self.PLCP: + Tppdu_fixed_Data_Frame = float(36 + 4 * HT_data_1) + PLCP_Configuration_int = 1 + elif "Greenfield" in self.PLCP: + Tppdu_fixed_Data_Frame = float(24 + 4 * HT_data_1) + PLCP_Configuration_int = 2 + + # c21 Tppdu_fixed (HT Control Frames) + + Data_min = min(Data_Voice_MCS_int, Highest_Basic) + temp_value = int(Data_min / 8) + 1 + HT_data_temp_1 = HT_value_func() + HT_data_2 = int(HT_data_temp_1) + + if "Mixed" in self.PLCP: + Tppdu_fixed_Control_Frames = float(36 + 4 * HT_data_2) + + elif "Greenfield" in self.PLCP: + Tppdu_fixed_Control_Frames = float(24 + 4 * HT_data_2) + + # Non-HT Reference Rate + # Offset calculation + + i = 0 + while True: + if Data_Voice_MCS_int == i: + Non_HT = Non_HT_Ref[i] + Nes_data = Nes[i] + break + i = i + 1 + + # PHY Bit Rate of Control Frames + + PHY_Bit_Rate_of_Control_Frames = max(Allowed_control6, Allowed_control9, + Allowed_control12, Allowed_control18, + Allowed_control24, Allowed_control36, + Allowed_control48, Allowed_control54, 6) + + # Ndbps, data bits per symbol (Data) + + def Ndbps_fun(): + if Data_Voice_MCS_int == 0 or Data_Voice_MCS_int == 8 or Data_Voice_MCS_int == 16 or Data_Voice_MCS_int == 24: + Ndbps_20 = Ndbps_20MHz[0] + Ndbps_40 = Ndbps_40MHz[0] + elif Data_Voice_MCS_int == 1 or Data_Voice_MCS_int == 9 or Data_Voice_MCS_int == 17 or Data_Voice_MCS_int == 25: + Ndbps_20 = Ndbps_20MHz[1] + Ndbps_40 = Ndbps_40MHz[1] + elif Data_Voice_MCS_int == 2 or Data_Voice_MCS_int == 10 or Data_Voice_MCS_int == 18 or Data_Voice_MCS_int == 26: + Ndbps_20 = Ndbps_20MHz[2] + Ndbps_40 = Ndbps_40MHz[2] + + elif Data_Voice_MCS_int == 3 or Data_Voice_MCS_int == 11 or Data_Voice_MCS_int == 19 or Data_Voice_MCS_int == 27: + Ndbps_20 = Ndbps_20MHz[3] + Ndbps_40 = Ndbps_40MHz[3] + elif Data_Voice_MCS_int == 4 or Data_Voice_MCS_int == 12 or Data_Voice_MCS_int == 20 or Data_Voice_MCS_int == 28: + Ndbps_20 = Ndbps_20MHz[4] + Ndbps_40 = Ndbps_40MHz[4] + elif Data_Voice_MCS_int == 5 or Data_Voice_MCS_int == 13 or Data_Voice_MCS_int == 21 or Data_Voice_MCS_int == 29: + Ndbps_20 = Ndbps_20MHz[5] + Ndbps_40 = Ndbps_40MHz[5] + elif Data_Voice_MCS_int == 6 or Data_Voice_MCS_int == 14 or Data_Voice_MCS_int == 22 or Data_Voice_MCS_int == 30: + Ndbps_20 = Ndbps_20MHz[6] + Ndbps_40 = Ndbps_40MHz[6] + elif Data_Voice_MCS_int == 7 or Data_Voice_MCS_int == 15 or Data_Voice_MCS_int == 23 or Data_Voice_MCS_int == 31: + Ndbps_20 = Ndbps_20MHz[7] + Ndbps_40 = Ndbps_40MHz[7] + Ndbps_20_int = int(Ndbps_20) + Ndbps_40_int = int(Ndbps_40) + return Ndbps_20_int, Ndbps_40_int + + if "20" in self.Channel_Bandwidth: + + if Data_Voice_MCS_int >= 0 and Data_Voice_MCS_int < 8: + Ndbps_20_int, Ndbps_40_int = Ndbps_fun() + data_bits = Ndbps_20_int * 1 + if Data_Voice_MCS_int >= 8 and Data_Voice_MCS_int < 16: + Ndbps_20_int, Ndbps_40_int = Ndbps_fun() + data_bits = Ndbps_20_int * 2 + if Data_Voice_MCS_int >= 16 and Data_Voice_MCS_int < 24: + Ndbps_20_int, Ndbps_40_int = Ndbps_fun() + data_bits = Ndbps_20_int * 3 + if Data_Voice_MCS_int >= 24 and Data_Voice_MCS_int < 32: + Ndbps_20_int, Ndbps_40_int = Ndbps_fun() + data_bits = Ndbps_20_int * 4 + + + elif "40" in self.Channel_Bandwidth: + + if Data_Voice_MCS_int >= 0 and Data_Voice_MCS_int < 8: + Ndbps_20_int, Ndbps_40_int = Ndbps_fun() + data_bits = Ndbps_40_int * 1 + if Data_Voice_MCS_int >= 8 and Data_Voice_MCS_int < 16: + Ndbps_20_int, Ndbps_40_int = Ndbps_fun() + data_bits = Ndbps_40_int * 2 + if Data_Voice_MCS_int >= 16 and Data_Voice_MCS_int < 24: + Ndbps_20_int, Ndbps_40_int = Ndbps_fun() + data_bits = Ndbps_40_int * 3 + if Data_Voice_MCS_int >= 24 and Data_Voice_MCS_int < 32: + Ndbps_20_int, Ndbps_40_int = Ndbps_fun() + data_bits = Ndbps_40_int * 4 + + # Ndbps, data bits per symbol (Control) + + if PHY_Bit_Rate_of_Control_Frames < 7: + if PHY_Bit_Rate_of_Control_Frames < 3: + PHY = PHY_Bit_Rate_of_Control_Frames + else: + if PHY_Bit_Rate_of_Control_Frames == 3: + PHY = 5.5 + else: + if PHY_Bit_Rate_of_Control_Frames == 4: + PHY = 11 + else: + if PHY_Bit_Rate_of_Control_Frames == 5: + PHY = 6 + else: + if PHY_Bit_Rate_of_Control_Frames == 6: + PHY = 9 + else: + if PHY_Bit_Rate_of_Control_Frames == 8: + PHY = 18 + else: + if PHY_Bit_Rate_of_Control_Frames == 9: + PHY = 24 + else: + if PHY_Bit_Rate_of_Control_Frames == 10: + PHY = 36 + else: + if PHY_Bit_Rate_of_Control_Frames == 11: + PHY = 48 + else: + PHY = 54 + Ndbps_data_bits_per_symbol_Control = PHY * 4 + MAC_Frames_per_A_MPDU = int(self.MAC_Frames_per_A_MPDU_str) + + # g22 A-MPDU Pad + + if ((MAC_Frames_per_A_MPDU == 0)): + MPDU_Pad = int(0) + + else: + x = int((MAC_MPDU_Size % 4)) + y = int((4 - x)) + MPDU_Pad = int((y % 4)) + + # c26 Nbits, Bits per MAC PPDU + + MAC_Frames_per_A_MPDU_loc = MAC_Frames_per_A_MPDU + 1 + if (MAC_Frames_per_A_MPDU == 0): + Nbits_Bits_per_MAC_PPDU = MAC_MPDU_Size * 8 + + else: + Nbits_Bits_per_MAC_PPDU = ((MAC_MPDU_Size + 4) * (MAC_Frames_per_A_MPDU_loc - 1) + MPDU_Pad * ( + MAC_Frames_per_A_MPDU_loc - 2)) * 8 + + # c27 Tsymbol(Data), Data Symbol Period + + if "400" in self.Guard_Interval_value: + Guard_Interval_1 = 1 + elif "800" in self.Guard_Interval_value: + Guard_Interval_1 = 0 + calculation = (((Data_Voice_MCS_int > 7 and PLCP_Configuration_int == 2) or PLCP_Configuration_int == 1)) + if (Guard_Interval_1 == 1) and calculation: + Tsymbol_Data_Symbol_Period = 3.60 + + else: + Tsymbol_Data_Symbol_Period = 4 + + Nes_data_int = int(Nes_data) + + # Ttxframe (DATA) + + if "40" in self.Channel_Bandwidth: + offset = 6 * Nes_data_int + else: + offset = 6 + + Ttxframe_1 = Tppdu_fixed_Data_Frame + int( + (16 + offset + Nbits_Bits_per_MAC_PPDU + data_bits - 1) / data_bits) * Tsymbol_Data_Symbol_Period + Ttxframe_2 = format(Ttxframe_1, '.2f') + Ttxframe = float(Ttxframe_2) + + # c30 Ttxframe (Ack) + + Ttxframe_Ack = int( + (22 + 14 * 8 + PHY_Bit_Rate_of_Control_Frames * 4 - 1) / (PHY_Bit_Rate_of_Control_Frames * 4)) * 4 + 20 + + # c31 Ttxframe (Compressed BlockAck) + + Ttxframe_Compressed_BlockAck = int( + (22 + 32 * 8 + PHY_Bit_Rate_of_Control_Frames * 4 - 1) / (PHY_Bit_Rate_of_Control_Frames * 4)) * 4 + 20 + + # g20 Use BlockAck + if (MAC_Frames_per_A_MPDU == 0): + Use_BlockAck = False + + else: + Use_BlockAck = True + + # c34 Ack Response Overhead + if Use_BlockAck: + Ack_Response_Overhead = 0 + + else: + Ack_Response_Overhead = SIFS + Ttxframe_Ack + + # c35 BlockAck Response Overhead + + if Use_BlockAck: + BlockAck_Response_Overhead = SIFS + Ttxframe_Compressed_BlockAck + + else: + BlockAck_Response_Overhead = 0 + CWmin_leave_alone_for_default = int(self.CWmin) + MeanBackoff = CWmin_leave_alone_for_default * Slot_Time / 2 + + # c32 RTS/CTS Handshake Overhead + + if "Yes" in self.RTS_CTS_Handshake: + if "20" in self.Channel_Bandwidth: + RTS_CTS_Handshake_Overhead = 2 * 20 + 4 * int((22 + (20 + 14) * 8 + 24 * 4 - 1) / (24 * 4)) + 2 * SIFS + + else: + RTS_CTS_Handshake_Overhead = 2 * 20 + int( + (22 + (20 + 14) * 8 + 24 - 1) / 24) * Tsymbol_Control + 2 * SIFS + + else: + RTS_CTS_Handshake_Overhead = 0 + + # c33CTS-to-self Handshake Overhead + + if "Yes" in self.RTS_CTS_Handshake: + CTS_to_self_Handshake_Overhead = 0 + + else: + if "Yes" in self.CTS_to_self: + if "20" in self.Channel_Bandwidth: + CTS_to_self_Handshake_Overhead = 20 + 4 * int((22 + 14 * 8 + 24 * 4 - 1) / (24 * 4)) + SIFS + + else: + CTS_to_self_Handshake_Overhead = 20 + int((22 + 14 * 8 + 24 - 1) / 24) * Tsymbol_Control + SIFS + + else: + CTS_to_self_Handshake_Overhead = 0 + + # MAC PPDU Interval + + # MeanBackoff + + MAC_PPDU_Interval_1 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 1) + self.Client_1_new = format(MAC_PPDU_Interval_1, '.2f') + MAC_PPDU_Interval_2 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 2) + Client_2_new = format(MAC_PPDU_Interval_2, '.2f') + MAC_PPDU_Interval_3 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 5) + Client_3_new = format(MAC_PPDU_Interval_3, '.2f') + MAC_PPDU_Interval_4 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 10) + Client_4_new = format(MAC_PPDU_Interval_4, '.2f') + MAC_PPDU_Interval_5 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 20) + Client_5_new = format(MAC_PPDU_Interval_5, '.2f') + MAC_PPDU_Interval_6 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 50) + Client_6_new = format(MAC_PPDU_Interval_6, '.2f') + MAC_PPDU_Interval_7 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 100) + Client_7_new = format(MAC_PPDU_Interval_7, '.3f') + + # Max PPDU Rate + + Max_PPDU_Rate_1 = 1000000 / MAC_PPDU_Interval_1 + self.Client_8_new = format(Max_PPDU_Rate_1, '.2f') + Max_PPDU_Rate_2 = 1000000 / MAC_PPDU_Interval_2 + Client_9_new = format(Max_PPDU_Rate_2, '.2f') + Max_PPDU_Rate_3 = 1000000 / MAC_PPDU_Interval_3 + Client_10_new = format(Max_PPDU_Rate_3, '.2f') + Max_PPDU_Rate_4 = 1000000 / MAC_PPDU_Interval_4 + Client_11_new = format(Max_PPDU_Rate_4, '.2f') + Max_PPDU_Rate_5 = 1000000 / MAC_PPDU_Interval_5 + Client_12_new = format(Max_PPDU_Rate_5, '.2f') + Max_PPDU_Rate_6 = 1000000 / MAC_PPDU_Interval_6 + Client_13_new = format(Max_PPDU_Rate_6, '.2f') + Max_PPDU_Rate_7 = 1000000 / MAC_PPDU_Interval_7 + Client_14_new = format(Max_PPDU_Rate_7, '.2f') + + # c44 Max_MAC_MPDU_Rate_1 + if (MAC_Frames_per_A_MPDU > 0): + Max_MAC_MPDU_Rate_1 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_1 + Max_MAC_MPDU_Rate_2 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_2 + Max_MAC_MPDU_Rate_3 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_3 + Max_MAC_MPDU_Rate_4 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_4 + Max_MAC_MPDU_Rate_5 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_5 + Max_MAC_MPDU_Rate_6 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_6 + Max_MAC_MPDU_Rate_7 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_7 + else: + Max_MAC_MPDU_Rate_1 = Max_PPDU_Rate_1 + Max_MAC_MPDU_Rate_2 = Max_PPDU_Rate_2 + Max_MAC_MPDU_Rate_3 = Max_PPDU_Rate_3 + Max_MAC_MPDU_Rate_4 = Max_PPDU_Rate_4 + Max_MAC_MPDU_Rate_5 = Max_PPDU_Rate_5 + Max_MAC_MPDU_Rate_6 = Max_PPDU_Rate_6 + Max_MAC_MPDU_Rate_7 = Max_PPDU_Rate_7 + + self.Client_15_new = round(Max_MAC_MPDU_Rate_1) + Client_16_new = round(Max_MAC_MPDU_Rate_2) + Client_17_new = round(Max_MAC_MPDU_Rate_3) + Client_18_new = round(Max_MAC_MPDU_Rate_4) + Client_19_new = round(Max_MAC_MPDU_Rate_5) + Client_20_new = round(Max_MAC_MPDU_Rate_6) + Client_21_new = round(Max_MAC_MPDU_Rate_7) + + # Max MAC MSDU Rate + + if (IP_Packets_MSDU > 0): + Max_MAC_MSDU_Rate_1 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_1 + Max_MAC_MSDU_Rate_2 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_2 + Max_MAC_MSDU_Rate_3 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_3 + Max_MAC_MSDU_Rate_4 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_4 + Max_MAC_MSDU_Rate_5 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_5 + Max_MAC_MSDU_Rate_6 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_6 + Max_MAC_MSDU_Rate_7 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_7 + + else: + Max_MAC_MSDU_Rate_1 = Max_MAC_MPDU_Rate_1 + Max_MAC_MSDU_Rate_2 = Max_MAC_MPDU_Rate_2 + Max_MAC_MSDU_Rate_3 = Max_MAC_MPDU_Rate_3 + Max_MAC_MSDU_Rate_4 = Max_MAC_MPDU_Rate_4 + Max_MAC_MSDU_Rate_5 = Max_MAC_MPDU_Rate_5 + Max_MAC_MSDU_Rate_6 = Max_MAC_MPDU_Rate_6 + Max_MAC_MSDU_Rate_7 = Max_MAC_MPDU_Rate_7 + + self.Client_22_new = round(Max_MAC_MSDU_Rate_1) + Client_23_new = round(Max_MAC_MSDU_Rate_2) + Client_24_new = round(Max_MAC_MSDU_Rate_3) + Client_25_new = round(Max_MAC_MSDU_Rate_4) + Client_26_new = round(Max_MAC_MSDU_Rate_5) + Client_27_new = round(Max_MAC_MSDU_Rate_6) + Client_28_new = round(Max_MAC_MSDU_Rate_7) + + # Max. 802.11 MAC Frame Data Rate + Max_802_11_MAC_Frame_Data_Rate_1 = Max_MAC_MPDU_Rate_1 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_2 = Max_MAC_MPDU_Rate_2 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_3 = Max_MAC_MPDU_Rate_3 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_4 = Max_MAC_MPDU_Rate_4 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_5 = Max_MAC_MPDU_Rate_5 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_6 = Max_MAC_MPDU_Rate_6 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_7 = Max_MAC_MPDU_Rate_7 * MAC_MPDU_Size * 8 / 1000000 + + self.Client_29_new = format(Max_802_11_MAC_Frame_Data_Rate_1, '.3f') + Client_30_new = format(Max_802_11_MAC_Frame_Data_Rate_2, '.3f') + Client_31_new = format(Max_802_11_MAC_Frame_Data_Rate_3, '.3f') + Client_32_new = format(Max_802_11_MAC_Frame_Data_Rate_4, '.3f') + Client_33_new = format(Max_802_11_MAC_Frame_Data_Rate_5, '.3f') + Client_34_new = format(Max_802_11_MAC_Frame_Data_Rate_6, '.3f') + Client_35_new = format(Max_802_11_MAC_Frame_Data_Rate_7, '.3f') + + # Max. 802.11 MAC Payload Goodput + + Max_802_11_MAC_Payload_Goodput_1 = MSDU * 8 * Max_MAC_MSDU_Rate_1 / 1000000 + Max_802_11_MAC_Payload_Goodput_2 = MSDU * 8 * Max_MAC_MSDU_Rate_2 / 1000000 + Max_802_11_MAC_Payload_Goodput_3 = MSDU * 8 * Max_MAC_MSDU_Rate_3 / 1000000 + Max_802_11_MAC_Payload_Goodput_4 = MSDU * 8 * Max_MAC_MSDU_Rate_4 / 1000000 + Max_802_11_MAC_Payload_Goodput_5 = MSDU * 8 * Max_MAC_MSDU_Rate_5 / 1000000 + Max_802_11_MAC_Payload_Goodput_6 = MSDU * 8 * Max_MAC_MSDU_Rate_6 / 1000000 + Max_802_11_MAC_Payload_Goodput_7 = MSDU * 8 * Max_MAC_MSDU_Rate_7 / 1000000 + + self.Client_36_new = format(Max_802_11_MAC_Payload_Goodput_1, '.3f') + Client_37_new = format(Max_802_11_MAC_Payload_Goodput_2, '.3f') + Client_38_new = format(Max_802_11_MAC_Payload_Goodput_3, '.3f') + Client_39_new = format(Max_802_11_MAC_Payload_Goodput_4, '.3f') + Client_40_new = format(Max_802_11_MAC_Payload_Goodput_5, '.3f') + Client_41_new = format(Max_802_11_MAC_Payload_Goodput_6, '.3f') + Client_42_new = format(Max_802_11_MAC_Payload_Goodput_7, '.3f') + + # MAC Goodput Per 802.11 Client + + MAC_Goodput_Per_802_11_Client_1 = Max_802_11_MAC_Payload_Goodput_1 / 1 + MAC_Goodput_Per_802_11_Client_2 = Max_802_11_MAC_Payload_Goodput_2 / 2 + MAC_Goodput_Per_802_11_Client_3 = Max_802_11_MAC_Payload_Goodput_3 / 5 + MAC_Goodput_Per_802_11_Client_4 = Max_802_11_MAC_Payload_Goodput_4 / 10 + MAC_Goodput_Per_802_11_Client_5 = Max_802_11_MAC_Payload_Goodput_5 / 20 + MAC_Goodput_Per_802_11_Client_6 = Max_802_11_MAC_Payload_Goodput_6 / 50 + MAC_Goodput_Per_802_11_Client_7 = Max_802_11_MAC_Payload_Goodput_7 / 100 + + self.Client_43_new = format(MAC_Goodput_Per_802_11_Client_1, '.3f') + Client_44_new = format(MAC_Goodput_Per_802_11_Client_2, '.3f') + Client_45_new = format(MAC_Goodput_Per_802_11_Client_3, '.3f') + Client_46_new = format(MAC_Goodput_Per_802_11_Client_4, '.3f') + Client_47_new = format(MAC_Goodput_Per_802_11_Client_5, '.3f') + Client_48_new = format(MAC_Goodput_Per_802_11_Client_6, '.3f') + Client_49_new = format(MAC_Goodput_Per_802_11_Client_7, '.3f') + + # Offered Load (802.3 Side) + + # c49 + if eth_value != 0: + + Offered_Load_8023_Side_1 = Max_MAC_MSDU_Rate_1 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_2 = Max_MAC_MSDU_Rate_2 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_3 = Max_MAC_MSDU_Rate_3 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_4 = Max_MAC_MSDU_Rate_4 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_5 = Max_MAC_MSDU_Rate_5 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_6 = Max_MAC_MSDU_Rate_6 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_7 = Max_MAC_MSDU_Rate_7 * Ethernet_value * 8 / 1000000 + self.Client_50_new = format(Offered_Load_8023_Side_1, '.3f') + Client_51_new = format(Offered_Load_8023_Side_2, '.3f') + Client_52_new = format(Offered_Load_8023_Side_3, '.3f') + Client_53_new = format(Offered_Load_8023_Side_4, '.3f') + Client_54_new = format(Offered_Load_8023_Side_5, '.3f') + Client_55_new = format(Offered_Load_8023_Side_6, '.3f') + Client_56_new = format(Offered_Load_8023_Side_7, '.3f') + + else: + self.Client_50_new = "N/A" + Client_51_new = "N/A" + Client_52_new = "N/A" + Client_53_new = "N/A" + Client_54_new = "N/A" + Client_55_new = "N/A" + Client_56_new = "N/A" + + IP_Packet_value_str = str(IP_Packet_value) + if "N/A" not in IP_Packet_value_str: + IP_Goodput_802_11_8023_1 = Max_MAC_MSDU_Rate_1 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_2 = Max_MAC_MSDU_Rate_2 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_3 = Max_MAC_MSDU_Rate_3 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_4 = Max_MAC_MSDU_Rate_4 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_5 = Max_MAC_MSDU_Rate_5 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_6 = Max_MAC_MSDU_Rate_6 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_7 = Max_MAC_MSDU_Rate_7 * ip_1 * 8 / 1000000 + self.Client_57_new = format(IP_Goodput_802_11_8023_1, '.3f') + Client_58_new = format(IP_Goodput_802_11_8023_2, '.3f') + Client_59_new = format(IP_Goodput_802_11_8023_3, '.3f') + Client_60_new = format(IP_Goodput_802_11_8023_4, '.3f') + Client_61_new = format(IP_Goodput_802_11_8023_5, '.3f') + Client_62_new = format(IP_Goodput_802_11_8023_6, '.3f') + Client_63_new = format(IP_Goodput_802_11_8023_7, '.3f') + + else: + self.Client_57_new = "N/A" + Client_58_new = "N/A" + Client_59_new = "N/A" + Client_60_new = "N/A" + Client_61_new = "N/A" + Client_62_new = "N/A" + Client_63_new = "N/A" + + # Theoretical Voice Call Capacity + + # c53 + if "Data" in self.Traffic_Type: + self.Maximum_Theoretical_R_value = "N/A" + self.Estimated_MOS_Score = "N/A" + else: + if "G.711" in self.Codec_Type: + self.Maximum_Theoretical_R_value = 85.9 + else: + if "G.723" in self.Codec_Type: + self.Maximum_Theoretical_R_value = 72.9 + else: + if "G.729" in self.Codec_Type: + self.Maximum_Theoretical_R_value = 81.7 + else: + self.Maximum_Theoretical_R_value = 93.2 + + if self.Maximum_Theoretical_R_value < 0: + self.Estimated_MOS_Score = 1 + else: + if self.Maximum_Theoretical_R_value > 100: + self.Estimated_MOS_Score = 4.5 + else: + self.Estimated_MOS_Score_1 = ( + 1 + 0.035 * self.Maximum_Theoretical_R_value + self.Maximum_Theoretical_R_value * ( + self.Maximum_Theoretical_R_value - 60) * ( + 100 - self.Maximum_Theoretical_R_value) * 7 * 0.000001) + self.Estimated_MOS_Score = format(self.Estimated_MOS_Score_1, '.2f') + + # Voice_Call_Range + try: + Voice_Call_Range = round(Max_PPDU_Rate_1 / Codec_Frame_Rate) + + # c55 Maximum Bidirectional Voice Calls + if Voice_Call_Range <= 1: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_1 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 2: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_2 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 5: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_3 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 10: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_4 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 20: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_5 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 50: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_6 / Codec_Frame_Rate + + else: + + Maximum_Bidirectional = Max_MAC_MSDU_Rate_7 / Codec_Frame_Rate + except ZeroDivisionError: + pass + + if "Data" in self.Traffic_Type: + self.Maximum_Bidirectional_Voice_Calls = "N/A" + else: + self.Maximum_Bidirectional_Voice_Calls = round(Maximum_Bidirectional, 2) + + + + def get_result(self): + + print("\n" + "******************Station : 11nCalculator*****************************" + "\n") + print("Theoretical Maximum Offered Load" + "\n") + print("1 Client:") + All_theoretical_output = {'MAC PPDU Interval(usec)': self.Client_1_new, + 'Max PPDU Rate(fps)': self.Client_8_new, + 'Max MAC MPDU Rate': self.Client_15_new, + 'Max MAC MSDU Rate': self.Client_22_new, + 'Max. 802.11 MAC Frame Data Rate(Mb/s)': self.Client_29_new, + 'Max. 802.11 MAC Payload Goodput(Mb/s)': self.Client_36_new, + 'MAC Goodput Per 802.11 Client(Mb/s)': self.Client_43_new, + 'Offered Load (802.3 Side)(Mb/s)': self.Client_50_new, + 'IP Goodput (802.11 -> 802.3)(Mb/s)': self.Client_57_new} + print(json.dumps(All_theoretical_output, indent=4)) + + print("\n" + "Theroretical Voice Call Capacity" + "\n") + + All_theoretical_voice = {'Maximum Theoretical R-value': self.Maximum_Theoretical_R_value, + 'Estimated MOS Score': self.Estimated_MOS_Score, + 'Maximum Bidirectional Voice Calls(calls)': self.Maximum_Bidirectional_Voice_Calls} + print(json.dumps(All_theoretical_voice, indent=4)) + + +##Class to take all user input (802.11ac Standard) + +class ac11_calculator(n11_calculator): + + + + def __init__(self, Traffic_Type, Data_Voice_MCS, spatial, Channel_Bandwidth, Guard_Interval_value, + Highest_Basic_str, Encryption, QoS,IP_Packets_MSDU_str, MAC_Frames_per_A_MPDU_str, BSS_Basic_Rate, MAC_MPDU_Size_Data_Traffic, + Codec_Type, CWmin, RTS_CTS,PLCP = None,RTS_CTS_Handshake=None,CTS_to_self=None): + super().__init__(Traffic_Type, Data_Voice_MCS, Channel_Bandwidth, Guard_Interval_value, Highest_Basic_str, + Encryption, QoS, + IP_Packets_MSDU_str, MAC_Frames_per_A_MPDU_str, BSS_Basic_Rate, MAC_MPDU_Size_Data_Traffic, + Codec_Type, PLCP, CWmin, RTS_CTS_Handshake, CTS_to_self) + + self.spatial = spatial + self.RTS_CTS = RTS_CTS + + + # This function is for calculate intermediate values and Theoretical values + + def calculate(self): + + SIFS = 16.00 + DIFS = 34.00 + Slot_Time = 9.00 + Tsymbol_control_Symbol_Period = 4.00 + Codec_IP_Packet_Size = 200 + Codec_Frame_Rate = 100 + # ********************Auxilliary data**************************** + + HT_LTFs = ['1', '2', '4', '4'] + Ndbps_20MHz = ['26', '52', '78', '104', '156', '208', '234', '260', '312', '1040'] + Ndbps_40MHz = ['54', '108', '162', '216', '324', '432', '486', '540', '648', '720'] + Ndbps_80MHz = ['117', '234', '351', '468', '702', '936', '1053', '1170', '1404', '1560'] + Non_HT_Ref = ['6', '12', '18', '24', '36', '48', '54', '54', '54', '54'] + Nes1 = ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', + '1', '1', '1', '1', '1', '1', '1', '1', '1'] + Nes2 = ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', + '2', '2', '2', '1', '1', '1', '2', '2', '2'] + Nes3 = ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', + '2', '2', '2', '1', '2', '2', '2', '2', '3'] + Nes4 = ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '1', + '1', '1', '1', '2', '2', '2', '3', '3', '3'] + + Data_Voice_MCS_int = int(self.Data_Voice_MCS) + CWmin_leave_alone_for_default = int(self.CWmin) + spatial_int = int(self.spatial) + MAC_Frames_per_A_MPDU = int(self.MAC_Frames_per_A_MPDU_str) + + i = 0 + while True: + if Data_Voice_MCS_int == i: + Non_HT = Non_HT_Ref[i] + break + i = i + 1 + + Non_HT_value = int(Non_HT) + + if Non_HT_value >= 6: + if "6" in self.BSS_Basic_Rate: + allowed_control6 = 6 + else: + if (not ("6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate + or "18" in self.BSS_Basic_Rate or "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or + "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + allowed_control6 = 6 + else: + allowed_control6 = 0 + else: + allowed_control6 = 0 + + if Non_HT_value >= 9: + if "9" in self.BSS_Basic_Rate: + allowed_control9 = 9 + else: + if (not ("6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate + or "18" in self.BSS_Basic_Rate or "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or + "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + allowed_control9 = 6 + else: + allowed_control9 = 0 + else: + allowed_control9 = 0 + + if Non_HT_value >= 12: + if "12" in self.BSS_Basic_Rate: + allowed_control12 = 12 + else: + if (not ("6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate + or "18" in self.BSS_Basic_Rate or "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or + "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + allowed_control12 = 12 + else: + allowed_control12 = 0 + else: + allowed_control12 = 0 + + if Non_HT_value >= 18: + if "18" in self.BSS_Basic_Rate: + allowed_control18 = 18 + else: + if (not ("6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate + or "18" in self.BSS_Basic_Rate or "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or + "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + allowed_control18 = 12 + else: + allowed_control18 = 0 + else: + allowed_control18 = 0 + + if Non_HT_value >= 24: + if "24" in self.BSS_Basic_Rate: + allowed_control24 = 24 + else: + if (not ("6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate + or "18" in self.BSS_Basic_Rate or "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or + "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + allowed_control24 = 24 + else: + allowed_control24 = 0 + else: + allowed_control24 = 0 + + if Non_HT_value >= 36: + if "36" in self.BSS_Basic_Rate: + allowed_control36 = 36 + else: + if (not ("6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate + or "18" in self.BSS_Basic_Rate or "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or + "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + allowed_control36 = 24 + else: + allowed_control36 = 0 + else: + allowed_control36 = 0 + + if Non_HT_value >= 48: + if "48" in self.BSS_Basic_Rate: + allowed_control48 = 48 + else: + if (not ("6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate + or "18" in self.BSS_Basic_Rate or "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or + "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + allowed_control48 = 24 + else: + allowed_control48 = 0 + else: + allowed_control48 = 0 + + if Non_HT_value >= 54: + if "54" in self.BSS_Basic_Rate: + allowed_control54 = 54 + else: + if (not ("6" in self.BSS_Basic_Rate or "9" in self.BSS_Basic_Rate or "12" in self.BSS_Basic_Rate + or "18" in self.BSS_Basic_Rate or "24" in self.BSS_Basic_Rate or "36" in self.BSS_Basic_Rate or + "48" in self.BSS_Basic_Rate or "54" in self.BSS_Basic_Rate)): + allowed_control54 = 24 + else: + allowed_control54 = 0 + else: + allowed_control54 = 0 + + MeanBackoff = CWmin_leave_alone_for_default * Slot_Time / 2 + + IP_Packets_MSDU = int(self.IP_Packets_MSDU_str) + if "Mixed" in self.Codec_Type: + plcp = 1 + elif "Greenfield" in self.Codec_Type: + plcp = 2 + + RTS_CTS_Handshake = 1 + if "No" in self.RTS_CTS: + CTS_to_self = 1 + elif "Yes" in self.RTS_CTS: + CTS_to_self = 2 + + # g24 QoS Hdr + + if "Yes" in self.QoS: + QoS_1 = 1 + elif "No" in self.QoS: + QoS_1 = 0 + if (QoS_1 == 1) or (IP_Packets_MSDU > 1): + QoS_Hdr = 2 + + else: + QoS_Hdr = 0 + + # g23 Encrypt Hdr + + if "None" in self.Encryption: + Encrypt_Hdr = 0 + + else: + if "WEP" in self.Encryption: + Encrypt_Hdr = 8 + + elif "TKIP" in self.Encryption: + Encrypt_Hdr = 20 + + else: + Encrypt_Hdr = 16 + + # c18 MAC MPDU Size + + IP_Packets_MSDU_loc = IP_Packets_MSDU + 1 + + if "Data" in self.Traffic_Type: + MAC_MPDU_Size = int(self.MAC_MPDU_Size_Data_Traffic) + + else: + if (IP_Packets_MSDU == 0): + MAC_MPDU_Size = (Codec_IP_Packet_Size + 28 + QoS_Hdr + Encrypt_Hdr + 8) + + else: + MAC_MPDU_Size_1 = ( + Codec_IP_Packet_Size + 28 + QoS_Hdr + Encrypt_Hdr + 8 + (IP_Packets_MSDU_loc - 1) * ( + 14 + 3)) + MAC_MPDU_Size = int(MAC_MPDU_Size_1 / (IP_Packets_MSDU_loc - 1)) + + # MSDU Size + + if IP_Packets_MSDU == 0: + MSDU_final = MAC_MPDU_Size - 28 - QoS_Hdr - Encrypt_Hdr + + + else: + MSDU_1 = (MAC_MPDU_Size - 28 - QoS_Hdr - Encrypt_Hdr - (IP_Packets_MSDU) * (14 + 3)) + MSDU_final = (int(MSDU_1 / (IP_Packets_MSDU))) + if MSDU_final < 0: + MSDU = MSDU_final - 1 + else: + MSDU = MSDU_final + + if (MSDU - 8) < 20: + IP_Packet_value = "N/A" + ip_value = 0 + else: + IP_Packet_value = MSDU - 8 + ip_value = 1 + + if ip_value == 0: + Ethernet_value = "N/A" + eth_value = 0 + else: + ip_1 = int(IP_Packet_value) + Ethernet_value = max(ip_1 + 18, 64) + eth_value = 1 + + i = 1 + while True: + if int(spatial_int) == i: + offset_str = HT_LTFs[i - 1] + break + i = i + 1 + offset = int(offset_str) + Tppdu_fixed = 36 + offset * 4 + + # Tppdu_fixed (HT Control Frames) + + offset_str_1 = HT_LTFs[0] + offset_1 = int(offset_str_1) + + Tppdu_fixed_control = 36 + offset_1 * 4 + + # c23 VHT Data Rate + # Ndbps, data bits per symbol (Data) + # offset + j = 0 + while True: + if Data_Voice_MCS_int == j: + if "20" in self.Channel_Bandwidth: + Ndbps_str = Ndbps_20MHz[j] + break + elif "40" in self.Channel_Bandwidth: + Ndbps_str = Ndbps_40MHz[j] + break + elif "80" in self.Channel_Bandwidth: + Ndbps_str = Ndbps_80MHz[j] + break + j = j + 1 + Ndbps = int(Ndbps_str) + + Ndbps_bits_per_symbol_Data = Ndbps * spatial_int + + # c27 Tsymbol(Data), Data Symbol Period + if "400" in self.Guard_Interval_value: + Guard_Interval_1 = 1 + elif "800" in self.Guard_Interval_value: + Guard_Interval_1 = 0 + + calculation = (((Data_Voice_MCS_int > 7 and plcp == 2) or plcp == 1)) + + if (Guard_Interval_1 == 1) and calculation: + Tsymbol_Data_Symbol_Period = 3.60 + + + else: + Tsymbol_Data_Symbol_Period = 4 + + VHT_Data_Rate = Ndbps_bits_per_symbol_Data / Tsymbol_Data_Symbol_Period + + # Non-HT Reference Rate + k = 0 + while True: + if Data_Voice_MCS_int == k: + Non_HT = Non_HT_Ref[k] + break + k = k + 1 + + Non_HT_value = int(Non_HT) + + # PHY Bit Rate of Control Frames + + PHY_Bit_Rate_of_Control_Frames = max(allowed_control6, allowed_control9, + allowed_control12, allowed_control18, + allowed_control24, allowed_control36, + allowed_control48, allowed_control54, 6) + + # c27 Ndbps, data bits per symbol (Control) + + if PHY_Bit_Rate_of_Control_Frames < 7: + if PHY_Bit_Rate_of_Control_Frames < 3: + PHY = PHY_Bit_Rate_of_Control_Frames + else: + if PHY_Bit_Rate_of_Control_Frames == 3: + PHY = 5.5 + else: + if PHY_Bit_Rate_of_Control_Frames == 4: + PHY = 11 + else: + if PHY_Bit_Rate_of_Control_Frames == 5: + PHY = 6 + else: + if PHY_Bit_Rate_of_Control_Frames == 6: + PHY = 9 + else: + if PHY_Bit_Rate_of_Control_Frames == 8: + PHY = 18 + else: + if PHY_Bit_Rate_of_Control_Frames == 9: + PHY = 24 + else: + if PHY_Bit_Rate_of_Control_Frames == 10: + PHY = 36 + else: + if PHY_Bit_Rate_of_Control_Frames == 11: + PHY = 48 + else: + PHY = 54 + Ndbps_bits_per_symbol_Control = PHY * 4 + + # Nbits, Bits per MAC PPDU + # A-MPDU Pad + + if ((MAC_Frames_per_A_MPDU == 0)): + MPDU_Pad = int(0) + + else: + x = int((MAC_MPDU_Size % 4)) + y = int((4 - x)) + MPDU_Pad = int((y % 4)) + + MAC_Frames_per_A_MPDU_loc = MAC_Frames_per_A_MPDU + 1 + if (MAC_Frames_per_A_MPDU == 0): + Nbits_Bits_per_MAC_PPDU = MAC_MPDU_Size * 8 + + else: + Nbits_Bits_per_MAC_PPDU = ((MAC_MPDU_Size + 4) * (MAC_Frames_per_A_MPDU_loc - 1) + MPDU_Pad * ( + MAC_Frames_per_A_MPDU_loc - 2)) * 8 + + # Nes, Number of BCC encoders + + l = 0 + while True: + if Data_Voice_MCS_int == l: + if "20" in self.Channel_Bandwidth: + Nes = 0 * 10 + Data_Voice_MCS_int + elif "40" in self.Channel_Bandwidth: + Nes = 1 * 10 + Data_Voice_MCS_int + elif "80" in self.Channel_Bandwidth: + Nes = 2 * 10 + Data_Voice_MCS_int + + if spatial_int == 1: + Nes_str = Nes1[Nes] + break + elif spatial_int == 2: + Nes_str = Nes2[Nes] + break + elif spatial_int == 3: + Nes_str = Nes3[Nes] + break + elif spatial_int == 4: + Nes_str = Nes4[Nes] + break + l = l + 1 + + Nes_int = int(Nes_str) + + # Ttxframe (DATA) + + Ttxframe = Tppdu_fixed + int((16 + 6 * Nes_int + Nbits_Bits_per_MAC_PPDU + Ndbps_bits_per_symbol_Data - 1) + / Ndbps_bits_per_symbol_Data) * Tsymbol_Data_Symbol_Period + + Ttxframe_Ack = int( + (22 + 14 * 8 + PHY_Bit_Rate_of_Control_Frames * 4 - 1) / (PHY_Bit_Rate_of_Control_Frames * 4)) * 4 + 20 + + # c34 Ttxframe (Compressed BlockAck) + + Ttxframe_Compressed_BlockAck = int((22 + 32 * 8 + PHY_Bit_Rate_of_Control_Frames * 4 - 1) + / (PHY_Bit_Rate_of_Control_Frames * 4)) * 4 + 20 + + # c35 RTS/CTS Handshake Overhead + + if RTS_CTS_Handshake == 2: + if "20" in self.Channel_Bandwidth: + RTS_CTS_Handshake_Overhead = 2 * 20 + 4 * int( + (22 + (20 + 14) * 8 + 24 * 4 - 1) / (24 * 4)) + 2 * SIFS + else: + RTS_CTS_Handshake_Overhead = 2 * 20 + int( + (22 + (20 + 14) * 8 + 24 - 1) / 24) * Tsymbol_control_Symbol_Period + 2 * SIFS + else: + RTS_CTS_Handshake_Overhead = 0 + + # c36 CTS-to-self Handshake Overhead + + if RTS_CTS_Handshake == 2: + CTS_to_self_Handshake_Overhead = 0 + else: + if CTS_to_self == 2: + if "20" in self.Channel_Bandwidth: + CTS_to_self_Handshake_Overhead = 20 + 4 * int((22 + 14 * 8 + 24 * 4 - 1) / (24 * 4)) + SIFS + else: + CTS_to_self_Handshake_Overhead = 20 + int( + (22 + 14 * 8 + 24 - 1) / 24) * Tsymbol_control_Symbol_Period + SIFS + else: + CTS_to_self_Handshake_Overhead = 0 + + # c37 Ack Response Overhead + # g20 Use BlockAck + + if (MAC_Frames_per_A_MPDU == 0): + Use_BlockAck = False + else: + Use_BlockAck = True + + if Use_BlockAck: + Ack_Response_Overhead = 0 + else: + Ack_Response_Overhead = SIFS + Ttxframe_Ack + + # c38BlockAck Response Overhead + + if Use_BlockAck: + BlockAck_Response_Overhead = SIFS + Ttxframe_Compressed_BlockAck + else: + BlockAck_Response_Overhead = 0 + + # c42 MAC PPDU Interval + + MAC_PPDU_Interval_1 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 1) + self.Client_1_new = format(MAC_PPDU_Interval_1, '.2f') + MAC_PPDU_Interval_2 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 2) + Client_2_new = format(MAC_PPDU_Interval_2, '.2f') + + MAC_PPDU_Interval_3 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 5) + Client_3_new = format(MAC_PPDU_Interval_3, '.2f') + + MAC_PPDU_Interval_4 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 10) + Client_4_new = format(MAC_PPDU_Interval_4, '.2f') + MAC_PPDU_Interval_5 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 20) + Client_5_new = format(MAC_PPDU_Interval_5, '.2f') + MAC_PPDU_Interval_6 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 50) + Client_6_new = format(MAC_PPDU_Interval_6, '.2f') + MAC_PPDU_Interval_7 = RTS_CTS_Handshake_Overhead + CTS_to_self_Handshake_Overhead + Ttxframe + Ack_Response_Overhead + BlockAck_Response_Overhead + DIFS + ( + MeanBackoff / 100) + Client_7_new = format(MAC_PPDU_Interval_7, '.3f') + + # Max PPDU Rate + + Max_PPDU_Rate_1 = 1000000 / MAC_PPDU_Interval_1 + self.Client_8_new = format(Max_PPDU_Rate_1, '.2f') + Max_PPDU_Rate_2 = 1000000 / MAC_PPDU_Interval_2 + Client_9_new = format(Max_PPDU_Rate_2, '.2f') + Max_PPDU_Rate_3 = 1000000 / MAC_PPDU_Interval_3 + Client_10_new = format(Max_PPDU_Rate_3, '.2f') + Max_PPDU_Rate_4 = 1000000 / MAC_PPDU_Interval_4 + Client_11_new = format(Max_PPDU_Rate_4, '.2f') + Max_PPDU_Rate_5 = 1000000 / MAC_PPDU_Interval_5 + Client_12_new = format(Max_PPDU_Rate_5, '.2f') + Max_PPDU_Rate_6 = 1000000 / MAC_PPDU_Interval_6 + Client_13_new = format(Max_PPDU_Rate_6, '.2f') + Max_PPDU_Rate_7 = 1000000 / MAC_PPDU_Interval_7 + Client_14_new = format(Max_PPDU_Rate_7, '.2f') + + # c44 Max_MAC_MPDU_Rate_1 + + if (MAC_Frames_per_A_MPDU > 0): + Max_MAC_MPDU_Rate_1 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_1 + Max_MAC_MPDU_Rate_2 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_2 + Max_MAC_MPDU_Rate_3 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_3 + Max_MAC_MPDU_Rate_4 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_4 + Max_MAC_MPDU_Rate_5 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_5 + Max_MAC_MPDU_Rate_6 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_6 + Max_MAC_MPDU_Rate_7 = (MAC_Frames_per_A_MPDU) * Max_PPDU_Rate_7 + else: + Max_MAC_MPDU_Rate_1 = Max_PPDU_Rate_1 + Max_MAC_MPDU_Rate_2 = Max_PPDU_Rate_2 + Max_MAC_MPDU_Rate_3 = Max_PPDU_Rate_3 + Max_MAC_MPDU_Rate_4 = Max_PPDU_Rate_4 + Max_MAC_MPDU_Rate_5 = Max_PPDU_Rate_5 + Max_MAC_MPDU_Rate_6 = Max_PPDU_Rate_6 + Max_MAC_MPDU_Rate_7 = Max_PPDU_Rate_7 + + self.Client_15_new = round(Max_MAC_MPDU_Rate_1) + Client_16_new = round(Max_MAC_MPDU_Rate_2) + Client_17_new = round(Max_MAC_MPDU_Rate_3) + Client_18_new = round(Max_MAC_MPDU_Rate_4) + Client_19_new = round(Max_MAC_MPDU_Rate_5) + Client_20_new = round(Max_MAC_MPDU_Rate_6) + Client_21_new = round(Max_MAC_MPDU_Rate_7) + + # Max MAC MSDU Rate + + if (IP_Packets_MSDU > 0): + Max_MAC_MSDU_Rate_1 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_1 + Max_MAC_MSDU_Rate_2 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_2 + Max_MAC_MSDU_Rate_3 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_3 + Max_MAC_MSDU_Rate_4 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_4 + Max_MAC_MSDU_Rate_5 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_5 + Max_MAC_MSDU_Rate_6 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_6 + Max_MAC_MSDU_Rate_7 = (IP_Packets_MSDU) * Max_MAC_MPDU_Rate_7 + + else: + Max_MAC_MSDU_Rate_1 = Max_MAC_MPDU_Rate_1 + Max_MAC_MSDU_Rate_2 = Max_MAC_MPDU_Rate_2 + Max_MAC_MSDU_Rate_3 = Max_MAC_MPDU_Rate_3 + Max_MAC_MSDU_Rate_4 = Max_MAC_MPDU_Rate_4 + Max_MAC_MSDU_Rate_5 = Max_MAC_MPDU_Rate_5 + Max_MAC_MSDU_Rate_6 = Max_MAC_MPDU_Rate_6 + Max_MAC_MSDU_Rate_7 = Max_MAC_MPDU_Rate_7 + + self.Client_22_new = round(Max_MAC_MSDU_Rate_1) + Client_23_new = round(Max_MAC_MSDU_Rate_2) + Client_24_new = round(Max_MAC_MSDU_Rate_3) + Client_25_new = round(Max_MAC_MSDU_Rate_4) + Client_26_new = round(Max_MAC_MSDU_Rate_5) + Client_27_new = round(Max_MAC_MSDU_Rate_6) + Client_28_new = round(Max_MAC_MSDU_Rate_7) + + # Max. 802.11 MAC Frame Data Rate + + Max_802_11_MAC_Frame_Data_Rate_1 = Max_MAC_MPDU_Rate_1 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_2 = Max_MAC_MPDU_Rate_2 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_3 = Max_MAC_MPDU_Rate_3 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_4 = Max_MAC_MPDU_Rate_4 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_5 = Max_MAC_MPDU_Rate_5 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_6 = Max_MAC_MPDU_Rate_6 * MAC_MPDU_Size * 8 / 1000000 + Max_802_11_MAC_Frame_Data_Rate_7 = Max_MAC_MPDU_Rate_7 * MAC_MPDU_Size * 8 / 1000000 + + self.Client_29_new = format(Max_802_11_MAC_Frame_Data_Rate_1, '.3f') + Client_30_new = format(Max_802_11_MAC_Frame_Data_Rate_2, '.3f') + Client_31_new = format(Max_802_11_MAC_Frame_Data_Rate_3, '.3f') + Client_32_new = format(Max_802_11_MAC_Frame_Data_Rate_4, '.3f') + Client_33_new = format(Max_802_11_MAC_Frame_Data_Rate_5, '.3f') + Client_34_new = format(Max_802_11_MAC_Frame_Data_Rate_6, '.3f') + Client_35_new = format(Max_802_11_MAC_Frame_Data_Rate_7, '.3f') + + # Max. 802.11 MAC Payload Goodput + + Max_802_11_MAC_Payload_Goodput_1 = MSDU * 8 * Max_MAC_MSDU_Rate_1 / 1000000 + Max_802_11_MAC_Payload_Goodput_2 = MSDU * 8 * Max_MAC_MSDU_Rate_2 / 1000000 + Max_802_11_MAC_Payload_Goodput_3 = MSDU * 8 * Max_MAC_MSDU_Rate_3 / 1000000 + Max_802_11_MAC_Payload_Goodput_4 = MSDU * 8 * Max_MAC_MSDU_Rate_4 / 1000000 + Max_802_11_MAC_Payload_Goodput_5 = MSDU * 8 * Max_MAC_MSDU_Rate_5 / 1000000 + Max_802_11_MAC_Payload_Goodput_6 = MSDU * 8 * Max_MAC_MSDU_Rate_6 / 1000000 + Max_802_11_MAC_Payload_Goodput_7 = MSDU * 8 * Max_MAC_MSDU_Rate_7 / 1000000 + + self.Client_36_new = format(Max_802_11_MAC_Payload_Goodput_1, '.3f') + Client_37_new = format(Max_802_11_MAC_Payload_Goodput_2, '.3f') + Client_38_new = format(Max_802_11_MAC_Payload_Goodput_3, '.3f') + Client_39_new = format(Max_802_11_MAC_Payload_Goodput_4, '.3f') + Client_40_new = format(Max_802_11_MAC_Payload_Goodput_5, '.3f') + Client_41_new = format(Max_802_11_MAC_Payload_Goodput_6, '.3f') + Client_42_new = format(Max_802_11_MAC_Payload_Goodput_7, '.3f') + + # MAC Goodput Per 802.11 Client + + MAC_Goodput_Per_802_11_Client_1 = Max_802_11_MAC_Payload_Goodput_1 / 1 + MAC_Goodput_Per_802_11_Client_2 = Max_802_11_MAC_Payload_Goodput_2 / 2 + MAC_Goodput_Per_802_11_Client_3 = Max_802_11_MAC_Payload_Goodput_3 / 5 + MAC_Goodput_Per_802_11_Client_4 = Max_802_11_MAC_Payload_Goodput_4 / 10 + MAC_Goodput_Per_802_11_Client_5 = Max_802_11_MAC_Payload_Goodput_5 / 20 + MAC_Goodput_Per_802_11_Client_6 = Max_802_11_MAC_Payload_Goodput_6 / 50 + MAC_Goodput_Per_802_11_Client_7 = Max_802_11_MAC_Payload_Goodput_7 / 100 + + self.Client_43_new = format(MAC_Goodput_Per_802_11_Client_1, '.3f') + Client_44_new = format(MAC_Goodput_Per_802_11_Client_2, '.3f') + Client_45_new = format(MAC_Goodput_Per_802_11_Client_3, '.3f') + Client_46_new = format(MAC_Goodput_Per_802_11_Client_4, '.3f') + Client_47_new = format(MAC_Goodput_Per_802_11_Client_5, '.3f') + Client_48_new = format(MAC_Goodput_Per_802_11_Client_6, '.3f') + Client_49_new = format(MAC_Goodput_Per_802_11_Client_7, '.3f') + + # Offered Load (802.3 Side) + + if eth_value != 0: + + Offered_Load_8023_Side_1 = Max_MAC_MSDU_Rate_1 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_2 = Max_MAC_MSDU_Rate_2 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_3 = Max_MAC_MSDU_Rate_3 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_4 = Max_MAC_MSDU_Rate_4 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_5 = Max_MAC_MSDU_Rate_5 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_6 = Max_MAC_MSDU_Rate_6 * Ethernet_value * 8 / 1000000 + Offered_Load_8023_Side_7 = Max_MAC_MSDU_Rate_7 * Ethernet_value * 8 / 1000000 + self.Client_50_new = format(Offered_Load_8023_Side_1, '.3f') + Client_51_new = format(Offered_Load_8023_Side_2, '.3f') + Client_52_new = format(Offered_Load_8023_Side_3, '.3f') + Client_53_new = format(Offered_Load_8023_Side_4, '.3f') + Client_54_new = format(Offered_Load_8023_Side_5, '.3f') + Client_55_new = format(Offered_Load_8023_Side_6, '.3f') + Client_56_new = format(Offered_Load_8023_Side_7, '.3f') + + else: + self.Client_50_new = "N/A" + Client_51_new = "N/A" + Client_52_new = "N/A" + Client_53_new = "N/A" + Client_54_new = "N/A" + Client_55_new = "N/A" + Client_56_new = "N/A" + + IP_Packet_value_str = str(IP_Packet_value) + if "N/A" not in IP_Packet_value_str: + IP_Goodput_802_11_8023_1 = Max_MAC_MSDU_Rate_1 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_2 = Max_MAC_MSDU_Rate_2 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_3 = Max_MAC_MSDU_Rate_3 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_4 = Max_MAC_MSDU_Rate_4 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_5 = Max_MAC_MSDU_Rate_5 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_6 = Max_MAC_MSDU_Rate_6 * ip_1 * 8 / 1000000 + IP_Goodput_802_11_8023_7 = Max_MAC_MSDU_Rate_7 * ip_1 * 8 / 1000000 + self.Client_57_new = format(IP_Goodput_802_11_8023_1, '.3f') + Client_58_new = format(IP_Goodput_802_11_8023_2, '.3f') + Client_59_new = format(IP_Goodput_802_11_8023_3, '.3f') + Client_60_new = format(IP_Goodput_802_11_8023_4, '.3f') + Client_61_new = format(IP_Goodput_802_11_8023_5, '.3f') + Client_62_new = format(IP_Goodput_802_11_8023_6, '.3f') + Client_63_new = format(IP_Goodput_802_11_8023_7, '.3f') + + else: + self.Client_57_new = "N/A" + Client_58_new = "N/A" + Client_59_new = "N/A" + Client_60_new = "N/A" + Client_61_new = "N/A" + Client_62_new = "N/A" + Client_63_new = "N/A" + + # Theoretical Voice Call Capacity + + if "Data" in self.Traffic_Type: + self.Maximum_Theoretical_R_value = "N/A" + self.Estimated_MOS_Score = "N/A" + else: + + self.Maximum_Theoretical_R_value = 85.9 + if self.Maximum_Theoretical_R_value < 0: + self.Estimated_MOS_Score = 1 + else: + if self.Maximum_Theoretical_R_value > 100: + self.Estimated_MOS_Score = 4.5 + else: + Estimated_MOS_Score_1 = (1 + 0.035 * self.Maximum_Theoretical_R_value + self.Maximum_Theoretical_R_value * ( + self.Maximum_Theoretical_R_value - 60) * (100 - self.Maximum_Theoretical_R_value) * 7 * 0.000001) + self.Estimated_MOS_Score = format(Estimated_MOS_Score_1, '.2f') + + # Voice_Call_Range + + try: + Voice_Call_Range = round(Max_PPDU_Rate_1 / Codec_Frame_Rate) + + # c55 Maximum Bidirectional Voice Calls + + if Voice_Call_Range <= 1: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_1 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 2: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_2 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 5: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_3 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 10: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_4 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 20: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_5 / Codec_Frame_Rate + + else: + if Voice_Call_Range <= 50: + Maximum_Bidirectional = Max_MAC_MSDU_Rate_6 / Codec_Frame_Rate + + else: + + Maximum_Bidirectional = Max_MAC_MSDU_Rate_7 / Codec_Frame_Rate + except ZeroDivisionError: + pass + + if "Data" in self.Traffic_Type: + self.Maximum_Bidirectional_Voice_Calls = "N/A" + else: + self.Maximum_Bidirectional_Voice_Calls = round(Maximum_Bidirectional, 2) + + + def get_result(self): + + print("\n" + "******************Station : 11ac Calculator*****************************" + "\n") + print("Theoretical Maximum Offered Load" + "\n") + print("1 Client:") + All_theoretical_output = {'MAC PPDU Interval(usec)': self.Client_1_new, 'Max PPDU Rate(fps)': self.Client_8_new, + 'Max MAC MPDU Rate': self.Client_15_new, + 'Max MAC MSDU Rate': self.Client_22_new, + 'Max. 802.11 MAC Frame Data Rate(Mb/s)': self.Client_29_new, + 'Max. 802.11 MAC Payload Goodput(Mb/s)': self.Client_36_new, + 'MAC Goodput Per 802.11 Client(Mb/s)': self.Client_43_new, + 'Offered Load (802.3 Side)(Mb/s)': self.Client_50_new, + 'IP Goodput (802.11 -> 802.3)(Mb/s)': self.Client_57_new} + print(json.dumps(All_theoretical_output, indent=4)) + + print("\n" + "Theroretical Voice Call Capacity" + "\n") + + All_theoretical_voice = {'Maximum Theoretical R-value': self.Maximum_Theoretical_R_value, + 'Estimated MOS Score': self.Estimated_MOS_Score, + 'Maximum Bidirectional Voice Calls(calls)': self.Maximum_Bidirectional_Voice_Calls} + print(json.dumps(All_theoretical_voice, indent=4)) \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-json/ws-sta-monitor.py b/lanforge/lanforge-scripts/py-json/ws-sta-monitor.py new file mode 100755 index 000000000..c957be299 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/ws-sta-monitor.py @@ -0,0 +1,361 @@ +#!/usr/bin/python3 +""" +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# - +# Example of how to filter messages from the :8081 websocket - +# - +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +You will need websocket-client: +apt install python3-websocket +""" +import sys +import os +import importlib +import argparse +import json +import logging +import traceback +from time import sleep +import websocket +import re +try: + import thread +except ImportError: + import _thread as thread + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit() + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") + + +cre={ + "phy": re.compile(r'^(1\.\d+):\s+(\S+)\s+\(phy', re.I), + "ifname": re.compile(r'(1\.\d+):\s+IFNAME=(\S+)\s+', re.I), + "port": re.compile(r'Port (\S+)', re.I), + "connected": re.compile(r'.*?CTRL-EVENT-CONNECTED - Connection to ([a-f0-9:]+) complete', re.I), + "associated": re.compile(r'^.*?Associated with ([a-f0-9:]+)$', re.I), + "auth": re.compile(r'.*: auth ([a-f0-9:]+) -> ([a-f0-9:]+) status: 0: Successful', re.I), + "authenticated": re.compile(r'.*?Authenticated with ([a-f0-9:]+)', re.I), + "associating": re.compile(r'.*?Trying to associate with ([a-f0-9:]+)', re.I), + "authenticating": re.compile(r'.*?[>]SME: Trying to authenticate with ([a-f0-9:]+)', re.I), +} + +ignore=[ + ": scan finished", + ": scan started", + ": scan aborted: ", + "CTRL-EVENT-SCAN-STARTED", + "SCAN-STARTED", + "SSID-TEMP-DISABLED", + "CTRL-EVENT-DISCONNECTED", + "CTRL-EVENT-REGDOM-CHANGE", + "CTRL-EVENT-SUBNET-STATUS-UPDATE", + "Reject scan trigger since one is already pending", + "Failed to initiate AP scan", + "new station", + "del station", + "ping", + ": Key negotiation completed with ", + "deleted-alert", + ": deauth ", + ": disconnected ", + "regulatory domain change", +] + +rebank = { + "ifname" : re.compile("IFNAME=(\S+)") +} +websock = None +host = "localhost" +base_url = None +port = 8081 + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def usage(): + print("""Example: __file__ --host 192.168.1.101 --port 8081\n""") + + +def main(): + global websock + # global host + # global base_url + # resource_id = 1 # typically you're using resource 1 in stand alone realm + + parser = argparse.ArgumentParser(description="test creating a station") + parser.add_argument("-m", "--host", type=str, help="websocket host to connect to") + parser.add_argument("-p", "--port", type=str, help="websoket port") + + host = "unset" + base_url = "unset" + try: + args = parser.parse_args() + if (args.host is None): + host = "localhost" + elif (type(args) is tuple) or (type(args) is list): + host = args.host[0] + else: + host = args.host + + base_url = "ws://%s:%s" % (host, port) + + except Exception as e: + print("Exception: "+e) + logging.exception(e) + usage() + exit(2) + + # open websocket + # print("Main: base_url: %s, host:%s, port:%s" % (base_url, host, port)) + websock = start_websocket(base_url, websock) + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +def sock_filter(wsock, text): + global ignore + global interesting + global rebank + global cre + debug = 0 + station_name = None + resource = None + + for test in ignore: + if (test in text): + if (debug): + print (" ignoring ",text) + return; + + try: + message = json.loads(text) + except Exception as ex: + print ("Json Exception: ", repr(ex)) + traceback.print_exc() + + try: + # big generic filter for wifi-message or details keys + try: + if ("details" in message.keys()): + for test in ignore: + if (test in message["details"]): + return; + except KeyError: + print("Message lacks key 'details'") + + try: + if ("wifi-event" in message.keys()): + for test in ignore: + # print (" is ",test, " in ", message["wifi-event"]) + if (test in message["wifi-event"]): + return; + except KeyError: + print("Message lacks key 'wifi-event'") + + if (("time" in message.keys()) and ("timestamp" in message.keys())): + return + + if ("name" in message.keys()): + station_name = message["name"] + if ("resource" in message.keys()): + resource = "1.", message["resource"] + + if ("event_type" in message.keys()): + match_result = cre["port"].match(message["details"]) + if (match_result is not None): + station_name = match_result.group(1) + + if (message["is_alert"]): + print("alert: ", message["details"]) + # LFUtils.debug_printer.pprint(message) + return + else: + # LFUtils.debug_printer.pprint(message) + if (" IP change from " in message["details"]): + if (" to 0.0.0.0" in messsage["details"]): + print("e: %s.%s lost IP address", [resource, station_name]) + else: + print("e: %s.%s gained IP address", [resource, station_name]) + if ("Link DOWN" in message["details"]): + return # duplicates alert + + print("event: ", message["details"]) + return + + if ("wifi-event" in message.keys()): + if ("CTRL-EVENT-CONNECTED" in message["wifi-event"]): + # redunant + return + if (("CTRL-EVENT-CONNECTED - Connection to " in message["wifi-event"]) and ( + " complete" in message["wifi-event"])): + return; + if ((": assoc " in message["wifi-event"]) and ("status: 0: Successful" in message["wifi-event"])): + return + if ((station_name is None) or (resource is None)): + try: + match_result = cre["phy"].match(message["wifi-event"]) + if (match_result is not None): + # LFUtils.debug_printer.pprint(match_result) + # LFUtils.debug_printer.pprint(match_result.groups()) + resource = match_result.group(1) + station_name = match_result.group(2) + else: + match_result = cre["ifname"].match(message["wifi-event"]) + # LFUtils.debug_printer.pprint(match_result) + # LFUtils.debug_printer.pprint(match_result.groups()) + if (match_result is not None): + resource = match_result.group(1) + station_name = match_result.group(2) + else: + print("Is there some other combination??? :", message["wifi-event"]) + station_name = 'no-sta' + resource_name = 'no-resource' + print("bleh!") + except Exception as ex2: + print("No regex match:") + print(repr(ex2)) + traceback.print_exc() + sleep(1) + + # print ("Determined station name: as %s.%s"%(resource, station_name)) + if ((": auth ") and ("status: 0: Successful" in message["wifi-event"])): + match_result = cre["auth"].match(message["wifi-event"]) + if (match_result and match_result.groups()): + bssid = match_result.group(1) + print("station %s.%s auth with %s" % (resource, station_name, bssid)) + return + else: + print("station %s.%s auth with ??" % (resource, station_name)) + LFUtils.debug_printer.pprint(match_result) + + if ("Associated with " in message["wifi-event"]): + match_result = cre["associated"].match(message["wifi-event"]) + if (match_result and match_result.groups()): + bssid = match_result.group(1) + print("station %s.%s assocated with %s" % (resource, station_name, bssid)) + return + else: + print("station %s.%s assocated with ??" % (resource, station_name)) + LFUtils.debug_printer.pprint(match_result) + + if (" - Connection to " in message["wifi-event"]): + match_result = cre["connected"].match(message["wifi-event"]) + if (match_result and match_result.groups()): + bssid = match_result.group(1) + print("station %s.%s connected to %s" % (resource, station_name, bssid)) + return + else: + print("station %s.%s connected to ??" % (resource, station_name)) + LFUtils.debug_printer.pprint(match_result) + + if ("disconnected" in message["wifi-event"]): + print("Station %s.%s down" % (resource, station_name)) + return + + if ("Trying to associate with " in message["wifi-event"]): + match_result = cre["associating"].match(message["wifi-event"]) + + if (match_result and match_result.groups()): + bssid = match_result.group(1) + print("station %s.%s associating with %s" % (resource, station_name, bssid)) + return + else: + print("station %s.%s associating with ??" % (resource, station_name)) + LFUtils.debug_printer.pprint(match_result) + + if ("Trying to authenticate" in message["wifi-event"]): + match_result = cre["authenticating"].match(message["wifi-event"]) + + if (match_result and match_result.groups()): + bssid = match_result.group(1) + print("station %s.%s authenticating with %s" % (resource, station_name, bssid)) + return + else: + print("station %s.%s authenticating with ??" % (resource, station_name)) + LFUtils.debug_printer.pprint(match_result) + + if ("Authenticated" in message["wifi-event"]): + match_result = cre["authenticed"].match(message["wifi-event"]) + LFUtils.debug_printer.pprint(match_result) + if (match_result and match_result.groups()): + bssid = match_result.group(1) + print("station %s.%s authenticated with %s" % (resource, station_name, bssid)) + else: + print("station %s.%s authenticated with ??" % (resource, station_name)) + + print("w: ", message["wifi-event"]) + else: + print("\nUnhandled: ") + LFUtils.debug_printer.pprint(message) + + except KeyError as kerr: + print("# ----- Bad Key: ----- ----- ----- ----- ----- ----- ----- ----- ----- -----") + print("input: ", text) + print(repr(kerr)) + traceback.print_exc() + print("# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----") + sleep(1) + return + except json.JSONDecodeError as derr: + print("# ----- Decode err: ----- ----- ----- ----- ----- ----- ----- ----- ----- -----") + print("input: ", text) + print(repr(derr)) + traceback.print_exc() + print("# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----") + sleep(1) + return + except Exception as ex: + print("# ----- Exception: ----- ----- ----- ----- ----- ----- ----- ----- ----- -----") + print(repr(ex)) + print("input: ", text) + LFUtils.debug_printer.pprint(message) + traceback.print_exc() + print("# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----") + sleep(1) + return + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +def m_error(wsock, err): + print("# ----- Error: ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----\n") + LFUtils.debug_printer.pprint(err) + print("# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----\n") + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +def m_open(wsock): + def run(*args): + sleep(0.1) + # ping = json.loads(); + wsock.send('{"text":"ping"}') + + thread.start_new_thread(run, ()) + print("Connected...") + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +def m_close(wsock): + LFUtils.debug_printer.pprint(wsock) + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +def start_websocket(uri, websock): + websock = websocket.WebSocketApp(uri, + on_message=sock_filter, + on_error=m_error, + on_close=m_close) + websock.on_open = m_open + websock.run_forever() + return websock + + +# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +if __name__ == '__main__': + main() + + +#### +#### +#### diff --git a/lanforge/lanforge-scripts/py-json/ws_generic_monitor.py b/lanforge/lanforge-scripts/py-json/ws_generic_monitor.py new file mode 100644 index 000000000..174e94039 --- /dev/null +++ b/lanforge/lanforge-scripts/py-json/ws_generic_monitor.py @@ -0,0 +1,25 @@ +""" +pip install websocket_client + +https://pypi.org/project/websocket_client/ + +WS_Listener has three arguments in general : lfclient_host, _scriptname, _callback +1. Enter the LF Client Host address on which you want to monitor events (by Default is localhost) +2. Enter the _scriptname that should be present in the event triggered by your script, + refer add_event() in lfcli_base.add_event() + _scriptname can be any string that you want to monitor in your websocket message +3. Enter the Callback function that you wanna see your messages in everytime your event will trigger up. + refer py-scripts/ws_generic_monitor_test.py to see an example + +""" + +class WS_Listener(): + def __init__(self, lfclient_host="localhost", _scriptname=None, _callback=None): + import websocket + self.scriptname = _scriptname + websocket.enableTrace(True) + self.ws = websocket.WebSocketApp("ws://"+lfclient_host+":8081", on_message=_callback) + self.ws.run_forever() + + + diff --git a/lanforge/lanforge-scripts/py-scripts/.gitignore b/lanforge/lanforge-scripts/py-scripts/.gitignore new file mode 100644 index 000000000..74bc44063 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/.gitignore @@ -0,0 +1,2 @@ +regression_test.txt +regression_test.rc diff --git a/lanforge/lanforge-scripts/py-scripts/CT_US_001.bash b/lanforge/lanforge-scripts/py-scripts/CT_US_001.bash new file mode 100755 index 000000000..dd3c6eb7d --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/CT_US_001.bash @@ -0,0 +1,74 @@ +#!/bin/bash + +# This bash script creates/updates a DUT, creates/updates a chamberview scenario, +# loads and builds that scenario, runs wifi capacity test, and saves the kpi.csv info +# into influxdb. As final step, it builds a grafana dashboard for the KPI information. + +set -x + +# Define some common variables. This will need to be changed to match your own testbed. +# MGR is LANforge GUI machine +MGR=192.168.100.116 +#MGR=localhost + +# Candela internal influx +INFLUXTOKEN=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== +INFLUX_HOST=192.168.100.201 +INFLUX_BUCKET=lanforge_qa_testing +INFLUX_ORG=Candela + +GRAFANATOKEN=eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ== +GRAFANA_HOST=192.168.100.201 +GROUP_FILE=/tmp/lf_cv_rpt_filelocation.txt +TESTBED=CT_US-001 +DUT=ASUSRT-AX88U +UPSTREAM=eth2 +#LF_WAN_PORT=eth3 +MGR_PORT=8080 + +if [ -f $HOME/influx_vars.sh ] +then + # Put private keys and other variable overrides in here. + . $HOME/influx_vars.sh +fi + + +# Create/update new DUT. +#Replace my arguments with your setup. Separate your ssid arguments with spaces and ensure the names are lowercase +echo "Make new DUT" +./create_chamberview_dut.py --lfmgr ${MGR} --port ${MGR_PORT} --dut_name ${DUT} \ + --ssid "ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=3c:7c:3f:55:4d:64" \ + --ssid "ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=3c:7c:3f:55:4d:64" \ + --sw_version "asus_version" --hw_version asus11ax --serial_num 0001 --model_num 88R + +# Create/update chamber view scenario and apply and build it. +# Easiest way to get these lines is to build it in the GUI and then +# copy/tweak what it shows in the 'Text Output' tab after saving and re-opening +# the scenario. +echo "Build Chamber View Scenario" +#change the lfmgr to your system, set the radio to a working radio on your LANforge system, same with the ethernet port. + +./create_chamberview.py --lfmgr ${MGR} --port ${MGR_PORT} --delete_scenario \ + --create_scenario ucentral-scenario \ + --raw_line "profile_link 1.1 STA-AC 1 'DUT: $DUT Radio-1' NA wiphy1,AUTO -1 NA" \ + --raw_line "profile_link 1.1 STA-AC 1 'DUT: $DUT Radio-1' NA wiphy3,AUTO -1 NA" \ + --raw_line "profile_link 1.1 upstream-dhcp 1 NA NA $UPSTREAM,AUTO -1 NA" \ + +# Run capacity test on the stations created by the chamber view scenario. +# Submit the KPI data into the influxdb. +#config_name doesn't matter, change the influx_host to your LANforge device, +# NOTE: My influx token is unlucky and starts with a '-', but using the syntax below +# with '=' right after the argument keyword works as hoped. +echo "run wifi capacity test" +./lf_wifi_capacity_test.py --config_name Custom --pull_report --influx_host ${INFLUX_HOST} \ + --influx_port 8086 --influx_org ${INFLUX_ORG} --influx_token=${INFLUXTOKEN} --influx_bucket ${INFLUX_BUCKET} --mgr ${MGR} \ + --port ${MGR_PORT} \ + --instance_name testing --upstream 1.1.$UPSTREAM --test_rig ${TESTBED} --graph_groups ${GROUP_FILE} \ + --batch_size "100" --protocol "TCP-IPv4" --duration 20000 --pull_report + +# Build grafana dashboard and graphs view for the KPI in the capacity test. +#echo "Adding grafana dashboard" +#./grafana_profile.py --create_custom --title ${TESTBED} --influx_bucket ${INFLUX_BUCKET} --grafana_token ${GRAFANATOKEN} \ +# --grafana_host ${GRAFANA_HOST} --testbed ${TESTBED} --graph-groups ${GROUPS} --scripts Dataplane --scripts 'WiFi Capacity' + +rm ${GROUP_FILE} diff --git a/lanforge/lanforge-scripts/py-scripts/README.md b/lanforge/lanforge-scripts/py-scripts/README.md new file mode 100644 index 000000000..e405dd9c0 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/README.md @@ -0,0 +1,268 @@ +# LANForge Python Scripts +This directory contains python scripts useful for unit-tests. It uses libraries in ../py-json. Please place new tests in this directory. Unless they are libraries, please avoid adding python scripts to ../py-json. Please read https://www.candelatech.com/cookbook/cli/json-python to learn about how to use the LANforge client JSON directly. Review http://www.candelatech.com/scripting_cookbook.php to understand more about scripts in general. + +# Getting Started + +The first step is to make sure all dependencies are installed in your system by running update_deps.py in this folder. + +Please consider using the `LFCliBase` class as your script superclass. It will help you with a consistent set of JSON handling methods and pass and fail methods for recording test results. Below is a sample snippet that includes LFCliBase: + + if 'py-json' not in sys.path: + sys.path.append('../py-json') + from LANforge import LFUtils + from LANforge import lfcli_base + from LANforge.lfcli_base import LFCliBase + from LANforge.LFUtils import * + import realm + from realm import Realm + + class Eggzample(LFCliBase): + def __init__(self, lfclient_host, lfclient_port): + super().__init__(lfclient_host, lfclient_port, debug=True) + + def main(): + eggz = Eggzample("http://localhost", 8080) + frontpage_json = eggz.json_get("/") + pprint.pprint(frontpage_json) + data = { + "message": "hello world" + } + eggz.json_post("/cli-json/gossip", data, debug_=True) + + if __name__ == "__main__": + main() + +The above example will stimulate output on the LANforge client websocket `ws://localhost:8081`. You can monitor system activity over that channel. + +## Useful URIs: +* /: provides version information and a list of supported URIs +* /DUT/: Device Under Test records +* /alerts/: port or connection alerts +* /cli-form: post multi-part form data to this URI +* /cli-json: post JSON data to this URI +* /help: list of CLI commands and refence links +* /help/set_port: each CLI command has a command composer +* /cx: connections +* /endp: endpoints that make up connections +* /gui-cli: post multi-part form data for GUI automation +* /gui-json: post JSON data to this URI for GUI automation +* /port: list ports and stations, oriented by shelf, resource and name: `/port/1/1/eth0` is typically your management port +* /stations: entities that are associated to your virtual access points (vAP) +There are more URIs you can explore, these are the more useful ones. + +#### Scripts included are: + +* `cicd_TipIntegration.py`: battery of TIP tests that include upgrading DUT and executing sta_connect script + +* `cicd_testrail.py`: + * `function send_get`: Issues a GET request (read) against the API. + * `function send_post`: Issues a write against the API. + * `function __send_request`: + * `function get_project_id`: Gets the project ID using the project name + * `function get_run_id`: Gets the run ID using test name and project name + * `function update_testrail`: Update TestRail for a given run_id and case_id + +* `cicd_testrailAndInfraSetup.py`: + * class `GetBuild`: + * function `get_latest_image`: extract a tar file from the latest file name from a URL + * function run_`opensyncgw_in_docker`: + * function `run_opensyncgw_in_aws`: + * class `openwrt_linksys`: + * function `ap_upgrade`: transfers file from local host to remote host. Upgrade access point with new information (?) + * class `RunTest`: + * function `TestCase_938`: checks single client connectivity + * function `TestCase_941`: checks for multi-client connectivity + * function `TestCase_939`: checks for client count in MQTT log and runs the clients (?) + +* `run_cv_scenario.py`: + * class `RunCvScenario`: imports the LFCliBase class. + * function `get_report_file_name`: returns report name + * function `build`: loads and sends the ports available? + * function `start`: /gui_cli takes commands keyed on 'cmd' and this function create an array of commands +* `sta_connect.py`: This function creates a station, create TCP and UDP traffic, run it a short amount of time, + and verify whether traffic was sent and received. It also verifies the station connected + to the requested BSSID if bssid is specified as an argument. + The script will clean up the station and connections at the end of the test. + * class `StaConnect(LFCliBase)`: + * function `get_realm`: returns the local realm + * function `get_station_url`: + * function `get_upstream_url`: + * function `compare_vals`: compares pre-test values to post-test values + * function `remove_stations`: removes all stations + * function `num_associated`: + * function `clear_test_results`: + * function `run`: + * function `setup`: + * function `start`: + * function `stop`: + * function `finish`: + * function `cleanup`: + * function `main`: +* `sta_connect2.py`: This will create a station, create TCP and UDP traffic, run it a short amount of time, + and verify whether traffic was sent and received. It also verifies the station connected + to the requested BSSID if bssid is specified as an argument. The script will clean up the station and connections at the end of the test. + * function `get_realm`: returns local realm + * function `get_station_url`: + * function `get_upstream_url`: + * function `compare_vals`: compares pre-test values to post-test values + * function `remove_stations`: removes all ports + * function `num_associated`: + * function `clear_test_results` + * function `setup`: verifies upstream url, creates stations and turns dhcp on, creates endpoints, + UDP endpoints, + * function `start`: + * function `stop`: + * function `cleanup`: + * function `main`: + +* `sta_connect_example.py`: example of how to instantiate StaConnect and run the test + +* `sta_connect_multi_example.py`: example of how to instantiate StaConnect and run the test and create multiple OPEN stations,have +some stations using WPA2 + +* `stations_connected.py`: Contains examples of using realm to query stations and get specific information from them + +* `test_ipv4_connection.py`: This script will create a variable number of stations that will attempt to connect to a chosen SSID using a provided password and security type. + The test is considered passed if all stations are able to associate and obtain IPV4 addresses + * class `IPv4Test` + * function `build`: This function will use the given parameters (Number of stations, SSID, password, and security type) to create a series of stations. + * function `start`: This function will admin-up the stations created in the build phase. It will then check all stations periodically for association and IP addresses. + This will continue until either the specified timeout has been reached or all stations obtain an IP address. + * function `stop`: This function will admin-down all stations once one of the ending criteria is met. + * function `cleanup`: This function will clean up all stations created during the test. + * command line options : + * `--mgr`: Specifies the hostname where LANforge is running. Defaults to http://localhost + * `--mgr_port`: Specifies the port to use when connecting to LANforge. Defaults to 8080 + * `--ssid`: Specifies SSID to be used in the test + * `--password`: Specifies the password for the SSID to be used in the test + * `--security`: Specifies security type (WEP, WPA, WPA2, WPA3, Open) of SSID to be used in the test + * `--num_stations`: Specifies number of stations to create for the test + * `--radio`: Specifies the radio to be used in the test. Eg wiphy0 + * `--debug`: Turns on debug output for the test + * `--help`: Displays help output for the script + +* `test_ipv6_connection.py`: This script will create a variable number of stations that will attempt to connect to a chosen SSID using a provided password and security type. + The test is considered passed if all stations are able to associate and obtain IPV6 addresses + * class `IPv6Test` + * function `build`: This function will use the given parameters (Number of stations, SSID, password, and security type) to create a series of stations. + * function `start`: This function will admin-up the stations created in the build phase. It will then check all stations periodically for association and IP addresses. + This will continue until either the specified timeout has been reached or all stations obtain an IP address. + * function `stop`: This function will admin-down all stations once one of the ending criteria is met. + * function `cleanup`: This function will clean up all stations created during the test. + * Command line options : + * `--mgr`: Specifies the hostname where LANforge is running. Defaults to http://localhost + * `--mgr_port`: Specifies the port to use when connecting to LANforge. Defaults to 8080 + * `--ssid`: Specifies SSID to be used in the test + * `--password`: Specifies the password for the SSID to be used in the test + * `--security`: Specifies security type (WEP, WPA, WPA2, WPA3, Open) of SSID to be used in the test + * `--num_stations`: Specifies number of stations to create for the test + * `--radio`: Specifies the radio to be used in the test. Eg wiphy0 + * `--debug`: Turns on debug output for the test + * `--help`: Displays help output for the script + +* `test_l3_unicast_traffic_gen.py`: This script will create stations, create traffic between upstream port and stations, run traffic. +The traffic on the stations will be checked once per minute to verify that traffic is transmitted and received. +Test will exit on failure of not receiving traffic for one minute on any station. + * class `L3VariableTimeLongevity` + * function `build`: This function will create a group of stations and cross connects that are used in the test. + * function `start`: This function will admin-up all stations and start traffic over the cross-connects. Values in the cross-connects + will be checked every minute to verify traffic is transmitted and received. + * function `stop`: This function will stop all cross-connects from generating traffic and admin-down all stations. + * function `cleanup`: This function will cleanup all cross-connects and stations created during the test. + * Command line options: + * `-d, --test_duration`: Determines the total length of the test. Consists of number followed by letter indicating length + 10m would be 10 minutes or 3d would be 3 days. Available options for length are Day (d), Hour (h), Minute (m), or Second (s) + * `-t, --endp_type`: Specifies type of endpoint to be used in the test. Options are lf_udp, lf_udp6, lf_tcp, lf_tcp6 + * `-u, --upstream_port`: This is the upstream port to be used for traffic. An upstream port is some data source on the wired LAN or WAN beyond the AP + * `-r, --radio`: This switch will determine the radio name, number of stations, ssid, and ssid password. Security type is fixed at WPA2. + Usage of this switch could look like: `--radio wiphy1 64 candelaTech-wpa2-x2048-5-3 candelaTech-wpa2-x2048-5-3` + +* `test_ipv4_l4_urls_per_ten.py`: This script measure the number of urls per ten minutes over layer 4 traffic + * class `IPV4L4` + * function `build`: This function will create all stations and cross-connects to be used in the test + * function `start`: This function will admin-up stations and start all traffic over the cross-connects. It will then measure the amount of traffic that passed through + the cross-connects every ten minutes. These values are compared to 90% of the chosen target traffic per ten minutes. If this value is exceeded, a pass will occur, + otherwise, a fail is recorded. + * function `stop`: This function will admin-down stations and stop all traffic. + * function `cleanup`: This function will cleanup any stations or cross-connects associated with the test. + * Command line options: + * `--mgr`: Specifies the hostname where LANforge is running. Defaults to http://localhost + * `--mgr_port`: Specifies the port to use when connecting to LANforge. Defaults to 8080 + * `--ssid`: Specifies SSID to be used in the test + * `--password`: Specifies the password for the SSID to be used in the test + * `--security`: Specifies security type (WEP, WPA, WPA2, WPA3, Open) of SSID to be used in the test + * `--num_stations`: Specifies number of stations to create for the test + * `--radio`: Specifies the radio to be used in the test. Eg wiphy0 + * `--requests_per_ten`: Configures the number of request per ten minutes + * `--num_tests`: Configures the number of tests to be run. Each test runs for ten minutes + * `--url`: Specifies the upload/download, address, and destination. Example: dl http://10.40.0.1 /dev/null + * `--target_per_ten`: Rate of target urls per ten minutes. 90% of this value will be considered the threshold for a passed test. + * `--debug`: Turns on debug output for the test + * `--help`: Displays help output for the script + + +* `test_ipv4_l4_ftp_urls_per_ten.py`: This script measure the number of urls per ten minutes over layer 4 ftp traffic + * class `IPV4L4` + * function `build`: This function will create all stations and cross-connects to be used in the test + * function `start`: This function will admin-up stations and start all traffic over the cross-connects. It will then measure the amount of traffic that passed through + the cross-connects every ten minutes. These values are compared to 90% of the chosen target traffic per ten minutes. If this value is exceeded, a pass will occur, + otherwise, a fail is recorded. + * function `stop`: This function will admin-down stations and stop all traffic. + * function `cleanup`: This function will cleanup any stations or cross-connects associated with the test. + * Command line options: + * `--mgr`: Specifies the hostname where LANforge is running. Defaults to http://localhost + * `--mgr_port`: Specifies the port to use when connecting to LANforge. Defaults to 8080 + * `--ssid`: Specifies SSID to be used in the test + * `--password`: Specifies the password for the SSID to be used in the test + * `--security`: Specifies security type (WEP, WPA, WPA2, WPA3, Open) of SSID to be used in the test + * `--num_stations`: Specifies number of stations to create for the test + * `--radio`: Specifies the radio to be used in the test. Eg wiphy0 + * `--requests_per_ten`: Configures the number of request per ten minutes + * `--num_tests`: Configures the number of tests to be run. Each test runs for ten minutes + * `--url`: Specifies the upload/download, address, and destination. Example: dl http://10.40.0.1 /dev/null + * `--target_per_ten`: Rate of target urls per ten minutes. 90% of this value will be considered the threshold for a passed test. + * `--debug`: Turns on debug output for the test + * `--help`: Displays help output for the script + +* `test_generic`: + * class `GenTest`: This script will create + * function `build`: This function will create the stations and cross-connects to be used during the test. + * function `start`: This function will start traffic and measure different values dependent on the command chosen. + Commands currently available for use: lfping, generic, and speedtest. + * function `stop`: This function will admin-down stations, stop traffic on cross-connects and cleanup any stations or cross-connects associated with the test. + * function `cleanup`: This function will remove any stations and cross-connects created during the test. + * Command line options: + * `--mgr`: Specifies the hostname where LANforge is running. Defaults to http://localhost + * `--mgr_port`: Specifies the port to use when connecting to LANforge. Defaults to 8080 + * `--ssid`: Specifies SSID to be used in the test + * `--password`: Specifies the password for the SSID to be used in the test + * `--security`: Specifies security type (WEP, WPA, WPA2, WPA3, Open) of SSID to be used in the test + * `--num_stations`: Specifies number of stations to create for the test + * `--radio`: Specifies the radio to be used in the test. Eg wiphy0 + * `--upstream_port`: This is the upstream port to be used for traffic. An upstream port is some data source on the wired LAN or WAN beyond the AP + * `--type`: Specifies type of generic connection to make. (generic, lfping, iperf3-client, speedtest, iperf3-server, lf_curl) + * `--dest`: Specifies the destination for some commands to use + * `--interval`: Specifies the interval between tests in the start function + * `--test_duration`: Specifies the full duration of the test. Consists of number followed by letter indicating length + 10m would be 10 minutes or 3d would be 3 days. Available options for length are Day (d), Hour (h), Minute (m), or Second (s) + * `--debug`: Turns on debug output for the test + * `--help`: Displays help output for the script + +* `test_ipv4_variable_time.py`: + * class `IPv4VariableTime` + * function `__set_all_cx_state`: + * function `run_test`: + * function `cleanup`: + * function `run`: + +* `test_wanlink.py`: + * class `LANtoWAN` + * function `run_test`: + * function `create_wanlinks`: + * function `run`: + * function `cleanup`: + +* `vap_stations_example.py`: + * class `VapStations` + * function `run`: + * function `main`: diff --git a/lanforge/lanforge-scripts/py-scripts/__init__.py b/lanforge/lanforge-scripts/py-scripts/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/lanforge/lanforge-scripts/py-scripts/artifacts/CandelaLogo2-90dpi-200x90-trans.png b/lanforge/lanforge-scripts/py-scripts/artifacts/CandelaLogo2-90dpi-200x90-trans.png new file mode 100755 index 0000000000000000000000000000000000000000..23cca07d9c982478dc76ac5a9b382ab98fd1f476 GIT binary patch literal 9122 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

    0GJ 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ℑ{fGCDhjFHU9i!o^AFk{5tk%-3F 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 G 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

    Ut?LrG6c@*``yxv1o<`A2v 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 literal 0 HcmV?d00001 diff --git a/lanforge/lanforge-scripts/py-scripts/artifacts/candela_swirl_small-72h.png b/lanforge/lanforge-scripts/py-scripts/artifacts/candela_swirl_small-72h.png new file mode 100755 index 0000000000000000000000000000000000000000..e288f8c96f53e311e57a020aa52d7e1f053c4b00 GIT binary patch literal 12989 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 literal 0 HcmV?d00001 diff --git a/lanforge/lanforge-scripts/py-scripts/artifacts/report.css b/lanforge/lanforge-scripts/py-scripts/artifacts/report.css new file mode 100644 index 000000000..542ba4292 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/artifacts/report.css @@ -0,0 +1,376 @@ +html, body,div { + margin: 0; + padding:0; + font-size: 14px; +} +h1,h2,h3,h4 { + padding: 0em; + line-height: 1.5; + text-align: left; + color: rgb(42,91,41); +} +@font-face { + font-family: CenturyGothic; + src: url("CenturyGothic.woff"), + url("images/CenturyGothic.woff"), + url("/images/CenturyGothic.woff"), + url("http://www.candelatech.com/images/CenturyGothic.woff"); +} +body,h1,h2,h3,h4 { + font-family: CenturyGothic, "Century Gothic", Arial, Helvetica, sans-serif; +} +h1 { font-size: 30px;} +h2 { font-size: 24px;} +h3 { font-size: 18px;} +h4 { font-size: 14px;} +li,pre,tt { + text-align: left; +} +pre { + font-size: 10px; +} +table { + border-collapse: collapse; + background: #e0e0e0; +} +table, td, th { + border: 1px solid gray; + padding 4px; + background: white; +} +table.noborder, table.noborder td, table.noborder th { + border: 0 none; +} +tr { + background: white; +} +td { + background: white; + padding: 6px; +} +td.ar { + text-align: right; +} +th { + color: rgb(42,91,41); + text-align: center; +} +td a, td a:visited { + color: #005500; +} +#lf_title { + text-align: center; + background-image: url(candela_swirl_small-72h.png); + background-position: right; + background-repeat: no-repeat; + height: 90px; +} +#new_chart { + display: block; + height: 250px; + min-width: 200px; + width: 80%; + border: 1px solid black; + margin: 14px auto; + padding: 14px; + vertical-align: bottom; + text-align: center; +} +.lf_chart { + margin: 1em; + padding: 5px; +} +#error_types ul { + background: #f0f0f0; + font-size: 12px; + line-height: 1.5; + margin: 1em; + padding: 0.25em inherit 0.25em inherit; + max-height: 8em; + overflow: auto; +} +li { + line-height: 1.5; +} +.contentDiv2 { + min-width: 800px; + max-width: 8in; + margin: 1em; + padding: 0; +} +.contentDiv { + min-width: 800px; + max-width: 8in; + margin: 1em auto; + padding: 0; +} +.ct-point { + stroke-width: 6px;} + +.o_el { + display: inline-block; + width: 100px; + height: 230px; + border: none; + margin: 1px 1px 16px 1px; + padding: 10px 10px 0 10px; + background: #eee; + text-align: center; + vertical-align: bottom; +} +.bar_el { + display: block; + background: green; + border: none; + min-height: 1px; + + margin: 0 0 5px 0; + padding: 0; + text-align: center; +} +.label_el { + color: black; + display: block; + font-size: 14px; + font-family: Arial,Helvetica,sans-serif,mono; + margin: 1px; + text-align: center; + vertical-align: bottom; + width: inherit; +} +.value_el { + font-family: Arial,Helvetica,sans-serif,mono; + color: black; + display: block; + font-size: 14px; + margin: 0 auto; + padding: none; + border: none; + background: white; + text-align: center; + vertical-align: bottom; + width: auto; +} +.value_el>span { + background: #f0f0f0a0; + border: 1px solid #f0f0f0a0; + border-radius: 5px; + padding: 1px; + min-width: 2em; +} +.error { + color: red; +} + +@media only screen { +.hideFromPrint { } +.hideFromScreen { display:none; } +} +@media only print { +.hideFromScreen { } +.hideFromPrint { display:none; } +} + +/* these styles will get overridden by custom.css */ +#BannerBack { + background-color: #e68b15; + height: 205px; + max-height: 205px; + border: 0 none; + margin: 0; + padding: 0; + top: 0; + left: 0; + width: 100%; +} +#Banner { + background-image:url("banner.png"); + background-repeat:no-repeat; + padding: 0; + margin: 0 auto; + min-width: 1000px; + min-height: 205px; + width: 1000px; + height: 205px; + max-width: 1000px; + max-height: 205px; +} + +#BannerLeft { + background-image:url("banner.png"); + background-repeat:no-repeat; + padding: 0; + margin: 0 1em; + min-width: 1000px; + min-height: 205px; + width: 1000px; + height: 205px; + max-width: 1000px; + max-height: 205px; +} + +#BannerLogo { + text-align: right; + padding: 25px; + margin: 5px; + width: 200px; + border: none; +} +#BannerLogoFooter { + text-align: right; + padding: 1px; + margin: 1px; + width: 200px; + border: none; +} +.TitleFontScreen { + margin-left: auto; + margin-right: auto; + margin-top: 1em; + margin-bottom: 0.2em; + font-size: 50px; + padding-top: 1em; +} + +.TitleFontPrint { + line-height: 1; + margin-left: 0px; + margin-right: auto; + margin-top: 0.5em; + margin-bottom: 0.2em; + padding-top: 20px; + padding-left: 20px; + color: darkgreen; +} + +.TitleFontPrintSub { + line-height: 1; + margin-left: 0px; + margin-right: auto; + margin-top: 0; + margin-bottom: 0; + /*font-size: 20px; Let 'h3', etc control this */ + padding-top: 0px; + padding-left: 20px; +} + +.HeaderFont {} +.TableFont {} +.TableBorder {} +.ImgStyle {} +div.Section h1, div.Section h2 { + margin: 0 0 0 0em; +} +div.HeaderStyle h1, div.HeaderStyle h2 { + text-align: left; + margin: 0 0 0 0; + max-width: 8in; + min-width: 800px; +} +div.Section { + padding 5px; + position: relative; +} +div.Section img { + margin: 0; + padding: 0; + position: relative; + top: 50%; + transform: translateY(-50%); +} +footer.FooterStyle, div.FooterStyle { + width: 100%; + vertical-align: middle; + border: 0 none; + border-top: 2px solid #2A5B29; + color: #2A5B29; + font-size: 12px; + margin-top: 2em; +} +footer.FooterStyle img, div.FooterStyle img { + width: auto; + height: auto; + text-align: right; +} +footer.FooterStyle span.Gradient, div.FooterStyle span.Gradient { + background: white; + color: #2A5B29; + display: inline-block; + height: 30px; + line-height: 1; + padding-top: 22px; + padding-bottom: 20px; + padding-left: 2em; + vertical-align: middle; + max-width:80%; + float:left; + width:50%; +} +.FooterStyle a, .FooterStyle a:visited { + color: #2A5B29; + font-size: 12px; + line-height: 1; + height: 30px; + margin: 0; + padding: 0; + vertical-align: middle; +} +footer.FooterStyle a.LogoImgLink, div.FooterStyle a.LogoImgLink { + display: inline-block; + text-align: right; + float: right; +} +a .LogoImgLink { +} +a.LogoImgLink img { +} + +.DateFont { + white-space: pre; + font-size: smaller; +} +.TimeFont { + white-space: pre; +} + +table.dataframe { + margin: 1em; + padding: 0; +} +table.dataframe tr th { + padding: 0.5em; +} + +.scriptdetails tt { + font-size: 10px; + overflow: auto; + font-family: Consolas,monaco,"Lucida Sans Typewriter","Lucida Typewriter","Courier New",monospace; + color: #777; + padding: 2px; + line-spacing: 1.05; + display: block; + margin: 0; + padding: 0; + width: inherit; + height: inherit; + background: inherit; + white-space: break-spaces; +} +.scriptdetails:hover tt { + background: #dfd; + cursor: pointer; +} +td.scriptdetails { + padding: 2px !important; +} +td.scriptdetails span.copybtn { + display: none; +} +td.scriptdetails:hover span.copybtn { + display: inline-block; + padding: 5px; + font-size: 12px; + float: left; + color: #050; + background: white; +} \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/check_argparse.py b/lanforge/lanforge-scripts/py-scripts/check_argparse.py new file mode 100644 index 000000000..1702e5c05 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/check_argparse.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +import os +import pandas as pd +import argparse + + +def get_tag(x, tag): + try: + return x[tag] + except: + return False + + +def main(): + parser = argparse.ArgumentParser( + prog="check_argparse.py", + formatter_class=argparse.RawTextHelpFormatter, + description=''' + Check each file in py-scripts, or user defined ''' + ) + parser.add_argument("--path", default='.') + parser.add_argument("--output", default='argparse_results') + args = parser.parse_args() + + files = [f for f in os.listdir(args.path) if '.py' in f] + results = dict() + for file in files: + text = open(os.path.join(args.path, file)).read() + results_file = dict() + results_file['argparse'] = 'argparse.' in text + if results_file['argparse'] is True: + results_file['create_basic'] = 'create_basic_argparse' in text + results_file['create_bare'] = 'create_bare_argparse' in text + results_file['prog'] = 'prog=' in text + results_file['formatter_class'] = 'formatter_class=' in text + results_file['description'] = 'description=' in text + results_file['epilog'] = 'epilog=' in text + results_file['usage'] = 'usage=' in text + results[file] = results_file + df = pd.DataFrame(results.items()) + df.columns = ['File', 'results'] + df['argparse'] = [x['argparse'] for x in df['results']] + for tag in ['create_basic', + 'create_bare', + 'prog', + 'formatter_class', + 'description', + 'epilog', + 'usage']: + df[tag] = [get_tag(x, tag) for x in df['results']] + df['details'] = df['description'] + df['epilog'] + df['usage'] + df.to_csv(args.output + '.csv', index=False) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/cicd_TipIntegration.py b/lanforge/lanforge-scripts/py-scripts/cicd_TipIntegration.py new file mode 100755 index 000000000..d7f860507 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/cicd_TipIntegration.py @@ -0,0 +1,547 @@ +# import os +# import sys +# import base64 +# import urllib.request +# from bs4 import BeautifulSoup +# import ssl +# import subprocess +# from artifactory import ArtifactoryPath +# import tarfile +# import paramiko +# from paramiko import SSHClient +# from scp import SCPClient +# import pexpect +# from pexpect import pxssh +# import paramiko +# from scp import SCPClient +# import pprint +# from pprint import pprint +# from os import listdir +# import re +# +# # For finding files +# # https://stackoverflow.com/questions/3207219/how-do-i-list-all-files-of-a-directory +# import glob +# +# external_results_dir = / var / tmp / lanforge +# +# local_dir = os.getenv('LOG_DIR') +# print("Local Directory where all files will be copied and logged", local_dir) +# cicd_user = os.getenv('CICD_USER') +# print("cicd_user = ", cicd_user) +# cicd_pw = os.getenv('CICD_PW') +# print("cicd pw =", cicd_pw) +# ap_pw = os.getenv('AP_PW') +# ap_user = os.getenv('AP_USER') +# tr_user = os.getenv('TR_USER') +# print("Testrail user id = ", tr_user) +# tr_pw = os.getenv('TR_PW') +# print("Testrail password =", tr_pw) +# aws_host = '3.96.56.0' +# aws_user = 'ubuntu' +# +# if sys.version_info[0] != 3: +# print("This script requires Python 3") +# exit(1) +# if 'py-json' not in sys.path: +# sys.path.append('../py-json') +# +# from LANforge.LFUtils import * +# # if you lack __init__.py in this directory you will not find sta_connect module# +# import sta_connect +# import testrail_api +# from sta_connect import StaConnect +# from testrail_api import APIClient +# +# client: APIClient = APIClient('https://telecominfraproject.testrail.com') +# client.user = tr_user +# client.password = tr_pw +# +# print('Beginning file download with requests') +# +# +# class GetBuild: +# def __init__(self): +# self.user = cicd_user +# self.password = cicd_pw +# ssl._create_default_https_context = ssl._create_unverified_context +# +# def get_latest_image(self, url): +# +# auth = str( +# base64.b64encode( +# bytes('%s:%s' % (cicd_user, cicd_pw), 'utf-8') +# ), +# 'ascii' +# ).strip() +# headers = {'Authorization': 'Basic ' + auth} +# +# ''' FIND THE LATEST FILE NAME''' +# print(url) +# req = urllib.request.Request(url, headers=headers) +# response = urllib.request.urlopen(req) +# html = response.read() +# soup = BeautifulSoup(html, features="html.parser") +# last_link = soup.find_all('a', href=True)[-1] +# latest_file = last_link['href'] +# +# filepath = local_dir +# os.chdir(filepath) +# # file_url = url + latest_file +# +# ''' Download the binary file from Jfrog''' +# path = ArtifactoryPath(url, auth=(cicd_user, cicd_pw)) +# path.touch() +# for file in path: +# print('File =', file) +# +# path = ArtifactoryPath(file, auth=(cicd_user, cicd_pw)) +# print("file to be downloaded :", latest_file) +# print("File Path:", file) +# with path.open() as des: +# with open(latest_file, "wb") as out: +# out.write(des.read()) +# des.close() +# print("Extract the tar.gz file and upgrade the AP ") +# housing_tgz = tarfile.open(latest_file) +# housing_tgz.extractall() +# housing_tgz.close() +# return "pass" +# print("Extract the tar file, and copying the file to Linksys AP directory") +# # with open("/Users/syamadevi/Desktop/syama/ea8300/ap_sysupgrade_output.log", "a") as output: +# # subprocess.call("scp /Users/syamadevi/Desktop/syama/ea8300/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin root@192.100.1.1:/tmp/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin",shell=True, stdout=output, +# # stderr=output) +# +# print('SSH to Linksys and upgrade the file') +# +# ''' +# +# ssh = SSHClient() +# ssh.load_system_host_keys() +# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +# ssh.connect(hostname='192.100.1.1', +# port='22', +# username='root', +# password='Dadun123$', +# look_for_keys=False, +# pkey='load_key_if_relevant') +# +# # SCPCLient takes a paramiko transport as its only argument +# scp = SCPClient(ssh.get_transport()) +# +# scp.put('test.txt', 'testD.txt') +# scp.close() +# +# +# +# # client = paramiko.SSHClient() +# #client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +# #client.connect('192.100.1.1', username='syama', password='Dadun123$') +# +# stdin, stdout, stderr = ssh.exec_command('sysupgrade /tmp/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin') +# +# for line in stdout: +# print (line.strip('\n')) +# client.close() +# ''' +# +# def run_opensyncgw_in_docker(self): +# # user_password = 'fepv6nj9guCPeEHC' +# # my_env = os.environ.copy() +# # my_env["userpass"] = user_password +# # my_command = 'python --version' +# # subprocess.Popen('echo', env=my_env) +# with open(local_dir + "docker_jfrog_login.log", "a") as output: +# subprocess.call( +# "docker login --username" + cicd_user + "--password" + cicd_pw + " https://tip-tip-wlan-cloud-docker-repo.jfrog.io", +# shell=True, stdout=output, +# stderr=output) +# with open(local_dir + "opensyncgw_upgrade.log", "a") as output: +# subprocess.call( +# "docker pull tip-tip-wlan-cloud-docker-repo.jfrog.io/opensync-gateway-and-mqtt:0.0.1-SNAPSHOT", +# shell=True, stdout=output, +# stderr=output) +# with open(local_dir + "opensyncgw.log", "a") as output: +# subprocess.call("docker run --rm -i -p 1883:1883 -p 6640:6640 -p 6643:6643 -p 4043:4043 \ +# -v ~/mosquitto/data:/mosquitto/data \ +# -v ~/mosquitto/log:/mosquitto/log \ +# -v ~/wlan-pki-cert-scripts:/opt/tip-wlan/certs \ +# -v ~/app/log:/app/logs \ +# -v ~//app/config:/app/config \ +# -e OVSDB_CONFIG_FILE='/app/config/config_2_ssids.json' \ +# tip-tip-wlan-cloud-docker-repo.jfrog.io/opensync-gateway-and-mqtt:0.0.1-SNAPSHOT", shell=True, stdout=output, +# stderr=output) +# print("opensync Gateway is running") +# return "pass" +# +# def run_opensyncgw_in_aws(self): +# try: +# s = pxssh.pxssh() +# +# os.chdir(local_dir) +# print("AWS OPENSYNC GW UPGRADE VIA HELM") +# print( +# 'Helm upgrades the latest image in the GW if a new image is found from jfrog and the AWS gateway is not upto date ') +# # makesure the client key file is in the fame directory to login to AWS VM +# s.login(aws_host, aws_user, ssh_key='id_key.pem') +# s.sendline('kubectl get pods') +# +# # run a command +# s.prompt() # match the prompt +# print(s.before) # print everything before the prompt. +# s.sendline( +# 'helm upgrade tip-wlan wlan-cloud-helm/tip-wlan/ -n default -f wlan-cloud-helm/tip-wlan/resources/environments/dev-amazon.yaml') +# s.prompt() # match the prompt +# print(s.before) # print everything before the prompt. +# s.sendline('kubectl get pods') +# +# # run a command +# s.prompt() # match the prompt +# print(s.before) # print everything before the prompt. +# s.logout() +# return "pass" +# +# except pxssh.ExceptionPxssh as e: +# print("ALERT !!!!!! pxssh failed on login.") +# print(e) +# +# +# class openwrt_ap: +# +# def ap_upgrade(src, user2, host2, tgt, pwd, opts='', timeout=60): +# ''' Performs the scp command. Transfers file(s) from local host to remote host ''' +# print("AP Model getting upgarded is :", apModel) +# if apModel == "ecw5410": +# ap_firmware = 'openwrt-ipq806x-generic-edgecore_ecw5410-squashfs-nand-sysupgrade.bin' +# AP_IP = '10.10.10.207' +# else: +# if apModel == "ea8300": +# ap_firmware = 'openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin' +# AP_IP = '10.10.10.208' +# host2 = AP_IP +# src = src + ap_firmware +# print("src =", src) +# print("AP IP ", AP_IP) +# print("AP USER =", ap_user) +# print("AP PASSWORD =", ap_pw) +# cmd = f'''/bin/bash -c "scp {opts} {src} {user2}@{AP_IP}:{tgt}"''' +# print("Executing the following cmd:", cmd, sep='\n') +# +# tmpFl = '/tmp/scp.log' +# fp = open(tmpFl, 'wb') +# print(tmpFl) +# childP = pexpect.spawn(cmd, timeout=timeout) +# try: +# childP.sendline(cmd) +# childP.expect([f"{user2}@{host2}'s password:"]) +# childP.sendline(pwd) +# childP.logfile = fp +# childP.expect(pexpect.EOF) +# childP.close() +# fp.close() +# fp = open(tmpFl, 'r') +# stdout = fp.read() +# fp.close() +# +# if childP.exitstatus != 0: +# raise Exception(stdout) +# except KeyboardInterrupt: +# childP.close() +# fp.close() +# return +# print(stdout) +# +# try: +# s = pxssh.pxssh() +# s.login(host2, user2, pwd) +# # s.sendline('sysupgrade /tmp/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin&') +# s.sendline('sysupgrade /tmp/openwrt-ipq806x-generic-edgecore_ecw5410-squashfs-nand-sysupgrade.bin&') +# # s.logout() +# # s.prompt() # match the prompt +# print(s.before) # print everything before the prompt. +# sleep(100) +# # s.login(host2, user2, pwd) +# s.prompt() +# # os.system(f"scp {local_dir}/cacert.pem root@10.10.10.207:/usr/plume/certs/ca.pem") +# # os.system(f"scp {local_dir}/clientcert.pem root@10.10.10.207:/usr/plume/certs/client.pem") +# # os.system(f"scp {local_dir}/clientkey_dec.pem root@10.10.10.207:/usr/plume/certs/client_dec.key") +# # s.sendline('service opensync restart') +# # s.prompt() # match the prompt +# # print(s.before) # print everything before the prompt. +# s.logout() +# return "pass" +# except pxssh.ExceptionPxssh as e: +# print("ALERT !!!!!! pxssh failed on login.") +# print(e) +# +# def apCopyCert(src, user2, host2, tgt, pwd, opts='', timeout=60): +# +# print("Copying the AP Certs") +# ''' +# s = pxssh.pxssh() +# print(src, users2,pwd) +# s.login(host2, user2, pwd) +# s.prompt() # match the prompt +# print("Copying ca.pem") +# os.system(f"scp {src}/cacert.pem root@10.10.10.207:/usr/plume/certs/ca.pem") +# print("Copying the client.pem") +# os.system(f"scp {src}/clientcert.pem root@10.10.10.207:/usr/plume/certs/client.pem") +# print("Copying the client_dec.key") +# os.system(f"scp {src}/clientkey_dec.pem root@10.10.10.207:/usr/plume/certs/client_dec.key") +# s.sendline('service opensync restart') +# s.prompt() # match the prompt +# print(s.before) # print everything before the prompt. +# s.logout() +# ''' +# cacert = src + "ca.pem" +# clientcert = src + "client.pem" +# clientkey = src + "client_dec.key" +# tgt = "/usr/plume/certs" +# ap_pw +# +# print("src =", src) +# print("AP IP ", host2) +# print("AP USER =", ap_user) +# print("AP PASSWORD =", ap_pw) +# # cmd = f'''/bin/bash -c "scp {opts} {src} {user2}@{AP_IP}:{tgt}"''' +# # cmd = f'''/bin/bash -c "scp {opts} {cacert} {user2}@{AP_IP}:{tgt}"''' +# # cmd = f'''/bin/bash -c "scp {opts} {clientcert} {user2}@{AP_IP}:{tgt}"''' +# cmd = f'''/bin/bash -c "scp {opts} {cacert} {clientcert} {clientkey} {user2}@{host2}:{tgt}"''' +# print("Executing the following cmd:", cmd, sep='\n') +# tmpFl = '/tmp/cert.log' +# fp = open(tmpFl, 'wb') +# print(tmpFl) +# childP = pexpect.spawn(cmd, timeout=timeout) +# try: +# childP.sendline(cmd) +# childP.expect([f"{user2}@{host2}'s password:"]) +# childP.sendline(ap_pw) +# childP.logfile = fp +# childP.expect(pexpect.EOF) +# fp.close() +# fp = open(tmpFl, 'r') +# stdout = fp.read() +# fp.close() +# +# if childP.exitstatus != 0: +# # raise Exception(stdout) +# print("there is an excess status non 0") +# except KeyboardInterrupt: +# childP.close() +# fp.close() +# return +# print(stdout) +# +# def restartGw(src, user2, host2, tgt, pwd, opts='', timeout=60): +# print("Restarting opensync GW") +# s = pxssh.pxssh() +# s.login(host2, user2, pwd) +# # s.sendline('sysupgrade /tmp/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin&') +# s.sendline('service opensync restart') +# # s.logout() +# # s.prompt() # match the prompt +# print(s.before) # print everything before the prompt. +# s.prompt() +# s.logout() +# +# +# class RunTest: +# def TestCase_938(self, rid): +# '''SINGLE CLIENT CONNECTIVITY''' +# staConnect = StaConnect("10.10.10.201", 8080, _debugOn=False) +# staConnect.sta_mode = 0 +# staConnect.upstream_resource = 1 +# staConnect.upstream_port = "eth2" +# staConnect.radio = "wiphy1" +# staConnect.resource = 1 +# staConnect.dut_ssid = "autoProvisionedSsid-5u" +# # staConnect.dut_passwd = "4C0nnectUS!" +# staConnect.dut_passwd = "12345678" +# staConnect.dut_security = "wpa2" +# staConnect.station_names = ["sta01010"] +# staConnect.runtime_secs = 30 +# staConnect.cleanup_on_exit = True +# staConnect.run() +# run_results = staConnect.get_result_list() +# for result in run_results: +# print("test result: " + result) +# # result = 'pass' +# print("Single Client Connectivity :", staConnect.passes) +# if staConnect.passes() == True: +# client.update_testrail(case_id=938, run_id=rid, status_id=1, +# msg='client Connectivity to 5GHZ Open SSID is Passed ') +# else: +# client.update_testrail(case_id=938, run_id=rid, status_id=5, +# msg='client connectivity to 5GHZ OPEN SSID is Failed') +# +# def TestCase_941(self, rid): +# # MULTI CLIENT CONNECTIVITY +# staConnect = StaConnect("10.10.10.201", 8080, _debugOn=False) +# staConnect.sta_mode = 0 +# staConnect.upstream_resource = 1 +# staConnect.upstream_port = "eth2" +# staConnect.radio = "wiphy1" +# staConnect.resource = 1 +# staConnect.dut_ssid = "autoProvisionedSsid-5u" +# # staConnect.dut_passwd = "4C0nnectUS!" +# staConnect.dut_passwd = "12345678" +# staConnect.dut_security = "wpa2" +# staConnect.station_names = ["sta0020", 'sta0021', 'sta0022', 'sta0023'] +# staConnect.runtime_secs = 20 +# staConnect.cleanup_on_exit = True +# staConnect.run() +# run_results = staConnect.get_result_list() +# for result in run_results: +# print("test result: " + result) +# if staConnect.passes() == True: +# client.update_testrail(case_id=941, run_id=rid, status_id=1, +# msg='client Connectivity to 5GHZ Open SSID is Passed ') +# else: +# client.update_testrail(case_id=941, run_id=rid, status_id=5, +# msg='client connectivity to 5GHZ OPEN SSID is Failed') +# +# # Check for externally run test case results. +# def TestCase_LF_External(self, rid): +# # https://stackoverflow.com/questions/3207219/how-do-i-list-all-files-of-a-directory +# results = glob.glob("%s/*_CICD_RESULTS.txt" % external_results_dir) +# for r in results: +# rfile = open(r, 'r') +# lines = rfile.readlines() +# +# # File contents looks something like: +# # CASE_ID 9999 +# # RUN_ID 15 +# # STATUS 1 +# # MSG Test passed nicely +# # MSG Build ID: deadbeef +# # MSG Results: http://cicd.telecominfraproject.com +# +# _case_id = -1 +# _status_id = 1 # Default to pass +# _msg = "" +# _rid = rid +# +# for line in Lines: +# m = re.search(r'(\S+) (.*)', line) +# k = m.group(0); +# v = m.group(1); +# +# if k == "CASE_ID": +# _case_id = v +# if k == "RUN_ID": +# _rid = v +# if k == "STATUS": +# _status_id = v +# if k == "MSG": +# if _msg == "": +# _msg == v +# else: +# _msg += "\n" +# _msg += v +# if _case_id != -1: +# client.update_testrail(case_id=_case_id, run_id=_rid, status_id=_status_id, msg=_msg) +# os.unlink(r) +# +# def TestCase_939(self, rid): +# ''' Client Count in MQTT Log''' +# try: +# print("Counting clients in MQTT") +# s = pxssh.pxssh() +# # aws_host = os.getenv(AWS_HOST) +# # aws_user=os.getenv(AWS_USER) +# os.chdir(local_dir) +# # makesure the client key file is in the fame directory to login to AWS VM +# s.login(aws_host, aws_user, ssh_key='id_key.pem') +# s.sendline('kubectl cp tip-wlan-opensync-gw-static-f795d45-ctb5z:/app/logs/mqttData.log mqttData.log') +# # run a command +# s.prompt() # match the prompt +# print(s.before) # print everything before the prompt. +# s.sendline() +# s.logout() +# # return "pass" +# print(aws_host, aws_user) +# ssh = paramiko.SSHClient() +# ssh.load_system_host_keys() +# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +# k = paramiko.RSAKey.from_private_key_file('id_key.pem') +# ssh.connect(aws_host, username=aws_user, pkey=k) +# print("Connected") +# scp = SCPClient(ssh.get_transport()) +# scp.get("mqttData.log") +# scp.close() +# # Get the client Count +# ClientCount = subprocess.getoutput( +# 'grep \'{\"nodeID\"\' mqttData.log | grep clientList | tail -1 |cut -d \'=\' -f 3 | json_pp | grep macAddres | grep \'04:F0:21:55\' | tr -d , | sort | uniq | wc -l ') +# print("client count =", ClientCount) +# if (int(ClientCount) >= 1): +# client.update_testrail(case_id=939, run_id=rid, status_id=1, +# msg=ClientCount + ' Client/Clients Connected ') +# else: +# client.update_testrail(case_id=939, run_id=rid, status_id=5, +# msg='No Client Connected') +# except pxssh.ExceptionPxssh as e: +# print("ALERT !!!!!! pxssh failed on login.") +# print(e) +# +# +# params = { +# 'src': local_dir, +# 'user2': ap_user, +# 'host2': '10.10.10.207', +# 'tgt': '/tmp/', +# 'pwd': ap_pw, +# 'opts': '' +# } +# apModel = "ecw5410" +# +# url = 'https://tip.jfrog.io/artifactory/tip-wlan-ap-firmware/' +# url = url + apModel +# projId = client.get_project_id(project_name='WLAN') +# print("TIP WLAN Project ID Is :", projId) +# +# rid = client.get_run_id(test_run_name='TIP-DEMO4') +# print(rid) +# Test: RunTest = RunTest() +# Build: GetBuild = GetBuild() +# ''' +# binary_fetch_result = Build.get_latest_image(url) +# print("UPDATING TEST RAIL WITH TEST RESULT FOR CASE_ID 940: Download latest openwrt image from Jfrog") +# +# if binary_fetch_result == 'pass': +# client.update_testrail(case_id=940, run_id=rid, status_id=1, msg='latest firmware downloaded') +# else: +# client.update_testrail(case_id=940, run_id=rid, status_id=5, msg='Firmware Download failed') +# +# sleep(10) +# print("Upgrading AP with latest image downloaded") +# ap_upgrade_result = openwrt_ap.ap_upgrade(**params) +# sleep(10) +# print("UPDATING TEST RAIL WITH TEST RESULT FOR CASE_ID 937") +# sleep(10) +# if ap_upgrade_result == 'pass': +# client.update_testrail(case_id=937, run_id=rid, status_id=1, msg='AP upgraded with latest Firmware') +# else: +# client.update_testrail(case_id=937, run_id=rid, status_id=5, msg='Firmware upgrade failed in AP ') +# print("Upgrading AWS Opensync gateway with latest docker image from Jfrog") +# OpensyncGw_UpgResult = Build.run_opensyncgw_in_aws() +# if OpensyncGw_UpgResult == 'pass': +# client.update_testrail(case_id=936, run_id=rid, status_id=1, msg='Opensync GW upgraded with latest Firmware') +# else: +# client.update_testrail(case_id=936, run_id=rid, status_id=5, msg='Firmware upgrade failed in Opensync Gateway') +# sleep(10) +# ''' +# pprint.pprint(params) +# ap_cert_result = openwrt_ap.apCopyCert(**params) +# print("Executing TestCase 938: single Client Connectivity test") +# openwrt_ap.restartGw(**params) +# Test.TestCase_938(rid) +# +# print("Executing TestCase 941: Multi Client Connectivity test") +# Test.TestCase_941(rid) +# sleep(100) +# print("Executing TestCase 939:Counting The number of Clients Connected from MQTT") +# Test.TestCase_939(rid) +# +# +# +# diff --git a/lanforge/lanforge-scripts/py-scripts/cicd_testrail.py b/lanforge/lanforge-scripts/py-scripts/cicd_testrail.py new file mode 100755 index 000000000..bfd387481 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/cicd_testrail.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 + +"""TestRail API binding for Python 3.x. + +""" + +import base64 +import json + +import requests +from pprint import pprint +import os +tr_user = os.getenv('TR_USER') +tr_pw=os.getenv('TR_PW') + + + +class APIClient: + def __init__(self, base_url): + self.user = tr_user + self.password = tr_pw + if not base_url.endswith('/'): + base_url += '/' + self.__url = base_url + 'index.php?/api/v2/' + + + def send_get(self, uri, filepath=None): + """Issue a GET request (read) against the API. + + Args: + uri: The API method to call including parameters, e.g. get_case/1. + filepath: The path and file name for attachment download; used only + for 'get_attachment/:attachment_id'. + + Returns: + A dict containing the result of the request. + """ + return self.__send_request('GET', uri, filepath) + + def send_post(self, uri, data): + """Issue a POST request (write) against the API. + + Args: + uri: The API method to call, including parameters, e.g. add_case/1. + data: The data to submit as part of the request as a dict; strings + must be UTF-8 encoded. If adding an attachment, must be the + path to the file. + + Returns: + A dict containing the result of the request. + """ + return self.__send_request('POST', uri, data) + + def __send_request(self, method, uri, data): + url = self.__url + uri + + auth = str( + base64.b64encode( + bytes('%s:%s' % (self.user, self.password), 'utf-8') + ), + 'ascii' + ).strip() + headers = {'Authorization': 'Basic ' + auth} + print("Method =" , method) + + if method == 'POST': + if uri[:14] == 'add_attachment': # add_attachment API method + files = {'attachment': (open(data, 'rb'))} + response = requests.post(url, headers=headers, files=files) + files['attachment'].close() + else: + headers['Content-Type'] = 'application/json' + payload = bytes(json.dumps(data), 'utf-8') + response = requests.post(url, headers=headers, data=payload) + else: + headers['Content-Type'] = 'application/json' + response = requests.get(url, headers=headers) + print("headers = ", headers) + print("resonse=", response) + print("response code =", response.status_code) + + if response.status_code > 201: + + try: + error = response.json() + except: # response.content not formatted as JSON + error = str(response.content) + #raise APIError('TestRail API returned HTTP %s (%s)' % (response.status_code, error)) + print('TestRail API returned HTTP %s (%s)' % (response.status_code, error)) + return + else: + print(uri[:15]) + if uri[:15] == 'get_attachments': # Expecting file, not JSON + try: + print('opening file') + print (str(response.content)) + open(data, 'wb').write(response.content) + print('opened file') + return (data) + except: + return ("Error saving attachment.") + else: + + try: + return response.json() + except: # Nothing to return + return {} + def get_project_id(self, project_name): + "Get the project ID using project name" + project_id = None + projects = client.send_get('get_projects') + pprint(projects) + for project in projects: + if project['name']== project_name: + project_id = project['id'] + # project_found_flag=True + break + print("project Id =",project_id) + return project_id + + def get_run_id(self, test_run_name): + "Get the run ID using test name and project name" + run_id = None + project_id = client.get_project_id(project_name='WLAN') + + try: + test_runs = client.send_get('get_runs/%s' % (project_id)) + print("------------TEST RUNS----------") + pprint(test_runs) + + except Exception: + print + 'Exception in update_testrail() updating TestRail.' + return None + else: + for test_run in test_runs: + if test_run['name'] == test_run_name: + run_id = test_run['id'] + print("run Id in Test Runs=",run_id) + break + return run_id + + def update_testrail(self, case_id, run_id, status_id, msg): + "Update TestRail for a given run_id and case_id" + update_flag = False + # Get the TestRail client account details + # Update the result in TestRail using send_post function. + # Parameters for add_result_for_case is the combination of runid and case id. + # status_id is 1 for Passed, 2 For Blocked, 4 for Retest and 5 for Failed + #status_id = 1 if result_flag is True else 5 + + print("result status is = ", status_id) + print("case id=", case_id) + print("run id passed to update is ", run_id, case_id) + if run_id is not None: + try: + result = client.send_post( + 'add_result_for_case/%s/%s' % (run_id, case_id), + {'status_id': status_id, 'comment': msg}) + print("result in post",result) + except Exception: + print + 'Exception in update_testrail() updating TestRail.' + + else: + print + 'Updated test result for case: %s in test run: %s with msg:%s' % (case_id, run_id, msg) + + return update_flag + + + +client: APIClient = APIClient('https://telecominfraproject.testrail.com') + +''' +case = client.send_get('get_case/1') +print("---------TEST CASE 1---------") +pprint(case) +case = client.send_get('get_case/2') +print("---------TEST CASE 2---------") +pprint(case) +print ("----------TEST Project ID----------") +proj_id = client.get_project_id(project_name= "WLAN") +pprint(proj_id) + +#REST API POSTMAN PROJECT +projId = client.get_project_id(project_name= "REST-API-POSTMAN") +pprint("REST API POSTMAN PROJECT ID IS :", projId) + +#print("---------TEST RUN ID-----------") +#rid = client.get_run_id(test_run_name='Master',project_name='WLAN') +rid=client.get_run_id(test_run_name= 'Master-Run3') +pprint(rid) + +result: bool= client.update_testrail(case_id = 1, run_id=rid, status_id = 5, msg ='Test Failed') + +#result = client.send_get('get_attachment/:1', '/Users/syamadevi/Desktop/syama/python-test/TestRail/testreport.pdf') +#print(result) +#project_report= client.send_get("get_reports/:%s" %proj_id) +#print(project_report) +''' +class APIError(Exception): + pass diff --git a/lanforge/lanforge-scripts/py-scripts/cicd_testrailAndInfraSetup.py b/lanforge/lanforge-scripts/py-scripts/cicd_testrailAndInfraSetup.py new file mode 100755 index 000000000..97930ebba --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/cicd_testrailAndInfraSetup.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 + +import base64 +import urllib.request +from bs4 import BeautifulSoup +import ssl +import subprocess +from artifactory import ArtifactoryPath +import tarfile +import os +import pexpect +from pexpect import pxssh +import sys +import paramiko +from scp import SCPClient + +local_dir=os.getenv('LOG_DIR') +print("Local Directory where all files will be copied and logged", local_dir) +cicd_user=os.getenv('CICD_USER') +print("cicd_user = ", cicd_user) +cicd_pw=os.getenv('CICD_PW') +print("cicd pw =",cicd_pw) +ap_pw=os.getenv('AP_PW') +ap_user=os.getenv('AP_USER') +tr_user=os.getenv('TR_USER') +print("Testrail user id = ", tr_user) +tr_pw=os.getenv('TR_PW') +print ("Testrail password =", tr_pw) + + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) +if 'py-json' not in sys.path: + sys.path.append('../py-json') + +from LANforge.LFUtils import * +# if you lack __init__.py in this directory you will not find sta_connect module# +import sta_connect +import testrail_api +from sta_connect import StaConnect +from testrail_api import APIClient + +client: APIClient = APIClient('https://telecominfraproject.testrail.com') +client.user = tr_user +client.password = tr_pw + + +print('Beginning file download with requests') + +class GetBuild: + def __init__(self): + self.user = cicd_user + self.password = cicd_pw + ssl._create_default_https_context = ssl._create_unverified_context + + def get_latest_image(self,url): + + auth = str( + base64.b64encode( + bytes('%s:%s' % (cicd_user,cicd_pw ), 'utf-8') + ), + 'ascii' + ).strip() + headers = {'Authorization': 'Basic ' + auth} + + ''' FIND THE LATEST FILE NAME''' + print(url) + req = urllib.request.Request(url, headers=headers) + response = urllib.request.urlopen(req) + html = response.read() + soup = BeautifulSoup(html, features="html.parser") + last_link = soup.find_all('a', href=True)[-1] + latest_file=last_link['href'] + + filepath = local_dir + os.chdir(filepath) + #file_url = url + latest_file + + ''' Download the binary file from Jfrog''' + path = ArtifactoryPath(url,auth=(cicd_user, cicd_pw)) + path.touch() + for file in path: + print('File =', file) + + path = ArtifactoryPath(file, auth=(cicd_user, cicd_pw)) + print("file to be downloaded :" ,latest_file) + print("File Path:",file) + with path.open() as des: + with open(latest_file, "wb") as out: + out.write(des.read()) + des.close() + print("Extract the tar.gz file and upgrade the AP ") + housing_tgz = tarfile.open(latest_file) + housing_tgz.extractall() + housing_tgz.close() + return "pass" + print("Extract the tar file, and copying the file to Linksys AP directory") + #with open("/Users/syamadevi/Desktop/syama/ea8300/ap_sysupgrade_output.log", "a") as output: + # subprocess.call("scp /Users/syamadevi/Desktop/syama/ea8300/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin root@192.100.1.1:/tmp/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin",shell=True, stdout=output, + # stderr=output) + + print('SSH to Linksys and upgrade the file') + + ''' + + ssh = SSHClient() + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(hostname=ap_ip, + port='22', + username=ap_user, + password=ap_pw, + look_for_keys=False, + pkey='load_key_if_relevant') + + # SCPCLient takes a paramiko transport as its only argument + scp = SCPClient(ssh.get_transport()) + + scp.put('test.txt', 'testD.txt') + scp.close() + + + stdin, stdout, stderr = ssh.exec_command('sysupgrade /tmp/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin') + + for line in stdout: + print (line.strip('\n')) + client.close() + ''' + + def run_opensyncgw_in_docker(self): + #my_env = os.environ.copy() + #my_env["userpass"] = user_password + #my_command = 'python --version' + #subprocess.Popen('echo', env=my_env) + with open(local_dir +"docker_jfrog_login.log", "a") as output: + subprocess.call("docker login --username" + cicd_user + "--password" + cicd_pw + " https://tip-tip-wlan-cloud-docker-repo.jfrog.io", shell=True, stdout=output, + stderr=output) + with open(local_dir +"opensyncgw_upgrade.log", "a") as output: + subprocess.call("docker pull tip-tip-wlan-cloud-docker-repo.jfrog.io/opensync-gateway-and-mqtt:0.0.1-SNAPSHOT", shell=True, stdout=output, + stderr=output) + with open(local_dir+"opensyncgw.log", "a") as output: + subprocess.call("docker run --rm -i -p 1883:1883 -p 6640:6640 -p 6643:6643 -p 4043:4043 \ + -v ~/mosquitto/data:/mosquitto/data \ + -v ~/mosquitto/log:/mosquitto/log \ + -v ~/wlan-pki-cert-scripts:/opt/tip-wlan/certs \ + -v ~/app/log:/app/logs \ + -v ~//app/config:/app/config \ + -e OVSDB_CONFIG_FILE='/app/config/config_2_ssids.json' \ + tip-tip-wlan-cloud-docker-repo.jfrog.io/opensync-gateway-and-mqtt:0.0.1-SNAPSHOT",shell=True, stdout=output, + stderr=output) + print("opensync Gateway is running") + return "pass" + + def run_opensyncgw_in_aws(self): + try: + s = pxssh.pxssh() + + os.chdir(local_dir) + print("AWS OPENSYNC GW UPGRADE VIA HELM") + print( + 'Helm upgrades the latest image in the GW if a new image is found from jfrog and the AWS gateway is not upto date ') + # makesure the client key file is in the fame directory to login to AWS VM + s.login(aws_host, aws_user, ssh_key='id_key.pem') + s.sendline('kubectl get pods') + + # run a command + s.prompt() # match the prompt + print(s.before) # print everything before the prompt. + s.sendline( + 'helm upgrade tip-wlan wlan-cloud-helm/tip-wlan/ -n default -f wlan-cloud-helm/tip-wlan/resources/environments/dev-amazon.yaml') + s.prompt() # match the prompt + print(s.before) # print everything before the prompt. + s.sendline('kubectl get pods') + + # run a command + s.prompt() # match the prompt + print(s.before) # print everything before the prompt. + s.logout() + return "pass" + + except pxssh.ExceptionPxssh as e: + print("ALERT !!!!!! pxssh failed on login.") + print(e) + + +class openwrt_linksys: + + def ap_upgrade(src,user2,host2,tgt,pwd,opts='', timeout=60): + ''' Performs the scp command. Transfers file(s) from local host to remote host ''' + print("AP Model getting upgarded is :", apModel) + if apModel == "ecw5410": + ap_firmware = 'openwrt-ipq806x-generic-edgecore_ecw5410-squashfs-nand-sysupgrade.bin' + AP_IP = '10.10.10.207' + else: + if apModel == "ea8300": + ap_firmware = 'openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin' + AP_IP = '10.10.10.208' + host2 = AP_IP + src = src+ ap_firmware + print("src =", src) + print("AP IP ", AP_IP) + print("AP USER =", ap_user) + print("AP PASSWORD =", ap_pw) + cmd = f'''/bin/bash -c "scp {opts} {src} {user2}@{AP_IP}:{tgt}"''' + print("Executing the following cmd:",cmd,sep='\n') + + tmpFl = '/tmp/scp.log' + fp = open(tmpFl,'wb') + print(tmpFl) + childP = pexpect.spawn(cmd,timeout=timeout) + try: + childP.sendline(cmd) + childP.expect([f"{user2}@{host2}'s password:"]) + childP.sendline(pwd) + childP.logfile = fp + childP.expect(pexpect.EOF) + childP.close() + fp.close() + fp = open(tmpFl,'r') + stdout = fp.read() + fp.close() + + if childP.exitstatus != 0: + raise Exception(stdout) + except KeyboardInterrupt: + childP.close() + fp.close() + return + print(stdout) + + try: + s = pxssh.pxssh() + s.login(host2, user2, pwd) + s.sendline('sysupgrade /tmp/openwrt-ipq40xx-generic-linksys_ea8300-squashfs-sysupgrade.bin') + s.prompt() # match the prompt + print(s.before) # print everything before the prompt. + s.sendline('service opensync restart') + s.prompt() # match the prompt + print(s.before) # print everything before the prompt. + s.logout() + return "pass" + except pxssh.ExceptionPxssh as e: + print("ALERT !!!!!! pxssh failed on login.") + print(e) + + +class RunTest: + def TestCase_938(self, rid): + '''SINGLE CLIENT CONNECTIVITY''' + staConnect = StaConnect("10.10.10.201", 8080, _debugOn=False) + staConnect.sta_mode = 0 + staConnect.upstream_resource = 1 + staConnect.upstream_port = "eth2" + staConnect.radio = "wiphy1" + staConnect.resource = 1 + staConnect.dut_ssid = "autoProvisionedSsid-5u" + #staConnect.dut_passwd = "4C0nnectUS!" + staConnect.dut_passwd = "[BLANK]" + staConnect.dut_security = "open" + staConnect.station_names = ["sta01010"] + staConnect.runtime_secs = 30 + staConnect.cleanup_on_exit = False + staConnect.run() + run_results = staConnect.get_result_list() + for result in run_results: + print("test result: " + result) + #result = 'pass' + print("Single Client Connectivity :",staConnect.passes) + if staConnect.passes() == True: + client.update_testrail(case_id=938, run_id=rid, status_id=1, msg='client Connectivity to 5GHZ Open SSID is Passed ') + else: + client.update_testrail(case_id=938, run_id=rid, status_id=5, msg='client connectivity to 5GHZ OPEN SSID is Failed') + + def TestCase_941(self, rid): + #MULTI CLIENT CONNECTIVITY + staConnect = StaConnect("10.10.10.201", 8080, _debugOn=False) + staConnect.sta_mode = 0 + staConnect.upstream_resource = 1 + staConnect.upstream_port = "eth2" + staConnect.radio = "wiphy1" + staConnect.resource = 1 + staConnect.dut_ssid = "autoProvisionedSsid-5u" + # staConnect.dut_passwd = "4C0nnectUS!" + staConnect.dut_passwd = "[BLANK]" + staConnect.dut_security = "open" + staConnect.station_names = ["sta0020", 'sta0021', 'sta0022', 'sta0023'] + staConnect.runtime_secs = 30 + staConnect.cleanup_on_exit = False + staConnect.run() + run_results = staConnect.get_result_list() + for result in run_results: + print("test result: " + result) + if staConnect.passes() == True: + client.update_testrail(case_id=941, run_id=rid, status_id=1, + msg='client Connectivity to 5GHZ Open SSID is Passed ') + else: + client.update_testrail(case_id=941, run_id=rid, status_id=5, + msg='client connectivity to 5GHZ OPEN SSID is Failed') + + def TestCase_939(self, rid): + ''' Client Count in MQTT Log''' + try: + print("Counting clients in MQTT") + s = pxssh.pxssh() + #aws_host = os.getenv(AWS_HOST) + #aws_user=os.getenv(AWS_USER) + os.chdir(local_dir) + # makesure the client key file is in the fame directory to login to AWS VM + s.login(aws_host,aws_user,ssh_key='id_key.pem') + s.sendline('kubectl cp tip-wlan-opensync-gw-static-f795d45-ctb5z:/app/logs/mqttData.log mqttData.log') + # run a command + s.prompt() # match the prompt + print(s.before) # print everything before the prompt. + s.sendline() + s.logout() + #return "pass" + print(aws_host, aws_user) + ssh = paramiko.SSHClient() + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + k = paramiko.RSAKey.from_private_key_file('id_key.pem') + ssh.connect(aws_host, username=aws_user, pkey=k) + print("Connected") + scp = SCPClient(ssh.get_transport()) + scp.get("mqttData.log") + scp.close() + # Get the client Count + ClientCount = subprocess.getoutput( + 'grep \'{\"nodeID\"\' mqttData.log | grep clientList | tail -n 1 |cut -d \'=\' -f 3 |jq --raw-output \'{ nodeID , ncl : [ .clients[]| [ .clientList[] | select ( . | .connected == true ) |.macAddress ] | length ] } | .ncl | add |@text\'') + print("client count =", ClientCount) + if (int(ClientCount) >= 1): + client.update_testrail(case_id=939, run_id=rid, status_id=1, + msg=ClientCount + ' Client/Clients Connected ') + else: + client.update_testrail(case_id=939, run_id=rid, status_id=5, + msg='No Client Connected') + except pxssh.ExceptionPxssh as e: + print("ALERT !!!!!! pxssh failed on login.") + print(e) + + +params = { + 'src': local_dir, + 'user2': ap_user, + 'host2': "x.x.x.x", + 'tgt': '/tmp/', + 'pwd': ap_pw, + 'opts': '' +} +apModel= "ecw5410" + + +url = 'https://tip.jfrog.io/artifactory/tip-wlan-ap-firmware/' +url = url + apModel +projId = client.get_project_id(project_name= 'WLAN') +print("TIP WLAN Project ID Is :", projId) + +rid = client.get_run_id(test_run_name= 'TIP-DEMO4') +print(rid) +Test: RunTest = RunTest() +Build: GetBuild = GetBuild() +binary_fetch_result = Build.get_latest_image(url) +print("UPDATING TEST RAIL WITH TEST RESULT FOR CASE_ID 940: Download latest openwrt image from Jfrog") + +if binary_fetch_result == 'pass': + client.update_testrail(case_id=940, run_id=rid, status_id=1, msg='latest firmware downloaded') +else: + client.update_testrail(case_id=940, run_id=rid, status_id=5, msg='Firmware Download failed') + +sleep(10) +print("Upgrading AP with latest image downloaded") +ap_upgrade_result = openwrt_linksys.ap_upgrade(**params) +sleep(10) +print("UPDATING TEST RAIL WITH TEST RESULT FOR CASE_ID 937") +sleep(10) +if ap_upgrade_result == 'pass': + client.update_testrail(case_id=937, run_id=rid, status_id=1, msg='AP upgraded with latest Firmware') +else: + client.update_testrail(case_id=937, run_id=rid, status_id=5, msg='Firmware upgrade failed in AP ') +print("Upgrading AWS Opensync gateway with latest docker image from Jfrog") +OpensyncGw_UpgResult = Build.run_opensyncgw_in_aws() +if OpensyncGw_UpgResult == 'pass': + client.update_testrail(case_id=936, run_id=rid, status_id=1, msg='Opensync GW upgraded with latest Firmware') +else: + client.update_testrail(case_id=936, run_id=rid, status_id=5, msg='Firmware upgrade failed in Opensync Gateway') +sleep(10) + +print("Executing TestCase 938: single Client Connectivity test") +Test.TestCase_938(rid) +sleep(10) +print("Executing TestCase 941: Multi Client Connectivity test") +Test.TestCase_941(rid) +sleep(10) +print("Executing TestCase 939:Counting The number of Clients Connected from MQTT") +Test.TestCase_939(rid) + + + + diff --git a/lanforge/lanforge-scripts/py-scripts/connection_test.py b/lanforge/lanforge-scripts/py-scripts/connection_test.py new file mode 100755 index 000000000..7e2a60967 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/connection_test.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +""" +Candela Technologies Inc. + +Info : Standard Script for Connection Testing - Creates HTML and pdf report as a result (Used for web-console) + +""" +import sys +import os +import importlib +import argparse +import datetime +import time +from test_utility import CreateHTML, StatusMsg +import pdfkit + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + +webconsole_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.getcwd()))) + + +class ConnectionTest(LFCliBase): + + def __init__(self, lfclient_host="localhost", lfclient_port=8080, radio="wiphy1", sta_prefix="sta", start_id=0, + num_sta=2, + dut_ssid="lexusdut", dut_security="open", dut_passwd="[BLANK]", upstream="eth1", name_prefix="L3Test", + session_id="Layer3Test", test_name="Client/s Connectivity Test", pass_criteria=20, _debug_on=False, + _exit_on_error=False, _exit_on_fail=False): + super().__init__(lfclient_host, lfclient_port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.host = lfclient_host + self.port = lfclient_port + self.radio = radio + self.upstream = upstream + self.monitor_interval = 1 + self.sta_prefix = sta_prefix + self.sta_start_id = start_id + self.pass_criteria = pass_criteria + self.num_sta = num_sta + self.name_prefix = name_prefix + self.ssid = dut_ssid + self.security = dut_security + self.password = dut_passwd + self.session_id = session_id + self.test_name = test_name + self.test_duration = 1 + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.pass_fail = "" + self.status_msg = StatusMsg(lfclient_host=self.host, lfclient_port=self.port, session_id=self.session_id) + station_list = [] + for i in range(0, self.num_sta): + station_list.append(self.sta_prefix + str(i).zfill(4)) + self.station_data = dict.fromkeys(station_list) + for i in station_list: + self.station_data[i] = "None" + + try: + self.status_msg.update('1', {"data": 'Initializing...', "data": [], "label": "Client Connectivity Time"}) + except: + pass + self.reports_path = webconsole_dir+"/reports/" + self.test_name + "_" + self.session_id + '/' + + + if not os.path.exists(self.reports_path): + os.makedirs(self.reports_path) + self.station_list = LFUtils.portNameSeries(prefix_=self.sta_prefix, start_id_=self.sta_start_id, + end_id_=self.num_sta - 1, padding_number_=10000, radio=self.radio) + try: + self.status_msg.update('2', {"data": 'Initialized...', "data": [], "label": "Client Connectivity Time"}) + except: + pass + + def precleanup(self): + sta_list = [] + for i in self.local_realm.station_list(): + if (list(i.keys())[0] == '1.1.wlan0'): + pass + elif (list(i.keys())[0] == '1.1.wlan1'): + pass + else: + sta_list.append(list(i.keys())[0]) + for sta in sta_list: + self.local_realm.rm_port(sta, check_exists=True) + time.sleep(1) + + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, + debug=self.debug) + try: + self.status_msg.update('3', {"data": 'Building...', "data": [], "label": "Client Connectivity Time"}) + except: + pass + + def build(self): + + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template("00") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.station_list, debug=self.debug) + self.local_realm.wait_until_ports_appear(sta_list=self.station_list) + self.update(status="build complete") + try: + self.status_msg.update('4', {"data": 'Starting...', "data": [], "label": "Client Connectivity Time"}) + except: + pass + + def update(self, status="None"): + for i in self.station_list: + self.station_data[i.split(".")[2]] = \ + self.json_get("port/1/1/" + i.split(".")[2] + "/?fields=ip,ap,down,phantom&cx%20time%20(us)")['interface'] + try: + self.status_msg.update('5', {"data": 'None', "data": [], "label": "Client Connectivity Time"}) + except: + pass + + def start(self): + self.station_profile.admin_up() + associated_map = {} + self.ip_map = {} + cx_time = {} + self.timeout = 60 + for sec in range(self.timeout): + for sta_name in self.station_profile.station_names: + sta_status = self.json_get("port/1/1/" + str(sta_name).split(".")[2] + "?fields=port,alias,ip,ap", + debug_=self.debug) + + if (sta_status is None or sta_status['interface'] is None) or (sta_status['interface']['ap'] is None): + continue + if (len(sta_status['interface']['ap']) == 17) and (sta_status['interface']['ap'][-3] == ':'): + associated_map[sta_name] = 1 + if (sta_status['interface']['ip'] != '0.0.0.0'): + self.ip_map[sta_name] = 1 + if (len(self.station_profile.station_names) == len(self.ip_map)) and ( + len(self.station_profile.station_names) == len(associated_map)): + break + else: + time.sleep(1) + + + if (len(self.station_profile.station_names) == len(self.ip_map)) and ( + len(self.station_profile.station_names) == len(associated_map)): + + #("Test Passed") + for sta_name in self.station_profile.station_names: + sta_status = self.json_get("port/1/1/" + str(sta_name).split(".")[2] + "?fields=cx%20time%20(us)", + debug_=self.debug) + #(sta_status) + while sta_status['interface']['cx time (us)'] == 0: + sta_status = self.json_get("port/1/1/" + str(sta_name).split(".")[2] + "?fields=cx%20time%20(us)", + debug_=self.debug) + # #(sta_status) + continue + cx_time[sta_name] = sta_status['interface']['cx time (us)'] + else: + for sta_name in self.ip_map.keys(): + sta_status = self.json_get("port/1/1/" + str(sta_name).split(".")[2] + "?fields=cx%20time%20(us)", + debug_=self.debug) + while sta_status['interface']['cx time (us)'] == 0: + sta_status = self.json_get("port/1/1/" + str(sta_name).split(".")[2] + "?fields=cx%20time%20(us)", + debug_=self.debug) + # #(sta_status) + continue + cx_time[sta_name] = sta_status['interface']['cx time (us)'] + self.test_result_data = [] + self.keys = ["Client Name", "BSSID", "Channel", "Connection Time (ms)", "DHCP (ms)", "IPv4 Address", "MAC Address", "Mode", "Result"] + for sta_name in self.station_profile.station_names: + sta_status = self.json_get( + "port/1/1/" + str(sta_name).split(".")[2] + "?fields=alias,ap,channel,cx%20time%20(us),ip,mac,mode,dhcp%20(ms)", + debug_=self.debug) + self.test_result_data.append(sta_status['interface']) + + offset = 0 + self.chart_data = {} + for data in self.test_result_data: + if (int(data["cx time (us)"])/1000 <= self.pass_criteria) and (int(data["cx time (us)"])/1000 > 0): + self.chart_data[data['alias']] = float(data["cx time (us)"])/1000 + data['Result'] = "PASS" + else: + self.chart_data[data['alias']] = float(data["cx time (us)"]) / 1000 + offset +=1 + data['Result'] = "FAIL" + data["cx time (us)"] = str(float(data["cx time (us)"])/1000)+" / "+str(self.pass_criteria)+"ms" + + objective = 'The Client Connectivity Test is designed to test the Performance of the Access Point. It will tell the Average Connection time that station takes to connect to Wifi Access Point. It will tell you Pass/Fail Criteria and detailed Report for Client Connection' + + if offset == 0: + summary_result = 'PASS ' + str(len(self.ip_map)) + "/" + str(self.num_sta) + ' Clients are Connected in less than ' + str(self.pass_criteria) + " ms" + self.pass_fail = "FAIL" + else: + summary_result = 'FAIL ' + str(len(self.ip_map)) + "/" + str(self.num_sta) + ' Clients are Connected, and/or Some might got connected in more than ' + str(self.pass_criteria) + " ms" + self.pass_fail = "FAIL" + + + self.html = open(self.reports_path + self.test_name + "_" + self.session_id + ".html", 'w') + self.html_data = CreateHTML(path=self.reports_path, test_name=self.test_name, time_snap=str(datetime.datetime.now()), dut_ssid=self.ssid, test_conf_data={"Number of Clients":str(self.num_sta)}, + objective=objective, test_results={"summary": summary_result, "detail": {"keys": self.keys, "data": self.test_result_data}}, chart_data=self.chart_data, + chart_params={"chart_head": "Client Connection Time", "xlabel": "Clients", "ylabel": "Connection Time"}) + self.html.write(self.html_data.report) + self.html.close() + options = { + "enable-local-file-access": None + } + pdfkit.from_file(self.reports_path + self.test_name + "_" + self.session_id + ".html", + self.reports_path + self.test_name + "_" + self.session_id + '_report.pdf', options=options) + + try: + self.status_msg.update('6', {"data": 'None', "data": [], "label": "Client Connectivity Time"}) + except: + pass + + def stop(self): + self.station_profile.admin_down() + LFUtils.wait_until_ports_admin_down(port_list=self.station_profile.station_names) + try: + self.status_msg.update('7', {"data": 'None', "data": [], "label": "Client Connectivity Time"}) + except: + pass + def postcleanup(self): + self.station_profile.cleanup(delay=1) + try: + self.status_msg.update('8', {"data": 'None', "data": [], "label": "Client Connectivity Time"}) + except: + pass + +def main(): + parser = LFCliBase.create_bare_argparse(prog="connection_test.py", formatter_class=argparse.RawTextHelpFormatter, + epilog="About This Script") + + # Adding More Arguments for custom use + parser.add_argument('--ssid', help='--ssid of DUT', default="lexusdut") + parser.add_argument('--passwd', help='--passwd of dut', default="[BLANK]") + parser.add_argument('--radio', help='--radio to use on LANforge', default="wiphy1") + parser.add_argument('--security', help='--security of dut', default="open") + parser.add_argument('--session_id', help='--session_id is for websocket', default=getSessionID()) + parser.add_argument('--test_name', help='--test_name is for webconsole reports', default="Client Connectivity Test") + parser.add_argument('--num_clients', type=int, help='--num_sta is number of stations you want to create', default=2) + parser.add_argument('--pass_criteria', type=int, help='--pass_criteria is pass criteria for connection Time', default=300) + args = parser.parse_args() + + # Start Test + obj = ConnectionTest(lfclient_host=args.mgr, lfclient_port=args.mgr_port, + session_id=args.session_id, test_name=args.test_name, + dut_ssid=args.ssid, dut_passwd=args.passwd, dut_security=args.security, + num_sta=args.num_clients, radio=args.radio, pass_criteria=args.pass_criteria) + obj.precleanup() + obj.build() + obj.start() + obj.stop() + obj.postcleanup() + + # #(obj.chart_data) + try: + obj.status_msg.update('10', {"data": 'done...', "data": [], "label": "Client Connectivity Time"}) + except: + pass + for i in obj.status_msg.read()['messages']: + print(i) +def getSessionID(): + x = datetime.datetime.now() + id = x.strftime("%x").replace("/","_")+"_"+x.strftime("%x") + "_" + x.strftime("%X").split(":")[0] + "_" + x.strftime("%X").split(":")[1] + "_" + x.strftime("%X").split(":")[2]+str(x).split(".")[1] + id = str(id).replace("/", "_").split("P")[0].replace(" ","") + return id + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_bond.py b/lanforge/lanforge-scripts/py-scripts/create_bond.py new file mode 100755 index 000000000..b094622ef --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_bond.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +"""create_bond.py Script to create a bond + +This script can be used to create a bond, only one can be created at a time. Network devices must be specified +as a list of comma-separated items with no spaces. + +Use './create_bond.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import argparse +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateBond(LFCliBase): + def __init__(self, _network_dev_list=None, + _host=None, + _port=None, + _shelf=1, + _resource=1, + _debug_on=False): + super().__init__(_host, _port) + self.host = _host + self.shelf = _shelf + self.resource = _resource + self.timeout = 120 + self.debug = _debug_on + self.network_dev_list = _network_dev_list + + def build(self): + data = { + 'shelf': self.shelf, + 'resource': self.resource, + 'port': 'bond0000', + 'network_devs': self.network_dev_list + } + self.json_post("cli-json/add_bond", data) + time.sleep(3) + bond_set_port = { + "shelf": self.shelf, + "resource": self.resource, + "port": "bond0000", + "current_flags": 0x80000000, + "interest": 0x4000 # (0x2 + 0x4000 + 0x800000) # current, dhcp, down + } + self.json_post("cli-json/set_port", bond_set_port) + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='create_bond.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create bonds + ''', + + description='''\ + create_bond.py +-------------------- +Command example: +./create_bond.py + --network_dev_list eth0,eth1 + --debug + ''') + + required = parser.add_argument_group('required arguments') + required.add_argument('--network_dev_list', help='list of network devices in the bond, must be comma separated ' + 'with no spaces', required=True) + + args = parser.parse_args() + + create_bond = CreateBond(_host=args.mgr, + _port=args.mgr_port, + _network_dev_list=args.network_dev_list, + _debug_on=args.debug + ) + create_bond.build() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_bridge.py b/lanforge/lanforge-scripts/py-scripts/create_bridge.py new file mode 100755 index 000000000..c830e6460 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_bridge.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 + +""" + Script for creating a variable number of bridges. +""" +import sys +import os +import importlib +import pprint +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateBridge(Realm): + def __init__(self,sta_list,resource,target_device,radio, + _ssid=None, + _security=None, + _password=None, + _host=None, + _port=None, + _bridge_list=None, + _number_template="00000", + _resource=1, + _debug_on=False): + super().__init__(_host, + _port) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.bridge_list = _bridge_list + self.radio = radio + self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.sta_list = sta_list + self.resource = resource + self.target_device = target_device + if self.debug: + print("----- bridge List ----- ----- ----- ----- ----- ----- \n") + pprint.pprint(self.sta_list) + print("---- ~bridge List ----- ----- ----- ----- ----- ----- \n") + + + def build(self): + # Build bridges + + data = { + "shelf": 1, + "resource": self.resource, + "port": "br0", + "network_devs": "eth1,%s" % self.target_device + } + self.json_post("cli-json/add_br", data) + + bridge_set_port = { + "shelf": 1, + "resource": self.resource, + "port": "br0", + "current_flags": 0x80000000, + "interest": 0x4000 # (0x2 + 0x4000 + 0x800000) # current, dhcp, down + } + self.json_post("cli-json/set_port", bridge_set_port) + + + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='create_bridge.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create bridges + ''', + + description='''\ + create_bridge.py +-------------------- +Command example: +./create_bridge.py + --upstream_port eth1 + --radio wiphy0 + --num_bridges 3 + --target_device wlan0 + --security open + --ssid netgear + --passwd BLANK + --debug + ''') + required = parser.add_argument_group('required arguments') + required.add_argument('--target_device', help='Where the bridges should be connecting', required=True) + #required.add_argument('--security', help='WiFi Security protocol: < open | wep | wpa | wpa2 | wpa3 >', required=True) + + optional = parser.add_argument_group('optional arguments') + optional.add_argument('--num_bridges', help='Number of bridges to Create', required=False) + args = parser.parse_args() + #if args.debug: + # pprint.pprint(args) + # time.sleep(5) + if (args.radio is None): + raise ValueError("--radio required") + + num_bridge = 2 + if (args.num_bridges is not None) and (int(args.num_bridges) > 0): + num_bridges_converted = int(args.num_bridges) + num_bridge = num_bridges_converted + + bridge_list = LFUtils.port_name_series(prefix="bridge", + start_id=0, + end_id=num_bridge-1, + padding_number=10000, + radio=args.radio) + + create_bridge = CreateBridge(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _bridge_list=bridge_list, + radio=args.radio, + _debug_on=args.debug, + sta_list=bridge_list, + resource=1, + target_device=args.target_device) + + create_bridge.build() + print('Created %s bridges' % num_bridge) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_chamberview.py b/lanforge/lanforge-scripts/py-scripts/create_chamberview.py new file mode 100755 index 000000000..c8b4f2af2 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_chamberview.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +Note: Scenario names should be different, for each run of this script. + in case of same scenario name scenario will be appended to the same name. + +Note: Script for creating a chamberview scenario. + Run this script to set/create a chamber view scenario. + ex. on how to run this script: + + create_chamberview.py -m "localhost" -o "8080" -cs "scenario_name" + --line "Resource=1.1 Profile=STA-AC Amount=1 Uses-1=wiphy0 Uses-2=AUTO Freq=-1 + DUT=Test DUT_Radio=Radio-1 Traffic=http VLAN=" + --line "Resource=1.1 Profile=upstream Amount=1 Uses-1=eth1 Uses-2=AUTO Freq=-1 + DUT=Test DUT_Radio=Radio-1 Traffic=http VLAN=" + + ******************************** OR ******************************** + + create_chamberview.py -m "localhost" -o "8080" -cs "scenario_name" + --raw_line "profile_link 1.1 STA-AC 10 'DUT: temp Radio-1' tcp-dl-6m-vi wiphy0,AUTO -1" + --raw_line "profile_link 1.1 upstream 1 'DUT: temp Radio-1' tcp-dl-6m-vi eth1,AUTO -1" + +Output: + You should see build scenario with the given arguments at the end of this script. + To verify this: + open Chamber View -> Manage scenario +""" +import sys +import os +import importlib +import argparse +import time +import re + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cv = cv_test_manager.cv_test + + +class CreateChamberview(cv): + def __init__(self, + lfmgr="localhost", + port="8080", + ): + super().__init__( + lfclient_host=lfmgr, + lfclient_port=port, + ) + self.lfmgr = lfmgr + self.port = port + + def clean_cv_scenario(self,type="Network-Connectivity",scenario_name=None): + self.rm_cv_text_blob(type,scenario_name) + + def setup(self, + create_scenario="", + line="", + raw_line=[]): + + if raw_line: + print("creating %s scenario" % create_scenario) + for create_lines in raw_line: + self.pass_raw_lines_to_cv(create_scenario,create_lines[0]) + + #check for lines + if line: + scenario_name = create_scenario + line = line + Resource = "1.1" + Profile = "STA-AC" + Amount = "1" + DUT = "DUT" + DUT_Radio = "Radio-1" + Uses1 = "wiphy0" + Uses2 = "AUTO" + Traffic = "http" + Freq = "-1" + VLAN = "" + + for i in range(len(line)): + if " " in line[i][0]: + line[i][0] = (re.split(' ', line[i][0])) + elif "," in line[i][0]: + line[i][0] = (re.split(',', line[i][0])) + elif ", " in line[i][0]: + line[i][0] = (re.split(',', line[i][0])) + elif " ," in line[i][0]: + line[i][0] = (re.split(',', line[i][0])) + else: + print("Wrong arguments entered !") + exit(1) + + print("creating %s scenario" % scenario_name) + for j in range(len(line[i][0])): + line[i][0][j] = line[i][0][j].split("=") + for k in range(len(line[i][0][j])): + name = line[i][0][j][k] + if str(name) == "Resource" or str(name) == "Res" or str(name) == "R": + Resource = line[i][0][j][k + 1] + elif str(name) == "Profile" or str(name) == "Prof" or str(name) == "P": + Profile = line[i][0][j][k + 1] + elif str(name) == "Amount" or str(name) == "Sta" or str(name) == "A": + Amount = line[i][0][j][k + 1] + elif str(name) == "Uses-1" or str(name) == "U1" or str(name) == "U-1": + Uses1 = line[i][0][j][k + 1] + elif str(name) == "Uses-2" or str(name) == "U2" or str(name) == "U-2": + Uses2 = line[i][0][j][k + 1] + elif str(name) == "Freq" or str(name) == "Freq" or str(name) == "F": + Freq = line[i][0][j][k + 1] + elif str(name) == "DUT" or str(name) == "dut" or str(name) == "D": + DUT = line[i][0][j][k + 1] + elif str(name) == "DUT_Radio" or str(name) == "dr" or str(name) == "DR": + DUT_Radio = line[i][0][j][k + 1] + elif str(name) == "Traffic" or str(name) == "Traf" or str(name) == "T": + Traffic = line[i][0][j][k + 1] + elif str(name) == "VLAN" or str(name) == "Vlan" or str(name) == "V": + VLAN = line[i][0][j][k + 1] + else: + continue + + self.add_text_blob_line(scenario_name, + Resource, + Profile, + Amount, + DUT, + DUT_Radio, + Uses1, + Uses2, + Traffic, + Freq, + VLAN + ); # To manage scenario + if not line and not raw_line: + raise Exception("scenario creation failed") + + return True + + def build(self,scenario_name): + self.sync_cv() # chamberview sync + time.sleep(2) + self.apply_cv_scenario(scenario_name) # Apply scenario + self.show_text_blob(None, None, False) # Show changes on GUI + self.apply_cv_scenario(scenario_name) # Apply scenario + self.build_cv_scenario() # build scenario + tries = 0 + while (True): + self.get_popup_info_and_close() + if not self.get_cv_is_built(): + # It can take a while to build a large scenario, so wait-time + # is currently max of 5 minutes. + print("Waiting %i/300 for Chamber-View to be built." % (tries)) + tries += 1 + if (tries > 300): + break + time.sleep(1) + else: + break + print("completed building %s scenario" %scenario_name) + + +def main(): + parser = argparse.ArgumentParser( + prog='create_chamberview.py', + formatter_class=argparse.RawTextHelpFormatter, + description=""" + For Two line scenario use --line twice as shown in example, for multi line scenario + use --line argument to create multiple lines + \n + create_chamberview.py -m "localhost" -o "8080" -cs "scenario_name" + --line "Resource=1.1 Profile=STA-AC Amount=1 Uses-1=wiphy0 Uses-2=AUTO Freq=-1 + DUT=Test DUT_Radio=Radio-1 Traffic=http VLAN=" + --line "Resource=1.1 Profile=upstream Amount=1 Uses-1=eth1 Uses-2=AUTO Freq=-1 + DUT=Test DUT_Radio=Radio-1 Traffic=http VLAN=" + ******************************** OR ******************************** + create_chamberview.py -m "localhost" -o "8080" -cs "scenario_name" + --raw_line "profile_link 1.1 STA-AC 10 'DUT: temp Radio-1' tcp-dl-6m-vi wiphy0,AUTO -1" + --raw_line "profile_link 1.1 upstream 1 'DUT: temp Radio-1' tcp-dl-6m-vi eth1,AUTO -1" + + """) + parser.add_argument("-m", "--lfmgr", type=str, + help="address of the LANforge GUI machine (localhost is default)") + parser.add_argument("-o", "--port", type=int, default=8080, + help="IP Port the LANforge GUI is listening on (8080 is default)") + parser.add_argument("-cs", "--create_scenario", "--create_lf_scenario", type=str, + help="name of scenario to be created") + parser.add_argument("-l", "--line", action='append', nargs='+', + help="line number", default=[]) + parser.add_argument("-rl", "--raw_line", action='append', nargs=1, + help="raw lines", default=[]) + parser.add_argument("-ds", "--delete_scenario", default=False, action='store_true', + help="delete scenario (by default: False)") + args = parser.parse_args() + + Create_Chamberview = CreateChamberview(lfmgr=args.lfmgr, + port=args.port, + ) + if args.delete_scenario: + Create_Chamberview.clean_cv_scenario(type="Network-Connectivity", scenario_name=args.create_scenario) + + Create_Chamberview.setup(create_scenario=args.create_scenario, + line=args.line, + raw_line=args.raw_line) + Create_Chamberview.build(args.create_scenario) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_chamberview_dut.py b/lanforge/lanforge-scripts/py-scripts/create_chamberview_dut.py new file mode 100755 index 000000000..b21c75a89 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_chamberview_dut.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +Note: This script is used to create a DUT in chamber view. + Manual steps: + 1. open GUI + 2. click Chamber View + 3. right click on empty space in Scenario configuration select "New DUT" + 4. Enter Name (DUT Name), SSID , Security type, BSsid (if available) + 5. click on apply and OK + 6. you will see a DUT created in chamber view under scenario configuration + +Note : If entered DUT name is already created in lanforge, + it will overwrite on to that DUT ( All information will be overwritten ) + Which means it will "Update the DUT". + + If entered DUT name is not already in lanforge, + then new DUT will be created will all the provided information + +How to Run this: + ./create_chamberview_dut --lfmgr "localhost" --port "8080" --dut_name "dut_name" + --ssid "ssid_idx=0 ssid=NET1 security=WPA|WEP|11r|EAP-PEAP bssid=78:d2:94:bf:16:41" + --ssid "ssid_idx=1 ssid=NET1 security=WPA password=test bssid=78:d2:94:bf:16:40" + + --lfmgr = IP of lanforge + --port = Default 8080 + --dut_name = Enter name of DUT ( to update DUT enter same DUT name ) + ( enter new DUT name to create a new DUT) + --ssid = "ssid_idx=0 ssid=NET1 security=WPA|WEP|11r|EAP-PEAP bssid=78:d2:94:bf:16:41" + + --ssid will take = ssid_idx (from 0 to 7) : we can add upto 7 ssids to a DUT + = ssid : Name of SSID + = security : Security type WPA|WEP|11r|EAP-PEAP ( in case of multiple security add "|" + after each type ex. WPA|WEP (this will select WPA and WEP both) + = bssid : Enter BSSID + (if you dont want to give bssid + --ssid "ssid_idx=0 ssid=NET1 security=WPA|WEP|11r|EAP-PEAP" + ) + +Output : DUT will be created in Chamber View +""" +import sys +import os +import importlib +import argparse +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +# from cv_dut_profile import cv_dut as dut +cv_dut_profile = importlib.import_module("py-json.cv_dut_profile") +dut = cv_dut_profile.cv_dut +# from cv_test_manager import cv_test as cvtest +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cvtest = cv_test_manager.cv_test + + +class DUT(dut): + def __init__(self, + lfmgr="localhost", + port="8080", + dut_name="DUT", + ssid=[], + sw_version="NA", + hw_version="NA", + serial_num="NA", + model_num="NA", + dut_flags=None, + ): + super().__init__( + lfclient_host=lfmgr, + lfclient_port=port, + sw_version=sw_version, + hw_version=hw_version, + serial_num=serial_num, + model_num=model_num, + desired_dut_flags=dut_flags, + desired_dut_flags_mask=dut_flags + ) + self.cv_dut_name = dut_name + self.cv_test = cvtest(lfmgr, port) + self.dut_name = dut_name + self.ssid = ssid + + + def setup(self): + self.create_dut() + + def add_ssids(self): + flags = dict() + flags['wep'] = 0x8 + flags['wpa'] = 0x10 + flags['wpa2'] = 0x20 + flags['wpa3'] = 0x100 + flags['11r'] = 0x200 + flags['eap-ttls'] = 0x400 + flags['eap-peap'] = 0x800 + if self.ssid: + for j in range(len(self.ssid)): + self.ssid[j] = self.ssid[j][0].split(' ') + for k in range(len(self.ssid[j])): + self.ssid[j][k] = self.ssid[j][k].split('=') + d = dict() + for item in self.ssid[j]: + d[item[0].lower()] = item[1] + self.ssid[j] = d + self.ssid[j]['flag'] = [] + self.ssid[j].keys + + flag=0x0 + if 'security' in self.ssid[j].keys(): + self.ssid[j]['security'] = self.ssid[j]['security'].split('|') + for security in self.ssid[j]['security']: + try: + flag |= flags[security.lower()] + except: + pass + self.ssid[j]['flag'] = flag + + if 'bssid' not in self.ssid[j].keys(): + self.ssid[j]['bssid'] = '00:00:00:00:00:00' + + if 'password' not in self.ssid[j].keys(): + self.ssid[j]['password'] = '[BLANK]' + + self.add_ssid(dut_name=self.dut_name, + ssid_idx=self.ssid[j]['ssid_idx'], + ssid=self.ssid[j]['ssid'], + passwd=self.ssid[j]['password'], + bssid=self.ssid[j]['bssid'], + ssid_flags=self.ssid[j]['flag'], + ssid_flags_mask=0xFFFFFFFF + ) + + +def main(): + parser = argparse.ArgumentParser( + prog='create_chamberview_dut.py', + formatter_class=argparse.RawTextHelpFormatter, + description=""" + ./create_chamberview_dut -m "localhost" -o "8080" -d "dut_name" + --ssid "ssid_idx=0 ssid=NET1 security=WPA|WEP|11r|EAP-PEAP bssid=78:d2:94:bf:16:41" + --ssid "ssid_idx=1 ssid=NET1 security=WPA password=test bssid=78:d2:94:bf:16:40" + """) + parser.add_argument("-m", "--lfmgr", type=str, default="localhost", + help="address of the LANforge GUI machine (localhost is default)") + parser.add_argument("-o", "--port", type=str, default="8080", + help="IP Port the LANforge GUI is listening on (8080 is default)") + parser.add_argument("-d", "--dut_name", type=str, default="DUT", + help="set dut name") + parser.add_argument("-s", "--ssid", action='append', nargs=1, + help="SSID", default=[]) + + parser.add_argument("--sw_version", default="NA", help="DUT Software version.") + parser.add_argument("--hw_version", default="NA", help="DUT Hardware version.") + parser.add_argument("--serial_num", default="NA", help="DUT Serial number.") + parser.add_argument("--model_num", default="NA", help="DUT Model Number.") + parser.add_argument('--dut_flag', help='station flags to add', default=None, action='append') + + args = parser.parse_args() + new_dut = DUT(lfmgr=args.lfmgr, + port=args.port, + dut_name=args.dut_name, + ssid=args.ssid, + sw_version = args.sw_version, + hw_version = args.hw_version, + serial_num = args.serial_num, + model_num = args.model_num, + dut_flags=args.dut_flag + ) + + new_dut.setup() + new_dut.add_ssids() + new_dut.cv_test.show_text_blob(None, None, True) # Show changes on GUI + new_dut.cv_test.sync_cv() + time.sleep(2) + new_dut.cv_test.sync_cv() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_l3.py b/lanforge/lanforge-scripts/py-scripts/create_l3.py new file mode 100755 index 000000000..68457ce1a --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_l3.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +""" + + Create Layer-3 Cross Connection Using LANforge JSON AP : https://www.candelatech.com/cookbook.php?vol=fire&book=scripted+layer-3+test + Written by Candela Technologies Inc. + Updated by: Erin Grimes + Example Command: + ./create_l3.py --endp_a 'eth1' --endp_b 'eth2' --min_rate_a '56000' --min_rate_b '40000' +""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LANforge = importlib.import_module("py-json.LANforge") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +TestGroupProfile = realm.TestGroupProfile + + +class CreateL3(Realm): + def __init__(self, + name_prefix, + endp_b, + endp_a, + host="localhost", port=8080, mode=0, + min_rate_a=56, max_rate_a=0, + min_rate_b=56, max_rate_b=0, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port) + self.host = host + self.port = port + self.endp_b = endp_b + self.endp_a = endp_a + self.mode = mode + self.name_prefix = name_prefix + self.station_profile = self.new_station_profile() + self.cx_profile = self.new_l3_cx_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_list= LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=2, padding_number_=10000, radio='wiphy0') #Make radio a user defined variable from terminal. + self.cx_profile.host = self.host + self.cx_profile.port = self.port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = min_rate_a + self.cx_profile.side_a_max_bps = max_rate_a + self.cx_profile.side_b_min_bps = min_rate_b + self.cx_profile.side_b_max_bps = max_rate_b + + def pre_cleanup(self): + self.cx_profile.cleanup_prefix() + + def build(self): + self.cx_profile.create(endp_type="lf_udp", + side_a=self.endp_a, + side_b=self.endp_b, + sleep_time=0) + self._pass("PASS: Cross-connect build finished") + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='create_l3.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Generate traffic between ports + ''', + description='''\ + ''') + + required_args = None + for group in parser._action_groups: + if group.title == "required arguments": + required_args = group + break + if required_args is not None: + required_args.add_argument('--min_rate_a', help='--min_rate_a bps rate minimum for side_a', default=56000) + required_args.add_argument('--min_rate_b', help='--min_rate_b bps rate minimum for side_b', default=56000) + required_args.add_argument('--endp_a', help='--endp_a station list', default=["eth1"], action="append") + required_args.add_argument('--endp_b', help='--upstream port', default="eth2") + + optional_args = None + for group in parser._action_groups: + if group.title == "optional arguments": + optional_args = group + break; + if optional_args is not None: + optional_args.add_argument('--mode', help='Used to force mode of stations', default=0) + optional_args.add_argument('--ap', help='Used to force a connection to a particular AP') + optional_args.add_argument('--number_template', help='Start the station numbering with a particular number. Default is 0000', default=0000) + args = parser.parse_args() + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_sta = int(args.num_stations) + + # station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=int(args.number_template), end_id_=num_sta+int(args.number_template) - 1, padding_number_=10000, + # radio=args.radio) + ip_var_test = CreateL3(host=args.mgr, + port=args.mgr_port, + name_prefix="VT", + endp_a=args.endp_a, + endp_b=args.endp_b, + min_rate_a=args.min_rate_a, + min_rate_b=args.min_rate_b, + mode=args.mode, + _debug_on=args.debug) + + ip_var_test.pre_cleanup() + ip_var_test.build() + if not ip_var_test.passes(): + print(ip_var_test.get_fail_message()) + ip_var_test.exit_fail() + print('Created %s stations and connections' % num_sta) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_l3_stations.py b/lanforge/lanforge-scripts/py-scripts/create_l3_stations.py new file mode 100755 index 000000000..655210436 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_l3_stations.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 + +""" + This script will create a variable number of layer3 stations each with their own set of cross-connects and endpoints. + + Example script: + './create_l3_stations.py --radio wiphy0 --ssid lanforge --password password --security wpa2' +""" + +import sys +import os + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + +import argparse +from LANforge.lfcli_base import LFCliBase +from LANforge import LFUtils +from realm import Realm + + +class CreateL3(Realm): + def __init__(self, + ssid, security, password, sta_list, name_prefix, upstream, radio, + host="localhost", port=8080, mode=0, ap=None, + side_a_min_rate=56, side_a_max_rate=0, + side_b_min_rate=56, side_b_max_rate=0, + number_template="00000", use_ht160=False, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port) + self.upstream = upstream + self.host = host + self.port = port + self.ssid = ssid + self.sta_list = sta_list + self.security = security + self.password = password + self.radio = radio + self.mode = mode + self.ap = ap + self.number_template = number_template + self.debug = _debug_on + self.name_prefix = name_prefix + self.station_profile = self.new_station_profile() + self.cx_profile = self.new_l3_cx_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.debug = self.debug + self.station_profile.use_ht160 = use_ht160 + if self.station_profile.use_ht160: + self.station_profile.mode = 9 + self.station_profile.mode = mode + if self.ap is not None: + self.station_profile.set_command_param("add_sta", "ap", self.ap) + # self.station_list= LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=2, padding_number_=10000, radio='wiphy0') #Make radio a user defined variable from terminal. + + self.cx_profile.host = self.host + self.cx_profile.port = self.port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = side_a_min_rate + self.cx_profile.side_a_max_bps = side_a_max_rate + self.cx_profile.side_b_min_bps = side_b_min_rate + self.cx_profile.side_b_max_bps = side_b_max_rate + + def pre_cleanup(self): + self.cx_profile.cleanup_prefix() + for sta in self.sta_list: + self.rm_port(sta, check_exists=True) + + def build(self): + + self.station_profile.use_security(self.security, + self.ssid, + self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, + sta_names_=self.sta_list, + debug=self.debug) + self.cx_profile.create(endp_type="lf_udp", + side_a=self.station_profile.station_names, + side_b=self.upstream, + sleep_time=0) + self._pass("PASS: Station build finished") + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='create_l3_stations.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create stations to test connection and traffic on VAPs of varying security types (WEP, WPA, WPA2, WPA3, Open) + ''', + + description='''\ + create_l3_stations.py: + -------------------- + Generic command layout: + + python3 ./create_l3_stations.py + --upstream_port eth1 + --radio wiphy0 + --num_stations 32 + --security {open|wep|wpa|wpa2|wpa3} \\ + --mode 1 + {"auto" : "0", + "a" : "1", + "b" : "2", + "g" : "3", + "abg" : "4", + "abgn" : "5", + "bgn" : "6", + "bg" : "7", + "abgnAC" : "8", + "anAC" : "9", + "an" : "10", + "bgnAC" : "11", + "abgnAX" : "12", + "bgnAX" : "13", + --ssid netgear + --password admin123 + --a_min 1000 + --b_min 1000 + --ap "00:0e:8e:78:e1:76" + --number_template 0000 + --debug + ''') + + required_args = None + for group in parser._action_groups: + if group.title == "required arguments": + required_args = group + break; + if required_args is not None: + required_args.add_argument('--a_min', help='--a_min bps rate minimum for side_a', default=256000) + required_args.add_argument('--b_min', help='--b_min bps rate minimum for side_b', default=256000) + + optional_args = None + for group in parser._action_groups: + if group.title == "optional arguments": + optional_args = group + break; + if optional_args is not None: + optional_args.add_argument('--mode', help='Used to force mode of stations') + optional_args.add_argument('--ap', help='Used to force a connection to a particular AP') + optional_args.add_argument('--number_template', help='Start the station numbering with a particular number. Default is 0000', default=0000) + optional_args.add_argument('--station_list', help='Optional: User defined station names', action='append',default=None) + args = parser.parse_args() + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_sta = int(args.num_stations) + + if args.station_list is None: + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=int(args.number_template), end_id_=num_sta+int(args.number_template) - 1, padding_number_=10000, + radio=args.radio) + else: + station_list = args.station_list + + ip_var_test = CreateL3(host=args.mgr, + port=args.mgr_port, + number_template=str(args.number_template), + sta_list=station_list, + name_prefix="VT", + upstream=args.upstream_port, + ssid=args.ssid, + password=args.passwd, + radio=args.radio, + security=args.security, + use_ht160=False, + side_a_min_rate=args.a_min, + side_b_min_rate=args.b_min, + mode=args.mode, + ap=args.ap, + _debug_on=args.debug) + + ip_var_test.pre_cleanup() + ip_var_test.build() + if not ip_var_test.passes(): + print(ip_var_test.get_fail_message()) + ip_var_test.exit_fail() + print('Creates %s stations and connections' % num_sta) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_l4.py b/lanforge/lanforge-scripts/py-scripts/create_l4.py new file mode 100755 index 000000000..12662b499 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_l4.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +""" + This script will create a variable number of layer4 stations each with their own set of cross-connects and endpoints. + + Use './create_l4.py --help' to see command line usage and options +""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +TestGroupProfile = realm.TestGroupProfile + + +class CreateL4(Realm): + def __init__(self, + ssid, security, password, sta_list, name_prefix, upstream, radio, + host="localhost", port=8080, mode = 0, ap=None, + side_a_min_rate=56, side_a_max_rate=0, + side_b_min_rate=56, side_b_max_rate=0, + number_template="00000", use_ht160=False, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port) + self.upstream = upstream + self.host = host + self.port = port + self.ssid = ssid + self.sta_list = sta_list + self.security = security + self.password = password + self.radio = radio + self.mode= mode + self.ap=ap + self.number_template = number_template + self.debug = _debug_on + self.name_prefix = name_prefix + self.station_profile = self.new_station_profile() + self.cx_profile = self.new_l4_cx_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.debug = self.debug + self.station_profile.use_ht160 = use_ht160 + if self.station_profile.use_ht160: + self.station_profile.mode = 9 + self.station_profile.mode = mode + if self.ap is not None: + self.station_profile.set_command_param("add_sta", "ap",self.ap) + #self.station_list= LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=2, padding_number_=10000, radio='wiphy0') #Make radio a user defined variable from terminal. + + + self.cx_profile.host = self.host + self.cx_profile.port = self.port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = side_a_min_rate + self.cx_profile.side_a_max_bps = side_a_max_rate + self.cx_profile.side_b_min_bps = side_b_min_rate + self.cx_profile.side_b_max_bps = side_b_max_rate + + def cleanup(self): + self.cx_profile.cleanup() + self.station_profile.cleanup() + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=self.station_profile.station_names, + debug=self.debug) + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5, debug_=self.debug, suppress_related_commands_=True) + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='create_l4.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create stations to test connection and traffic on VAPs of varying security types (WEP, WPA, WPA2, WPA3, Open) + ''', + + description='''\ +layer4.py: +-------------------- +Generic command layout: + +python3 ./layer4.py + --upstream_port eth1 + --radio wiphy0 + --num_stations 32 + --security {open|wep|wpa|wpa2|wpa3} \\ + --mode 1 + {"auto" : "0", + "a" : "1", + "b" : "2", + "g" : "3", + "abg" : "4", + "abgn" : "5", + "bgn" : "6", + "bg" : "7", + "abgnAC" : "8", + "anAC" : "9", + "an" : "10", + "bgnAC" : "11", + "abgnAX" : "12", + "bgnAX" : "13", + --ssid netgear + --password admin123 + --a_min 1000 + --b_min 1000 + --ap "00:0e:8e:78:e1:76" + --debug + ''') + + required_args=None + for group in parser._action_groups: + if group.title == "required arguments": + required_args=group + break; + if required_args is not None: + required_args.add_argument('--a_min', help='--a_min bps rate minimum for side_a', default=256000) + required_args.add_argument('--b_min', help='--b_min bps rate minimum for side_b', default=256000) + + optional_args=None + for group in parser._action_groups: + if group.title == "optional arguments": + optional_args=group + break; + if optional_args is not None: + optional_args.add_argument('--mode',help='Used to force mode of stations', default=0) + optional_args.add_argument('--ap',help='Used to force a connection to a particular AP') + args = parser.parse_args() + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_sta = int(args.num_stations) + + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=num_sta-1, padding_number_=10000, radio=args.radio) + ip_var_test = CreateL4(host=args.mgr, + port=args.mgr_port, + number_template="0000", + sta_list=station_list, + name_prefix="VT", + upstream=args.upstream_port, + ssid=args.ssid, + password=args.passwd, + radio=args.radio, + security=args.security, + use_ht160=False, + side_a_min_rate=args.a_min, + side_b_min_rate=args.b_min, + mode=args.mode, + ap=args.ap, + _debug_on=args.debug) + + ip_var_test.cleanup() + ip_var_test.build() + if not ip_var_test.passes(): + print(ip_var_test.get_fail_message()) + ip_var_test.exit_fail() + + print('Created %s stations and connections' % num_sta) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_macvlan.py b/lanforge/lanforge-scripts/py-scripts/create_macvlan.py new file mode 100755 index 000000000..5694f0068 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_macvlan.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +add_file_endp = importlib.import_module("py-json.LANforge.add_file_endp") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateMacVlan(Realm): + def __init__(self, host, port, + radio="wiphy0", + upstream_port="eth1", + num_ports=1, + macvlan_parent=None, + first_mvlan_ip=None, + netmask=None, + gateway=None, + dhcp=True, + port_list=[], + ip_list=None, + connections_per_port=1, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port) + self.port = port + self.radio = radio + self.upstream_port = upstream_port + self.port_list = [] + self.connections_per_port = connections_per_port + self.ip_list = ip_list + self.netmask = netmask + self.gateway = gateway + self.dhcp = dhcp + if macvlan_parent is not None: + self.macvlan_parent = macvlan_parent + self.port_list = port_list + + self.mvlan_profile = self.new_mvlan_profile() + + + self.mvlan_profile.num_macvlans = int(num_ports) + self.mvlan_profile.desired_macvlans = self.port_list + self.mvlan_profile.macvlan_parent = self.macvlan_parent + self.mvlan_profile.dhcp = dhcp + self.mvlan_profile.netmask = netmask + self.mvlan_profile.first_ip_addr = first_mvlan_ip + self.mvlan_profile.gateway = gateway + + self.created_ports = [] + + def build(self): + # Build stations + print("Creating MACVLANs") + self.mvlan_profile.create(admin_down=False, sleep_time=.5, debug=self.debug) + self._pass("PASS: MACVLAN build finished") + self.created_ports += self.mvlan_profile.created_macvlans + +def main(): + parser = LFCliBase.create_bare_argparse( + prog='create_macvlan.py', + # formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Creates MACVLAN endpoints.''', + + description='''\ +create_macvlan.py: +-------------------- +Generic command layout: +./create_macvlan.py --macvlan_parent --num_ports + --first_mvlan_ip --netmask --gateway + +./create_macvlan.py --macvlan_parent eth2 --num_ports 3 --first_mvlan_ip 192.168.92.13 + --netmask 255.255.255.0 --gateway 192.168.92.1 + +./create_macvlan.py --radio 1.wiphy0 --macvlan_parent eth1 --num_ports 3 + --use_ports eth1#0,eth1#1,eth1#2 --connections_per_port 2 + +./create_macvlan.py --radio 1.wiphy0 --macvlan_parent eth1 --num_ports 3 + --first_mvlan_ip 10.40.3.100 --netmask 255.255.240.0 --gateway 10.40.0.1 + --add_to_group test_wo + +./create_macvlan.py --radio 1.wiphy0 --macvlan_parent eth1 --num_ports 3 + --use_ports eth1#0=10.40.3.103,eth1#1,eth1#2 --connections_per_port 2 + --netmask 255.255.240.0 --gateway 10.40.0.1 + +''') + parser.add_argument('--num_stations', help='Number of stations to create', default=0) + parser.add_argument('--radio', help='radio EID, e.g: 1.wiphy2') + parser.add_argument('-u', '--upstream_port', + help='non-station port that generates traffic: ., e.g: 1.eth1', + default='1.eth1') + parser.add_argument('--macvlan_parent', help='specifies parent port for macvlan creation', default=None) + parser.add_argument('--first_port', help='specifies name of first port to be used', default=None) + parser.add_argument('--num_ports', help='number of ports to create', default=1) + parser.add_argument('--connections_per_port', help='specifies number of connections to be used per port', default=1, + type=int) + parser.add_argument('--use_ports', help='list of comma separated ports to use with ips, \'=\' separates name and ip' + '{ port_name1=ip_addr1,port_name1=ip_addr2 }. ' + 'Ports without ips will be left alone', default=None) + parser.add_argument('--first_mvlan_ip', help='specifies first static ip address to be used or dhcp', default=None) + parser.add_argument('--netmask', help='specifies netmask to be used with static ip addresses', default=None) + parser.add_argument('--gateway', help='specifies default gateway to be used with static addressing', default=None) + parser.add_argument('--cxs', help='list of cxs to add/remove depending on use of --add_to_group or --del_from_group' + , default=None) + args = parser.parse_args() + + port_list = [] + ip_list = [] + if args.first_port is not None and args.use_ports is not None: + if args.first_port.startswith("sta"): + if (args.num_ports is not None) and (int(args.num_ports) > 0): + start_num = int(args.first_port[3:]) + num_ports = int(args.num_ports) + port_list = LFUtils.port_name_series(prefix="sta", start_id=start_num, end_id=start_num + num_ports - 1, + padding_number=10000, + radio=args.radio) + else: + if (args.num_ports is not None) and args.macvlan_parent is not None and (int(args.num_ports) > 0) \ + and args.macvlan_parent in args.first_port: + start_num = int(args.first_port[args.first_port.index('#') + 1:]) + num_ports = int(args.num_ports) + port_list = LFUtils.port_name_series(prefix=args.macvlan_parent + "#", start_id=start_num, + end_id=start_num + num_ports - 1, padding_number=100000, + radio=args.radio) + else: + raise ValueError("Invalid values for num_ports [%s], macvlan_parent [%s], and/or first_port [%s].\n" + "first_port must contain parent port and num_ports must be greater than 0" + % (args.num_ports, args.macvlan_parent, args.first_port)) + else: + if args.use_ports is None: + num_ports = int(args.num_ports) + port_list = LFUtils.port_name_series(prefix=args.macvlan_parent + "#", start_id=0, + end_id=num_ports - 1, padding_number=100000, + radio=args.radio) + else: + temp_list = args.use_ports.split(',') + for port in temp_list: + port_list.append(port.split('=')[0]) + if '=' in port: + ip_list.append(port.split('=')[1]) + else: + ip_list.append(0) + + if len(port_list) != len(ip_list): + raise ValueError(temp_list, " ports must have matching ip addresses!") + + if args.first_mvlan_ip is not None: + if args.first_mvlan_ip.lower() == "dhcp": + dhcp = True + else: + dhcp = False + else: + dhcp = True + # print(port_list) + + # exit(1) + ip_test = CreateMacVlan(args.mgr, + args.mgr_port, + port_list=port_list, + ip_list=ip_list, + upstream_port=args.upstream_port, + _debug_on=args.debug, + macvlan_parent=args.macvlan_parent, + first_mvlan_ip=args.first_mvlan_ip, + netmask=args.netmask, + gateway=args.gateway, + dhcp=dhcp, + num_ports=args.num_ports, + connections_per_port=args.connections_per_port, + # want a mount options param + ) + + ip_test.build() + print('Created %s MacVlan connections' % args.num_ports) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_qvlan.py b/lanforge/lanforge-scripts/py-scripts/create_qvlan.py new file mode 100755 index 000000000..aa6ac8a1b --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_qvlan.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +add_file_endp = importlib.import_module("py-json.LANforge.add_file_endp") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateQVlan(Realm): + def __init__(self, + host="localhost", + port=8080, + qvlan_parent=None, + num_ports=1, + dhcp=True, + netmask=None, + first_qvlan_ip=None, + gateway=None, + port_list=[], + ip_list=[], + exit_on_error=False, + debug=False): + super().__init__(host, port) + self.host = host + self.port = port + self.qvlan_parent = qvlan_parent + self.debug = debug + self.port_list = port_list + self.ip_list = ip_list + self.exit_on_error = exit_on_error + + self.qvlan_profile = self.new_qvlan_profile() + self.qvlan_profile.num_qvlans = int(num_ports) + self.qvlan_profile.desired_qvlans = self.port_list + self.qvlan_profile.qvlan_parent = self.qvlan_parent + self.qvlan_profile.dhcp = dhcp + self.qvlan_profile.netmask = netmask + self.qvlan_profile.first_ip_addr = first_qvlan_ip + self.qvlan_profile.gateway = gateway + self.qvlan_profile.dhcp = dhcp + + def build(self): + print("Creating QVLAN stations") + self.qvlan_profile.create(admin_down=False, sleep_time=.5, debug=self.debug) + + +def main(): + parser = LFCliBase.create_bare_argparse( + prog='create_qvlan.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Creates Q-VLAN stations attached to the Eth port of the user's choice.''', + + description='''\ + create_qvlan.py: + --------------------- + Generic command ''') + parser.add_argument('--radio', help='radio EID, e.g: 1.wiphy2') + parser.add_argument('--qvlan_parent', help='specifies parent port for qvlan creation', default=None) + parser.add_argument('--first_port', help='specifies name of first port to be used', default=None) + parser.add_argument('--num_ports', help='number of ports to create', default=1) + parser.add_argument('--first_qvlan_ip', help='specifies first static ip address to be used or dhcp', default=None) + parser.add_argument('--netmask', help='specifies netmask to be used with static ip addresses', default=None) + parser.add_argument('--gateway', help='specifies default gateway to be used with static addressing', default=None) + parser.add_argument('--use_ports', + help='list of comma separated ports to use with ips, \'=\' separates name and ip { port_name1=ip_addr1,port_name1=ip_addr2 }. Ports without ips will be left alone', + default=None) + tg_group = parser.add_mutually_exclusive_group() + tg_group.add_argument('--add_to_group', help='name of test group to add cxs to', default=None) + parser.add_argument('--cxs', help='list of cxs to add/remove depending on use of --add_to_group or --del_from_group' + , default=None) + parser.add_argument('--use_qvlans', help='will create qvlans', action='store_true', default=False) + + + args = parser.parse_args() + + update_group_args = { + "name": None, + "action": None, + "cxs": None + } + # update_group_args['name'] = + if args.first_qvlan_ip in ["dhcp", "DHCP"]: + dhcp = True + else: + dhcp = False + update_group_args['action'] = "add" + update_group_args['cxs'] = args.cxs + port_list = [] + ip_list = [] + if args.first_port is not None and args.use_ports is not None: + if args.first_port.startswith("sta"): + if (args.num_ports is not None) and (int(args.num_ports) > 0): + start_num = int(args.first_port[3:]) + num_ports = int(args.num_ports) + port_list = LFUtils.port_name_series(prefix="sta", start_id=start_num, end_id=start_num + num_ports - 1, + padding_number=10000, + radio=args.radio) + print(1) + else: + if (args.num_ports is not None) and args.qvlan_parent is not None and (int(args.num_ports) > 0) \ + and args.qvlan_parent in args.first_port: + start_num = int(args.first_port[args.first_port.index('#') + 1:]) + num_ports = int(args.num_ports) + port_list = LFUtils.port_name_series(prefix=args.qvlan_parent + "#", start_id=start_num, + end_id=start_num + num_ports - 1, padding_number=10000, + radio=args.radio) + print(2) + else: + raise ValueError("Invalid values for num_ports [%s], qvlan_parent [%s], and/or first_port [%s].\n" + "first_port must contain parent port and num_ports must be greater than 0" + % (args.num_ports, args.qvlan_parent, args.first_port)) + else: + if args.use_ports is None: + num_ports = int(args.num_ports) + port_list = LFUtils.port_name_series(prefix=args.qvlan_parent + "#", start_id=1, + end_id=num_ports, padding_number=10000, + radio=args.radio) + print(3) + else: + temp_list = args.use_ports.split(',') + for port in temp_list: + port_list.append(port.split('=')[0]) + if '=' in port: + ip_list.append(port.split('=')[1]) + else: + ip_list.append(0) + + if len(port_list) != len(ip_list): + raise ValueError(temp_list, " ports must have matching ip addresses!") + + print(port_list) + print(ip_list) + create_qvlan = CreateQVlan(args.mgr, + args.mgr_port, + qvlan_parent=args.qvlan_parent, + num_ports=args.num_ports, + dhcp=dhcp, + netmask=args.netmask, + first_qvlan_ip=args.first_qvlan_ip, + gateway=args.gateway, + port_list=port_list, + ip_list=ip_list, + debug=args.debug) + create_qvlan.build() + print('Created %s QVLAN stations' % num_ports) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_station.py b/lanforge/lanforge-scripts/py-scripts/create_station.py new file mode 100755 index 000000000..55b1da060 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_station.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +""" + Script for creating a variable number of stations. +""" +import sys +import os +import importlib +import argparse +import pprint + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateStation(Realm): + def __init__(self, + _ssid=None, + _security=None, + _password=None, + _host=None, + _port=None, + _mode=0, + _sta_list=None, + _sta_flags=None, + _number_template="00000", + _radio="wiphy0", + _proxy_str=None, + _debug_on=False, + _up=True, + _set_txo_data=None, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(_host, + _port) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.mode = _mode + self.sta_list = _sta_list + self.sta_flags = _sta_flags + self.radio = _radio + self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.up = _up + self.set_txo_data = _set_txo_data + self.station_profile = self.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = self.mode + if self.sta_flags is not None: + self.station_profile.desired_add_sta_flags = self.sta_flags + self.station_profile.desired_add_sta_mask = self.sta_flags + + if self.debug: + print("----- Station List ----- ----- ----- ----- ----- ----- \n") + pprint.pprint(self.sta_list) + print("---- ~Station List ----- ----- ----- ----- ----- ----- \n") + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + if self.set_txo_data is not None: + self.station_profile.set_wifi_txo(txo_ena=self.set_txo_data["txo_enable"], + tx_power=self.set_txo_data["txpower"], + pream=self.set_txo_data["pream"], + mcs=self.set_txo_data["mcs"], + nss=self.set_txo_data["nss"], + bw=self.set_txo_data["bw"], + retries=self.set_txo_data["retries"], + sgi=self.set_txo_data["sgi"], ) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + if self.up: + self.station_profile.admin_up() + + self._pass("PASS: Station build finished") + + +def main(): + parser = LFCliBase.create_basic_argparse( # see create_basic_argparse in ../py-json/LANforge/lfcli_base.py + prog='create_station.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create stations + ''', + + description='''\ + create_station.py + -------------------- + Command example: + ./create_station.py + --radio wiphy0 + --start_id 2 + --num_stations 3 + --security open + --ssid netgear + --passwd BLANK + --debug + ''') + required = parser.add_argument_group('required arguments') + required.add_argument('--start_id', help='--start_id default 0', default=0) + + optional = parser.add_argument_group('Optional arguments') + optional.add_argument('--mode', help='Mode for your station (as a number)',default=0) + optional.add_argument('--station_flag', help='station flags to add', required=False, default=None, action='append') + + args = parser.parse_args() + # if args.debug: + # pprint.pprint(args) + # time.sleep(5) + if (args.radio is None): + raise ValueError("--radio required") + + start_id = 0 + if (args.start_id != 0): + start_id = int(args.start_id) + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + station_list = LFUtils.port_name_series(prefix="sta", + start_id=start_id, + end_id=start_id + num_sta - 1, + padding_number=10000, + radio=args.radio) + + print("station_list {}".format(station_list)) + set_txo_data={ + "txo_enable": 1, + "txpower": 255, + "pream": 0, + "mcs": 0, + "nss": 0, + "bw": 3, + "retries": 1, + "sgi": 0 + } + + create_station = CreateStation(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _sta_list=station_list, + _sta_flags=args.station_flag, + _mode=args.mode, + _radio=args.radio, + _set_txo_data=None, + _proxy_str=args.proxy, + _debug_on=args.debug) + + create_station.build() + print('Created %s stations' % num_sta) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_station_from_df.py b/lanforge/lanforge-scripts/py-scripts/create_station_from_df.py new file mode 100755 index 000000000..29189dbdd --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_station_from_df.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +""" + Script for creating a variable number of stations. +""" +import sys +import os +import importlib +import argparse +import pandas as pd +import pprint + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateStation(Realm): + def __init__(self, + _ssid=None, + _security=None, + _password=None, + _host=None, + _port=None, + _sta_list=None, + _number_template="00000", + _radio="wiphy0", + _proxy_str=None, + _debug_on=False, + _up=True, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(_host, + _port) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.sta_list = _sta_list + self.radio = _radio + self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.up = _up + self.station_profile = self.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + if self.debug: + print("----- Station List ----- ----- ----- ----- ----- ----- \n") + pprint.pprint(self.sta_list) + print("---- ~Station List ----- ----- ----- ----- ----- ----- \n") + + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + if self.up: + self.station_profile.admin_up() + + self._pass("PASS: Station build finished") + + +def main(): + required=[] + required.append({'name':'--df','help':'Which file you want to build stations off of?'}) + parser = LFCliBase.create_basic_argparse( + prog='create_station_from_df.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create stations + ''', + description='''\ + create_station.py + -------------------- + Command example: + ./create_station_from_df.py + --upstream_port eth1 + --df df.csv + --security open + --ssid netgear + --passwd BLANK + --debug + ''', + more_required=required) + + args = parser.parse_args() + + df=pd.read_csv(args.df) + unique=df[['radio','ssid','passwd','security']].drop_duplicates().reset_index(drop=True) + for item in unique.index: + uniquedf=unique.iloc[item] + df1=df.merge(pd.DataFrame(uniquedf).transpose(),on=['radio','ssid','passwd','security']) + try: + radio=uniquedf['radio'] + except: + radio=args.radio + station_list=df1['station'] + try: + ssid=uniquedf['ssid'] + passwd=uniquedf['passwd'] + security=uniquedf['security'] + except: + ssid=args.ssid + passwd=args.passwd + security=args.security + create_station = CreateStation(_host=args.mgr, + _port=args.mgr_port, + _ssid=ssid, + _password=passwd, + _security=security, + _sta_list=station_list, + _radio=radio, + _proxy_str=args.proxy, + _debug_on=args.debug) + + create_station.build() + print('Created %s stations' % len(unique.index)) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_vap.py b/lanforge/lanforge-scripts/py-scripts/create_vap.py new file mode 100755 index 000000000..d627998bf --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_vap.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +""" + Script for creating a variable number of VAPs. +""" +import sys +import os +import importlib +import argparse +import pprint + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateVAP(Realm): + def __init__(self, + _ssid=None, + _security=None, + _password=None, + _mac=None, + _host=None, + _port=None, + _vap_list=None, + _resource=None, + _vap_flags=None, + _mode=None, + _number_template="00000", + _radio=None, + _channel=36, + _country_code=0, + _nss=False, + _bridge=False, + _proxy_str=None, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False, + _dhcp=True): + super().__init__(_host, + _port) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.vap_list = _vap_list + self.resource = _resource + if _vap_flags is None: + self.vap_flags = ["wpa2_enable", "80211u_enable", "create_admin_down"] + else: + self.vap_flags = _vap_flags + self.mode = _mode + self.radio = _radio + self.channel = _channel + self.country_code = _country_code + self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.dhcp = _dhcp + self.bridge = _bridge + self.vap_profile = self.new_vap_profile() + self.vap_profile.vap_name = self.vap_list + self.vap_profile.ssid = self.ssid + self.vap_profile.security = self.security + self.vap_profile.ssid_pass = self.password + self.vap_profile.dhcp = self.dhcp + self.vap_profile.mode = self.mode + self.vap_profile.desired_add_vap_flags = self.vap_flags + ["wpa2_enable", "80211u_enable", "create_admin_down"] + self.vap_profile.desired_add_vap_flags_mask = self.vap_flags + ["wpa2_enable", "80211u_enable", "create_admin_down"] + if self.debug: + print("----- VAP List ----- ----- ----- ----- ----- ----- \n") + pprint.pprint(self.vap_list) + print("---- ~VAP List ----- ----- ----- ----- ----- ----- \n") + + def build(self): + # Build VAPs + self.vap_profile.use_security(self.security, self.ssid, passwd=self.password) + + print("Creating VAPs") + self.vap_profile.create(resource = self.resource, + radio = self.radio, + channel = self.channel, + country=self.country_code, + up_ = True, + debug = False, + use_ht40=True, + use_ht80=True, + use_ht160=False, + suppress_related_commands_ = True, + use_radius=False, + hs20_enable=False, + bridge=self.bridge) + self._pass("PASS: VAP build finished") + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='create_vap.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create VAPs + ''', + + description='''\ + create_vap.py +-------------------- +Command example: +./create_vap.py + --upstream_port eth1 + --radio wiphy0 + --num_vaps 3 + --security open + --ssid netgear + --passwd BLANK + --debug + ''') + + optional = parser.add_argument_group('optional arguments') + optional.add_argument('--num_vaps', help='Number of VAPs to Create', required=False, default=1) + optional.add_argument('--vap_flag', help='VAP flags to add', required=False, default=None, action='append') + optional.add_argument('--bridge', help='Create a bridge connecting the VAP to a port', required=False, default=False) + optional.add_argument('--mac', help='Custom mac address', default="xx:xx:xx:xx:*:xx") + optional.add_argument('--mode', default='AUTO') + optional.add_argument('--channel', default=36) + optional.add_argument('--country_code', default=0) + optional.add_argument('--nss', default=False) + optional.add_argument('--resource', default=1) + optional.add_argument('--start_id', default=0) + optional.add_argument('--vap_name',default=None) + args = parser.parse_args() + #if args.debug: + # pprint.pprint(args) + # time.sleep(5) + if (args.radio is None): + raise ValueError("--radio required") + + num_vap = int(args.num_vaps) + + vap_list = LFUtils.port_name_series(prefix="vap", + start_id=int(args.start_id), + end_id=num_vap-1, + padding_number=10000, + radio=args.radio) + #print(args.passwd) + #print(args.ssid) + + if args.vap_name is None: + for vap in vap_list: + create_vap = CreateVAP(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _mode=args.mode, + _vap_list=vap, + _resource=args.resource, + _vap_flags=args.vap_flag, + _radio=args.radio, + _channel=args.channel, + _country_code=args.country_code, + _nss=args.nss, + _proxy_str=args.proxy, + _bridge=args.bridge, + _debug_on=args.debug) + print('Creating VAP') + + create_vap.build() + else: + vap_name = "vap"+args.vap_name + create_vap = CreateVAP(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _mode=args.mode, + _vap_list=vap_name, + _resource=args.resource, + _vap_flags=args.vap_flag, + _radio=args.radio, + _channel=args.channel, + _country_code=args.country_code, + _nss=args.nss, + _proxy_str=args.proxy, + _bridge=args.bridge, + _debug_on=args.debug) + print('Creating VAP') + + create_vap.build() + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/create_vr.py b/lanforge/lanforge-scripts/py-scripts/create_vr.py new file mode 100755 index 000000000..3d9f23206 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_vr.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" + Script for creating a variable number of virtual routers. +""" +import sys +import os +import importlib +import time +from pprint import pprint + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateVR(Realm): + def __init__(self, + lfclient_host="localhost", + lfclient_port=8080, + debug=False, + # resource=1, # USE name=1.2.vr0 convention instead + vr_name=None, + ports_list=(), + services_list=(), + _exit_on_error=False, + _exit_on_fail=False, + _proxy_str=None, + _capture_signal_list=()): + super().__init__(lfclient_host=lfclient_host, + lfclient_port=lfclient_port, + debug_=debug, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail, + _proxy_str=_proxy_str, + _capture_signal_list=_capture_signal_list) + + eid_name = self.name_to_eid(vr_name) + self.vr_name = eid_name + self.ports_list = ports_list + self.services_list = services_list + self.vr_profile = self.new_vr_profile() + + def clean(self): + if (self.vr_name is None) or (self.vr_profile.vr_eid is None) and (self.vr_profile.vr_eid) == "": + print("No vr_eid to clean") + return + self.rm_port("1.1.rd90a", debug_=self.debug) + self.rm_port("1.1.rd90b", debug_=self.debug) + self.wait_until_ports_disappear(sta_list=["1.1.rd90a", "1.1.rd90b"], + debug_=self.debug) + + if (self.vr_profile.vr_eid is not None) \ + and (self.vr_profile.vr_eid[1] is not None) \ + and (self.vr_profile.vr_eid[2] is not None): + self.vr_profile.cleanup(debug=self.debug) + + if (self.vr_name is not None) \ + and (self.vr_name[1] is not None) \ + and (self.vr_name[2] is not None): + data = { + "shelf": 1, + "resource": self.vr_name[1], + "router_name": self.vr_name[2] + } + self.json_post("/cli-json/rm_vr", data, debug_=self.debug) + time.sleep(1) + self.json_post("/cli-json/nc_show_vr", { + "shelf": 1, + "resource": self.vr_name[1], + "router": "all" + }, debug_=self.debug) + self.json_post("/cli-json/nc_show_vrcx", { + "shelf": 1, + "resource": self.vr_name[1], + "cx_name": "all" + }, debug_=self.debug) + + + def build(self): + self.vr_profile.apply_netsmith(self.vr_name[1], delay=5, debug=self.debug) + self.json_post("/cli-json/add_rdd", { + "shelf": 1, + "resource": self.vr_name[1], + "port": "rd90a", + "peer_ifname": "rd90b", + "report_timer": "3000" + }) + self.json_post("/cli-json/add_rdd", { + "shelf": 1, + "resource": self.vr_name[1], + "port": "rd90b", + "peer_ifname": "rd90a", + "report_timer": "3000" + }) + self.wait_until_ports_appear(sta_list=["1.1.rd90a", "1.1.rd90b"], debug_=self.debug) + self.vr_profile.vrcx_list(resource=self.vr_name[1], do_sync=True) # do_sync + self.vr_profile.create(vr_name=self.vr_name, debug=self.debug) + self.vr_profile.sync_netsmith(resource=self.vr_name[1], debug=self.debug) + self._pass("created router") + + def start(self): + """ + Move a vrcx into a router and then movie it out + :return: void + """ + # move rd90a into router + self.vr_profile.refresh_netsmith(resource=self.vr_name[1], debug=self.debug) + if self.debug: + pprint(("vr_eid", self.vr_name)) + self.vr_profile.wait_until_vrcx_appear(resource=self.vr_name[1], name_list=["rd90a", "rd90b"]) + self.vr_profile.add_vrcx(vr_eid=self.vr_name, connection_name_list="rd90a", debug=True) + + self.vr_profile.refresh_netsmith(resource=self.vr_name[1], debug=self.debug) + # test to make sure that vrcx is inside vr we expect + self.vr_profile.vrcx_list(resource=self.vr_name[1], do_sync=True) + vr_list = self.vr_profile.router_list(resource=self.vr_name[1], do_refresh=True) + router = self.vr_profile.find_cached_router(resource=self.vr_name[1], router_name=self.vr_name[2]) + pprint(("cached router 120: ", router)) + router_eid = LFUtils.name_to_eid(router["eid"]) + pprint(("router eid 122: ", router_eid)) + full_router = self.json_get("/vr/1/%s/%s/%s" %(router_eid[0], router_eid[1], self.vr_name[2]), debug_=True) + pprint(("full router: ", full_router)) + time.sleep(5) + if router is None: + self._fail("Unable to find router after vrcx move "+self.vr_name) + self.exit_fail() + + def stop(self): + pass + + +def main(): + parser = LFCliBase.create_bare_argparse( + prog=__file__, + description="""\ +{f} +-------------------- +Command example: +{f} --vr_name 1.vr0 --ports 1.br0,1.rdd0a --services 1.br0=dhcp,nat --services 1.vr0=radvd +{f} --vr_name 2.vr0 --ports 2.br0,2.vap2 --services + + --debug +""".format(f=__file__)) + required = parser.add_argument_group('required arguments') + required.add_argument('--vr_name', '--vr_names', required=True, + help='EID of virtual router, like 1.2.vr0') + + optional = parser.add_argument_group('optional arguments') + + optional.add_argument('--ports', default=None, required=False, + help='Comma separated list of ports to add to virtual router') + optional.add_argument('--services', default=None, required=False, + help='Add router services to a port, "br0=nat,dhcp"') + + args = parser.parse_args() + + create_vr = CreateVR(lfclient_host=args.mgr, + lfclient_port=args.mgr_port, + vr_name=args.vr_name, + ports_list=args.ports, + services_list=args.services, + debug=args.debug, + _exit_on_error=True, + _exit_on_fail=True) + create_vr.clean() + create_vr.build() + create_vr.start() + create_vr.monitor() + #create_vr.stop() + #create_vr.clean() + print('Created Virtual Router') + +if __name__ == "__main__": + main() + +# diff --git a/lanforge/lanforge-scripts/py-scripts/create_wanlink.py b/lanforge/lanforge-scripts/py-scripts/create_wanlink.py new file mode 100755 index 000000000..40c20e3ea --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/create_wanlink.py @@ -0,0 +1,314 @@ +#!/usr/bin/python3 +""" + + Create and modify WAN Links Using LANforge JSON AP : http://www.candelatech.com/cookbook.php?vol=cli&book=JSON:+Managing+WANlinks+using+JSON+and+Python + Written by Candela Technologies Inc. + Updated by: Erin Grimes + +""" +import sys + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + + + +from time import sleep +import urllib +import pprint + +sys.path.append("../py-json") +from LANforge import LFRequest +from LANforge import LFUtils +from LANforge.lfcli_base import LFCliBase + +j_printer = pprint.PrettyPrinter(indent=2) +# todo: this needs to change +resource_id = 1 + + +def main(): + parser = LFCliBase.create_basic_argparse() + args = parser.parse_args() + base_url = 'http://%s:%s' % (args.mgr, args.mgr_port) + print(base_url) + json_post = "" + json_response = "" + num_wanlinks = -1 + + # force a refresh on the ports and wanlinks + lf_r = LFRequest.LFRequest(base_url+"/cli-json/nc_show_ports", debug_=True) + lf_r.addPostData({ + "shelf": 1, + "resource": 1, + "port": "all", + }) + json_response = lf_r.jsonPost(debug=True) + + lf_r = LFRequest.LFRequest(base_url+"/cli-json/nc_show_endpoints", debug_=True) + lf_r.addPostData({ + "endpoint": "all" + }) + json_response = lf_r.jsonPost(debug=True) + + sleep(1) + + # see if there are old wanlinks to remove + lf_r = LFRequest.LFRequest(base_url+"/wl_ep/list", debug_=True) + json_reponse = lf_r.get_as_json() + + endpA = args['name']+"-A" + endpB = args['name']+"-B" + + # count the number of wanlink endpoints + if "endpoint" in json_response: + endpoint_map = LFUtils.list_to_alias_map(json_list=json_reponse, from_element="endpoint") + if endpA in endpoint_map: + num_wanlinks += 1 + if endpB in endpoint_map: + num_wanlinks += 1 + + # remove old wanlinks + if (num_wanlinks > 0): + print("Removing old wanlinks...") + lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_cx", debug_=True) + lf_r.addPostData({ + 'test_mgr': 'all', + 'cx_name': args['name'] + }) + lf_r.jsonPost() + + lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_endp", debug_=True) + lf_r.addPostData({ + 'endp_name': endpA + }) + lf_r.jsonPost() + + lf_r = LFRequest.LFRequest(base_url+"/cli-json/rm_endp", debug_=True) + lf_r.addPostData({ + 'endp_name': endpB + }) + lf_r.jsonPost() + sleep(1) + + # check to see if we have ports + lf_r = LFRequest.LFRequest(base_url+"/ports/1/1/list", debug_=True) + port_response = lf_r.getAsJson() + + if "interfaces" not in port_response: + print("No interfaces in port_response!") + pprint.pprint(port_response) + exit(1) + + if "interfaces" in port_response: + port_map = LFUtils.list_to_alias_map(json_list=port_response, from_element="interfaces") + ports_created = 0 + if args["port_A"] not in port_map: + lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_rdd", debug_=True) + lf_r.addPostData({ + "shelf": 1, + "resource": 1, + "port": args["port_A"], + "peer_ifname": args["port_A"]+"b", + }) + json_reponse = lf_r.jsonPost(debug=True) + if not json_response: + print("could not create port "+args["port_A"]) + exit(1) + sleep(0.1) + ports_created += 1 + if args["port_B"] not in port_map: + lf_r.addPostData({ + "shelf": 1, + "resource": 1, + "port": args["port_B"], + "peer_ifname": args["port_B"]+"b", + }) + json_reponse = lf_r.jsonPost(debug=True) + if not json_response: + print("could not create port " + args["port_B"]) + exit(1) + ports_created += 1 + sleep(0.1) + if ports_created > 0: + LFUtils.wait_until_ports_appear(base_url=base_url, + port_list=(args["port_A"], args["port_B"]), + debug=True) + print("Created {} ports".format(ports_created)) + + # create wanlink endpoint A + print("Adding WL Endpoints...", end='') + lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_wl_endp", debug_=True) + lf_r.addPostData({ + 'alias': endpA, + 'shelf': 1, + 'resource': '1', + 'port': args['port_A'], + 'latency': args['latency_A'], + 'max_rate': args['rate_A'], + }) + json_response = lf_r.jsonPost(debug=True) + if not json_response: + print("Unable to create "+endpA) + else: + print("A, ", end='') + # create wanlink endpoint B + lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_wl_endp", debug_=True) + lf_r.addPostData({ + 'alias': endpB, + 'shelf': 1, + 'resource': '1', + 'port': args['port_B'], + 'latency': args['latency_B'], + 'max_rate': args['rate_B'], + }) + json_response = lf_r.jsonPost() + if not json_response: + print("Unable to create "+endpB) + else: + print("B") + sleep(1) + + # create cx + lf_r = LFRequest.LFRequest(base_url+"/cli-json/add_cx", debug_=True) + lf_r.addPostData({ + 'alias': args['name'], + 'test_mgr': 'default_tm', + 'tx_endp': endpA, + 'rx_endp': endpB + }) + lf_r.jsonPost(debug=True) + sleep(0.5) + + # modify wanlink endpoint A + lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_wanlink_info", debug_=True) + lf_r.addPostData({ + 'name': endpA, + 'max_jitter': args['jitter_A'], + 'jitter_freq': args['jitter_freq_A'], + 'drop_freq': args['drop_A'] + }) + lf_r.jsonPost(debug=True) + + # modify wanlink endpoint B + lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_wanlink_info", debug_=True) + lf_r.addPostData({ + 'name': endpB, + 'max_jitter': args['jitter_B'], + 'jitter_freq': args['jitter_freq_B'], + 'drop_freq': args['drop_B'] + }) + lf_r.jsonPost() + + # start wanlink once we see it + seen = 0 + print("Looking for {} and {}: ".format(endpA, endpB), end='') + while (seen < 2): + sleep(1) + lf_r = LFRequest.LFRequest(base_url+"/wl_ep/list?fields=name,eid") + try: + json_response = lf_r.getAsJson() + if json_response is None: + print(".", end="") + continue + LFUtils.debug_printer.pprint(json_response) + if "endpoint" not in json_response: + print("-", end="") + continue + + endpoint_map = LFUtils.list_to_alias_map(json_list=json_response["endpoint"], + from_element="endpoint") + if endpA in endpoint_map: + seen += 1 + print("+", end="") + if endpB in endpoint_map: + seen += 1 + print("+", end="") + + except urllib.error.HTTPError as error: + print("Error code {}".format(error.code)) + continue + print("") + print("Starting wanlink:") + # print("the latency is {laten}".format(laten=latency)) + lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_cx_state") + lf_r.addPostData({ + 'test_mgr': 'all', + 'cx_name': args['name'], + 'cx_state': 'RUNNING' + }) + lf_r.jsonPost() + + running = 0 + while (running < 1): + sleep(1) + lf_r = LFRequest.LFRequest(base_url+"/wl/"+args['name']+"?fields=name,state,_links") + try: + json_response = lf_r.getAsJson() + if (json_response is None): + continue + for key, value in json_response.items(): + if (isinstance(value, dict)): + if ("_links" in value): + if (value["name"] == args['name']): + if (value["state"].startswith("Run")): + LFUtils.debug_printer.pprint(json_response) + running = 1 + + except urllib.error.HTTPError as error: + print("Error code {}".format(error.code)) + continue + + print("Wanlink is running") + + # stop wanlink + lf_r = LFRequest.LFRequest(base_url+"/cli-json/set_cx_state") + lf_r.addPostData({ + 'test_mgr': 'all', + 'cx_name': args['name'], + 'cx_state': 'STOPPED' + }) + lf_r.jsonPost() + running = 1 + while (running > 0): + sleep(1) + lf_r = LFRequest.LFRequest(base_url+"/wl/"+args['name']+"?fields=name,eid,state,_links") + LFUtils.debug_printer.pprint(json_response) + try: + json_response = lf_r.getAsJson() + if (json_response is None): + continue + for key, value in json_response.items(): + if (isinstance(value, dict)): + if ("_links" in value): + if (value["name"] == args['name']): + if (value["state"].startswith("Stop")): + LFUtils.debug_printer.pprint(json_response) + running = 0 + + except urllib.error.HTTPError as error: + print("Error code {}".format(error.code)) + continue + + print("Wanlink is stopped.") + + # print("Wanlink info:") + # lf_r = LFRequest.LFRequest(base_url+"/wl/wl_eg1") + # json_response = lf_r.getAsJson() + # LFUtils.debug_printer.pprint(json_response) + + # lf_r = LFRequest.LFRequest(base_url+"/wl_ep/wl_eg1-A") + # json_response = lf_r.getAsJson() + # LFUtils.debug_printer.pprint(json_response) + + # lf_r = LFRequest.LFRequest(base_url+"/wl_ep/wl_eg1-B") + # json_response = lf_r.getAsJson() + # LFUtils.debug_printer.pprint(json_response) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/csv_convert.py b/lanforge/lanforge-scripts/py-scripts/csv_convert.py new file mode 100755 index 000000000..a0ac52556 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/csv_convert.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +""" + + This program is used to read in a LANforge Dataplane CSV file and output + a csv file that works with a customer's RvRvO visualization tool. + + Example use case: + + Read in ~/text-csv-0-candela.csv, output is stored at outfile.csv + ./py-scripts/csv_convert.py -i ~/text-csv-0-candela.csv + + Output is csv file with mixxed columns, top part: + Test Run,Position [Deg],Attenuation 1 [dB], Pal Stats Endpoint 1 Control Rssi [dBm], Pal Stats Endpoint 1 Data Rssi [dBm] + + Second part: + Step Index,Position [Deg],Attenuation [dB],Traffic Pair 1 Throughput [Mbps] +""" +import sys +import os +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + + +class CSVParcer(): + def __init__(self,csv_infile=None,csv_outfile=None): + + idx = 0 + i_atten = -1 + i_rotation = -1 + i_rxbps = -1 + i_beacon_rssi = -1 + i_data_rssi = -1 + fpo = open(csv_outfile, "w") + with open(csv_infile) as fp: + line = fp.readline() + if not line: + exit(1) + # Read in initial line, this is the CSV headers. Parse it to find the column indices for + # the columns we care about. + x = line.split(",") + cni = 0 + for cn in x: + if (cn == "Attenuation [dB]"): + i_atten = cni + if (cn == "Position [Deg]"): + i_rotation = cni + if (cn == "Throughput [Mbps]"): + i_rxbps = cni + if (cn == "Beacon RSSI [dBm]"): + i_beacon_rssi = cni + if (cn == "Data RSSI [dBm]"): + i_data_rssi = cni + cni += 1 + + # Write out out header for the new file. + fpo.write("Test Run,Position [Deg],Attenuation 1 [dB],Pal Stats Endpoint 1 Control Rssi [dBm],Pal Stats Endpoint 1 Data Rssi [dBm]\n") + + # Read rest of the input lines, processing one at a time. Covert the columns as + # needed, and write out new data to the output file. + line = fp.readline() + + bottom_half="Step Index,Position [Deg],Attenuation [dB],Traffic Pair 1 Throughput [Mbps]\n" + + test_run="1" + + step_i = 0 + while line: + x = line.split(",") + #print(x) + #print([test_run, x[i_rotation], x[i_atten], x[i_beacon_rssi], x[i_data_rssi]]) + fpo.write("%s,%s,%s,%s,%s" % (test_run, x[i_rotation], x[i_atten], x[i_beacon_rssi], x[i_data_rssi])) + bottom_half += ("%s,%s,%s,%s\n" % (step_i, x[i_rotation], x[i_atten], x[i_rxbps])) + line = fp.readline() + step_i += 1 + + # First half is written out now, and second half is stored... + fpo.write("\n\n# RvRvO Data\n\n") + fpo.write(bottom_half) + +def main(): + + #debug_on = False + parser = argparse.ArgumentParser( + prog='csv_convert.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Useful Information: + ''', + + description=''' +csv_convert.py: + converts the candela brief csv into the data for specific customer, + ''') + + # for testing parser.add_argument('-i','--infile', help="input file of csv data", default='text-csv-0-candela.csv') + parser.add_argument('-i','--infile', help="input file of csv data", required=True) + parser.add_argument('-o','--outfile', help="output file in .csv format", default='outfile.csv') + + + args = parser.parse_args() + csv_outfile_name = None + + if args.infile: + csv_infile_name = args.infile + if args.outfile: + csv_outfile_name = args.outfile + + print("infile: %s outfile: %s"%(csv_infile_name, csv_outfile_name)) + + CSVParcer(csv_infile_name, csv_outfile_name) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/csv_processor.py b/lanforge/lanforge-scripts/py-scripts/csv_processor.py new file mode 100755 index 000000000..7abd87183 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/csv_processor.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +import sys +import os +import argparse +import pandas as pd + +#https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html +#https://queirozf.com/entries/pandas-dataframe-plot-examples-with-matplotlib-pyplot + + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +class L3CSVParcer(): + def __init__(self,csv_file): + + + # left this in for testing + '''csv_obj = open(csv_file, 'r') + csv_reader = csv.reader(csv_obj) + print(csv_reader) + + for row in csv_reader: + if row[1] == 'rx': + print(row)''' + + include_summary = ['Time epoch','Time','Monitor','least','most','average'] + self.csv_file = csv_file + df_s = pd.read_csv(self.csv_file,header = 0, usecols = lambda column : any(substr in column for substr in include_summary)) + + print('{}'.format(csv_file)) + csv_file_summary = self.csv_file.replace('results_','results_summary_') + + df_s.to_csv(csv_file_summary, index = False, header=True) + + include_raw = ['Time epoch','Time','Monitor','LT','MT'] + self.csv_file = csv_file + df_r = pd.read_csv(self.csv_file,header = 0, usecols = lambda column : any(substr in column for substr in include_raw)) + + csv_file_raw = self.csv_file.replace('results_','results_raw_') + df_r.to_csv(csv_file_raw, index = False, header=True) + + '''df_rx_delta = df_r.loc[df['Monitor'] == 'rx_delta'] + + df_rx_delta.plot(x='Time epoch', y='average_rx_data') + plt.show() + + total_cols = len(df.axes[0]) + + + print(df.columns) + + print(df.loc[df['Monitor'] == 'rx_delta']) + print(df.loc[df['Monitor'] == 'rx']) + + print(df.loc[df['Monitor'] == 'rx_delta', df.columns != 'Time']) + + + df_rx_delta = df.loc[df['Monitor'] == 'rx_delta'] + + print(df_rx_delta.describe()) + + df_rx_delta.plot(x='Time epoch', y='average_rx_data') + plt.show() + + df_rx_delta.plot(x='Time', y='average_rx_data') + plt.show() + + df_rx_drop_pct = df.loc[df['Monitor'] == 'rx_drop_percent'] + print(df_rx_drop_pct) + df_rx_delta.plot(x='Time epoch', y='rx_drop_percent') + plt.show() + + + df2 = df.filter(regex='LT-s') + print(df2) + #plt.plot(df2[0], df2[1] + #plt.show() + + df2_mean = df2.mean().sort_values(ascending=False) + + print(df2_mean) + + df2_mean_no_outliers = df2_mean[df2_mean(df2_mean.quantile(.10), df2_mean.quantile(.90))] + + print("no outliers") + print(df2_mean_no_outliers) + + print("Top 10") + print(df2_mean.head(10)) + print("Bottom 10") + print(df2_mean.tail(10)) + + print("mean others") + + + + # set display format otherwise get scientific notation + pd.set_option('display.float_format', lambda x: '%.3f' % x) + + df_mean = df_rx_delta.mean().sort_values() + + #print(df_mean) + + print(df_mean[0]) + + + + + #df_uni_cast = [col for col in df_rx_delta if 'LT' in col] + #df_LT_rx_delta_mean = df_uni_cast.mean().sort_values() + + #print(df_LT_rx_delta_mean) + x = np.linspace(0, 20, 100) + plt.plot(x, np.sin(x)) + plt.show()''' + + +def main(): + + #debug_on = False + parser = argparse.ArgumentParser( + prog='csv_processor.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Useful Information: + ''', + + description='''quick_test.py: + + ''') + + + parser.add_argument('-i','--infile', help="file of csv data", default='longevity_results_08_14_2020_14_37.csv') + parser.add_argument('--debug', help='--debug: Enable debugging',default=True) + + + args = parser.parse_args() + + #debug_on = args.debug + + if args.infile: + csv_file_name = args.infile + + L3CSVParcer(csv_file_name) + + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/csv_to_grafana.py b/lanforge/lanforge-scripts/py-scripts/csv_to_grafana.py new file mode 100755 index 000000000..6fe7718b8 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/csv_to_grafana.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import argparse +import json +import random +import string + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +csv_to_influx = importlib.import_module("py-scripts.csv_to_influx") +CSVtoInflux = csv_to_influx.CSVtoInflux +influx_add_parser_args = csv_to_influx.influx_add_parser_args +grafana_profile = importlib.import_module("py-scripts.grafana_profile") +UseGrafana = grafana_profile.UseGrafana +influx = importlib.import_module("py-scripts.influx") +RecordInflux = influx.RecordInflux +InfluxRequest = importlib.import_module("py-dashboard.InfluxRequest") +influx_add_parser_args = InfluxRequest.influx_add_parser_args + + +class data_to_grafana(LFCliBase): + def __init__(self, + _bucket=None, + _script=None, + _panel_name=None): + self.bucket = _bucket + self.script = _script + self.panel_name = _panel_name + pass + + @property + def json_parser(self): + options = string.ascii_lowercase+string.ascii_uppercase+string.digits + uid = ''.join(random.choice(options) for i in range(9)) + print(uid) + json_dict = { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": True, + "hide": True, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": True, + "gnetId": None, + "graphTooltip": 0, + "id": 10, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": False, + "dashLength": 10, + "dashes": False, + "datasource": "InfluxDB", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": False, + "id": 2, + "legend": { + "avg": False, + "current": False, + "max": False, + "min": False, + "show": True, + "total": False, + "values": False + }, + "lines": True, + "linewidth": 1, + "NonePointMode": "None", + "options": { + "alertThreshold": True + }, + "percentage": False, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": False, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": False, + "steppedLine": False, + "targets": [ + { + "delimiter": ",", + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "None" + ], + "type": "fill" + } + ], + "header": True, + "ignoreUnknown": False, + "orderByTime": "ASC", + "policy": "default", + "query": ("from(bucket: \" %s \")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"script\"] == \" %s\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: False)\n |> yield(name: \"mean\")\n " % self.bucket, self.script ), + "refId": "A", + "resultFormat": "time_series", + "schema": [], + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "skipRows": 0, + "tags": [] + } + ], + "thresholds": [], + "timeRegions": [], + "title": "json test", + "tooltip": { + "shared": True, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": None, + "mode": "time", + "name": None, + "show": True, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": None, + "logBase": 1, + "max": None, + "min": None, + "show": True + }, + { + "format": "short", + "label": None, + "logBase": 1, + "max": None, + "min": None, + "show": True + } + ], + "yaxis": { + "align": False, + "alignLevel": None + } + } + ], + "refresh": False, + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": str(self.script), + "uid": uid, + "version": 2 + } + return json.dumps(json_dict) + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='csv_to_grafana.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Auto-create Grafana dashboard from a CSV''', + description='''\ + csv_to_grafana.py + -------------------- + Command example: + ./csv_to_grafana.py + --grafana_token + --influx_host + --influx_org + --influx_token + --influx_bucket + --target_csv + --panel_name''' + ) + required = parser.add_argument_group('required arguments') + required.add_argument('--grafana_token', help='token to access your Grafana database', required=True) + + optional = parser.add_argument_group('optional arguments') + optional.add_argument('--grafana_port', help='Grafana port if different from 3000', default=3000) + optional.add_argument('--grafana_host', help='Grafana host', default='localhost') + optional.add_argument('--panel_name', help='Custom name of the panel', default=None) + + influx_add_parser_args(parser) + + # This argument is specific to this script, so not part of the generic influxdb parser args + # method above. + parser.add_argument('--target_csv', help='CSV file to record to influx database', default="") + + args = parser.parse_args() + + influxdb = RecordInflux(_influx_host=args.influx_host, + _influx_port=args.influx_port, + _influx_org=args.influx_org, + _influx_token=args.influx_token, + _influx_bucket=args.influx_bucket) + + csvtoinflux = CSVtoInflux(influxdb=influxdb, + target_csv=args.target_csv, + _influx_tag=args.influx_tag) + + GrafanaDB = UseGrafana(args.grafana_token, + args.grafana_port, + args.grafana_host) + + scriptname = csvtoinflux.script_name() + + csvtoinflux.post_to_influx() + + GrafanaDB.create_custom_dashboard(scripts=[scriptname], + title=args.panel_name, + bucket=args.influx_bucket) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/csv_to_influx.py b/lanforge/lanforge-scripts/py-scripts/csv_to_influx.py new file mode 100755 index 000000000..ad3ee8d91 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/csv_to_influx.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +from pathlib import Path +import argparse + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser +InfluxRequest = importlib.import_module("py-dashboard.InfluxRequest") +RecordInflux = InfluxRequest.RecordInflux +influx_add_parser_args = InfluxRequest.influx_add_parser_args + +class CSVtoInflux: + def __init__(self, + influx_host, + influx_port, + influx_org, + influx_token, + influx_bucket, + path): + self.path = path + self.influxdb = RecordInflux(_influx_host=influx_host, + _influx_port=influx_port, + _influx_org=influx_org, + _influx_token=influx_token, + _influx_bucket=influx_bucket) + + def glob(self): + path = Path(self.path) + self.kpi_list = list(path.glob('**/kpi.csv')) + for kpi in self.kpi_list: + self.influxdb.RecordInflux.csv_to_influx(kpi) + + +def main(): + parser = argparse.ArgumentParser( + prog='csv_to_influx.py' + ) + cv_add_base_parser(parser) + + parser.add_argument('--path', action='append') + + args = parser.parse_args() + + cv_base_adjust_parser(args) + + csvtoinflux = CSVtoInflux(args.influx_host, + args.influx_port, + args.influx_org, + args.influx_token, + args.influx_bucket, + args.path) + + csvtoinflux.glob() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/ct_002_igg.json b/lanforge/lanforge-scripts/py-scripts/ct_002_igg.json new file mode 100644 index 000000000..c1d4491d3 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ct_002_igg.json @@ -0,0 +1,38 @@ +{ + "ct_igg":{ + "Notes":[ + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code.", + "this file contains the Influx, Grafana and Ghost configuration", + "Influx, Ghost, and Grafana are up and running on v-centos8s 192.168.100.153" + ] + }, + "test_database":{ + "database_config_influx": "True", + "database_host_influx": "192.168.100.153", + "database_port_influx": "8086", + "database_token_influx": "PwYwrDjUSpLyUa8-0QeJGuf9p6KgPgmTVs0Zz0BZiyele74pNasBMJR-dKiF3LE8Qft5tADHtPSIS0WcVXHc_g==", + "database_org_influx": "Candela", + "database_bucket_influx": "candela", + "database_tag_influx": "testbed CT-US-002", + "test_rig_influx": "CT-US-002" + }, + "test_dashboard":{ + "dashboard_config_grafana": "True", + "dashboard_host_grafana": "192.168.100.153", + "dashboard_token_grafana": "eyJrIjoid1hpM0pwZFRSc3c0bGU2TEpITEVscHh4T0pPMVdZRzEiLCJuIjoiY2h1Y2siLCJpZCI6MX0=" + }, + "test_blog":{ + "blog_config_ghost": "True", + "blog_host_ghost": "192.168.100.153", + "blog_token_ghost": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors_ghost": "Matthew", + "blog_customer_ghost": "candela", + "blog_user_push_ghost": "lanforge", + "blog_password_push_ghost": "lanforge", + "blog_flag_ghost": "--kpi_to_ghost" + } +} \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/ct_004_igg.json b/lanforge/lanforge-scripts/py-scripts/ct_004_igg.json new file mode 100644 index 000000000..051caa8fe --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ct_004_igg.json @@ -0,0 +1,39 @@ +{ + "ct_igg":{ + "Notes":[ + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code.", + "this file contains the Influx, Grafana and Ghost configuration", + "Influx, Ghost, and Grafana are up and running on v-centos8s 192.168.100.153" + ] + }, + "test_database":{ + "database_config_influx": "True", + "database_host_influx": "192.168.100.153", + "database_port_influx": "8086", + "database_token_influx": "PwYwrDjUSpLyUa8-0QeJGuf9p6KgPgmTVs0Zz0BZiyele74pNasBMJR-dKiF3LE8Qft5tADHtPSIS0WcVXHc_g==", + "database_org_influx": "Candela", + "database_bucket_influx": "candela", + "database_tag_influx": "testbed CT-US-004", + "test_rig_influx": "CT-US-004" + }, + "test_dashboard":{ + "dashboard_config_grafana": "True", + "dashboard_host_grafana": "192.168.100.153", + "dashboard_token_grafana": "eyJrIjoid1hpM0pwZFRSc3c0bGU2TEpITEVscHh4T0pPMVdZRzEiLCJuIjoiY2h1Y2siLCJpZCI6MX0=" + }, + "test_blog":{ + "blog_config_ghost": "True", + "blog_host_ghost": "192.168.100.153", + "blog_token_ghost": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors_ghost": "Matthew", + "blog_customer_ghost": "candela", + "blog_user_push_ghost": "lanforge", + "blog_password_push_ghost": "lanforge", + "blog_flag_ghost": "--kpi_to_ghost" + } +} + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/ct_igg.json b/lanforge/lanforge-scripts/py-scripts/ct_igg.json new file mode 100644 index 000000000..8dcc304d6 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ct_igg.json @@ -0,0 +1,39 @@ +{ + "ct_igg":{ + "Notes":[ + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code.", + "this file contains the Influx, Grafana and Ghost configuration", + "Influx, Ghost, and Grafana are up and running on v-centos8s 192.168.100.153" + ] + }, + "test_database":{ + "database_config_influx": "True", + "database_host_influx": "192.168.100.153", + "database_port_influx": "8086", + "database_token_influx": "PwYwrDjUSpLyUa8-0QeJGuf9p6KgPgmTVs0Zz0BZiyele74pNasBMJR-dKiF3LE8Qft5tADHtPSIS0WcVXHc_g==", + "database_org_influx": "Candela", + "database_bucket_influx": "candela", + "database_tag_influx": "testbed CT-US-001", + "test_rig_influx": "CT-US-001" + }, + "test_dashboard":{ + "dashboard_config_grafana": "True", + "dashboard_host_grafana": "192.168.100.153", + "dashboard_token_grafana": "eyJrIjoid1hpM0pwZFRSc3c0bGU2TEpITEVscHh4T0pPMVdZRzEiLCJuIjoiY2h1Y2siLCJpZCI6MX0=" + }, + "test_blog":{ + "blog_config_ghost": "True", + "blog_host_ghost": "192.168.100.153", + "blog_token_ghost": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors_ghost": "Matthew", + "blog_customer_ghost": "candela", + "blog_user_push_ghost": "lanforge", + "blog_password_push_ghost": "lanforge", + "blog_flag_ghost": "--kpi_to_ghost" + } +} + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/ct_us_001_orig.json b/lanforge/lanforge-scripts/py-scripts/ct_us_001_orig.json new file mode 100644 index 000000000..631b23ec4 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ct_us_001_orig.json @@ -0,0 +1,587 @@ +{ + "ct_us_001":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-001", + "lf_mgr_ip": "192.168.100.116", + "lf_mgr_port": "8080", + "dut_set_name": "DUT_NAME ASUSRT-AX88U", + "dut_name": "ASUSRT-AX88U", + "dut_bssid_2g": "3c:7c:3f:55:4d:60", + "dut_bssid_5g": "3c:7c:3f:55:4d:64", + "dut_sw": "3.0.0.4.386_44266", + "test_timeout": 300, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge QA Testing CT-US-001", + "email_txt": "Lanforge QA Testing CT-US-001 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 1, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth2" + }, + "test_database":{ + "database_config": "True", + "database_host": "192.168.100.201", + "database_port": "8086", + "database_token": "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==", + "database_org": "Candela", + "database_bucket": "lanforge_qa_testing", + "database_tag": "testbed CT-US-001", + "test_rig": "CT-US-001" + }, + "test_dashboard":{ + "dashboard_config": "True", + "dashboard_host": "192.168.100.201", + "dashboard_token": "eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ==" + }, + "test_blog":{ + "blog_config": "True", + "blog_host": "192.168.100.153", + "blog_token": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors": "Matthew", + "blog_customer": "candela", + "blog_user_push": "lanforge", + "blog_password_push": "lanforge", + "blog_flag": "--kpi_to_ghost" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_l3":{ + "test_l3_longevity":{"enabled":"TRUE","load_db":"skip","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth2 --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "auto_suite":{ + "CT-US-001_create_chamberview_dut_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-001_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-001_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 4 --max_stations_5 32 --max_stations_dual 4 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_lf_ap_auto_test1": { + "enabled": "FALSE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_2G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_lf_ap_auto_test_2": { + "enabled": "FALSE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"ghost_profile.py", + "args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp_mt":{ + "CT-US-001_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp_short":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG " + ] + }, + "CT-US-001_QA":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"./tools/lf_qa.py", + "args":"", + "args_list":[ + " --path REPORT_PATH --store --png --database ./tools/qa_test_db" + ] + } + }, + "suite_wc_dp_short_igg":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_dataplane_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan1", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_dut_for_AX210":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + "--lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy3,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\" " + ] + }, + "CT-US-001_wifi_capacity_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.wlan3 --test_tag 'AX210'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_dataplane_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan3", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'AX210'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_dut_for_mt7915e":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-001_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-001_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/ct_us_001_tests_igg.json b/lanforge/lanforge-scripts/py-scripts/ct_us_001_tests_igg.json new file mode 100644 index 000000000..a226d32fc --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ct_us_001_tests_igg.json @@ -0,0 +1,455 @@ +{ + "ct_tests_001":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_suites":{ + "suite_l3":{ + "test_l3_longevity":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"test_l3_longevity.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --test_duration 15s --polling_interval 5s --upstream_port eth2 ", + " --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' ", + " --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000" + ] + } + }, + "suite_l3_ap":{ + "test_l3_longevity":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"test_l3_longevity.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --test_duration 15s --polling_interval 5s --upstream_port eth2 ", + " --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' ", + " --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000", + " --ap_read --ap_test_mode" + ] + } + }, + "suite_wc_dp_shorter":{ + "CT-US-001_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "iterations":"1", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,10,19 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG", + " --set DUT_SET_NAME" + ] + }, + "CT-US-001_QA":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"./tools/lf_qa.py", + "args":"", + "args_list":[ + " --path REPORT_PATH --store --png --database ./tools/qa_001_test_db" + ] + } + }, + "suite_wc_dp_short":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "iterations":"1", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG ", + " --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "iterations":"1", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,10,15,19 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG", + " --set DUT_SET_NAME" + ] + }, + "CT-US-001_QA":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"./tools/lf_qa.py", + "args":"", + "args_list":[ + " --path REPORT_PATH --store --png --database ./tools/qa_001_test_db" + ] + } + }, + "suite_wc_dp":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "iterations":"1", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG ", + " --set DUT_SET_NAME", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-ATH10K-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_dataplane_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "timeout":"600", + "iterations":"1", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan1", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;Custom' ", + " --raw_line 'cust_pkt_sz: 88;256;512;768;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: AUTO'", + " --raw_line 'spatial_streams: AUTO' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)' ", + " --test_rig TEST_RIG", + " --set DUT_SET_NAME", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG" + ] + }, + "CT-US-001_create_chamberview_dut_for_AX210":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + "--lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-AX210-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy3,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\" " + ] + }, + "CT-US-001_wifi_capacity_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "iterations":"1", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.wlan3 --test_tag 'AX210'", + " --test_rig TEST_RIG", + " --set DUT_SET_NAME", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG" + ] + }, + "CT-US-001_dataplane_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "iterations":"1", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan3", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: AUTO'", + " --raw_line 'spatial_streams: AUTO' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'AX210'", + " --test_rig TEST_RIG", + " --set DUT_SET_NAME", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG" + ] + }, + "CT-US-001_create_chamberview_dut_for_mt7915e":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-mt7915e-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "iterations":"1", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,10,15,19 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG", + " --set DUT_SET_NAME", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-mt7915e-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "iterations":"1", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: AUTO'", + " --raw_line 'spatial_streams: AUTO' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG", + " --set DUT_SET_NAME", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG" + ] + }, + "CT-US-001_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-001_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ap-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-001_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "iterations":"1", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG", + " --set DUT_SET_NAME", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG" + ] + }, + "CT-US-001_QA":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"./tools/lf_qa.py", + "args":"", + "args_list":[ + " --path REPORT_PATH --store --png --database ./tools/qa_001_test_db" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/ct_us_002_orig.json b/lanforge/lanforge-scripts/py-scripts/ct_us_002_orig.json new file mode 100644 index 000000000..62281a61e --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ct_us_002_orig.json @@ -0,0 +1,341 @@ +{ + "ct_us_002":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-002", + "lf_mgr_ip": "192.168.100.200", + "lf_mgr_port": "8080", + "dut_set_name": "DUT_NAME NETGEAR59-5G", + "dut_name": "NETGEAR-AX12", + "dut_bssid_5g": "94:a6:7e:54:d4:33", + "test_timeout": 1200, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge QA Testing CT-US-002", + "email_txt": "Lanforge QA Testing CT-US-002" + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "NETGEAR59-5G", + "ssid_pw_used": "crispynest798", + "security_used": "wpa2", + "num_sta": 4, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "1.1.eth2" + }, + "test_database":{ + "database_config": "True", + "database_host": "192.168.100.201", + "database_port": "8086", + "database_token": "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==", + "database_org": "Candela", + "database_bucket": "lanforge_qa_testing", + "database_tag": "testbed CT-US-002", + "test_rig": "CT-US-002" + }, + "test_dashboard":{ + "dashboard_config": "True", + "dashboard_host": "192.168.100.201", + "dashboard_token": "eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ==" + }, + "test_blog":{ + "blog_config": "True", + "blog_host": "192.168.100.153", + "blog_token": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors": "Matthew", + "blog_customer": "candela", + "blog_user_push": "lanforge", + "blog_password_push": "lanforge", + "blog_flag": "--kpi_to_ghost" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"4","SSID":"NETGEAR59-5G","PASSWD":"crispynest798","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"NETGEAR59-5G","PASSWD":"crispynest798","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_two":{ + "test_l3_longevity":{"enabled":"TRUE","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port UPSTREAM_PORT --radio 'radio==wiphy1,stations==4,ssid==ct523c-vap,ssid_pw==ct523c-vap,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "auto_suite":{ + "CT-US-002_create_chamberview_dut_1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"4800", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"ghost_profile.py", + "args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp":{ + "CT-US-002_create_chamberview_dut_1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_ATH10k_sta64":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_wifi_capacity_ATH10k(9984)":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream UPSTREAM_PORT --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-002_create_chamberview_dut_ATH10K_wan1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_ATH10k_wan1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_dataplane_ATH10k(9984) CT-US-002":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream UPSTREAM_PORT --dut DUT_NAME --duration 30s --station 1.1.wlan1", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' ", + " --raw_line 'spatial_streams: 4' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-002_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_AX200_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_wifi_capacity_AX200 CT-US-002":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream UPSTREAM_PORT --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.wlan4 --test_tag 'ATH10K(9984)' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-002_create_chamberview_dut_AX200_wan1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_AX200_wan1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_dataplane_AX200":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream UPSTREAM_PORT --dut DUT_NAME --duration 30s --station 1.1.wlan4", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 4' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'AX200'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-002_create_chamberview_dut_auto":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_auto":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_lf_ap_auto_test": { + "enabled": "FALSE", + "command": "lf_ap_auto_test.py", + "timeout": "1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'AX200'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/ct_us_003_orig.json b/lanforge/lanforge-scripts/py-scripts/ct_us_003_orig.json new file mode 100644 index 000000000..a28518936 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ct_us_003_orig.json @@ -0,0 +1,72 @@ +{ + "test_parameters":{ + "test_timeout": 200, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,chuck.rekiere@candelatech.com,", + "host_ip_production": "192.168.95.6", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.95.6", + "lf_mgr": "192.168.100.116", + "email_title_txt": "Lanforge QA Testing CT-US-001", + "email_txt": "Lanforge QA Testing CT-US-001 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 4, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth1" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"4","SSID":"ct523c-vap","PASSWD":"ct523c-vap","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"ct523c-vap","PASSWD":"ct523c-vap","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_one":{ + "create_l3":{"enabled":"TRUE","command":"create_l4.py","args":"--radio RADIO_USED --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"RADIO_1_CFG --debug"}, + "create_l4_2":{"enabled":"TRUE","command":"create_l4.py","args":"--radio wiphy1 --ssid ct523c-vap --passwd ct523c-vap --security wpa2 --debug"} + }, + "suite_two":{ + "test_l3_longevity":{"enabled":"TRUE","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth1 --radio 'radio==wiphy1,stations==4,ssid==ct523c-vap,ssid_pw==ct523c-vap,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "suite_dp_short":{ + "dataplane_0":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "} + }, + "suite_dp_cmd":{ + "dataplane_0":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "} + }, + "suite_dp_long":{ + "dataplane_00":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_01":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_02":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_03":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_04":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_05":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_06":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_07":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_08":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_09":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "dataplane_10":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "} + }, + "TEST_DICTIONARY":{ + "dataplane_0":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"}, + "dataplane_1":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"}, + "dataplane_2":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"} + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/ct_us_004_orig.json b/lanforge/lanforge-scripts/py-scripts/ct_us_004_orig.json new file mode 100644 index 000000000..def4e3067 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ct_us_004_orig.json @@ -0,0 +1,472 @@ +{ + "ct_us_004":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_004", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-004", + "lf_mgr_ip": "192.168.100.194", + "lf_mgr_port": "8080", + "dut_set_name": "DUT_NAME Asus-RT-AX88U", + "dut_name": "Asus-RT-AX88U", + "dut_bssid_2g": "d4:5d:64:a0:7f:78", + "dut_bssid_5g": "d4:5d:64:a0:7f:7c", + "dut_sw": "3.0.0.4.386_44266", + "test_timeout": 300, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge QA Testing CT-US-004", + "email_txt": "Lanforge QA Testing CT-US-004 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 1, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth2" + }, + "test_database":{ + "database_config": "True", + "database_host": "192.168.100.201", + "database_port": "8086", + "database_token": "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==", + "database_org": "Candela", + "database_bucket": "lanforge_qa_testing", + "database_tag": "testbed CT-US-004", + "test_rig": "CT-US-004" + }, + "test_dashboard":{ + "dashboard_config": "True", + "dashboard_host": "192.168.100.201", + "dashboard_token": "eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ==" + }, + "test_blog":{ + "blog_config": "True", + "blog_host": "192.168.100.153", + "blog_token": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors": "Matthew", + "blog_customer": "candela", + "blog_user_push": "lanforge", + "blog_password_push": "lanforge", + "blog_flag": "--kpi_to_ghost" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_l3":{ + "test_l3_longevity":{"enabled":"TRUE","load_db":"skip","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth2 --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "auto_suite":{ + "CT-US-004_create_chamberview_dut_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-004_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-004_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_lf_ap_auto_test_2": { + "enabled": "FALSE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"ghost_profile.py", + "args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp_mt":{ + "CT-US-004_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-004_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-004_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp":{ + "CT-US-004_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-004_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-004_dataplane_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan1", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_dut_1":{ + "enabled":"FALSE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + "--lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_wiphy3_AX210_sta1":{ + "enabled":"FALSE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy3,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\" " + ] + }, + "CT-US-004_wifi_capacity_wiphy3_AX210_sta1":{ + "enabled":"FALSE", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.wlan3", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_dataplane_wiphy3_AX210_sta1":{ + "enabled":"FALSE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan3", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_dut_for_mt7915e":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-004_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-004_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 32 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-004_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag ATH10K(9984)", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/cv_examples/ferndale_ucentral.bash b/lanforge/lanforge-scripts/py-scripts/cv_examples/ferndale_ucentral.bash new file mode 100755 index 000000000..e91038355 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/cv_examples/ferndale_ucentral.bash @@ -0,0 +1,78 @@ +#!/bin/bash + +# This bash script creates/updates a DUT, creates/updates a chamberview scenario, +# loads and builds that scenario, runs wifi capacity test, and saves the kpi.csv info +# into influxdb. As final step, it builds a grafana dashboard for the KPI information. + +set -x + +# Define some common variables. This will need to be changed to match your own testbed. +# MGR is LANforge GUI machine +#MGR=192.168.100.209 +MGR=localhost + +# Candela internal influx +INFLUXTOKEN=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== +INFLUX_HOST=192.168.100.201 +INFLUX_BUCKET=ben +INFLUX_ORG=Candela + +GRAFANATOKEN=eyJrIjoiVXVyZ0dQSXlNdGlQNGQ1R282S2p6SE1PZXJVOVpvM1UiLCJuIjoiYmVuLWdyYWZhbmEiLCJpZCI6MX0= +GRAFANA_HOST=192.168.100.201 +GROUP_FILE=/tmp/lf_cv_rpt_filelocation.txt +TESTBED=Ferndale-01 +DUT=linksys-8450 +UPSTREAM=eth2 +LF_WAN_PORT=eth3 +MGR_PORT=8080 + +if [ -f $HOME/influx_vars.sh ] +then + # Put private keys and other variable overrides in here. + . $HOME/influx_vars.sh +fi + + +# Create/update new DUT. +#Replace my arguments with your setup. Separate your ssid arguments with spaces and ensure the names are lowercase +echo "Make new DUT" +./create_chamberview_dut.py --lfmgr ${MGR} --port ${MGR_PORT} --dut_name ${DUT} \ + --ssid "ssid_idx=0 ssid=Default-SSID-2g security=WPA2 password=12345678 bssid=c4:41:1e:f5:3f:24" \ + --ssid "ssid_idx=1 ssid=Default-SSID-5gl security=WPA2 password=12345678 bssid=c4:41:1e:f5:3f:25" \ + --sw_version "ucentral-01" --hw_version ea8450 --serial_num 1001 --model_num 8450 + +# Create/update chamber view scenario and apply and build it. +# Easiest way to get these lines is to build it in the GUI and then +# copy/tweak what it shows in the 'Text Output' tab after saving and re-opening +# the scenario. +echo "Build Chamber View Scenario" +#change the lfmgr to your system, set the radio to a working radio on your LANforge system, same with the ethernet port. + +./create_chamberview.py --lfmgr ${MGR} --port ${MGR_PORT} --delete_scenario \ + --create_scenario ucentral-scenario \ + --raw_line "profile_link 1.1 STA-AC 50 'DUT: $DUT Radio-1' NA wiphy0,AUTO -1 NA" \ + --raw_line "profile_link 1.1 STA-AC 50 'DUT: $DUT Radio-1' NA wiphy2,AUTO -1 NA" \ + --raw_line "profile_link 1.1 STA-AC 50 'DUT: $DUT Radio-2' NA wiphy1,AUTO -1 NA" \ + --raw_line "profile_link 1.1 STA-AC 46 'DUT: $DUT Radio-2' NA wiphy3,AUTO -1 NA" \ + --raw_line "profile_link 1.1 upstream-dhcp 1 NA NA $UPSTREAM,AUTO -1 NA" \ + --raw_line "profile_link 1.1 uplink-nat 1 'DUT: upstream LAN 192.168.100.1/24' NA $LF_WAN_PORT,$UPSTREAM -1 NA" \ + --raw_line "profile_link 1.1 STA-AC 1 'DUT: $DUT Radio-2' NA ALL-AX,AUTO -1 NA" + +# Run capacity test on the stations created by the chamber view scenario. +# Submit the KPI data into the influxdb. +#config_name doesn't matter, change the influx_host to your LANforge device, +# NOTE: My influx token is unlucky and starts with a '-', but using the syntax below +# with '=' right after the argument keyword works as hoped. +echo "run wifi capacity test" +./lf_wifi_capacity_test.py --config_name Custom --pull_report --influx_host ${INFLUX_HOST} \ + --influx_port 8086 --influx_org ${INFLUX_ORG} --influx_token=${INFLUXTOKEN} --influx_bucket ${INFLUX_BUCKET} --mgr ${MGR} \ + --port ${MGR_PORT} \ + --instance_name testing --upstream 1.1.$UPSTREAM --test_rig ${TESTBED} --graph_groups ${GROUP_FILE} \ + --batch_size "100" --protocol "TCP-IPv4" --duration 20000 + +# Build grafana dashboard and graphs view for the KPI in the capacity test. +#echo "Adding grafana dashboard" +#./grafana_profile.py --create_custom --title ${TESTBED} --influx_bucket ${INFLUX_BUCKET} --grafana_token ${GRAFANATOKEN} \ +# --grafana_host ${GRAFANA_HOST} --testbed ${TESTBED} --graph-groups ${GROUPS} --scripts Dataplane --scripts 'WiFi Capacity' + +rm ${GROUP_FILE} diff --git a/lanforge/lanforge-scripts/py-scripts/cv_manager.py b/lanforge/lanforge-scripts/py-scripts/cv_manager.py new file mode 100755 index 000000000..57bf23cd6 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/cv_manager.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cv_test = cv_test_manager.cv_test + + +class CVManager(cv_test): + def __init__(self, + scenario=None, + debug=False, + lfclient_host='localhost'): + self.scenario = scenario + self.debug = debug + self.exit_on_error = False + self.lfclient_host = lfclient_host + + def apply_and_build_scenario(self): + self.apply_cv_scenario(self.scenario) + self.build_cv_scenario() + +def main(): + parser = argparse.ArgumentParser( + prog='cv_manager.py', + formatter_class=argparse.RawTextHelpFormatter, + description='''This is a simple driver script to load a CV Scenario''') + parser.add_argument('--scenario', help='Scenario you wish to build') + parser.add_argument('--debug', help='Enable debugging', default=False, action="store_true") + parser.add_argument('--mgr', default='localhost') + + args = parser.parse_args() + + manager = CVManager(scenario=args.scenario, + debug=args.debug, + lfclient_host=args.mgr) + manager.apply_and_build_scenario() + +if __name__ =="__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/cv_to_grafana.py b/lanforge/lanforge-scripts/py-scripts/cv_to_grafana.py new file mode 100755 index 000000000..8be615e32 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/cv_to_grafana.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +''' +This script loads and builds a Chamber View Scenario, runs WiFi Capacity Test, runs Dataplane Test, +and posts the results to Influx. +There are optional arguments which will create a Grafana dashboard which will import the data posted to +Influx from this script. +./cv_to_grafana.py +--mgr 192.168.1.4 +--influx_host 192.168.100.201 +--influx_token TOKEN +--influx_tag testbed Stidmatt-01 +--influx_bucket stidmatt +--influx_org Candela +--pull_report +--ssid_dut "ssid_idx=0 ssid=lanforge security=WPA2 password=password bssid=04:f0:21:2c:41:84" +--line "Resource=1.1 Profile=default Amount=4 Uses-1=wiphy1 DUT=DUT_TO_GRAFANA_DUT Traffic=wiphy1 Freq=-1" +--line "Resource=1.1 Profile=upstream Amount=1 Uses-1=eth1 DUT=DUT_TO_GRAFANA_DUT Traffic=eth1 Freq=-1" +--dut DUT_TO_GRAFANA +--create_scenario DUT_TO_GRAFANA_SCENARIO +--station 1.1.sta00002 +--duration 15s +--upstream 1.1.eth1 +--radio2 1.1.wiphy1 +--radio5 1.1.wiphy2 +--dut5_0 linksys-8450 +--set 'Basic Client Connectivity' 1 +--set 'Multi-Station Throughput vs Pkt Size' 0 +--set 'Multi Band Performance' 1 +--set Stability 1 +--set 'Throughput vs Pkt Size' 0 +--set Capacity 0 +--set Band-Steering 0 + +OPTIONAL GRAFANA ARGUMENTS +--grafana_token TOKEN +--grafana_host 192.168.100.201 +--title "Grafana Dashboard" + +The Grafana arguments are only required once. After the Grafana dashboard is built it will automatically update +as more data is added to the Influx database. Running the Grafana arguments to create a dashboard will do nothing. + +The pull_report flag is to be used when running this on a computer which is different from the LANforge Manager. +It downloads the reports to the device which is running the script. + +Each line argument adds a line to the Chamber View Scenario which you create in the script. + +DUT flag gives the name of the DUT which is created by this script. It can be found in the DUT tab in LANforge Manager. + +The station flag tells Dataplane test which station to test with. + +The AP Auto test is triggered by the radio2 or radio5 flag. Select which tests in the AP Auto Test with the set argument. + +AP Auto test has the following argument: +* max_stations_2: Specify maximum 2.4Ghz stations +* max_stations_5: Specify maximum 5Ghz stations +* max_stations_dual: Specify maximum stations for dual-band tests +* dut5_0: Specify 5Ghz DUT entry +* dut2_0: Specify 2Ghz DUT entry +DUT syntax is somewhat tricky: DUT-name SSID BSID (bssid-idx), example: linksys-8450 Default-SSID-5gl c4:41:1e:f5:3f:25 (2) +* radio2: Specify 2.4Ghz radio. May be specified multiple times. +* radio5: Specify 5Ghz radio. May be specified multiple times. +''' +import sys +import os +import importlib +import argparse +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lf_wifi_capacity_test = importlib.import_module("py-scripts.lf_wifi_capacity_test") +WiFiCapacityTest = lf_wifi_capacity_test.WiFiCapacityTest +cv_test_manager = importlib.import_module("py-scripts.cv_test_manager") +create_chamberview = importlib.import_module("py-scripts.create_chamberview") +CreateChamberview = create_chamberview.CreateChamberview +DUT = create_chamberview.DUT +lf_dataplane_test = importlib.import_module("py-scripts.lf_dataplane_test") +DataplaneTest = lf_dataplane_test.DataplaneTest +grafana_profile = importlib.import_module("py-scripts.grafana_profile") +UseGrafana = grafana_profile.UseGrafana +lf_ap_auto_test = importlib.import_module("py-scripts.lf_ap_auto_test") + + +def main(): + parser = argparse.ArgumentParser( + prog='cv_to_grafana.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Run Wifi Capacity and Dataplane Test and record results to Grafana''', + description='''\ + cv_to_grafana.py + ------------------ + ./cv_to_grafana.py + --mgr + --influx_host + --influx_token + --influx_tag testbed + --influx_bucket + --influx_org + --pull_report + --ssid_dut + --line + --line + --dut + --create_scenario + --station + --influx_tag + --duration + --upstream + ''' + ) + + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument("-b", "--batch_size", type=str, default="", + help="station increment ex. 1,2,3") + parser.add_argument("-l", "--loop_iter", type=str, default="", + help="Loop iteration ex. 1") + parser.add_argument("-p", "--protocol", type=str, default="", + help="Protocol ex.TCP-IPv4") + parser.add_argument("-d", "--duration", type=str, default="", + help="duration in ms. ex. 5000") + parser.add_argument("--download_rate", type=str, default="1Gbps", + help="Select requested download rate. Kbps, Mbps, Gbps units supported. Default is 1Gbps") + parser.add_argument("--upload_rate", type=str, default="10Mbps", + help="Select requested upload rate. Kbps, Mbps, Gbps units supported. Default is 10Mbps") + parser.add_argument("--sort", type=str, default="interleave", + help="Select station sorting behaviour: none | interleave | linear Default is interleave.") + parser.add_argument('--number_template', + help='Start the station numbering with a particular number. Default is 0000', + default=0000) + parser.add_argument('--mode', help='Used to force mode of stations') + parser.add_argument('--ap', help='Used to force a connection to a particular AP') + parser.add_argument("--num_stations", default=2) + parser.add_argument("--mgr_port", default=8080) + parser.add_argument("--upstream_port", default="1.1.eth1") + parser.add_argument("--scenario", help="", default=None) + parser.add_argument("--line", action='append', nargs='+', + help="line number", default=[]) + parser.add_argument("-ds", "--delete_scenario", default=False, action='store_true', + help="delete scenario (by default: False)") + + parser.add_argument("--create_scenario", "--create_lf_scenario", type=str, + help="name of scenario to be created") + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for wifi capacity test ex. 1.1.eth2") + parser.add_argument("--station", type=str, default="", + help="Station to be used in this test, example: 1.1.sta01500") + + parser.add_argument("--dut", default="", + help="Specify DUT used by this test, example: linksys-8450") + parser.add_argument("--download_speed", default="", + help="Specify requested download speed. Percentage of theoretical is also supported.") + parser.add_argument("--upload_speed", default="", + help="Specify requested upload speed. Percentage of theoretical is also supported. Default: 0") + parser.add_argument("--graph_groups", help="File to save graph_groups to", default=None) + parser.add_argument("--ssid_dut", action='append', nargs=1, help="SSID", default=[]) + + parser.add_argument("--sw_version", default="NA", help="DUT Software version.") + parser.add_argument("--hw_version", default="NA", help="DUT Hardware version.") + parser.add_argument("--serial_num", default="NA", help="DUT Serial number.") + parser.add_argument("--model_num", default="NA", help="DUT Model Number.") + parser.add_argument("--report_dir", default="") + parser.add_argument('--grafana_token', help='token to access your Grafana database') + parser.add_argument('--grafana_port', help='Grafana port if different from 3000', default=3000) + parser.add_argument('--grafana_host', help='Grafana host', default='localhost') + + #Flags for AP-Auto Test config + + parser.add_argument("--max_stations_2", type=int, default=-1, + help="Specify maximum 2.4Ghz stations") + parser.add_argument("--max_stations_5", type=int, default=-1, + help="Specify maximum 5Ghz stations") + parser.add_argument("--max_stations_dual", type=int, default=-1, + help="Specify maximum stations for dual-band tests") + parser.add_argument("--dut5_0", type=str, default="", + help="Specify 5Ghz DUT entry. Syntax is somewhat tricky: DUT-name SSID BSID (bssid-idx), example: linksys-8450 Default-SSID-5gl c4:41:1e:f5:3f:25 (2)") + parser.add_argument("--dut2_0", type=str, default="", + help="Specify 2Ghz DUT entry. Syntax is somewhat tricky: DUT-name SSID BSID (bssid-idx), example: linksys-8450 Default-SSID-2g c4:41:1e:f5:3f:24 (1)") + + parser.add_argument("--radio2", action='append', nargs=1, default=[], + help="Specify 2.4Ghz radio. May be specified multiple times.") + parser.add_argument("--radio5", action='append', nargs=1, default=[], + help="Specify 5Ghz radio. May be specified multiple times.") + + #Flags for Grafana + + parser.add_argument('--dashboard_title', help='Titles of dashboards', default=None, action='append') + parser.add_argument('--scripts', help='Scripts to graph in Grafana', default=None, action='append') + parser.add_argument('--title', help='title of your Grafana Dashboard', default=None) + parser.add_argument('--testbed', help='Which testbed you want to query', default=None) + parser.add_argument('--graph_groups_file', help='File which determines how you want to filter your graphs on your dashboard', + default=None) + parser.add_argument('--kpi', help='KPI file(s) which you want to graph form', action='append', default=None) + parser.add_argument('--datasource', help='Name of Influx database if different from InfluxDB', default='InfluxDB') + parser.add_argument('--from_date', help='Date you want to start your Grafana dashboard from', default='now-1y') + parser.add_argument('--graph_height', help='Custom height for the graph on grafana dashboard', default=8) + parser.add_argument('--graph_width', help='Custom width for the graph on grafana dashboard', default=12) + + args = parser.parse_args() + + cv_base_adjust_parser(args) + + # Create/update new DUT + print("Make new DUT") + new_dut = DUT(lfmgr=args.mgr, + port=args.port, + dut_name=args.dut, + ssid=args.ssid_dut, + sw_version=args.sw_version, + hw_version=args.hw_version, + serial_num=args.serial_num, + model_num=args.model_num, + ) + new_dut.setup() + new_dut.add_ssids() + new_dut.cv_test.show_text_blob(None, None, True) # Show changes on GUI + new_dut.cv_test.sync_cv() + time.sleep(2) + new_dut.cv_test.sync_cv() + + print("Build Chamber View Scenario") + Create_Chamberview = CreateChamberview(lfmgr=args.mgr, + port=args.port, + ) + if args.delete_scenario: + Create_Chamberview.clean_cv_scenario(type="Network-Connectivity", scenario_name=args.create_scenario) + + Create_Chamberview.setup(create_scenario=args.create_scenario, + line=args.line, + raw_line=args.raw_line) + Create_Chamberview.build(args.create_scenario) + + print("Run WiFi Capacity Test") + wifi_capacity = WiFiCapacityTest(lfclient_host=args.mgr, + lf_port=args.mgr_port, + lf_user=args.lf_user, + lf_password=args.lf_password, + instance_name='testing', + config_name=args.config_name, + upstream=args.upstream_port, + batch_size=args.batch_size, + loop_iter=args.loop_iter, + protocol=args.protocol, + duration=args.duration, + pull_report=args.pull_report, + load_old_cfg=args.load_old_cfg, + download_rate=args.download_rate, + upload_rate=args.upload_rate, + sort=args.sort, + enables=args.enable, + disables=args.disable, + raw_lines=args.raw_line, + raw_lines_file=args.raw_lines_file, + sets=args.set, + graph_groups=args.graph_groups_file) + wifi_capacity.apply_cv_scenario(args.scenario) + wifi_capacity.build_cv_scenario() + wifi_capacity.setup() + wifi_capacity.run() + wifi_capacity.check_influx_kpi(args) + + print("Run Dataplane test") + + CV_Test = DataplaneTest(lf_host=args.mgr, + lf_port=args.port, + lf_user=args.lf_user, + lf_password=args.lf_password, + instance_name='dataplane-instance', + config_name=args.config_name, + upstream=args.upstream, + pull_report=args.pull_report, + load_old_cfg=args.load_old_cfg, + download_speed=args.download_speed, + upload_speed=args.upload_speed, + duration=args.duration, + dut=args.dut, + station=args.station, + enables=args.enable, + disables=args.disable, + raw_lines=args.raw_line, + raw_lines_file=args.raw_lines_file, + sets=args.set, + graph_groups=args.graph_groups_file + ) + CV_Test.setup() + CV_Test.run() + + CV_Test.check_influx_kpi(args) + + if len(args.radio2) + len(args.radio5) > 0: + ApAuto = ApAutoTest(lf_host=args.mgr, + lf_port=args.port, + lf_user=args.lf_user, + lf_password=args.lf_password, + instance_name=args.instance_name, + config_name=args.config_name, + upstream=args.upstream, + pull_report=args.pull_report, + dut5_0=args.dut5_0, + dut2_0=args.dut2_0, + load_old_cfg=args.load_old_cfg, + max_stations_2=args.max_stations_2, + max_stations_5=args.max_stations_5, + max_stations_dual=args.max_stations_dual, + radio2=args.radio2, + radio5=args.radio5, + enables=args.enable, + disables=args.disable, + raw_lines=args.raw_line, + raw_lines_file=args.raw_lines_file, + sets=args.set, + graph_groups=args.graph_groups_file + ) + ApAuto.setup() + ApAuto.run() + + ApAuto.check_influx_kpi(args) + + if args.grafana_token: + print("Create Grafana dashboard") + Grafana = UseGrafana(args.grafana_token, + args.grafana_port, + args.grafana_host + ) + Grafana.create_custom_dashboard(scripts=args.scripts, + title=args.title, + bucket=args.influx_bucket, + graph_groups=args.graph_groups, + graph_groups_file=args.graph_groups_file, + testbed=args.testbed, + datasource=args.datasource, + from_date=args.from_date, + graph_height=args.graph_height, + graph__width=args.graph_width) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/docstrings.py b/lanforge/lanforge-scripts/py-scripts/docstrings.py new file mode 100755 index 000000000..39638660f --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/docstrings.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +""" Script for mapping module names to their docstrings, will print output in json format. Will look for all python + files in the current directory and map filename:docstring +""" + +import ast +import os + + +class DocstringCollector: + def __init__(self): + self.docstring_map = {} + self.cur_path = os.getcwd() + self.files = [] + + def get_python_files(self): + for file in os.listdir(self.cur_path): + if file.endswith('.py'): + self.files.append(file) + + def map_docstrings(self): + if len(self.files) > 0: + for file in self.files: + try: + with open(file, 'r') as f: + tree = ast.parse(f.read()) + docstring = ast.get_docstring(tree) + if docstring is not None: + self.docstring_map[file] = docstring + except Exception as e: + continue + # print("Exception %s on %s" % (e, file)) + else: + raise ValueError("No python files found in directory") + + +def main(): + collector = DocstringCollector() + collector.get_python_files() + collector.map_docstrings() + # print(collector.docstring_map.keys()) + print(collector.docstring_map) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/download_test.py b/lanforge/lanforge-scripts/py-scripts/download_test.py new file mode 100644 index 000000000..ad063ce6d --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/download_test.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""download_test.py will do lf_report::add_kpi(tags, 'throughput-download-bps', $my_value);""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +influx = importlib.import_module("py-scripts.influx") +RecordInflux = influx.RecordInflux +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class DownloadTest(Realm): + def __init__(self, + _sta_list=None, + _ssid=None, + _password=None, + _security=None, + ): + super().__init__(_host, + _port) + self.host = _host + self.ssid=_ssid + self.security = _security + self.password = _password + + self.sta_list= _sta_list + +def main(): + parser = LFCliBase.create_bare_argparse( + prog='download_test.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog=''' + Download throughput test''', + + ) + parser.add_argument('--influx_user', help='Username for your Influx database', required=True) + parser.add_argument('--influx_passwd', help='Password for your Influx database', required=True) + parser.add_argument('--influx_db', help='Name of your Influx database', required=True) + parser.add_argument('--longevity', help='How long you want to gather data', default='4h') + parser.add_argument('--device', help='Device to monitor', action='append', required=True) + parser.add_argument('--monitor_interval', help='How frequently you want to append to your database', default='5s') + parser.add_argument('--target_kpi', help='Monitor only selected columns', action='append', default=target_kpi) + + args = parser.parse_args() + + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + station_list = LFUtils.port_name_series(prefix="sta", + start_id=0, + end_id=num_sta-1, + padding_number=10000, + radio=args.radio) + + monitor_interval = LFCliBase.parse_time(args.monitor_interval).total_seconds() + longevity = LFCliBase.parse_time(args.longevity).total_seconds() + grapher = DownloadTest(_host=args.mgr, + _port=args.mgr_port, + _influx_db=args.influx_db, + _influx_user=args.influx_user, + _influx_passwd=args.influx_passwd, + _longevity=longevity, + _devices=args.device, + _monitor_interval=monitor_interval, + _target_kpi=args.target_kpi, + _ssid=args.ssid, + _password=args.passwd, + ) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/event_breaker.py b/lanforge/lanforge-scripts/py-scripts/event_breaker.py new file mode 100755 index 000000000..15a2fa2b8 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/event_breaker.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +This file is intended to expose concurrency +problems in the /events/ URL handler by querying events rapidly. +Please use concurrently with event_flood.py. +""" +import sys +import os +import importlib +from datetime import datetime +import pprint +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class EventBreaker(Realm): + def __init__(self, host, port, + duration=None, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port) + self.counter = 0 + self.test_duration=duration + if (self.test_duration is None): + raise ValueError("run wants numeric run_duration_sec") + + def create(self): + pass + + def run(self): + + now = datetime.now() + now_ms = 0 + end_time = self.parse_time(self.test_duration) + now + client_time_ms = 0 + prev_client_time_ms = 0 + start_loop_time_ms = 0 + loop_time_ms = 0 + prev_loop_time_ms = 0 + num_events = 0 + prev_num_events = 0 + bad_events = [] + while datetime.now() < end_time: + bad_events = [] + start_loop_time_ms = int(self.get_milliseconds(datetime.now())) + print ('\r♦ ', end='') + #prev_loop_time_ms = loop_time_ms + # loop_time_ms = self.get_milliseconds(datetime.now()) + prev_client_time_ms = client_time_ms + response = self.json_get("/events/all") + #pprint.pprint(response) + + if "events" not in response: + pprint.pprint(response) + raise AssertionError("no events in response") + events = response["events"] + prev_num_events = num_events + num_events = len(events) + if num_events != prev_num_events: + print("%s events Δ%s"%(num_events, (num_events - prev_num_events))) + if "candela.lanforge.HttpEvents" in response: + client_time_ms = float(response["candela.lanforge.HttpEvents"]["duration"]) + # print(" client_time %d"%client_time_ms) + + if abs(prev_client_time_ms - client_time_ms) > 30: + print(" client time %d ms Δ%d"%(client_time_ms, (prev_client_time_ms - client_time_ms)), + end='') + event_index = 0 + for record in events: + + for k in record.keys(): + if record[k] is None: + print (' ☠no %s☠'%k, end='') + continue + # pprint.pprint( record[k]) + if "NA" == record[k]["event"] \ + or "NA" == record[k]["name"] \ + or "NA" == record[k]["type"] \ + or "NA" == record[k]["priority"]: + bad_events.append(int(k)) + pprint.pprint(record[k]) + # print( " ☠id[%s]☠"%k, end='') + if len(bad_events) > 0: + pprint.pprint(events[event_index]) + print( " ☠id[%s]☠"%bad_events, end='') + exit(1) + event_index += 1 + prev_loop_time_ms = loop_time_ms + now_ms = int(self.get_milliseconds(datetime.now())) + loop_time_ms = now_ms - start_loop_time_ms + if (prev_loop_time_ms - loop_time_ms) > 15: + print(" loop time %d ms Δ%d " + %(loop_time_ms, (prev_loop_time_ms - loop_time_ms)), + end='') + if (prev_loop_time_ms - loop_time_ms) > 30: + print("") + + def cleanup(self): + pass + +def main(): + parser = LFCliBase.create_bare_argparse( + prog='event_breaker.py', + formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument("--test_duration", help='test duration', default="30s" ) + # if optional_args is not None: + args = parser.parse_args() + + event_breaker = EventBreaker(host=args.mgr, + port=args.mgr_port, + duration=args.test_duration, + _debug_on=True, + _exit_on_error=True, + _exit_on_fail=True) + event_breaker.create() + event_breaker.run() + event_breaker.cleanup() + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/event_flood.py b/lanforge/lanforge-scripts/py-scripts/event_flood.py new file mode 100755 index 000000000..573800b8b --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/event_flood.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +This file is intended to expose concurrency +problems in the /events/ URL handler by inserting events rapidly. +Please concurrently use with event_breaker.py. +""" +import sys +import os +import importlib +import argparse +from datetime import datetime +from time import sleep + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class EventBreaker(Realm): + def __init__(self, host, port, + duration=None, + pause_ms=None, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port) + self.counter = 0 + self.test_duration = duration + self.pause_ms = pause_ms + if (self.test_duration is None): + raise ValueError("run wants numeric run_duration_sec") + + def create(self): + pass + + def run(self): + last_second_ms = 0 + start_time = datetime.now() + now_ms = 0 + end_time = self.parse_time(self.test_duration) + start_time + client_time_ms = 0 + prev_client_time_ms = 0 + start_loop_time_ms = 0 + loop_time_ms = 0 + prev_loop_time_ms = 0 + num_events = 0 + prev_num_events = 0 + + while datetime.now() < end_time: + sleep( self.pause_ms / 1000 ) + start_loop_time_ms = int(self.get_milliseconds(datetime.now())) + print ('\r♦ ', end='') + #prev_loop_time_ms = loop_time_ms + # loop_time_ms = self.get_milliseconds(datetime.now()) + prev_client_time_ms = client_time_ms + response_list = [] + response = self.json_post("/cli-json/add_event", + { + "event_id": "new", + "details": "event_flood %d"%start_loop_time_ms, + "priority": "INFO", + "name": "custom" + }, + response_json_list_=response_list) + # pprint.pprint(response_list) + prev_client_time_ms = client_time_ms + prev_loop_time_ms = loop_time_ms + now = int(self.get_milliseconds(datetime.now())) + loop_time_ms = now - start_loop_time_ms + + client_time_ms = response_list[0]["LAST"]["duration"] + if (client_time_ms != prev_client_time_ms): + print(" client %d ms %d"%(client_time_ms, + (prev_client_time_ms - client_time_ms)), + end='') + if (loop_time_ms != prev_loop_time_ms): + print(" loop %d ms %d "%(loop_time_ms, + (prev_loop_time_ms - loop_time_ms)), + end='') + if (last_second_ms + 1000) < now: + last_second_ms = now + print("") + def cleanup(self): + pass + +def main(): + parser = LFCliBase.create_bare_argparse( + prog='event_breaker.py', + formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument("--test_duration", help='test duration', default="30s" ) + parser.add_argument("--pause_ms", help='interval between submitting events', default="30" ) + # if optional_args is not None: + args = parser.parse_args() + + event_breaker = EventBreaker(host=args.mgr, + port=args.mgr_port, + duration=args.test_duration, + pause_ms=int(args.pause_ms), + _debug_on=True, + _exit_on_error=True, + _exit_on_fail=True) + event_breaker.create() + event_breaker.run() + event_breaker.cleanup() + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/example-configs/mesh-ferndale-cfg.txt b/lanforge/lanforge-scripts/py-scripts/example-configs/mesh-ferndale-cfg.txt new file mode 100644 index 000000000..3007059f9 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/example-configs/mesh-ferndale-cfg.txt @@ -0,0 +1,47 @@ +chamber-0: RootAP +chamber-1: Node1 +chamber-2: Node2 +chamber-3: +chamber-4: MobileStations +sta_amount-0: 1 +sta_amount-1: 1 +sta_amount-2: 1 +sta_amount-3: 1 +sta_amount-4: 1 +radios-0-0: 1.2.wiphy0 +radios-0-1: +radios-0-2: +radios-0-3: 1.2.wiphy1 +radios-0-4: +radios-0-5: +radios-1-0: 1.3.wiphy0 +radios-1-1: +radios-1-2: +radios-1-3: 1.3.wiphy1 +radios-1-4: +radios-1-5: +radios-2-0: 1.4.wiphy0 +radios-2-1: +radios-2-2: +radios-2-3: 1.4.wiphy1 +radios-2-4: +radios-2-5: +radios-3-0: +radios-3-1: +radios-3-2: +radios-3-3: +radios-3-4: +radios-3-5: +radios-4-0: 1.1.2 wiphy0 +radios-4-1: +radios-4-2: +radios-4-3: 1.1.3 wiphy1 +radios-4-4: +radios-4-5: +ap_arrangements: Current Position +tests: Roam +traf_combo: STA +sta_position: Current Position +traffic_types: UDP +direction: Download +path: Orbit Current diff --git a/lanforge/lanforge-scripts/py-scripts/example-configs/tr398-ferndale-ac-cfg.txt b/lanforge/lanforge-scripts/py-scripts/example-configs/tr398-ferndale-ac-cfg.txt new file mode 100644 index 000000000..097614d3d --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/example-configs/tr398-ferndale-ac-cfg.txt @@ -0,0 +1,43 @@ +# Example radio setup, calibration data, and attenuator setup. +# At least the attenuation will be unique for your testbed +# so run the calibration step, view the config, and paste the appropriate +# lines into a file similar to this. + +radio-0: 1.1.2 wiphy0 +radio-1: 1.1.3 wiphy1 +radio-2: 1.1.4 wiphy2 +radio-3: 1.1.5 wiphy3 +radio-4: 1.1.6 wiphy4 +radio-5: 1.1.7 wiphy5 +rssi_0_2-0: -26 +rssi_0_2-1: -26 +rssi_0_2-2: -26 +rssi_0_2-3: -26 +rssi_0_2-4: -27 +rssi_0_2-5: -27 +rssi_0_2-6: -27 +rssi_0_2-7: -27 +rssi_0_2-8: -25 +rssi_0_2-9: -25 +rssi_0_2-10: -25 +rssi_0_2-11: -25 +rssi_0_5-0: -38 +rssi_0_5-1: -38 +rssi_0_5-2: -38 +rssi_0_5-3: -38 +rssi_0_5-4: -38 +rssi_0_5-5: -38 +rssi_0_5-6: -38 +rssi_0_5-7: -38 +rssi_0_5-8: -47 +rssi_0_5-9: -47 +rssi_0_5-10: -47 +rssi_0_5-11: -47 +atten-0: 1.1.85.0 +atten-1: 1.1.85.1 +atten-2: 1.1.85.2 +atten-3: 1.1.85.3 +atten-4: 1.1.1002.0 +atten-5: 1.1.1002.1 +atten-8: 1.1.1002.2 +atten-9: 1.1.1002.3 diff --git a/lanforge/lanforge-scripts/py-scripts/example-configs/tr398v2-ferndale-ac-cfg.txt b/lanforge/lanforge-scripts/py-scripts/example-configs/tr398v2-ferndale-ac-cfg.txt new file mode 100644 index 000000000..e66c38b73 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/example-configs/tr398v2-ferndale-ac-cfg.txt @@ -0,0 +1,193 @@ +show_events: 1 +show_log: 1 +port_sorting: 2 +kpi_id: TR_398v2 +bg: 0xE0ECF8 +dut_info_override: Anonymous Enterprise AX AP +test_rig: +test_tag: +show_scan: 1 +auto_helper: 1 +skip_ac: 0 +skip_ax: 0 +skip_2: 0 +skip_5: 0 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +selected_dut5: TR398-DUT-r750 ruckus-r750-5g 4c:b1:cd:18:e8:ec (1) +selected_dut2: TR398-DUT-r750 ruckus-r750-2g 4c:b1:cd:18:e8:e8 (2) +upstream_port: 1.2.2 eth2 +operator: +mconn: 5 +txpower: 20 +band2_freq: 2437 +band5_freq: 5180 +tos: 0 +speed: 65% +ospeed: 20000 +max_cx_random: 0 +speed_max_cx_adjust: 1000000 +speed_max_cx_2: 2000000 +speed_max_cx_ax_2: 3000000 +speed_max_cx_5: 8000000 +speed_max_cx_ax_5: 10000000 +max_tput_speed_2: 100000000 +max_tput_speed_5: 560000000 +max_tput_speed_ax_2: 200000000 +max_tput_speed_ax_5: 720000000 +max_peak_tput_speed_ax_2: 300000000 +max_peak_tput_speed_ax_5: 1100000000 +max_peak_tput_speed_ax_5_4: 1100000000 +atf_max_nss: 2 +atf_extra_2m_atten: 0 +rxsens_deg_rot: 180 +rxsens_pre_steps: 4 +stability_udp_dur: 900 +stability_iter: 16 +calibrate_mode: 4 +calibrate_nss: 1 +dur120: 30 +dur180: 180 +i_5g_80: 195000000 +i_5g_40: 90000000 +i_2g_20: 32000000 +i_5g_80_ax: 195000000 +i_5g_40_ax: 90000000 +i_2g_20_ax: 32000000 +spatial_deg_rot: 30 +spatial_retry: 0 +reset_pp: 99 +bidir_dp_prcnt: 0.05 +rxsens_stop_at_pass: 0 +spatial_pause_on_zero_tput: 0 +auto_coex: 0 +use_virtual_ax_sta: 0 +rvr_adj: 0 +rssi_2m_2: -26 +rssi_2m_5: -30 +extra_dl_path_loss: 0 +dur60: 20 +turn_table: TR-398 +radio-0: 1.1.2 wiphy0 +radio-1: 1.1.3 wiphy1 +radio-2: 1.1.4 wiphy2 +radio-3: 1.1.5 wiphy3 +radio-4: 1.1.6 wiphy4 +radio-5: 1.1.7 wiphy5 +ax_radio-0: 1.2.wiphy0 +ax_radio-1: 1.2.wiphy1 +ax_radio-2: 1.2.wiphy2 +ax_radio-3: 1.2.wiphy3 +ax_radio-4: 1.2.wiphy4 +ax_radio-5: 1.2.wiphy5 +ax_radio-6: 1.2.wiphy6 +ax_radio-7: 1.2.wiphy7 +ax_radio-8: 1.2.wiphy8 +ax_radio-9: 1.2.wiphy9 +ax_radio-10: 1.2.wiphy10 +ax_radio-11: 1.2.wiphy11 +ax_radio-12: 1.3.wiphy0 +ax_radio-13: 1.3.wiphy5 +ax_radio-14: 1.3.wiphy10 +ax_radio-15: 1.3.wiphy15 +ax_radio-16: 1.3.wiphy1 +ax_radio-17: 1.3.wiphy6 +ax_radio-18: 1.3.wiphy11 +ax_radio-19: 1.3.wiphy16 +ax_radio-20: 1.3.wiphy2 +ax_radio-21: 1.3.wiphy7 +ax_radio-22: 1.3.wiphy12 +ax_radio-23: 1.3.wiphy17 +ax_radio-24: 1.3.wiphy3 +ax_radio-25: 1.3.wiphy8 +ax_radio-26: 1.3.wiphy13 +ax_radio-27: 1.3.wiphy18 +ax_radio-28: 1.3.wiphy4 +ax_radio-29: 1.3.wiphy9 +ax_radio-30: 1.3.wiphy14 +ax_radio-31: 1.3.wiphy19 +rssi_0_2-0: -28 +rssi_0_2-1: -28 +rssi_0_2-2: -28 +rssi_0_2-3: -28 +rssi_0_2-4: -22 +rssi_0_2-5: -22 +rssi_0_2-6: -22 +rssi_0_2-7: -22 +rssi_0_2-8: -25 +rssi_0_2-9: -25 +rssi_0_2-10: -25 +rssi_0_2-11: -25 +ax_rssi_0_2-0: -29 +ax_rssi_0_2-1: -29 +ax_rssi_0_2-2: -29 +ax_rssi_0_2-3: -29 +ax_rssi_0_2-4: -23 +ax_rssi_0_2-5: -23 +ax_rssi_0_2-6: -23 +ax_rssi_0_2-7: -23 +ax_rssi_0_2-8: -26 +ax_rssi_0_2-9: -26 +ax_rssi_0_2-10: -26 +ax_rssi_0_2-11: -26 +rssi_0_5-0: -35 +rssi_0_5-1: -35 +rssi_0_5-2: -35 +rssi_0_5-3: -35 +rssi_0_5-4: -33 +rssi_0_5-5: -33 +rssi_0_5-6: -33 +rssi_0_5-7: -33 +rssi_0_5-8: -39 +rssi_0_5-9: -39 +rssi_0_5-10: -39 +rssi_0_5-11: -39 +ax_rssi_0_5-0: -35 +ax_rssi_0_5-1: -35 +ax_rssi_0_5-2: -35 +ax_rssi_0_5-3: -35 +ax_rssi_0_5-4: -32 +ax_rssi_0_5-5: -32 +ax_rssi_0_5-6: -32 +ax_rssi_0_5-7: -32 +ax_rssi_0_5-8: -39 +ax_rssi_0_5-9: -39 +ax_rssi_0_5-10: -39 +ax_rssi_0_5-11: -39 +atten-0: 1.1.3094.0 +atten-1: 1.1.3094.1 +atten-2: 1.1.3094.2 +atten-3: 1.1.3094.3 +atten-4: 1.1.3102.0 +atten-5: 1.1.3102.1 +atten-6: 1.1.3099.0 +atten-7: 1.1.3099.1 +atten-8: 1.1.3102.2 +atten-9: 1.1.3102.3 +ax_atten-0: 1.1.3100.3 +ax_atten-1: 1.1.3100.2 +ax_atten-2: NA +ax_atten-3: NA +ax_atten-4: 1.1.3100.1 +ax_atten-5: 1.1.3100.0 +ax_atten-8: 1.1.3099.3 +ax_atten-9: 1.1.3099.2 +atten_cal_ac: 0 +atten_cal_ax: 0 +rxsens: 0 +max_cx: 0 +max_tput: 1 +peak_perf: 0 +max_tput_bi: 0 +dual_band_tput: 0 +atf: 0 +atf3: 0 +qos3: 0 +rvr: 0 +spatial: 0 +multi_sta: 0 +reset: 0 +mu_mimo: 0 +stability: 0 +ap_coex: 0 diff --git a/lanforge/lanforge-scripts/py-scripts/example_security_connection.py b/lanforge/lanforge-scripts/py-scripts/example_security_connection.py new file mode 100755 index 000000000..54fe14633 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/example_security_connection.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class IPv4Test(LFCliBase): + def __init__(self, ssid, security, password, sta_list=None, ap=None, mode = 0, number_template="00000", host="localhost", port=8080,radio = "wiphy0",_debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.ssid = ssid + self.mode = mode + self.ap = ap + self.radio = radio + self.security = security + self.password = password + self.sta_list = sta_list + self.timeout = 120 + self.number_template = number_template + self.debug = _debug_on + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.mode =self.mode + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = mode + if self.ap is not None: + self.station_profile.set_command_param("add_sta", "ap",self.ap) + + def build(self): + # Build stations + #print("We've gotten into the build stations function") + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(station_list=self.sta_list, debug=self.debug, timeout_sec=30): + self._pass("Station build finished") + self.exit_success() + else: + self._fail("Stations not able to acquire IP. Please check network input.") + self.exit_fail() + + + def cleanup(self, sta_list): + self.station_profile.cleanup(sta_list) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, + debug=self.debug) + +def main(): + + parser = LFCliBase.create_basic_argparse( + prog='example_security_connection.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + This python script creates an inputted number of stations using user-inputted security. This verifies that the most basic form of security works with the LANforge device. + ''', + description='''\ + example_security_connection.py + --------------------------------------------------------------------------- + + Example of command line to run: + ./example_security_connection.py + --mgr localhost + --mgr_port 8080 + --num_stations 6 + --mode 1 + --radio wiphy2 + --security {open|wep|wpa|wpa2|wpa3} + --ssid netgear-wpa3 + --ap "00:0e:8e:78:e1:76" + --passwd admin123-wpa3 + --debug + ''') + required=None + for agroup in parser._action_groups: + if agroup.title == "required arguments": + required = agroup + #if required is not None: + optional = None + for agroup in parser._action_groups: + if agroup.title == "optional arguments": + optional = agroup + if optional is not None: + optional.add_argument('--mode',help=LFCliBase.Help_Mode) + optional.add_argument('--ap',help='Add BSSID of access point to connect to') + + args = parser.parse_args() + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + station_list = LFUtils.portNameSeries(prefix_="sta", + start_id_=0, + end_id_=num_sta-1, + padding_number_=10000, + radio=args.radio) + ip_test = IPv4Test(host=args.mgr, port=args.mgr_port, + ssid=args.ssid, password=args.passwd, + radio=args.radio, mode= args.mode, + security=args.security, sta_list=station_list, + ap=args.ap) + ip_test.cleanup(station_list) + ip_test.timeout = 60 + ip_test.build() + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/ftp_html.py b/lanforge/lanforge-scripts/py-scripts/ftp_html.py new file mode 100644 index 000000000..3d7f57887 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ftp_html.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 + +from matplotlib import pyplot as plt +import numpy as np +import os.path +from os import path +import sys +import pdfkit +sys.path.append('/home/lanforge/.local/lib/python3.6/site-packages') +def report_banner(date): + banner_data = """ + + + + + + LANforge Report + + FTP Test + +

    +
    + +
    +
    +

    FTP Test

    +

    """ + str(date) + """

    +
    +
    +

    + """ + return str(banner_data) +def test_objective(objective= 'This FTP Test is used to "Verify that N clients connected on Specified band and can simultaneously download some amount of file from FTP server and measuring the time taken by client to Download/Upload the file."'): + test_objective = """ + +

    Objective

    +

    """ + str(objective) + """

    +
    + """ + return str(test_objective) +def test_setup_information(test_setup_data=None): + if test_setup_data is None: + return None + else: + var = "" + for i in test_setup_data: + var = var + "" + i + "" + str(test_setup_data[i]) + "" + + setup_information = """ + + + + + + + + + +
    Test Setup Information
    Device Under Test + + """ + str(var) + """ +
    +
    +
    + """ + return str(setup_information) + + +def pass_fail_description(data=" This Table will give Pass/Fail results. "): + pass_fail_info = """ + +

    PASS/FAIL Results

    +

    """ + str(data) + """

    +
    + """ + return str(pass_fail_info) + + +def download_upload_time_description(data=" This Table will FTP Download/Upload Time of Clients."): + download_upload_time= """ + +

    File Download/Upload Time (sec)

    +

    """ + str(data) + """

    +
    + """ + return str(download_upload_time) + + +def add_pass_fail_table(result_data, row_head_list, col_head_list): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + str(row) + "" + list_data = [] + dict_data = {} + bands = result_data[1]["bands"] + file_sizes = result_data[1]["file_sizes"] + directions = result_data[1]["directions"] + for b in bands: + final_data = "" + for size in file_sizes: + for d in directions: + for data in result_data.values(): + if data["band"] == b and data["direction"] == d and data["file_size"] == size: + if data["result"] == "Pass": + final_data = final_data + "Pass" + elif data["result"] == "Fail": + final_data = final_data + "Fail" + + list_data.append(final_data) + + #print(list_data) + j = 0 + for i in row_head_list: + dict_data[i] = list_data[j] + j = j + 1 + #print(dict_data) + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + dict_data[col]) + "" + + pass_fail_table = """ + + + +
    + + """ + str(var_row) + """ + + """ + str(var_col) + """ +
    + +






    + """ + return pass_fail_table + + +def download_upload_time_table(result_data, row_head_list, col_head_list): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + str(row) + "" + list_data = [] + dict_data = {} + bands = result_data[1]["bands"] + file_sizes = result_data[1]["file_sizes"] + directions = result_data[1]["directions"] + for b in bands: + final_data = "" + for size in file_sizes: + for d in directions: + for data in result_data.values(): + data_time = data['time'] + if data_time.count(0) == 0: + Min = min(data_time) + Max = max(data_time) + Sum = int(sum(data_time)) + Len = len(data_time) + Avg = round(Sum / Len,2) + elif data_time.count(0) == len(data_time): + Min = "-" + Max = "-" + Avg = "-" + else: + data_time = [i for i in data_time if i != 0] + Min = min(data_time) + Max = max(data_time) + Sum = int(sum(data_time)) + Len = len(data_time) + Avg = round(Sum / Len,2) + string_data = "Min=" + str(Min) + ",Max=" + str(Max) + ",Avg=" + str(Avg) + " (sec)" + if data["band"] == b and data["direction"] == d and data["file_size"] == size: + final_data = final_data + """""" + string_data + """""" + + list_data.append(final_data) + + #print(list_data) + j = 0 + for i in row_head_list: + dict_data[i] = list_data[j] + j = j + 1 + #print(dict_data) + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + dict_data[col]) + "" + + download_upload_table = """ + + + +
    + + """ + str(var_row) + """ + + """ + str(var_col) + """ +
    + +






    + """ + return download_upload_table +def graph_html(graph_path="",graph_name="",graph_description=""): + graph_html_obj = """ +

    """ +graph_name+ """

    +

    """ +graph_description+ """

    + +

    + """ + return str(graph_html_obj) + + +def bar_plot(ax,x_axis, data, colors=None, total_width=0.8, single_width=1, legend=True): + # Check if colors where provided, otherwhise use the default color cycle + if colors is None: + colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] + + # Number of bars per group + n_bars = len(data) + + # The width of a single bar + bar_width = total_width / n_bars + + # List containing handles for the drawn bars, used for the legend + bars = [] + + # Iterate over all data + for i, (name, values) in enumerate(data.items()): + # The offset in x direction of that bar + x_offset = (i - n_bars / 2) * bar_width + bar_width / 2 + + # Draw a bar for every value of that type + for x, y in enumerate(values): + bar = ax.bar(x + x_offset, y, width=bar_width * single_width, color=colors[i % len(colors)]) + + # Add a handle to the last drawn bar, which we'll need for the legend + bars.append(bar[0]) + + # Draw legend if we need + if legend: + ax.legend(bars, data.keys(),bbox_to_anchor=(1.1,1.05),loc='upper right') + ax.set_ylabel('Time in seconds') + ax.set_xlabel("stations") + x_data = x_axis + idx = np.asarray([i for i in range(len(x_data))]) + ax.set_xticks(idx) + + ax.set_xticklabels(x_data) + +def generate_graph(result_data, x_axis,band,size,graph_path): + # bands = result_data[1]["bands"] + # file_sizes = result_data[1]["file_sizes"] + num_stations = result_data[1]["num_stations"] + # for b in bands: + # for size in file_sizes: + + dict_of_graph = {} + color = [] + graph_name = "" + graph_description="" + count = 0 + for data in result_data.values(): + if data["band"] == band and data["file_size"] == size and data["direction"] == "Download": + dict_of_graph["Download"] = data["time"] + color.append("Orange") + graph_name = "File size "+ size +" " + str(num_stations) + " Clients " +band+ "-File Download Times(secs)" + graph_description = "Out of "+ str(data["num_stations"])+ " clients, "+ str(data["num_stations"] - data["time"].count(0))+ " are able to download " + "within " + str(data["duration"]) + " min." + count = count + 1 + if data["band"] == band and data["file_size"] == size and data["direction"] == "Upload": + dict_of_graph["Upload"] = data["time"] + color.append("Blue") + graph_name = "File size "+ size +" " + str(num_stations) + " Clients " +band+ "-File Upload Times(secs)" + graph_description = graph_description + "Out of " + str(data["num_stations"]) + " clients, " + str( + data["num_stations"] - data["time"].count(0)) + " are able to upload " + "within " +str(data["duration"]) + " min." + count = count + 1 + if count == 2: + graph_name = "File size "+ size +" " + str(num_stations) + " Clients " +band+ "-File Download and Upload Times(secs)" + if len(dict_of_graph) != 0: + fig, ax = plt.subplots() + bar_plot(ax, x_axis, dict_of_graph, total_width=.8, single_width=.9, colors=color) + my_dpi = 96 + figure = plt.gcf() # get current figure + figure.set_size_inches(18, 6) + + # when saving, specify the DPI + plt.savefig(graph_path + "/image"+band+size+".png", dpi=my_dpi) + return str(graph_html(graph_path + "/image"+band+size+".png", graph_name,graph_description)) + else: + return "" +def input_setup_info_table(input_setup_info=None): + if input_setup_info is None: + return None + else: + var = "" + for i in input_setup_info: + var = var + "" + i + "" + str(input_setup_info[i]) + "" + + setup_information = """ + + + + + + + + + +
    Input Setup Information
    Information + + """ + str(var) + """ +
    +
    +
    + """ + return str(setup_information) + + +def generate_report(result_data=None, + date=None, + test_setup_info={}, + input_setup_info={}, + graph_path="/home/lanforge/html-reports/FTP-Test"): + # Need to pass this to test_setup_information() + input_setup_info = input_setup_info + test_setup_data = test_setup_info + x_axis = [] + num_stations = result_data[1]["num_stations"] + for i in range(1, num_stations + 1, 1): + x_axis.append(i) + column_head = [] + rows_head = [] + bands = result_data[1]["bands"] + file_sizes = result_data[1]["file_sizes"] + directions = result_data[1]["directions"] + + for size in file_sizes: + for direction in directions: + column_head.append(size + " " + direction) + for band in bands: + if band != "Both": + rows_head.append(str(num_stations) + " Clients-" + band) + else: + rows_head.append(str(num_stations // 2) + "+" + str(num_stations // 2) + " Clients-2.4G+5G") + + reports_root = graph_path + "/" + str(date) + if path.exists(graph_path): + os.mkdir(reports_root) + print("Reports Root is Created") + + else: + os.mkdir(graph_path) + os.mkdir(reports_root) + print("Reports Root is created") + print("Generating Reports in : ", reports_root) + + html_report = report_banner(date) + \ + test_setup_information(test_setup_data) + \ + test_objective() + \ + pass_fail_description() + \ + add_pass_fail_table(result_data, rows_head, column_head) + \ + download_upload_time_description() + \ + download_upload_time_table(result_data, rows_head, column_head) + + for b in bands: + for size in file_sizes: + html_report = html_report + \ + generate_graph(result_data, x_axis, b, size, graph_path=reports_root) + + html_report = html_report + input_setup_info_table(input_setup_info) + + # write the html_report into a file in /home/lanforge/html_reports in a directory named FTP-Test and html_report name should be having a timesnap with it + f = open(reports_root + "/report.html", "a") + # f = open("report.html", "a") + f.write(html_report) + f.close() + # write logic to generate pdf here + pdfkit.from_file(reports_root + "/report.html", reports_root + "/report.pdf") + + +# test blocks from here +if __name__ == '__main__': + generate_report() diff --git a/lanforge/lanforge-scripts/py-scripts/ghost_profile.py b/lanforge/lanforge-scripts/py-scripts/ghost_profile.py new file mode 100755 index 000000000..5a91843ae --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/ghost_profile.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +""" +NAME: ghost_profile.py +PURPOSE: modify ghost database from the command line. +SETUP: A Ghost installation which the user has admin access to. +EXAMPLE: ./ghost_profile.py --article_text_file text.txt --title Test --authors Matthew --ghost_token SECRET_KEY --host 192.168.1.1 + +There is a specific class for uploading kpi graphs called kpi_to_ghost. + +EXAMPLE: ./ghost_profile.py --ghost_token TOKEN --ghost_host 192.168.100.147 +--folders /home/lanforge/html-reports/wifi-capacity-2021-06-04-02-51-07 +--kpi_to_ghost appl --authors Matthew --title 'wifi capacity 2021 06 04 02 51 07' --server 192.168.93.51 +--user_pull lanforge --password_pull lanforge --customer candela --testbed heather --test_run test-run-6 +--user_push matt --password_push PASSWORD + +EXAMPLE 2: ./ghost_profile.py --ghost_token TOKEN +--ghost_host 192.168.100.147 --server 192.168.93.51 --customer candela +--testbed heather --user_push matt --password_push "amount%coverage;Online" --kpi_to_ghost app +--folders /home/lanforge/html-reports/wifi-capacity-2021-06-14-10-42-29 --grafana_token TOKEN +--grafana_host 192.168.100.201 + +this script uses pyjwt. If you get the issue module 'jwt' has no attribute 'encode', run this: pip3 uninstall jwt pyjwt && pip install pyjwt + Matthew Stidham + Copyright 2021 Candela Technologies Inc + License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +# from GhostRequest import GhostRequest +GhostRequest = importlib.import_module("py-dashboard.GhostRequest") + + +class UseGhost(GhostRequest): + def __init__(self, + _ghost_token=None, + host="localhost", + port=8080, + _debug_on=False, + _exit_on_fail=False, + _ghost_host="localhost", + _ghost_port=2368, + influx_host=None, + influx_port=None, + influx_org=None, + influx_token=None, + influx_bucket=None): + super().__init__(_ghost_host, + str(_ghost_port), + _api_token=_ghost_token, + influx_host=influx_host, + influx_port=influx_port, + influx_org=influx_org, + influx_token=influx_token, + influx_bucket=influx_bucket, + debug_=_debug_on) + self.ghost_host = _ghost_host + self.ghost_port = _ghost_port + self.ghost_token = _ghost_token + self.influx_host = influx_host + self.influx_port = influx_port + self.influx_org = influx_org + self.influx_token = influx_token + self.influx_bucket = influx_bucket + + def create_post_from_file(self, title, file, tags, authors): + text = open(file).read() + return self.create_post(title=title, text=text, tags=tags, authors=authors) + + def kpi(self, + authors, + folders, + parent_folder, + title, + server_pull, + ghost_host, + port, + user_push, + password_push, + customer, + testbed, + test_run, + grafana_token, + grafana_host, + grafana_port, + datasource, + grafana_bucket): + target_folders = list() + return self.kpi_to_ghost(authors, + folders, + parent_folder, + title, + server_pull, + ghost_host, + port, + user_push, + password_push, + customer, + testbed, + test_run, + target_folders, + grafana_token, + grafana_host, + grafana_port, + datasource, + grafana_bucket) + + +def main(): + parser = argparse.ArgumentParser( + prog='ghost_profile.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Manage Ghost Website''', + description=''' + ghost_profile.py + ---------------- + Command example: + ./ghost_profile.py + --ghost_token''' + ) + optional = parser.add_argument_group('optional arguments') + optional.add_argument('--ghost_token', default=None) + optional.add_argument('--create_post', default=None) + optional.add_argument('--article_text_file', default=None) + + optional.add_argument('--ghost_port', help='Ghost port if different from 2368', default=2368) + optional.add_argument('--ghost_host', help='Ghost host if different from localhost', default='localhost') + optional.add_argument('--article_text') + optional.add_argument('--article_tags', action='append') + optional.add_argument('--authors', action='append') + optional.add_argument('--title', default=None) + optional.add_argument('--image', default=None) + optional.add_argument('--folder', default=None) + optional.add_argument('--custom_post', default=None) + optional.add_argument('--kpi_to_ghost', help='Generate a Ghost report from KPI spreadsheets', action="store_true") + optional.add_argument('--folders', action='append', default=None) + optional.add_argument('--server_pull') + optional.add_argument('--port', default=22) + optional.add_argument('--user_push') + optional.add_argument('--password_push') + optional.add_argument('--customer') + optional.add_argument('--testbed') + optional.add_argument('--test_run', default=None) + optional.add_argument('--grafana_token', default=None) + optional.add_argument('--grafana_host', default=None) + optional.add_argument('--grafana_port', default=3000) + optional.add_argument('--parent_folder', default=None) + optional.add_argument('--datasource', default='InfluxDB') + optional.add_argument('--grafana_bucket', default=None) + optional.add_argument('--influx_host') + optional.add_argument('--influx_token', help='Username for your Influx database') + optional.add_argument('--influx_bucket', help='Password for your Influx database') + optional.add_argument('--influx_org', help='Name of your Influx database') + optional.add_argument('--influx_port', help='Port where your influx database is located', default=8086) + optional.add_argument('--influx_tag', action='append', nargs=2, + help='--influx_tag Can add more than one of these.') + optional.add_argument('--influx_mgr', + help='IP address of the server your Influx database is hosted if different from your LANforge Manager', + default=None) + optional.add_argument('--debug', help='Enable debugging', default=False, action="store_true") + args = parser.parse_args() + + Ghost = UseGhost(_ghost_token=args.ghost_token, + _ghost_port=args.ghost_port, + _ghost_host=args.ghost_host, + influx_host=args.influx_host, + influx_port=args.influx_port, + influx_org=args.influx_org, + influx_token=args.influx_token, + influx_bucket=args.influx_bucket, + _debug_on=args.debug) + + if args.create_post is not None: + Ghost.create_post(args.title, args.article_text, args.article_tags, args.authors) + if args.article_text_file is not None: + Ghost.create_post_from_file(args.title, args.article_text_file, args.article_tags, args.authors) + + if args.image is not None: + Ghost.upload_image(args.image) + + if args.custom_post is not None: + if args.folders is not None: + Ghost.custom_post(args.folders, args.authors) + else: + Ghost.custom_post(args.folder, args.authors) + else: + if args.folder is not None: + Ghost.upload_images(args.folder) + + if args.kpi_to_ghost is True: + Ghost.kpi(args.authors, + args.folders, + args.parent_folder, + args.title, + args.server_pull, + args.ghost_host, + args.port, + args.user_push, + args.password_push, + args.customer, + args.testbed, + args.test_run, + args.grafana_token, + args.grafana_host, + args.grafana_port, + args.datasource, + args.grafana_bucket) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/grafana_profile.py b/lanforge/lanforge-scripts/py-scripts/grafana_profile.py new file mode 100755 index 000000000..ce83005dd --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/grafana_profile.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 + +""" +The best way to use create_custom_dashboard by creating a graph_groups_file +The Graph_groups_file command is a txt file which lists the files which are going to be added to the Grafana Dashboard +It gets the columns of the files and from that it automatically determines the necessary titles on your dashboard. +""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +# from GrafanaRequest import GrafanaRequest +GrafanaRequest = importlib.import_module("py-dashboard.GrafanaRequest") +GrafanaRequest = GrafanaRequest.GrafanaRequest +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class UseGrafana(GrafanaRequest): + + def read_csv(self, file): + csv = open(file).read().split('\n') + rows = list() + for x in csv: + if len(x) > 0: + rows.append(x.split('\t')) + return rows + + def get_values(self, csv, target): + value = csv[0].index(target) + results = [] + for row in csv[1:]: + results.append(row[value]) + return results + + def get_units(self, target_csv): + csv = self.read_csv(target_csv) + graph_group = self.get_values(csv, 'Graph-Group') + units = self.get_values(csv, 'Units') + return dict(zip(graph_group, units)) + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='grafana_profile.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Manage Grafana database''', + description='''\ + grafana_profile.py + ------------------ + Command example: + ./grafana_profile.py + --grafana_token + --dashbaord_name + --scripts "Wifi Capacity" + + Create a custom dashboard with the following command: + ./grafana_profile.py --create_custom yes + --title Dataplane + --influx_bucket lanforge + --grafana_token TOKEN + --graph_groups 'Per Stations Rate DL' + --graph_groups 'Per Stations Rate UL' + --graph_groups 'Per Stations Rate UL+DL' + + Create a snapshot of a dashboard: + ./grafana_profile.py --grafana_token TOKEN + --grafana_host HOST + --create_snapshot + --title TITLE_OF_DASHBOARD + ''') + required = parser.add_argument_group('required arguments') + required.add_argument('--grafana_token', help='token to access your Grafana database', required=True) + + optional = parser.add_argument_group('optional arguments') + optional.add_argument('--dashboard_name', help='name of dashboard to create', default=None) + optional.add_argument('--dashboard_uid', help='UID of dashboard to modify', default=None) + optional.add_argument('--delete_dashboard', + help='Call this flag to delete the dashboard defined by UID', + default=None) + optional.add_argument('--grafana_port', help='Grafana port if different from 3000', default=3000) + optional.add_argument('--grafana_host', help='Grafana host', default='localhost') + optional.add_argument('--list_dashboards', help='List dashboards on Grafana server', default=None) + optional.add_argument('--dashboard_json', help='JSON of existing Grafana dashboard to import', default=None) + optional.add_argument('--create_custom', help='Guided Dashboard creation', action='store_true') + optional.add_argument('--dashboard_title', help='Titles of dashboards', default=None, action='append') + optional.add_argument('--scripts', help='Scripts to graph in Grafana', default=None, action='append') + optional.add_argument('--title', help='title of your Grafana Dashboard', default=None) + optional.add_argument('--influx_bucket', help='Name of your Influx Bucket', default=None) + optional.add_argument('--graph_groups', help='How you want to filter your graphs on your dashboard', + action='append', default=[None]) + optional.add_argument('--graph_groups_file', + help='File which determines how you want to filter your graphs on your dashboard', + default=None) + optional.add_argument('--testbed', help='Which testbed you want to query', default=None) + optional.add_argument('--kpi', help='KPI file(s) which you want to graph form', action='append', default=None) + optional.add_argument('--datasource', help='Name of Influx database if different from InfluxDB', default='InfluxDB') + optional.add_argument('--from_date', help='Date you want to start your Grafana dashboard from', default='now-1y') + optional.add_argument('--graph_height', help='Custom height for the graph on grafana dashboard', default=8) + optional.add_argument('--graph_width', help='Custom width for the graph on grafana dashboard', default=12) + optional.add_argument('--create_snapshot', action='store_true') + optional.add_argument('--list_snapshots', action='store_true') + args = parser.parse_args() + + Grafana = UseGrafana(args.grafana_token, + args.grafana_host, + grafanajson_port=args.grafana_port + ) + if args.dashboard_name is not None: + Grafana.create_dashboard(args.dashboard_name) + + if args.delete_dashboard is not None: + Grafana.delete_dashboard(args.dashboard_uid) + + if args.list_dashboards is not None: + Grafana.list_dashboards() + + if args.dashboard_json is not None: + Grafana.create_dashboard_from_data(args.dashboard_json) + + if args.kpi is not None: + args.graph_groups = args.graph_groups + Grafana.get_graph_groups(args.graph_groups) + + if args.create_custom: + Grafana.create_custom_dashboard(scripts=args.scripts, + title=args.title, + bucket=args.influx_bucket, + graph_groups=args.graph_groups, + graph_groups_file=args.graph_groups_file, + testbed=args.testbed, + datasource=args.datasource, + from_date=args.from_date, + graph_height=args.graph_height, + graph__width=args.graph_width) + + if args.create_snapshot: + Grafana.create_snapshot(args.title) + + if args.list_snapshots: + Grafana.list_snapshots() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/html_template.py b/lanforge/lanforge-scripts/py-scripts/html_template.py new file mode 100644 index 000000000..c55309103 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/html_template.py @@ -0,0 +1,1005 @@ +""" This script is used for DFS Test Report generation + it has generic methods inside which can be used for other test report generation + date - 11- feb - 2021 + -Nikita Yadav +""" +import os.path +import sys + +print(sys.path) +sys.path.append('/home/lanforge/.local/lib/python3.6/site-packages') +import pdfkit + +# dev complete +def report_banner(date): + banner_data = """ + + + + + + LANforge Report + + + + DFS TEST + +
    +
    + + + +
    +
    +

    Dynamic Frequency Selection

    +

    """ + date + """

    +
    +
    + +

    + + """ + return str(banner_data) + +# dev complete +def test_objective(objective="The DFS Test is designed to test the Performance of the Netgear Access Point.Dynamic frequency selection is a technology that is designed to ensure that wireless devices operating in the unlicensed WLAN 5 GHz bands are able to detect when they may be interfering with military and weather radar systems and automatically switch over to another frequency where they will not cause any disturbance. "): + test_objective = """ + +

    Objective

    +

    """ + str(objective) + """

    +
    + """ + return str(test_objective) + +def radar_detect_discription(data= " This Table will give you results in YES or NO if the AP detects the Radar"): + test_radar = """ + +

    Radar Detection Detail

    +

    """ + str(data) + """

    +
    + """ + return str(test_radar) + +def client_connection_detail(data = "This Table will give you results in seconds which is measured value of the time taken by the client to connect and generate traffic after Radar detection"): + test_client = """ + +

    Client Connection Details

    +

    """ + str(data) + """

    +
    + """ + return str(test_client) + +def detection_time_details(data= "This Table will give you results in seconds which is measured value of the time difference when the radar was sent and detected"): + test_detection = """ + +

    Detection Time Details

    +

    """ + str(data) + """

    +
    + """ + return str(test_detection) + +def switched_channel_details(data = "This Table will give you result value of channel number to which the client switches after radar detection X - channel never set in AP AUTO - State when we cannot determine the channel at which the client is associated"): + switch_channel = """ + +

    Switcing Channel Details

    +

    """ + str(data) + """

    +
    + """ + return str(switch_channel) + + + +# dev complete +def test_setup_information(test_setup_data=None): + if test_setup_data is None: + return None + else: + var = "" + for i in test_setup_data: + var = var + "" + i + "" + str(test_setup_data[i]) + "" + + setup_information = """ + + + + + + + + + +
    Test Setup Information
    Device Under Test + + """ + var + """ +
    +
    + +
    + """ + return str(setup_information) + + +# yet to test on dev level +def graph_html(graph_path=""): + graph_html_obj = """ + +

    Detection Time Graph

    + +

    + """ + return str(graph_html_obj) + + +def bar_plot(ax, data, x_axis_info=[], colors=None, total_width=0.8, single_width=1, legend=True): + # Check if colors where provided, otherwhise use the default color cycle + if colors is None: + colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] + + # Number of bars per group + n_bars = len(data) + + # The width of a single bar + bar_width = total_width / n_bars + + # List containing handles for the drawn bars, used for the legend + bars = [] + + # Iterate over all data + for i, (name, values) in enumerate(data.items()): + # The offset in x direction of that bar + x_offset = (i - n_bars / 2) * bar_width + bar_width / 2 + # print(values) + + # Draw a bar for every value of that type + for x, y in enumerate(values): + bar = ax.bar(x + x_offset, y, width=bar_width * single_width, color=colors[i % len(colors)]) + # Add a handle to the last drawn bar, which we'll need for the legend + bars.append(bar[0]) + + # Draw legend if we need + if legend: + ax.legend(bars, data.keys(), bbox_to_anchor=(1.1, 1.05)) + + ax.set_ylabel('Time in seconds') + ax.set_xlabel('Channels') + # ax.set_xticks(1) + x_data = x_axis_info + idx = np.asarray([i for i in range(len(x_data))]) + ax.set_xticks(idx) + + ax.set_xticklabels(x_data) + + +def generate_graph(result_data, x_axis_info, graph_path): + detection_data = dict.fromkeys(result_data.keys()) + for i in detection_data: + try: + detection_data[i] = result_data[i]['detection_time_lst'] + except: + detection_data[i] = [] + print(detection_data) + + fig, ax = plt.subplots() + bar_plot(ax, detection_data, x_axis_info=x_axis_info, total_width=.8, single_width=1.2) + + my_dpi = 96 + figure = plt.gcf() # get current figure + figure.set_size_inches(18, 6) + + # when saving, specify the DPI + str(datetime.now()).split(",")[0].replace(" ", "-").split(".")[0] + plt.savefig(graph_path + "/image.png", dpi=my_dpi) + return str(graph_html(graph_path + "/image.png")) + + +# yet to test on dev level +def add_radar_table(result_data, row_head_list=None, col_head_list=None): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + row + "" + + radar_html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["radar_lst"] + final_data = "" + for i in fcc_type: + if i == "YES": + final_data = final_data + "YES" + + else: + final_data = final_data + "NO" + radar_html_struct[fcc] = final_data + + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + radar_html_struct[col]) + "" + + radar_html = """ + + + + + +
    Radar Detected
    + + """ + var_row + """ + + """ + var_col + """ +
    + +






    + """ + return str(radar_html) + + +# yet to test on dev level +def add_client_cx_table(result_data, row_head_list, col_head_list): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + row + "" + + client_html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["connection_time_lst"] + final_data = "" + for i in fcc_type: + if i == 0: + final_data = final_data + "" + str(i) + "" + else: + final_data = final_data + "" + str(i) + "" + + client_html_struct[fcc] = final_data + + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + client_html_struct[col]) + "" + client_cx_html = """ + + + + + +
    Client Connection Time (sec)
    + + """ + var_row + """ + + """ + var_col + """ +
    + +






    + """ + return str(client_cx_html) + + +# yet to test on dev level +def add_detection_table(result_data, row_head_list, col_head_list): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + row + "" + + detection_html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["detection_time_lst"] + final_data = "" + for i in fcc_type: + if i == 0: + final_data = final_data + "" + str(i) + " " + else: + final_data = final_data + "" + str(i) + " " + + detection_html_struct[fcc] = final_data + + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + detection_html_struct[col]) + "" + + detection_html = """ + + + + + +
    Detection Time (sec)
    + + """ + var_row + """ + + """ + var_col + """ +
    + +






    + """ + return detection_html + + +# yet to test on dev level +def add_switched_channel_table(result_data, row_head_list, col_head_list): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + row + "" + + switched_html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["switched_ch_lst"] + final_data = "" + for i in fcc_type: + if i == "X" or i == "AUTO": + final_data = final_data + "" + str(i) + "" + elif i == " - ": + final_data = final_data + "" + str(i) + "" + + else: + final_data = final_data + "" + str(i) + "" + + switched_html_struct[fcc] = final_data + + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + switched_html_struct[col]) + "" + + switched_data = """ + + + + + +
    Switched Channel
    + + """ + var_row + """ + + """ + var_col + """ +
    + +






    + """ + + return switched_data + +'''def input_usedinformation(ip ="", user="", passwd="", radio= "", help= ""): + setup_information = """ + + + + + + + + + +
    Input Given Information
    Input used in script + + + + + + + + + + + + + + + + + + + + + +
    AP ip""" + ip + """
    user name""" + user + """
    password""" + passwd + """
    radio""" + radio + """
    help""" + help + """
    +
    + +
    + """ + return str(setup_information)''' +def input_setup_info_table(input_setup_info=None): + if input_setup_info is None: + return None + else: + var = "" + for i in input_setup_info: + var = var + "" + i + "" + str(input_setup_info[i]) + "" + + setup_information = """ + + + + + + + + + +
    Input Setup Information
    Information + + """ + var + """ +
    +
    + +
    + """ + return str(setup_information) + + +def generate_report(result_data=None, + date=None, + test_setup_info={}, + input_setup_info = {}, + test_channel=None, + + graph_path="/home/lanforge/html-reports/dfs"): + # Need to pass this to test_setup_information() + input_setup_info = input_setup_info + test_setup_data = test_setup_info + test_chal = test_channel + + # test_setup_data = { + # "AP Name": "TestAP", + # "SSID": "NETGEAR",f + # "Number of Stations": "1", + # "Test Duration": "5:00 Mins" + # } + + """result_data = {'FCC0': {'switched_ch_lst': ["1"], 'detection_time_lst': [1, 5], 'radar_lst': ['NO', 'YES'], + 'connection_time_lst': [0, 65]} + }""" + reports_root = graph_path + "/" + str(date) + if path.exists(graph_path): + os.mkdir(reports_root) + print("Reports Root is Created") + + else: + os.mkdir(graph_path) + os.mkdir(reports_root) + print("Reports Root is created") + print("Generating Reports in : ", reports_root) + + html_report = report_banner(date) + \ + test_setup_information(test_setup_data) + \ + test_objective() + \ + generate_graph(result_data, test_chal, graph_path=reports_root) + \ + radar_detect_discription() + \ + add_radar_table(result_data, result_data.keys(), test_chal) + \ + client_connection_detail() + \ + add_client_cx_table(result_data, result_data.keys(), test_chal) + \ + detection_time_details() + \ + add_detection_table(result_data, result_data.keys(), test_chal) + \ + switched_channel_details() + \ + add_switched_channel_table(result_data, result_data.keys(), test_chal) + \ + input_setup_info_table(input_setup_info) + + + + + # write the html_report into a file in /home/lanforge/html_reports in a directory named DFS_TEST and html_report name should be having a timesnap with it + f = open(reports_root + "/report.html", "a") + # f = open("report.html", "a") + f.write(html_report) + f.close() + # write logic to generate pdf here + pdfkit.from_file(reports_root + "/report.html", reports_root + "/report.pdf") + + +# test blocks from here +if __name__ == '__main__': + generate_report() + # generate_graph() + + + + + + + + + +""" This script is used for DFS Test Report generation + it has generic methods inside which can be used for other test report generation + date - 11- feb - 2021 + -Nikita Yadav +""" + +from matplotlib import pyplot as plt +from datetime import datetime +import numpy as np +import os.path +from os import path +import sys + +print(sys.path) +sys.path.append('/home/lanforge/.local/lib/python3.6/site-packages') +import pdfkit + +# dev complete +def report_banner(date): + banner_data = """ + + + + + + LANforge Report + + + + DFS TEST + +
    +
    + + + +
    +
    +

    Dynamic Frequency Selection

    +

    """ + date + """

    +
    +
    + +

    + + """ + return str(banner_data) + +# dev complete +def test_objective(objective="The DFS Test is designed to test the Performance of the Netgear Access Point.Dynamic frequency selection is a technology that is designed to ensure that wireless devices operating in the unlicensed WLAN 5 GHz bands are able to detect when they may be interfering with military and weather radar systems and automatically switch over to another frequency where they will not cause any disturbance. "): + test_objective = """ + +

    Objective

    +

    """ + str(objective) + """

    +
    + """ + return str(test_objective) + +def radar_detect_discription(data= " This Table will give you results in YES or NO if the AP detects the Radar"): + test_radar = """ + +

    Radar Detection Detail

    +

    """ + str(data) + """

    +
    + """ + return str(test_radar) + +def client_connection_detail(data = "This Table will give you results in seconds which is measured value of the time taken by the client to connect and generate traffic after Radar detection"): + test_client = """ + +

    Client Connection Details

    +

    """ + str(data) + """

    +
    + """ + return str(test_client) + +def detection_time_details(data= "This Table will give you results in seconds which is measured value of the time difference when the radar was sent and detected"): + test_detection = """ + +

    Detection Time Details

    +

    """ + str(data) + """

    +
    + """ + return str(test_detection) + +def switched_channel_details(data = "This Table will give you result value of channel number to which the client switches after radar detection X - channel never set in AP AUTO - State when we cannot determine the channel at which the client is associated"): + switch_channel = """ + +

    Switcing Channel Details

    +

    """ + str(data) + """

    +
    + """ + return str(switch_channel) + + + +# dev complete +def test_setup_information(test_setup_data=None): + if test_setup_data is None: + return None + else: + var = "" + for i in test_setup_data: + var = var + "" + i + "" + str(test_setup_data[i]) + "" + + setup_information = """ + + + + + + + + + +
    Test Setup Information
    Device Under Test + + """ + var + """ +
    +
    + +
    + """ + return str(setup_information) + + +# yet to test on dev level +def graph_html(graph_path=""): + graph_html_obj = """ + +

    Detection Time Graph

    + +

    + """ + return str(graph_html_obj) + + +def bar_plot(ax, data, x_axis_info=[], colors=None, total_width=0.8, single_width=1, legend=True): + # Check if colors where provided, otherwhise use the default color cycle + if colors is None: + colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] + + # Number of bars per group + n_bars = len(data) + + # The width of a single bar + bar_width = total_width / n_bars + + # List containing handles for the drawn bars, used for the legend + bars = [] + + # Iterate over all data + for i, (name, values) in enumerate(data.items()): + # The offset in x direction of that bar + x_offset = (i - n_bars / 2) * bar_width + bar_width / 2 + # print(values) + + # Draw a bar for every value of that type + for x, y in enumerate(values): + bar = ax.bar(x + x_offset, y, width=bar_width * single_width, color=colors[i % len(colors)]) + # Add a handle to the last drawn bar, which we'll need for the legend + bars.append(bar[0]) + + # Draw legend if we need + if legend: + ax.legend(bars, data.keys(), bbox_to_anchor=(1.1, 1.05)) + + ax.set_ylabel('Time in seconds') + ax.set_xlabel('Channels') + # ax.set_xticks(1) + x_data = x_axis_info + idx = np.asarray([i for i in range(len(x_data))]) + ax.set_xticks(idx) + + ax.set_xticklabels(x_data) + + +def generate_graph(result_data, x_axis_info, graph_path): + detection_data = dict.fromkeys(result_data.keys()) + for i in detection_data: + try: + detection_data[i] = result_data[i]['detection_time_lst'] + except: + detection_data[i] = [] + print(detection_data) + + fig, ax = plt.subplots() + bar_plot(ax, detection_data, x_axis_info=x_axis_info, total_width=.8, single_width=1.2) + + my_dpi = 96 + figure = plt.gcf() # get current figure + figure.set_size_inches(18, 6) + + # when saving, specify the DPI + str(datetime.now()).split(",")[0].replace(" ", "-").split(".")[0] + plt.savefig(graph_path + "/image.png", dpi=my_dpi) + return str(graph_html(graph_path + "/image.png")) + + +# yet to test on dev level +def add_radar_table(result_data, row_head_list=None, col_head_list=None): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + row + "" + + radar_html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["radar_lst"] + final_data = "" + for i in fcc_type: + if i == "YES": + final_data = final_data + "YES" + + else: + final_data = final_data + "NO" + radar_html_struct[fcc] = final_data + + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + radar_html_struct[col]) + "" + + radar_html = """ + + + + + +
    Radar Detected
    + + """ + var_row + """ + + """ + var_col + """ +
    + +






    + """ + return str(radar_html) + + +# yet to test on dev level +def add_client_cx_table(result_data, row_head_list, col_head_list): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + row + "" + + client_html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["connection_time_lst"] + final_data = "" + for i in fcc_type: + if i == 0: + final_data = final_data + "" + str(i) + "" + else: + final_data = final_data + "" + str(i) + "" + + client_html_struct[fcc] = final_data + + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + client_html_struct[col]) + "" + client_cx_html = """ + + + + + +
    Client Connection Time (sec)
    + + """ + var_row + """ + + """ + var_col + """ +
    + +






    + """ + return str(client_cx_html) + + +# yet to test on dev level +def add_detection_table(result_data, row_head_list, col_head_list): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + row + "" + + detection_html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["detection_time_lst"] + final_data = "" + for i in fcc_type: + if i == 0: + final_data = final_data + "" + str(i) + " " + else: + final_data = final_data + "" + str(i) + " " + + detection_html_struct[fcc] = final_data + + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + detection_html_struct[col]) + "" + + detection_html = """ + + + + + +
    Detection Time (sec)
    + + """ + var_row + """ + + """ + var_col + """ +
    + +






    + """ + return detection_html + + +# yet to test on dev level +def add_switched_channel_table(result_data, row_head_list, col_head_list): + var_row = "" + for row in col_head_list: + var_row = var_row + "" + row + "" + + switched_html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["switched_ch_lst"] + final_data = "" + for i in fcc_type: + if i == "X" or i == "AUTO": + final_data = final_data + "" + str(i) + "" + elif i == " - ": + final_data = final_data + "" + str(i) + "" + + else: + final_data = final_data + "" + str(i) + "" + + switched_html_struct[fcc] = final_data + + var_col = "" + for col in row_head_list: + var_col = var_col + "" + str(col) + "" + str( + switched_html_struct[col]) + "" + + switched_data = """ + + + + + +
    Switched Channel
    + + """ + var_row + """ + + """ + var_col + """ +
    + +






    + """ + + return switched_data + +'''def input_usedinformation(ip ="", user="", passwd="", radio= "", help= ""): + setup_information = """ + + + + + + + + + +
    Input Given Information
    Input used in script + + + + + + + + + + + + + + + + + + + + + +
    AP ip""" + ip + """
    user name""" + user + """
    password""" + passwd + """
    radio""" + radio + """
    help""" + help + """
    +
    + +
    + """ + return str(setup_information)''' +def input_setup_info_table(input_setup_info=None): + if input_setup_info is None: + return None + else: + var = "" + for i in input_setup_info: + var = var + "" + i + "" + str(input_setup_info[i]) + "" + + setup_information = """ + + + + + + + + + +
    Input Setup Information
    Information + + """ + var + """ +
    +
    + +
    + """ + return str(setup_information) + + +def generate_report(result_data=None, + date=None, + test_setup_info={}, + input_setup_info = {}, + test_channel=None, + + graph_path="/home/lanforge/html-reports/dfs"): + # Need to pass this to test_setup_information() + input_setup_info = input_setup_info + test_setup_data = test_setup_info + test_chal = test_channel + + # test_setup_data = { + # "AP Name": "TestAP", + # "SSID": "NETGEAR",f + # "Number of Stations": "1", + # "Test Duration": "5:00 Mins" + # } + + """result_data = {'FCC0': {'switched_ch_lst': ["1"], 'detection_time_lst': [1, 5], 'radar_lst': ['NO', 'YES'], + 'connection_time_lst': [0, 65]} + }""" + reports_root = graph_path + "/" + str(date) + if path.exists(graph_path): + os.mkdir(reports_root) + print("Reports Root is Created") + + else: + os.mkdir(graph_path) + os.mkdir(reports_root) + print("Reports Root is created") + print("Generating Reports in : ", reports_root) + + html_report = report_banner(date) + \ + test_setup_information(test_setup_data) + \ + test_objective() + \ + generate_graph(result_data, test_chal, graph_path=reports_root) + \ + radar_detect_discription() + \ + add_radar_table(result_data, result_data.keys(), test_chal) + \ + client_connection_detail() + \ + add_client_cx_table(result_data, result_data.keys(), test_chal) + \ + detection_time_details() + \ + add_detection_table(result_data, result_data.keys(), test_chal) + \ + switched_channel_details() + \ + add_switched_channel_table(result_data, result_data.keys(), test_chal) + \ + input_setup_info_table(input_setup_info) + + + + + # write the html_report into a file in /home/lanforge/html_reports in a directory named DFS_TEST and html_report name should be having a timesnap with it + f = open(reports_root + "/report.html", "a") + # f = open("report.html", "a") + f.write(html_report) + f.close() + # write logic to generate pdf here + pdfkit.from_file(reports_root + "/report.html", reports_root + "/report.pdf") + + +# test blocks from here +if __name__ == '__main__': + generate_report() + # generate_graph() + + + + + + + + + diff --git a/lanforge/lanforge-scripts/py-scripts/influx.py b/lanforge/lanforge-scripts/py-scripts/influx.py new file mode 100755 index 000000000..184c97a8e --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/influx.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# pip3 install influxdb +import sys +import os +import importlib +import requests +import json +from influxdb import InfluxDBClient +import datetime +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +class RecordInflux(LFCliBase): + def __init__(self, + _lfjson_host="lanforge", + _lfjson_port=8080, + _influx_host="localhost", + _influx_port=8086, + _influx_user=None, + _influx_passwd=None, + _influx_db=None, + _debug_on=False, + _exit_on_fail=False): + super().__init__(_lfjson_host, _lfjson_port, + _debug=_debug_on, + _exit_on_fail=_exit_on_fail) + self.influx_host = _influx_host + self.influx_port = _influx_port + self.influx_user = _influx_user + self.influx_passwd = _influx_passwd + self.influx_db = _influx_db + self.client = InfluxDBClient(self.influx_host, + self.influx_port, + self.influx_user, + self.influx_passwd, + self.influx_db) + + def post_to_influx(self, key, value, tags): + data = dict() + data["measurement"] = key + data["tags"] = tags + data["time"] = str(datetime.datetime.utcnow().isoformat()) + data["fields"] = dict() + data["fields"]["value"] = value + data1 = [data] + self.client.write_points(data1) + + # Don't use this unless you are sure you want to. + # More likely you would want to generate KPI in the + # individual test cases and poke those relatively small bits of + # info into influxdb. + # This will not end until the 'longevity' timer has expired. + # This function pushes data directly into the Influx database and defaults to all columns. + def monitor_port_data(self, + lanforge_host="localhost", + devices=None, + longevity=None, + monitor_interval=None): + url = 'http://' + lanforge_host + ':8080/port/1/1/' + end = datetime.datetime.now() + datetime.timedelta(0, longevity) + while datetime.datetime.now() < end: + for station in devices: + url1 = url + station + response = json.loads(requests.get(url1).text) + + # Poke everything into influx db + for key in response['interface'].keys(): + tags = dict() + tags["region"] = 'us-west' + self.posttoinflux("%s-%s" % (station, key), response['interface'][key], tags) + + time.sleep(monitor_interval) diff --git a/lanforge/lanforge-scripts/py-scripts/influx2.py b/lanforge/lanforge-scripts/py-scripts/influx2.py new file mode 100644 index 000000000..48ead5359 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/influx2.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +# pip3 install influxdb-client + +# Version 2.0 influx DB Client + +import sys +import os + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + + +import requests +import json +import influxdb_client +from influxdb_client.client.write_api import SYNCHRONOUS +import datetime +#from LANforge.lfcli_base import LFCliBase +import time + +class RecordInflux: + def __init__(self, + _influx_host="localhost", + _influx_port=8086, + _influx_org=None, + _influx_token=None, + _influx_bucket=None, + _debug_on=False, + _exit_on_fail=False): + self.influx_host = _influx_host + self.influx_port = _influx_port + self.influx_org = _influx_org + self.influx_token = _influx_token + self.influx_bucket = _influx_bucket + self.url = "http://%s:%s"%(self.influx_host, self.influx_port) + self.client = influxdb_client.InfluxDBClient(url=self.url, + token=self.influx_token, + org=self.influx_org, + debug=_debug_on) + self.write_api = self.client.write_api(write_options=SYNCHRONOUS) + + def post_to_influx(self, key, value, tags, time): + p = influxdb_client.Point(key) + for tag_key, tag_value in tags.items(): + p.tag(tag_key, tag_value) + print(tag_key, tag_value) + p.time(time) + p.field("value", value) + self.write_api.write(bucket=self.influx_bucket, org=self.influx_org, record=p) + + def set_bucket(self, b): + self.influx_bucket = b + + # Don't use this unless you are sure you want to. + # More likely you would want to generate KPI in the + # individual test cases and poke those relatively small bits of + # info into influxdb. + # This will not end until the 'longevity' timer has expired. + # This function pushes data directly into the Influx database and defaults to all columns. + def monitor_port_data(self, + lanforge_host="localhost", + devices=None, + longevity=None, + monitor_interval=None, + bucket=None, + tags=None): # dict + url = 'http://' + lanforge_host + ':8080/port/1/1/' + end = datetime.datetime.now() + datetime.timedelta(0, longevity) + while datetime.datetime.now() < end: + for station in devices: + url1 = url + station + response = json.loads(requests.get(url1).text) + + current_time = str(datetime.datetime.utcnow().isoformat()) + + # Poke everything into influx db + for key in response['interface'].keys(): + self.post_to_influx("%s-%s" % (station, key), response['interface'][key], tags, current_time) + + time.sleep(monitor_interval) diff --git a/lanforge/lanforge-scripts/py-scripts/influxgrafanaghost.sh b/lanforge/lanforge-scripts/py-scripts/influxgrafanaghost.sh new file mode 100755 index 000000000..995874511 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/influxgrafanaghost.sh @@ -0,0 +1,61 @@ +#! /bin/bash + +MGR=192.168.100.213 +LFUSER=lanforge +LOCALDIR=/home/matthew/Documents/lanforge-scripts/py-scripts/lftest +TESTRIG="Matthew-ct523c" + +GHOSTTOKEN=60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742 + +INFLUXTOKEN=31N9QDhjJHBu4eMUlMBwbK3sOjXLRAhZuCzZGeO8WVCj-xvR8gZWWvRHOcuw-5RHeB7xBFnLs7ZV023k4koR1A== +INFLUXHOST=c7-grafana.candelatech.com +INFLUXBUCKET=stidmatt + +GRAFANATOKEN=eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ== + +rm -r ${LOCALDIR} + +mkdir ${LOCALDIR} + +./scenario.py --mgr ${MGR} --load BLANK + +sleep 10s + +./create_l3.py --mgr ${MGR} --num_stations 4 --ssid stidmatt2 --password stidmatt2 --security wpa2 --radio wiphy0 + +./lf_dataplane_test.py --mgr ${MGR} --lf_user ${LFUSER} --lf_password lanforge --instance_name wct_instance \ +--config_name 64_stations --upstream 1.1.eth1 --influx_host c7-grafana.candelatech.com --influx_org Candela \ +--influx_token ${INFLUXTOKEN} --influx_bucket ${INFLUXBUCKET} --test_rig ${TESTRIG} --influx_tag testbed ${TESTRIG} \ +--station 1.1.sta0000 --set DUT_NAME linksys-8450 --local_lf_report_dir ${LOCALDIR} \ +--pull_report \ +--download_speed 85% --upload_speed 0 \ +--raw_line 'cust_pkt_sz: 88 1200' \ +--raw_line 'directions: DUT Transmit;DUT Receive' \ +--raw_line 'traffic_types: UDP' --pull_report --test_tag influxgrafanaghost.sh +#--raw_line 'pkts: Custom;60;142;256;512;1024;MTU' + +./lf_wifi_capacity_test.py --mgr ${MGR} --lf_user ${LFUSER} --lf_password lanforge --instance_name linksys-8450 \ +--config_name wifi_config --upstream 1.1.eth1 --radio wiphy0 --ssid lanforge --paswd lanforge --security wpa2 \ +--influx_host ${INFLUXHOST} --influx_org Candela --influx_bucket ${INFLUXBUCKET} --test_rig ${TESTRIG} \ +--influx_token ${INFLUXTOKEN} --influx_tag testbed ${TESTRIG} --set DUT_NAME linksys-8450 --local_lf_report_dir \ +${LOCALDIR} --enable FALSE --pull_report --test_tag influxgrafanaghost.sh + +./lf_wifi_capacity_test.py --mgr ${MGR} --lf_user ${LFUSER} --lf_password lanforge --instance_name linksys-8450 \ +--config_name wifi_config --upstream 1.1.eth1 --radio wiphy0 --ssid lanforge --paswd lanforge --security wpa2 \ +--influx_host ${INFLUXHOST} --influx_org Candela --influx_bucket ${INFLUXBUCKET} --test_rig ${TESTRIG} \ +--influx_token ${INFLUXTOKEN} --influx_tag testbed ${TESTRIG} --set DUT_NAME linksys-8450 --local_lf_report_dir \ +${LOCALDIR} --enable FALSE --pull_report --test_tag Can_we_use_two_test_tags + +./lf_ap_auto_test.py --mgr ${MGR} --instance_name ap-auto-instance --config_name test_con --upstream 1.1.eth1 \ +--dut5_0 'matthew-router lanforge 04:f0:21:c0:65:7b (1)' --dut2_0 'matthew-router lanforge 04:f0:21:c0:65:7b (1)' \ +--max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy0 --radio5 1.1.wiphy0 \ +--set 'Basic Client Connectivity' 1 --set 'Multi Band Performance' 1 --set 'Stability' 0 --set 'Capacity' 0 \ +--set 'Multi-Station Throughput vs Pkt Size' 0 --set 'Throughput vs Pkt Size' 0 --set 'Band-Steering' 1 \ +--influx_host ${INFLUXHOST} --influx_org Candela --influx_bucket ${INFLUXBUCKET} --test_rig ${TESTRIG} \ +--influx_token ${INFLUXTOKEN} --influx_tag testbed ${TESTRIG} --pull_report --test_tag influxgrafanaghost.sh \ +--local_lf_report_dir ${LOCALDIR} + +./ghost_profile.py --ghost_token ${GHOSTTOKEN} --ghost_host 192.168.100.153 --authors Matthew --customer candela \ +--user_push lanforge --password_push lanforge --kpi_to_ghost --grafana_token ${GRAFANATOKEN} \ +--grafana_host 192.168.100.201 --grafana_bucket ${INFLUXBUCKET} --influx_host ${INFLUXHOST} --influx_org Candela \ +--influx_token ${INFLUXTOKEN} --influx_bucket ${INFLUXBUCKET} --parent_folder ${LOCALDIR} \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/layer3_test.py b/lanforge/lanforge-scripts/py-scripts/layer3_test.py new file mode 100755 index 000000000..6199babf9 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/layer3_test.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import argparse +import datetime +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class Layer3Test(LFCliBase): + + def __init__(self, lfclient_host="localhost", lfclient_port=8080, radio="wiphy1", sta_prefix="sta", start_id=0, num_sta=2, + dut_ssid="lexusdut", dut_security="open", dut_passwd="[BLANK]", upstream="eth1", name_prefix="L3Test", + traffic_type="lf_udp",side_a_speed="0M", side_b_speed="10M", session_id="Layer3Test", duration="1m",_debug_on=False, _exit_on_error=False, _exit_on_fail=False): + super().__init__(lfclient_host, lfclient_port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + print("Test is about to start") + self.host = lfclient_host + self.port = lfclient_port + self.radio = radio + self.upstream = upstream + self.monitor_interval = 1 + self.sta_prefix = sta_prefix + self.sta_start_id = start_id + self.test_duration = duration + self.num_sta = num_sta + self.name_prefix = name_prefix + self.ssid = dut_ssid + self.security = dut_security + self.password = dut_passwd + self.session_id = session_id + self.traffic_type = traffic_type + self.side_a_speed = side_a_speed + self.side_b_speed = side_b_speed + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_l3_cx_profile() + + self.cx_profile.host = self.host + self.cx_profile.port = self.port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = self.local_realm.parse_speed(self.side_a_speed) + self.cx_profile.side_a_max_bps = self.local_realm.parse_speed(self.side_a_speed) + self.cx_profile.side_b_min_bps = self.local_realm.parse_speed(self.side_b_speed) + self.cx_profile.side_b_max_bps = self.local_realm.parse_speed(self.side_b_speed) + + print("Test is Initialized") + + + def precleanup(self): + print("precleanup started") + self.station_list = LFUtils.portNameSeries(prefix_=self.sta_prefix, start_id_=self.sta_start_id, end_id_=self.num_sta - 1, padding_number_=10000, radio=self.radio) + self.cx_profile.cleanup_prefix() + for sta in self.station_list: + self.local_realm.rm_port(sta, check_exists=True) + time.sleep(1) + self.cx_profile.cleanup() + + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + print("precleanup done") + pass + + def build(self): + print("Building Test Configuration") + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template("00") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.station_list, debug=self.debug) + self.local_realm.wait_until_ports_appear(sta_list=self.station_list) + self.cx_profile.create(endp_type=self.traffic_type, side_a=self.station_profile.station_names, side_b=self.upstream, sleep_time=0) + print("Test Build done") + pass + + def start(self, print_pass=False, print_fail=False): + print("Test is starting") + self.cx_names =[] + self.station_profile.admin_up() + temp_stas = self.station_profile.station_names.copy() + temp_stas.append(self.upstream) + if (self.local_realm.wait_for_ip(temp_stas)): + self._pass("All stations got IPs", print_pass) + else: + self._fail("Stations failed to get IPs", print_fail) + exit(1) + self.cx_profile.start_cx() + try: + for i in self.cx_profile.get_cx_names(): + self.cx_names.append(i) + while self.local_realm.json_get("/cx/" + i).get(i).get('state') != 'Run': + continue + except Exception as e: + pass + print("Test Started") + self.cur_time = datetime.datetime.now() + self.end_time = self.local_realm.parse_time(self.test_duration) + self.cur_time + print(self.end_time-self.cur_time) + self.start_monitor() + pass + + def my_monitor(self): + print("Monitoring Test") + print(self.end_time - datetime.datetime.now()) + if (datetime.datetime.now() > self.end_time): + self.stop_monitor() + for i in self.cx_names: + self.add_event(message= self.cx_profile.get_cx_report()[i]['bps rx b'], name=self.session_id) + return self.cx_profile.get_cx_report() + + def stop(self): + print("Stopping Test") + self.cx_profile.stop_cx() + self.station_profile.admin_down() + pass + + def postcleanup(self): + self.cx_profile.cleanup() + self.station_profile.cleanup() + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + print("Test Completed") + pass + +def main(): + # This has --mgr, --mgr_port and --debug + parser = LFCliBase.create_basic_argparse( + prog="layer3_test.py", + formatter_class=argparse.RawTextHelpFormatter, + epilog="About This Script") + + # Adding More Arguments for custom use + parser.add_argument('--test_duration', help='--test_duration sets the duration of the test', default="1m") + parser.add_argument('--session_id', help='--session_id is for websocket', default="local") + parser.add_argument('--num_client', type=int, help='--num_sta is number of stations you want to create', default=2) + parser.add_argument('--side_a_min_speed', help='--speed you want to monitor traffic with (max is 10G)', default="0M") + parser.add_argument('--side_b_min_speed', help='--speed you want to monitor traffic with (max is 10G)', default="10M") + parser.add_argument('--traffic_type', help='--traffic_type is used for traffic type (lf_udp, lf_tcp)', default="lf_udp") + args = parser.parse_args() + print(args) + + # Start Test + obj = Layer3Test(lfclient_host=args.mgr, lfclient_port=args.mgr_port, + duration=args.test_duration, session_id=args.session_id, + traffic_type=args.traffic_type, + dut_ssid=args.ssid, dut_passwd=args.passwd, dut_security=args.security, num_sta=args.num_client, side_a_speed=args.side_a_min_speed, side_b_speed=args.side_b_min_speed, radio=args.radio) + obj.precleanup() + obj.build() + obj.start() + obj.monitor() + obj.stop() + obj.postcleanup() + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/layer4_test.py b/lanforge/lanforge-scripts/py-scripts/layer4_test.py new file mode 100755 index 000000000..6507ef2a8 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/layer4_test.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +""" +Candela Technologies Inc. + +Info : Standard Script for Layer 4 Testing +Date : +Author : Shivam Thakur +""" +import sys +import os +import importlib +import argparse +import datetime +import time +import json +import re + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +PortUtils = realm.PortUtils +test_utility = importlib.import_module("py-json.test_utility") +CreateHTML = test_utility.CreateHTML +RuntimeUpdates = test_utility.RuntimeUpdates + +webconsole_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.getcwd()))) +print(webconsole_dir) + + +class HTTPTest(LFCliBase): + + def __init__(self, lfclient_host="localhost", lfclient_port=8080, radio="wiphy1", sta_prefix="sta", start_id=0, num_sta=2, + dut_ssid="lexusdut", dut_security="open", dut_passwd="[BLANK]", upstream="eth1", name_prefix="L3Test", _test_update="", + url="", url_ps=600, session_id="Layer3Test", duration="1m",_debug_on=False, _exit_on_error=False, _exit_on_fail=False): + super().__init__(lfclient_host, lfclient_port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + print("Test is about to start") + self.host = lfclient_host + self.port = lfclient_port + self.radio = radio + self.upstream = upstream + self.monitor_interval = 1 + self.sta_prefix = sta_prefix + self.sta_start_id = start_id + + self.num_sta = num_sta + self.name_prefix = name_prefix + self.ssid = dut_ssid + self.security = dut_security + self.password = dut_passwd + self.session_id = session_id + self.url = url + self.urls_ps = url_ps + self.test_update =_test_update + self.test_update.send_update({"test_status": '1', "duration_left": "initializing...", "data": 'None'}) + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.test_duration = self.local_realm.parse_time(duration) + self.station_profile = self.local_realm.new_station_profile() + self.port_util = PortUtils(self.local_realm) + self.cx_profile = self.local_realm.new_http_profile() + self.cx_profile.requests_per_ten = self.urls_ps + self.cx_profile.url = self.url + self.cx_profile.direction = "dl" + self.cx_profile.dest = "/dev/null" + print("Test Setup Initialized") + self.test_update.send_update({"test_status": '2', "duration_left": "Initialized...", "data": 'None'}) + + def precleanup(self): + print("precleanup started") + self.station_list = LFUtils.portNameSeries(prefix_=self.sta_prefix, start_id_=self.sta_start_id, end_id_=self.num_sta - 1, padding_number_=10000, radio=self.radio) + self.cx_profile.cleanup() + for sta in self.station_list: + self.local_realm.rm_port(sta, check_exists=True) + self.cx_profile.cleanup() + + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + self.test_update.send_update({"test_status": '3', "duration_left": "building test...", "data": 'None'}) + print("precleanup done") + pass + + def build(self): + print("Building Test Configuration") + self.station_list = LFUtils.portNameSeries(prefix_=self.sta_prefix, start_id_=self.sta_start_id, + end_id_=self.num_sta - 1, padding_number_=10000, radio=self.radio) + + #self.port_util.set_http(port_name=name, resource=resource, on=True) + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template("00") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.station_list, debug=self.debug) + self.local_realm.wait_until_ports_appear(sta_list=self.station_list) + print("Test Build done") + self.test_update.send_update({"test_status": '4', "duration_left": "starting test...", "data": 'None'}) + pass + + def start(self, print_pass=False, print_fail=False): + print("Test is starting") + self.station_profile.admin_up() + temp_stas = self.station_profile.station_names.copy() + if (self.local_realm.wait_for_ip(temp_stas)): + self._pass("All stations got IPs", print_pass) + else: + self._fail("Stations failed to get IPs", print_fail) + exit(1) + self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None, http=True) + time.sleep(5) + self.cx_profile.start_cx() + try: + for i in self.cx_profile.get_cx_names(): + while self.local_realm.json_get("/cx/" + i).get(i).get('state') != 'Run': + continue + except Exception as e: + pass + print("Test Started") + self.test_update.send_update({"test_status": '5', "duration_left": "Test started...", "data": 'None'}) + self.cur_time = datetime.datetime.now() + self.end_time = self.test_duration + self.cur_time + print(self.end_time-self.cur_time) + self.start_monitor() + pass + + def my_monitor(self): + print("Monitoring Test") + print(self.end_time - datetime.datetime.now()) + self.test_update.send_update({"test_status": '6', "duration_left": str(self.end_time - datetime.datetime.now()), "data": 'None'}) + if (datetime.datetime.now() > self.end_time): + self.stop_monitor() + return self.cx_profile.get_cx_report() + + def stop(self): + print("Stopping Test") + self.test_update.send_update({"test_status": '7', "duration_left": "stopping...", "data": "None"}) + self.cx_profile.stop_cx() + for sta_name in self.station_list: + data = LFUtils.port_down_request(1, self.local_realm.name_to_eid(sta_name)[2]) + url = "cli-json/set_port" + self.json_post(url, data) + + + def postcleanup(self): + self.test_update.send_update({"test_status": '8', "duration_left": "finishing...", "data": "None"}) + self.cx_profile.cleanup() + for sta in self.station_list: + self.local_realm.rm_port(sta, check_exists=True) + time.sleep(1) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_list, + debug=self.debug) + print("Test Completed") + + pass + + +def main(): + # This has --mgr, --mgr_port and --debug + parser = LFCliBase.create_basic_argparse(prog="layer4_test.py", formatter_class=argparse.RawTextHelpFormatter, epilog="About This Script") + + # Adding More Arguments for custom use + #parser.add_argument('--ssid', help='--ssid of DUT', default="WebAP") + #parser.add_argument('--passwd', help='--passwd of dut', default="[BLANK]") + #parser.add_argument('--radio', help='--radio to use on LANforge', default="wiphy1") + #parser.add_argument('--security', help='--security of dut', default="open") + parser.add_argument('--test_duration', help='--test_duration sets the duration of the test', default="1m") + parser.add_argument('--session_id', help='--session_id is for websocket', default="local") + parser.add_argument('--num_client', type=int, help='--num_sta is number of stations you want to create', default=2) + parser.add_argument('--url_ps', help='--speed you want to monitor traffic with (max is 10G)', default=600) + parser.add_argument('--url', help='--url on which you want to test HTTP', default="www.google.com") + parser.add_argument('--test_name', help='--test_name is for webconsole reports', default="HTTP Traffic Test") + args = parser.parse_args() + # print(args) + update = RuntimeUpdates(args.session_id, {"test_status": '0', "data": 'None'}) + # # Start Test + http_obj = HTTPTest(lfclient_host="192.168.200.12", lfclient_port=args.mgr_port, + duration=args.test_duration, session_id=args.session_id, + dut_ssid=args.ssid, dut_passwd=args.passwd, dut_security=args.security, num_sta=args.num_client, url_ps=args.url_ps, radio=args.radio, _test_update=update) + http_obj.precleanup() + http_obj.build() + http_obj.start() + http_obj.monitor() + http_obj.stop() + http_obj.postcleanup() + http_obj.test_update.send_update({"test_status": '8', "duration_left": "generating report...", "data": "None"}) + result_file = open("../py-run/"+args.session_id + ".txt", "r") + result_data = result_file.readlines() + final_data = result_data[len(result_data)-1] + test_detail_data = [] + client_names = [] + timesnap=[] + obj = re.sub('[\']', '"', final_data) + data = json.loads(obj) + for i in data: + for j in data[i]['endpoint']: + for k in j: + client_names.append(k) + + summary_data = dict.fromkeys(client_names) + count = 0 + + for i in summary_data: + summary_data[i] = 0 + + for i in result_data: + obj = re.sub('[\']', '"', i) + data = json.loads(obj) + for j in data: + timesnap.append(j) + for k in range(0, len(client_names)): + if (data[j]['endpoint'][k][client_names[k]]['uc-avg'] == 0): + count += 1 + else: + summary_data[client_names[k]] = summary_data[client_names[k]] + \ + data[j]['endpoint'][k][client_names[k]]['uc-avg'] + count = len(result_data) - count / len(client_names) + # print(summary_data) + + for i in summary_data: + summary_data[i] = summary_data[i]/count + print("iron ee") + print(summary_data) + + # Detailed Table Data + obj = re.sub('[\']', '"', final_data) + data = json.loads(obj) + for i in data: + for j in data[i]['endpoint']: + for k in j: + client_names.append(k) + if (summary_data[k] != 0) and (summary_data[k] < 3000): + temp = {"Client Name": k, "Avg. Response Time": str(int(summary_data[k]))+ " ms", + "RX Rate": str(int(j[k]['rx rate']) / 8000) + " kbps", + "Total URL's": j[k]['total-urls'], "Total Timeouts": j[k]['timeout'], + "Total Errors": j[k]['total-err'], "HTTP Range Error": j[k]['http-r'], + "HTTP Port Error": j[k]['http-p'], "result": "PASS"} + else: + temp = {"Client Name": k, "Avg. Response Time": str(int(summary_data[k])) + " ms", + "RX Rate": str(int(j[k]['rx rate']) / 8000) + " kbps", + "Total URL's": j[k]['total-urls'], "Total Timeouts": j[k]['timeout'], + "Total Errors": j[k]['total-err'], "HTTP Range Error": j[k]['http-r'], + "HTTP Port Error": j[k]['http-p'], "result": "FAIL"} + test_detail_data.append(temp) + + reports_path = webconsole_dir + "/reports/" + args.test_name + "_" + args.session_id + '/' + + if not os.path.exists(reports_path): + os.makedirs(reports_path) + + html = open(reports_path + args.test_name + "_" + args.session_id + ".html", 'w') + + objective = "The HTTP Traffic Test is designed to test the HTTP Performance of the Access Point. In this Test, Stations are sending HTTP Requests and it is monitoring the response time as a metric" + + count =0 + for i in summary_data: + if (summary_data[i] < 3000) and (summary_data[i] !=0): + pass + else: + count+=1 + if (count == 0): + pass_fail = "PASS" + summary_result = "PASS, All clients are succesfully connected and Passed HTTP Test" + else: + pass_fail = "FAIL" + summary_result = "FAIL, Not All clients are succesfully connected and Passed HTTP Test" + + html_data = CreateHTML(path=reports_path, test_name=args.test_name, + time_snap=str(datetime.datetime.now()), dut_ssid=args.ssid, + test_conf_data={"Number of Clients": str(args.num_client), "URL": args.url}, + objective=objective, test_results={"summary": summary_result, + "detail": {"keys": ["Client Name", "Average Response Time", "RX Rate", "Total URL's", "Total Timeouts", "Total Errors", "HTTP Range Error", "HTTP Port Error","Result"], + "data": test_detail_data}}, + chart_data=summary_data, + chart_params={"chart_head": "HTTP Response Time", "xlabel": "Time", + "ylabel": "Average Response Time"}) + html.write(html_data.report) + html.close() + + + + http_obj.test_update.send_update({"test_status": '10', "duration_left": "done", "data": "None", "result": pass_fail}) + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_ap_auto_test.py b/lanforge/lanforge-scripts/py-scripts/lf_ap_auto_test.py new file mode 100755 index 000000000..2b21d55e5 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_ap_auto_test.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 + +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +This script is used to automate running AP-Auto tests. You +may need to view an AP Auto test configured through the GUI to understand +the options and how best to input data. + + ./lf_ap_auto_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name ap-auto-instance --config_name test_con --upstream 1.1.eth2 \ + --dut5_0 'linksys-8450 Default-SSID-5gl c4:41:1e:f5:3f:25 (2)' \ + --dut2_0 'linksys-8450 Default-SSID-2g c4:41:1e:f5:3f:24 (1)' \ + --max_stations_2 100 --max_stations_5 100 --max_stations_dual 200 \ + --radio2 1.1.wiphy0 --radio2 1.1.wiphy2 \ + --radio5 1.1.wiphy1 --radio5 1.1.wiphy3 --radio5 1.1.wiphy4 \ + --radio5 1.1.wiphy5 --radio5 1.1.wiphy6 --radio5 1.1.wiphy7 \ + --set 'Basic Client Connectivity' 1 --set 'Multi Band Performance' 1 \ + --set 'Skip 2.4Ghz Tests' 1 --set 'Skip 5Ghz Tests' 1 \ + --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Stability' 0 --set 'Band-Steering' 0 \ + --set 'Multi-Station Throughput vs Pkt Size' 0 --set 'Long-Term' 0 \ + --pull_report \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01 + +Note: + --enable [option] will attempt to select any checkbox of that name to true. + --disable [option] will attempt to un-select any checkbox of that name to true. + --raw_line 'line contents' will add any setting to the test config. This is + useful way to support any options not specifically enabled by the + command options. + --set modifications will be applied after the other config has happened, + so it can be used to override any other config. + +Example of raw text config for ap-auto, to show other possible options: + +sel_port-0: 1.1.sta00500 +show_events: 1 +show_log: 0 +port_sorting: 0 +kpi_id: AP Auto +bg: 0xE0ECF8 +show_scan: 1 +auto_helper: 1 +skip_2: 1 +skip_5: 1 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +dut5b-0: NA +dut5-0: linksys-8450 Default-SSID-5gl c4:41:1e:f5:3f:25 (2) +dut2-0: linksys-8450 Default-SSID-2g c4:41:1e:f5:3f:24 (1) +dut5b-1: NA +dut5-1: NA +dut2-1: NA +dut5b-2: NA +dut5-2: NA +dut2-2: NA +spatial_streams: AUTO +bandw_options: AUTO +modes: Auto +upstream_port: 1.1.2 eth2 +operator: +mconn: 1 +tos: 0 +vid_buf: 1000000 +vid_speed: 700000 +reset_stall_thresh_udp_dl: 9600 +cx_prcnt: 950000 +cx_open_thresh: 35 +cx_psk_thresh: 75 +cx_1x_thresh: 130 +reset_stall_thresh_udp_ul: 9600 +reset_stall_thresh_tcp_dl: 9600 +reset_stall_thresh_tcp_ul: 9600 +reset_stall_thresh_l4: 100000 +reset_stall_thresh_voip: 20000 +stab_mcast_dl_min: 100000 +stab_mcast_dl_max: 0 +stab_udp_dl_min: 56000 +stab_udp_dl_max: 0 +stab_udp_ul_min: 56000 +stab_udp_ul_max: 0 +stab_tcp_dl_min: 500000 +stab_tcp_dl_max: 0 +stab_tcp_ul_min: 500000 +stab_tcp_ul_max: 0 +dl_speed: 85% +ul_speed: 85% +max_stations_2: 100 +max_stations_5: 100 +max_stations_5b: 64 +max_stations_dual: 200 +max_stations_tri: 64 +lt_sta: 2 +voip_calls: 0 +lt_dur: 3600 +reset_dur: 600 +lt_gi: 30 +dur20: 20 +hunt_retries: 1 +hunt_iter: 15 +bind_bssid: 1 +set_txpower_default: 0 +cap_dl: 1 +cap_ul: 0 +cap_use_pkt_sizes: 0 +stability_reset_radios: 0 +stability_use_pkt_sizes: 0 +pkt_loss_thresh: 10000 +frame_sizes: 200, 512, 1024, MTU +capacities: 1, 2, 5, 10, 20, 40, 64, 128, 256, 512, 1024, MAX +pf_text0: 2.4 DL 200 70Mbps +pf_text1: 2.4 DL 512 110Mbps +pf_text2: 2.4 DL 1024 115Mbps +pf_text3: 2.4 DL MTU 120Mbps +pf_text4: +pf_text5: 2.4 UL 200 88Mbps +pf_text6: 2.4 UL 512 106Mbps +pf_text7: 2.4 UL 1024 115Mbps +pf_text8: 2.4 UL MTU 120Mbps +pf_text9: +pf_text10: 5 DL 200 72Mbps +pf_text11: 5 DL 512 185Mbps +pf_text12: 5 DL 1024 370Mbps +pf_text13: 5 DL MTU 525Mbps +pf_text14: +pf_text15: 5 UL 200 90Mbps +pf_text16: 5 UL 512 230Mbps +pf_text17: 5 UL 1024 450Mbps +pf_text18: 5 UL MTU 630Mbps +radio2-0: 1.1.4 wiphy0 +radio2-1: 1.1.6 wiphy2 +radio5-0: 1.1.5 wiphy1 +radio5-1: 1.1.7 wiphy3 +radio5-2: 1.1.8 wiphy4 +radio5-3: 1.1.9 wiphy5 +radio5-4: 1.1.10 wiphy6 +radio5-5: 1.1.11 wiphy7 +basic_cx: 0 +tput: 0 +tput_multi: 0 +tput_multi_tcp: 1 +tput_multi_udp: 1 +tput_multi_dl: 1 +tput_multi_ul: 1 +dual_band_tput: 1 +capacity: 0 +band_steering: 0 +longterm: 0 +mix_stability: 0 +loop_iter: 1 +reset_batch_size: 1 +reset_duration_min: 10000 +reset_duration_max: 60000 +bandsteer_always_5g: 0 + +""" +import sys +import os +import importlib +import argparse +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cvtest = cv_test_manager.cv_test +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser + + +class ApAutoTest(cvtest): + def __init__(self, + lf_host="localhost", + lf_port=8080, + lf_user="lanforge", + lf_password="lanforge", + ssh_port=22, + local_lf_report_dir="", + instance_name="ap_auto_instance", + config_name="ap_auto_config", + upstream="1.1.eth1", + pull_report=False, + dut5_0="NA", + dut2_0="NA", + load_old_cfg=False, + max_stations_2=100, + max_stations_5=100, + max_stations_dual=200, + radio2=[], + radio5=[], + enables=[], + disables=[], + raw_lines=[], + raw_lines_file="", + sets=[], + graph_groups=None + ): + super().__init__(lfclient_host=lf_host, lfclient_port=lf_port) + + self.lf_host = lf_host + self.lf_port = lf_port + self.lf_user = lf_user + self.lf_password =lf_password + self.instance_name = instance_name + self.config_name = config_name + self.upstream = upstream + self.pull_report = pull_report + self.load_old_cfg = load_old_cfg + self.test_name = "AP-Auto" + self.dut5_0 = dut5_0 + self.dut2_0 = dut2_0 + self.max_stations_2 = max_stations_2 + self.max_stations_5 = max_stations_5 + self.max_stations_dual = max_stations_dual + self.radio2 = radio2 + self.radio5 = radio5 + self.enables = enables + self.disables = disables + self.raw_lines = raw_lines + self.raw_lines_file = raw_lines_file + self.sets = sets + self.ssh_port = ssh_port + self.graph_groups = graph_groups + self.local_lf_report_dir = local_lf_report_dir + + def setup(self): + # Nothing to do at this time. + return + + + def run(self): + self.sync_cv() + time.sleep(2) + self.sync_cv() + + blob_test = "%s-"%(self.test_name) + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + self.show_text_blob(None, None, False) + + # Test related settings + cfg_options = [] + + ridx = 0 + for r in self.radio2: + cfg_options.append("radio2-%i: %s"%(ridx, r[0])) + ridx += 1 + + ridx = 0 + for r in self.radio5: + cfg_options.append("radio5-%i: %s"%(ridx, r[0])) + ridx += 1 + + self.apply_cfg_options(cfg_options, self.enables, self.disables, self.raw_lines, self.raw_lines_file) + + # Command line args take precedence. + if self.upstream != "": + cfg_options.append("upstream_port: " + self.upstream) + if self.dut5_0 != "": + cfg_options.append("dut5-0: " + self.dut5_0) + if self.dut2_0 != "": + cfg_options.append("dut2-0: " + self.dut2_0) + if self.max_stations_2 != -1: + cfg_options.append("max_stations_2: " + str(self.max_stations_2)) + if self.max_stations_5 != -1: + cfg_options.append("max_stations_5: " + str(self.max_stations_5)) + if self.max_stations_dual != -1: + cfg_options.append("max_stations_dual: " + str(self.max_stations_dual)) + + # We deleted the scenario earlier, now re-build new one line at a time. + self.build_cfg(self.config_name, blob_test, cfg_options) + + cv_cmds = [] + self.create_and_run_test(self.load_old_cfg, self.test_name, self.instance_name, + self.config_name, self.sets, + self.pull_report, self.lf_host, self.lf_user, self.lf_password, + cv_cmds, ssh_port=self.ssh_port, local_lf_report_dir=self.local_lf_report_dir, + graph_groups_file=self.graph_groups) + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + + +def main(): + + parser = argparse.ArgumentParser( + prog="lf_ap_auto_test.py", + formatter_class=argparse.RawTextHelpFormatter, + description=""" + Open this file in an editor and read the top notes for more details. + + Example: + ./lf_ap_auto_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name ap-auto-instance --config_name test_con --upstream 1.1.eth2 \ + --dut5_0 'linksys-8450 Default-SSID-5gl c4:41:1e:f5:3f:25 (2)' \ + --dut2_0 'linksys-8450 Default-SSID-2g c4:41:1e:f5:3f:24 (1)' \ + --max_stations_2 100 --max_stations_5 100 --max_stations_dual 200 \ + --radio2 1.1.wiphy0 --radio2 1.1.wiphy2 \ + --radio5 1.1.wiphy1 --radio5 1.1.wiphy3 --radio5 1.1.wiphy4 \ + --radio5 1.1.wiphy5 --radio5 1.1.wiphy6 --radio5 1.1.wiphy7 \ + --set 'Basic Client Connectivity' 1 --set 'Multi Band Performance' 1 \ + --set 'Skip 2.4Ghz Tests' 1 --set 'Skip 5Ghz Tests' 1 \ + --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Stability' 0 --set 'Band-Steering' 0 \ + --set 'Multi-Station Throughput vs Pkt Size' 0 --set 'Long-Term' 0 \ + --test_rig Testbed-01 --test_tag ATH10K --pull_report \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01 + """ + ) + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for wifi capacity test ex. 1.1.eth1") + + parser.add_argument("--max_stations_2", type=int, default=-1, + help="Specify maximum 2.4Ghz stations") + parser.add_argument("--max_stations_5", type=int, default=-1, + help="Specify maximum 5Ghz stations") + parser.add_argument("--max_stations_dual", type=int, default=-1, + help="Specify maximum stations for dual-band tests") + parser.add_argument("--dut5_0", type=str, default="", + help="Specify 5Ghz DUT entry. Syntax is somewhat tricky: DUT-name SSID BSID (bssid-idx), example: linksys-8450 Default-SSID-5gl c4:41:1e:f5:3f:25 (2)") + parser.add_argument("--dut2_0", type=str, default="", + help="Specify 5Ghz DUT entry. Syntax is somewhat tricky: DUT-name SSID BSID (bssid-idx), example: linksys-8450 Default-SSID-2g c4:41:1e:f5:3f:24 (1)") + + parser.add_argument("--radio2", action='append', nargs=1, default=[], + help="Specify 2.4Ghz radio. May be specified multiple times.") + parser.add_argument("--radio5", action='append', nargs=1, default=[], + help="Specify 5Ghz radio. May be specified multiple times.") + parser.add_argument("--local_lf_report_dir", help="--local_lf_report_dir default '' put where dataplane script run from",default="") + + args = parser.parse_args() + + cv_base_adjust_parser(args) + + CV_Test = ApAutoTest(lf_host = args.mgr, + lf_port = args.port, + lf_user = args.lf_user, + lf_password = args.lf_password, + instance_name = args.instance_name, + config_name = args.config_name, + upstream = args.upstream, + pull_report = args.pull_report, + local_lf_report_dir = args.local_lf_report_dir, + dut5_0 = args.dut5_0, + dut2_0 = args.dut2_0, + load_old_cfg = args.load_old_cfg, + max_stations_2 = args.max_stations_2, + max_stations_5 = args.max_stations_5, + max_stations_dual = args.max_stations_dual, + radio2 = args.radio2, + radio5 = args.radio5, + enables = args.enable, + disables = args.disable, + raw_lines = args.raw_line, + raw_lines_file = args.raw_lines_file, + sets = args.set + ) + CV_Test.setup() + CV_Test.run() + + CV_Test.check_influx_kpi(args) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_atten_mod_test.py b/lanforge/lanforge-scripts/py-scripts/lf_atten_mod_test.py new file mode 100755 index 000000000..9a7e91d58 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_atten_mod_test.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +""" +NAME: lf_atten_mod_test.py + +PURPOSE: +This program is used to modify the LANforge attenuator (through the LANforge manager/server processes) by using create() method. +You can check the attenuator details and serial number by using show() method. + +EXAMPLE: +Run with all serial number and module: python3 lf_atten_mod_test.py -hst 192.168.200.12 -port 8080 -atten_serno all --atten_idx all --atten_val 220 +Run with particular serial number(2222) and module(2): python3 lf_atten_mod_test.py -hst 192.168.200.12 -port 8080 -atten_serno 2222 --atten_idx 3 --atten_val 220 + +"atten_serno" = serial number +"atten_idx" = module name + + +Use './lf_atten_mod_test.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class CreateAttenuator(LFCliBase): + def __init__(self, host, port, serno, idx, val, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _local_realm=realm.Realm(host, port), _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.serno = serno + self.idx = idx + self.val = val + self.attenuator_profile = self.local_realm.new_attenuator_profile() + self.attenuator_profile.atten_idx = self.idx + self.attenuator_profile.atten_val = self.val + self.attenuator_profile.atten_serno = self.serno + + def build(self): + self.attenuator_profile.create() + self.attenuator_profile.show() + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='lf_atten_mod_test.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog=None, + description='''\ + lf_atten_mod_test.py + -------------------- + set and show Attenuator: + python3 lf_atten_mod_test.py -hst 192.168.200.12 -port 8080 -atten_serno all --atten_idx all --atten_val 220 + ''') + + parser.add_argument('-hst', '--host', help='host name', default='192.168.200.12') + parser.add_argument('-port', '--port', help='port name', default=8080) + parser.add_argument('-atten_serno', '--atten_serno', help='Serial number for requested Attenuator, or \'all\'', default=2222) + parser.add_argument('-atten_idx', '--atten_idx', help='Attenuator index eg. For module 1 = 0,module 2 = 1', default='all') + parser.add_argument('-atten_val', '--atten_val', help='Requested attenution in 1/10ths of dB (ddB).', default=550) + args = parser.parse_args() + + atten_mod_test = CreateAttenuator(host=args.host, port=args.port, serno=args.atten_serno, idx=args.atten_idx, val=args.atten_val) + atten_mod_test.build() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_check.json b/lanforge/lanforge-scripts/py-scripts/lf_check.json new file mode 100644 index 000000000..5783c39dc --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_check.json @@ -0,0 +1,61 @@ +{ + "test_parameters":{ + "test_timeout": 200, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "chuck.rekiere@candelatech.com", + "host_ip_production": "192.168.95.6", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.95.6", + "lf_mgr": "192.168.100.116", + "email_title_txt": "Dataplane Test", + "email_txt": "Dataplane Regression testing " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "ct523c-vap", + "ssid_pw_used": "ct523c-vap", + "security_used": "wpa2", + "num_sta": 4, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth1" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"4","SSID":"ct523c-vap","PASSWD":"ct523c-vap","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"ct523c-vap","PASSWD":"ct523c-vap","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_one":{ + "create_l3":{"enabled":"TRUE","command":"create_l4.py","args":"--radio RADIO_USED --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"RADIO_1_CFG --debug"}, + "create_l4_2":{"enabled":"TRUE","command":"create_l4.py","args":"--radio wiphy1 --ssid ct523c-vap --passwd ct523c-vap --security wpa2 --debug"} + }, + "suite_two":{ + "test_l3_longevity":{"enabled":"TRUE","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth1 --radio 'radio==wiphy1,stations==4,ssid==ct523c-vap,ssid_pw==ct523c-vap,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "suite_dp":{ + "create_station":{"enabled":"TRUE","command":"create_station.py","args":"--radio RADIO_USED --start_id 2 --num_stations 1 --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED --debug"}, + "data_plane_0":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"}, + "data_plane_1":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"}, + "data_plane_2":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"} + }, + "TEST_DICTIONARY":{ + "create_station":{"enabled":"TRUE","command":"create_station.py","args":"--radio RADIO_USED --start_id 2 --num_stations 1 --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED --debug"}, + "data_plane_0":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"}, + "data_plane_1":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"}, + "data_plane_2":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--json lf_dp.json --influx_json lf_influx_db.json"}, + "lf_ap_auto_test": {"enabled":"True","command":"lf_ap_auto_test.py","args": "--instance_name ap-auto-instance --config_name test_con --upstream 1.1.eth1 --dut5_0 'linksys-8450 lanforge 04:f0:21:2c:41:84 (1)' --dut2_0 'linksys-8450 lanforge 04:f0:21:2c:41:84 (1)' --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1 --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1 --set 'Multi Band Performance' 1 --set 'Skip 2.4 Ghz Tests' 1 –set ‘Stability’ 0 --set ‘Multi-Station Throughput vs Pkt Size’ 0 --set ‘Throughput vs Pkt Size’ 0 --set ‘Capacity’ 0 --set ‘Band-Steering’ 0 –pull_report"} + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/lf_check_95_6.json b/lanforge/lanforge-scripts/py-scripts/lf_check_95_6.json new file mode 100644 index 000000000..19a9223ca --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_check_95_6.json @@ -0,0 +1,135 @@ +{ + "script_qa":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-001_QA", + "lf_mgr_ip": "192.168.100.116", + "lf_mgr_port": "8080", + "dut_name": "ASUSRT-AX88U", + "dut_bssid_5G": "3c:7c:3f:55:4d:64", + "dut_sw": "3.0.0.4.386_42820", + "test_timeout": 300, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com,logan.lipke@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge Script QA Testing CT-US-001", + "email_txt": "Lanforge Script QA Testing CT-US-001 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 4, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth2" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"4","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_short":{ + "create_l3":{"enabled":"TRUE","command":"create_l4.py","args":"--radio RADIO_USED --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED --debug"}, + "test_l3_longevity":{"enabled":"TRUE","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth1 --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "suite_l3":{ + "test_l3_longevity":{"enabled":"TRUE","load_db":"NONE","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth1 --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "suite_daily":{ + "test_l3_longevity":{ + "enabled":"TRUE", + "load_db":"NONE", + "command":"test_l3_longevity.py", + "args":"", + "args_list":[ + " --test_duration 15s --polling_interval 5s --upstream_port UPSTREAM_PORT", + " --radio 'radio==RADIO_USED,stations==4,ssid==SSID_USED,ssid_pw==SSID_PS_USED,security==SECURITY_USED'", + " --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000" + ] + }, + "example_security_connection0":{ + "enabled":"TRUE", + "command":"example_security_connection.py", + "args":"", + "args_list":[ + " --num_stations 4 --ssid SSID_USED --passwd SSID_PW_USED --radio RADIO_USED --security SECURITY_USED --debug" + ] + }, + "sta_connect2":{ + "enabled":"TRUE", + "command":"sta_connect2.py", + "args":" --dut_ssid SSID_USED --dut_passwd SSID_PW_USED --dut_security SECURITY_USED" + }, + "test_fileio":{ + "enabled":"TRUE", + "command":"test_fileio.py", + "args":"", + "args_list":[ + " --macvlan_parent eth2 --num_ports 3 --use_macvlans --first_mvlan_ip 192.168.92.13", + " --netmask 255.255.255.0 --test_duration 30s --gateway 192.168.92.1" + ] + }, + "test_ip_connection-ipv4":{ + "enabled":"TRUE", + "command":"test_ip_connection.py", + "args":"--radio RADIO_USED --num_stations 4 --ssid SSID_USED --passwd SSID_PS_USED --security SECURITY_USED --debug"}, + "test_ip_variable_time0-ipv4":{ + "enabled":"TRUE", + "command":"test_ip_variable_time.py", + "args":"", + "args_list":[ + " --radio RADIO_USED --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED", + " --test_duration 15s --output_format excel --layer3_cols name,tx_bytes,rx_bytes,dropped", + " --traffic_type lf_udp --debug" + ] + }, + "test_ip_variable_time1-ipv4":{"enabled":"TRUE","command":"test_ip_variable_time.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --test_duration 15s --output_format csv --layer3_cols name,tx_bytes,rx_bytes,dropped --traffic_type lf_udp --debug"}, + "test_ip_connection-ipv6":{"enabled":"FALSE","command":"test_ip_connection.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --ipv6 --debug"}, + "test_ip_variable_time0-ipv6":{"enabled":"TRUE","command":"test_ip_variable_time.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --test_duration 15s --output_format excel --layer3_cols name,tx_bytes,rx_bytes,dropped --ipv6 --traffic_type lf_udp --debug"}, + "test_ip_variable_time1-ipv6":{"enabled":"TRUE","command":"test_ip_variable_time.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --test_duration 15s --output_format csv --layer3_cols name,tx_bytes,rx_bytes,dropped --ipv6 --traffic_type lf_udp --debug"}, + "test_l4_bytes-rd":{"enabled":"TRUE","command":"test_l4.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid asus11ax-5 --passwd hello123 --test_type bytes-rd --test_duration 15s --url 'dl http://10.40.0.1 /dev/null' --debug"}, + "test_l4_bytes-wr":{"enabled":"FALSE","command":"test_l4.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid asus11ax-5 --passwd hello123 --test_type bytes-wr --test_duration 15s --url 'ul http://10.40.0.1' --debug"}, + "test_l4_urls_s":{"enabled":"TRUE","command":"test_l4.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid asus11ax-5 --passwd hello123 --test_type urls --test_duration 15s --requests_per_ten 600 --target_per_ten 600 --url 'dl http://10.40.0.1 /dev/null' --debug"}, + "test_l4_ftp_bytes-rd":{"enabled":"TRUE","command":"test_l4.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid asus11ax-5 --passwd hello123 --ftp --test_type bytes-rd --test_duration 15s --url 'dl ftp://10.40.0.1 /dev/null' --debug"}, + "test_l4_ftp_bytes-wr":{"enabled":"FALSE","command":"test_l4.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid asus11ax-5 --passwd hello123 --ftp --test_type bytes-wr --test_duration 15s --url 'ul ftp://10.40.0.1' --debug"}, + "test_l4_ftp_urls_s":{"enabled":"TRUE","command":"test_l4.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid asus11ax-5 --passwd hello123 --ftp --test_type urls --requests_per_ten 600 --target_per_ten 600 --test_duration 15s --url 'dl ftp://10.40.0.1 /dev/null' --debug"}, + "test_l3_longevity_1":{"enabled":"TRUE","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth2 --radio 'radio==wiphy0,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"}, + "test_l3_powersave_traffic":{"enabled":"TRUE","command":"test_l3_powersave_traffic.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --debug"}, + "test_status_msg":{"enabled":"TRUE","command":"test_status_msg.py","args":"--action run_test"}, + "test_wanlink":{"enabled":"TRUE","command":"test_wanlink.py","args":"--debug"}, + "create_bridge":{"enabled":"TRUE","command":"create_bridge.py","args":"--radio wiphy1 --upstream_port eth2 --target_device sta0000 --debug"}, + "create_l3":{"enabled":"TRUE","command":"create_l3.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --debug"}, + "create_macvlan":{"enabled":"TRUE","command":"create_macvlan.py","args":"--radio wiphy1 --macvlan_parent eth2 --debug"}, + "create_station":{"enabled":"TRUE","command":"create_station.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --debug"}, + "create_vap":{"enabled":"TRUE","command":"create_vap.py","args":"--radio wiphy1 --ssid asus11ax-5 --passwd hello123 --security wpa2 --debug"}, + "create_qvlan":{"enabled":"TRUE","command":"create_qvlan.py","args":"--radio wiphy1 --qvlan_parent eth2"}, + "wlan_capacity_calculator1":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11abg -t Voice -p 48 -m 106 -e WEP -q Yes -b 1 2 5.5 11 -pre Long -s N/A -co G.711 -r Yes -c Yes"}, + "wlan_capacity_calculator2":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11n -t Voice -d 17 -ch 40 -gu 800 -high 9 -e WEP -q Yes -ip 5 -mc 42 -b 6 9 12 24 -m 1538 -co G.729 -pl Greenfield -cw 15 -r Yes -c Yes"}, + "wlan_capacity_calculator3":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11ac -t Voice -d 9 -spa 3 -ch 20 -gu 800 -high 1 -e TKIP -q Yes -ip 3 -mc 0 -b 6 12 24 54 -m 1518 -co Greenfield -cw 15 -rc Yes"} + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/lf_check_config_template.ini b/lanforge/lanforge-scripts/py-scripts/lf_check_config_template.ini new file mode 100755 index 000000000..7207f314d --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_check_config_template.ini @@ -0,0 +1,187 @@ +# NAME : +# lf_check_config_template.ini + +# PURPOSE : +# The lf_check_config_template.ini is a template to be copied to the test configuration file : lf_check_config.ini which +# is used by the lf_check.py. +# The lf_check_config_template.in is devided into section that are used for test selection, test configuration or configuration of lanforge. +# Test arguments for the test do not need to use the pre-defined values. The command arguments can be entered directly. + +# SETUP: +# Copy lf_check_config_template.ini to lf_check_config.ini + +# 1. Update the lf_check_config.ini with the tests to be run by setting the enable flag to TRUE +# 2. TEST_DICTIONARY contains the test list: test key, test name, test arguments + +# NOTE: each test dictionary key must be unique +# NOTE: { } placement important, will cause parcing errors + +# NO quotes around parameters in TEST_PARAMETERS section +[TEST_PARAMETERS] +TEST_TIMEOUT = 200 +LOAD_BLANK_DB = FALSE +LOAD_FACTORY_DEFAULT_DB = TRUE +LOAD_CUSTOM_DB = FALSE +CUSTOM_DB = DFLT_ETH1_GEN +PRODUCTION_RUN = FALSE # determine whom to send emails to +EMAIL_LIST_PRODUCTION = chuck.rekiere@candelatech.com +HOST_IP_PRODUCTION = 192.168.95.6 +EMAIL_LIST_TEST = chuck.rekiere@candelatech.com +HOST_IP_TEST = 192.168.95.6 + +# Command line arguments: Configures network information used as inputs to test command line +[TEST_NETWORK] +HTTP_TEST_IP = "10.40.0.10" +FTP_TEST_IP = "10.40.0.10" +TEST_IP = "192.168.0.104" + +# Command line arguments: LANForge configuration +# Also can use for single lanforge radio configuraiton , the RADIO_DICT may also be used for radio configuration +[TEST_GENERIC] +RADIO_USED = wiphy1 +SSID_USED = ct523c-vap +SSID_PW_USED = ct523c-vap +SECURITY_USED = wpa2 +NUM_STA = 4 +COL_NAMES = name,tx_bytes,rx_bytes,dropped +UPSTREAM_PORT = eth1 + +# Command line arguments +# radio configuraiton may also be done in the TEST_GENEERIC arguments +# NOTE: KEY must match ELEMENT of the DICTIONARY (RADIO_1_CFG == "KEY":"RADIO_1_CFG") +[RADIO_DICTIONARY] +RADIO_DICT: { + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"4","SSID":"ssid-wpa2","PASSWD":"ssidpw-wpa2","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"ct523c-vap","PASSWD":"ct523c-vap","SECURITY":"wpa2"}, + "RADIO_2_CFG":{"KEY":"RADIO_2_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"ssid-wpa","PASSWD":"ssidpw-wpa","SECURITY":"wpa"}, + "RADIO_3_CFG":{"KEY":"RADIO_3_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"ssid-wep","PASSWD":"ssidpw-wep","SECURITY":"wep"}, + "RADIO_4_CFG":{"KEY":"RADIO_4_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"ssid-wpa3","PASSWD":"ssidpw-wpa3","SECURITY":"wpa3"} + } + + +[LF_MGR] +LF_MGR_IP= localhost +LF_MGR_PORT=8080 + +# REPORTS are in /home/lanforge/html-reports +# if /home/lanforge/html-reports not present then reports stored in local directory +#[REPORTS] +#REPORT_DIR="/home/lanforge/html-reports" + +# TEST_DICTIONARY used by lf_check, Other section names will be ignored so can save other test lists +# TEST_DICTIONARY_ENABLE_1 is an example, it will not run unless the name is changed to TEST_DICTIONARY +[TEST_DICTIONARY_EXAMPLE_1] +#[TEST_DICTIONARY] +TEST_DICT: { + "test_ipv4_l4":{"enabled":"FALSE","command":"test_ipv4_l4.py","args":"--radio wiphy1 --ssid ct523c-vap --passwd ct523c-vap --security wpa2 --num_stations 4 --test_duration 15s --debug"}, + "test_ipv4_variable_time2":{"enabled":"TRUE","command":"test_ipv4_variable_time.py","args":"--radio wiphy1 --ssid ct523c-vap --passwd ct523c-vap --security wpa2 --test_duration 15s --output_format excel --layer3_cols name,tx_bytes,rx_bytes,dropped --traffic_type lf_udp --debug"} + } + +# TEST_DICTIONARY used by lf_check, Other section names will be ignored so can save other test lists +[TEST_DICTIONARY_EXAMPLE_2] +#[TEST_DICTIONARY] +TEST_DICT: { + "create_l3":{"enabled":"TRUE","command":"create_l3.py","args":"RADIO_1_CFG --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"RADIO_1_CFG --debug"} + } + +# This is an EXAMPLE dictionary of tests that can be run, copy to TEST_DICTIONARY to test. +# Feature update pass in the DICTIONARY name to be run +[TEST_DICTIONARY_EXAMPLE_3] +#[TEST_DICTIONARY] +TEST_DICT: { + "example_security_connection0":{"enabled":"TRUE","command":"example_security_connection.py","args":"RADIO_1_CFG --debug"}, + "example_security_connection1":{"enabled":"TRUE","command":"example_security_connection.py","args":"RADIO_2_CFG --debug"}, + "example_security_connection2":{"enabled":"TRUE","command":"example_security_connection.py","args":"RADIO_3_CFG --debug"}, + "example_security_connection3":{"enabled":"TRUE","command":"example_security_connection.py","args":"RADIO_4_CFG --debug"}, + "sta_connect2":{"enabled":"TRUE","command":"sta_connect2.py","args":"--dut_ssid ssid-wpa2 --dut_passwd ssidpw-wpa2 --dut_security wpa2"}, + "sta_connect_example":{"enabled":"TRUE","command":"sta_connect_example.py"}, + "test_generic0":{"enabled":"TRUE","command":"test_generic.py","args":"RADIO_1_CFG --type lfping --dest TEST_IP --debug"}, + "test_generic1":{"enabled":"TRUE","command":"test_generic.py","args":"RADIO_1_CFG --type lfping --dest TEST_IP --debug"}, + "test_generic2":{"enabled":"TRUE","command":"test_generic.py","args":"RADIO_1_CFG --type lfping --dest TEST_IP --debug"}, + "testgroup":{"enabled":"TRUE","command":"testgroup.py","args":"--group_name group1 --add_group --list_groups --debug"}, + "test_ipv4_connection":{"enabled":"TRUE","command":"test_ipv4_connection.py","args":"RADIO_1_CFG --debug"}, + "test_ipv4_l4_urls_per_ten":{"enabled":"TRUE","command":"test_ipv4_l4_urls_per_ten.py","args":"RADIO_1_CFG --num_tests 1 --requests_per_ten 600 --target_per_ten 600 --debug"}, + "test_ipv4_l4_wifi":{"enabled":"TRUE","command":"test_ipv4_l4_wifi.py","args":"RADIO_1_CFG --test_duration 15s --debug"}, + "test_ipv4_l4":{"enabled":"TRUE","command":"test_ipv4_l4.py","args":"--radio wiphy1 --ssid ct523c-vap --passwd ct523c-vap --security wpa2 --num_stations 4 --test_duration 15s --debug"}, + "test_ipv4_variable_time0":{"enabled":"TRUE","command":"test_ipv4_variable_time.py","args":"RADIO_1_CFG --test_duration 15s --output_format excel --layer3_cols COL_NAMES --traffic_type lf_udp --debug"}, + "test_ipv4_variable_time1":{"enabled":"TRUE","command":"test_ipv4_variable_time.py","args":"RADIO_1_CFG --test_duration 15s --output_format csv --layer3_cols COL_NAMES --traffic_type lf_udp --debug"}, + "test_ipv4_variable_time2":{"enabled":"TRUE","command":"test_ipv4_variable_time.py","args":"--radio wiphy1 --ssid ct523c-vap --passwd ct523c-vap --security wpa2 --test_duration 15s --output_format excel --layer3_cols name,tx_bytes,rx_bytes,dropped --traffic_type lf_udp --debug"}, + "test_ipv4_variable_time3":{"enabled":"TRUE","command":"test_ipv4_variable_time.py","args":"RADIO_1_CFG --test_duration 15s --output_format csv --layer3_cols COL_NAMES --traffic_type lf_udp --debug"}, + "create_bridge":{"enabled":"TRUE","command":"create_bridge.py","args":"--radio RADIO_USED --upstream_port UPSTREAM_PORT --target_device sta0000 --debug"}, + "create_l3":{"enabled":"TRUE","command":"create_l3.py","args":"RADIO_1_CFG --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"RADIO_1_CFG --debug"}, + "create_station":{"enabled":"TRUE","command":"create_station.py","args":"--radio RADIO_USED --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED --debug"}, + "test_fileio":{"enabled":"TRUE","command":"test_fileio.py","args":"--macvlan_parent eth2 --num_ports 3 --use_macvlans --first_mvlan_ip 192.168.92.13 --netmask 255.255.255.0 --gateway 192.168.92.1 --test_duration 30s"}, + "test_l3_longevity":{"enabled":"TRUE","command":"test_l3_longevity.py","args":"--test_duration 20s --polling_interval 5s --upstream_port eth1 + --radio 'radio==wiphy1,stations==4,ssid==ct523c-vap,ssid_pw==ct523c-vap,security==wpa2' + --radio 'radio==wiphy2,stations==4,ssid==ct523c-vap,ssid_pw==ct523c-vap,security==wpa2' + --radio 'radio==wiphy3,stations==4,ssid==ct523c-vap,ssid_pw==ct523c-vap,security==wpa2' + --endp_type lf_udp --ap_read --ap_test_mode --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"}, + "test_status_msg":{"enabled":"TRUE","command":"test_status_msg.py","args":"--action run_test"}, + "test_wanlink":{"enabled":"TRUE","command":"test_wanlink.py","args":"--debug"}, + "sta_connect_example":{"enabled":"TRUE","command":"sta_connect_example.py","args":"RADIO_1_CFG --upstream_port UPSTREAM_PORT"}, + "wlan_capacity_calculator1":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11abg -t Voice -p 48 -m 106 -e WEP -q Yes -b 1 2 5.5 11 -pre Long -s N/A -co G.711 -r Yes -c Yes"}, + "wlan_capacity_calculator2":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11n -t Voice -d 17 -ch 40 -gu 800 -high 9 -e WEP -q Yes -ip 5 -mc 42 -b 6 9 12 24 -m 1538 -co G.729 -pl Greenfield -cw 15 -r Yes -c Yes"}, + "wlan_capacity_calculator3":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11ac -t Voice -d 9 -spa 3 -ch 20 -gu 800 -high 1 -e TKIP -q Yes -ip 3 -mc 0 -b 6 12 24 54 -m 1518 -co Greenfield -cw 15 -rc Yes"} + } + + +# This LISA is used currelty for facilitating getting testing on LISA +[TEST_DICTIONARY_LISA_SHORT] +#[TEST_DICTIONARY] +TEST_DICT: { + "create_l3":{"enabled":"TRUE","command":"create_l3.py","args":"--radio RADIO_USED --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"RADIO_1_CFG --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"} + } + +#[TEST_DICTIONARY] +[TEST_DICTIONARY_LISA] +TEST_DICT: { + "example_security_connection0":{"enabled":"FALSE","command":"example_security_connection.py","args":"--num_stations 4 --ssid jedway-wpa-1 --passwd jedway-wpa-1 --radio wiphy1 --security wpa --debug"}, + "example_security_connection1":{"enabled":"FALSE","command":"example_security_connection.py","args":"--num_stations 4 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --radio wiphy1 --security wpa2 --debug"}, + "example_security_connection2":{"enabled":"FALSE","command":"example_security_connection.py","args":"--num_stations 4 --ssid jedway-wep-48 --passwd 0123456789 --radio wiphy1 --security wep --debug"}, + "example_security_connection2":{"enabled":"FALSE","command":"example_security_connection.py","args":"--num_stations 4 --ssid jedway-wpa3-1 --passwd jedway-wpa3-1 --radio wiphy1 --security wpa3 --debug"}, + "sta_connect2":{"enabled":"FALSE","command":"sta_connect2.py","args":"--dut_ssid ssid-wpa2 --dut_passwd ssidpw-wpa2 --dut_security wpa2"}, + "sta_connect_example":{"enabled":"FALSE","command":"sta_connect_example.py","args":""}, + "test_fileio":{"enabled":"FALSE","command":"test_fileio.py","args":"--macvlan_parent eth2 --num_ports 3 --use_macvlans --first_mvlan_ip 192.168.92.13 --netmask 255.255.255.0 --test_duration 30s --gateway 192.168.92.1"}, + "test_generic0":{"enabled":"FALSE","command":"test_generic.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --num_stations 4 --type lfping --dest 10.40.0.1 --debug"}, + "test_generic1":{"enabled":"FALSE","command":"test_generic.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --num_stations 4 --type speedtest --speedtest_min_up 20 --speedtest_min_dl 20 --speedtest_max_ping 150 --security wpa2 --debug"}, + "test_generic2":{"enabled":"FALSE","command":"test_generic.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --num_stations 4 --type iperf3 --debug"}, + "test_generic3":{"enabled":"FALSE","command":"test_generic.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --num_stations 4 --type lfcurl --dest 10.40.0.1 --file_output /home/lanforge/Documents/lfcurl_output.txt --debug"}, + "testgroup":{"enabled":"FALSE","command":"testgroup.py","args":"--group_name group1 --add_group --list_groups --debug"}, +# testgroup_list_groups +# testgroup_list_connections +# testgroup_delete_group + "testgroup5":{"enabled":"TRUE","command":"testgroup.py","args":"--num_stations 4 --ssid lanforge --passwd password --security wpa2 --radio wiphy0 --group_name group0 --add_group"}, + "test_ipv4_connection":{"enabled":"TRUE","command":"test_ipv4_connection.py","args":"--radio wiphy1 --num_stations 4 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"}, + "test_ipv4_l4_urls_per_ten":{"enabled":"TRUE","command":"test_ipv4_l4_urls_per_ten.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --num_tests 1 --requests_per_ten 600 --target_per_ten 600 --debug"}, + "test_ipv4_l4_wifi":{"enabled":"TRUE","command":"test_ipv4_l4_wifi.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --test_duration 15s --debug"}, + "test_ipv4_l4":{"enabled":"TRUE","command":"test_ipv4_l4.py","args":"--radio wiphy1 --num_stations 4 --security wpa2 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --test_duration 15s --debug"}, + "test_ipv4_variable_time0":{"enabled":"TRUE","command":"test_ipv4_variable_time.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --test_duration 15s --output_format excel --layer3_cols name,tx_bytes,rx_bytes,dropped --traffic_type lf_udp --debug"}, + "test_ipv4_variable_time1":{"enabled":"TRUE","command":"test_ipv4_variable_time.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --test_duration 15s --output_format csv --layer3_cols name,tx_bytes,rx_bytes,dropped --traffic_type lf_udp --debug"}, + "test_ipv4_l4_ftp_upload":{"enabled":"TRUE","command":"test_ipv4_l4_ftp_upload.py","args":"--upstream_port eth1 --radio wiphy1 --num_stations 4 --security wpa2 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --test_duration 15s --debug"}, + "test_ipv6_connection":{"enabled":"TRUE","command":"test_ipv6_connection.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"}, + "test_ipv6_variable_time":{"enabled":"TRUE","command":"test_ipv6_variable_time.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --test_duration 15s --cx_type tcp6 --debug"}, + "test_ipv6_variable_time":{"enabled":"TRUE","command":"test_ipv6_variable_time.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"}, + "test_l3_longevity":{"enabled":"TRUE","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth1 + --radio 'radio==wiphy0,stations==4,ssid==jedway-wpa2-x2048-5-3,ssid_pw==jedway-wpa2-x2048-5-3,security==wpa2' + --radio 'radio==wiphy2,stations==4,ssid==jedway-wpa2-x2048-5-3,ssid_pw==jedway-wpa2-x2048-5-3,security==wpa2' + --radio 'radio==wiphy3,stations==4,ssid==ct523c-vap,ssid_pw==ct523c-vap,security==wpa2' + --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"}, + "test_l3_powersave_traffic":{"enabled":"TRUE","command":"test_l3_powersave_traffic.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"}, + "test_status_msg":{"enabled":"TRUE","command":"test_status_msg.py","args":"--action run_test"}, + "test_wanlink":{"enabled":"TRUE","command":"test_wanlink.py","args":"--debug"}, + "create_bridge":{"enabled":"TRUE","command":"create_bridge.py","args":"--radio wiphy1 --upstream_port eth1 --target_device sta0000 --debug"}, + "create_l3":{"enabled":"TRUE","command":"create_l3.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"}, + "create_macvlan":{"enabled":"TRUE","command":"create_macvlan.py","args":"--radio wiphy1 --macvlan_parent eth1 --debug"}, + "create_station":{"enabled":"TRUE","command":"create_station.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"}, + "create_vap":{"enabled":"TRUE","command":"create_vap.py","args":"--radio wiphy1 --ssid jedway-wpa2-x2048-5-3 --passwd jedway-wpa2-x2048-5-3 --security wpa2 --debug"}, + "create_vr":{"enabled":"TRUE","command":"create_vr.py","args":"--vr_name 2.vr0 --ports 2.br0,2.vap2 --services"}, + "create_qvlan":{"enabled":"TRUE","command":"create_qvlan.py","args":"--radio wiphy1 --qvlan_parent eth1"}, + "wlan_capacity_calculator1":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11abg -t Voice -p 48 -m 106 -e WEP -q Yes -b 1 2 5.5 11 -pre Long -s N/A -co G.711 -r Yes -c Yes"}, + "wlan_capacity_calculator2":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11n -t Voice -d 17 -ch 40 -gu 800 -high 9 -e WEP -q Yes -ip 5 -mc 42 -b 6 9 12 24 -m 1538 -co G.729 -pl Greenfield -cw 15 -r Yes -c Yes"}, + "wlan_capacity_calculator3":{"enabled":"TRUE","command":"./wlan_capacity_calculator.py","args":"-sta 11ac -t Voice -d 9 -spa 3 -ch 20 -gu 800 -high 1 -e TKIP -q Yes -ip 3 -mc 0 -b 6 12 24 54 -m 1518 -co Greenfield -cw 15 -rc Yes"} + } \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/lf_check_ct_us_001.json b/lanforge/lanforge-scripts/py-scripts/lf_check_ct_us_001.json new file mode 100644 index 000000000..f5789df27 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_check_ct_us_001.json @@ -0,0 +1,60 @@ +{ + "test_parameters":{ + "test_timeout": 200, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com,logan.lipke@candelatech.com", + "host_ip_test": "192.168.100.201", + "lf_mgr": "192.168.100.116", + "email_title_txt": "Lanforge QA Testing CT-US-001", + "email_txt": "Lanforge QA Testing CT-US-001 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 4, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth2" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"4","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_one":{ + "create_l3":{"enabled":"TRUE","command":"create_l4.py","args":"--radio RADIO_USED --ssid SSID_USED --passwd SSID_PW_USED --security SECURITY_USED --debug"}, + "create_l4":{"enabled":"TRUE","command":"create_l4.py","args":"RADIO_1_CFG --debug"}, + "create_l4_2":{"enabled":"TRUE","command":"create_l4.py","args":"--radio wiphy1 --ssid ct523c-vap --passwd ct523c-vap --security wpa2 --debug"} + }, + "suite_two":{ + "test_l3_longevity":{"enabled":"TRUE","load_db":"skip","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth2 --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "suite_wc_dp_ig_dut":{ + "CT-US-001_wifi_capacity_ATH10K(9984)":{"enabled":"TRUE","load_db":"skip","command":"lf_wifi_capacity_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name cicd-wct --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000 --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.sta0000,1.1.sta0001 --create_stations --radio wiphy1 --ssid asus11ax-5 --security wpa2 --paswd hello123 --test_rig CT-US-001 --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 --raw_line 'dut-model-num: ASUS RT-AX88U'"}, + "CT-US-001_dataplane_ATH10K(9984)":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"--ghost_token 60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742 --ghost_host 192.168.100.153 --authors LANForge --grafana_host 192.168.100.201 --grafana_token eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ== --parent_folder REPORT_PATH --user_push lanforge --password_push lanforge --customer candela --grafana_bucket lanforge_qa_testing --kpi_to_ghost"} + }, + "suite_wc_dp":{ + "CT-US-001_wifi_capacity_ATH10K(9984)":{"enabled":"TRUE","load_db":"skip","command":"lf_wifi_capacity_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name cicd-wct --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000 --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.sta0000,1.1.sta0001 --create_stations --radio wiphy1 --ssid asus11ax-5 --security wpa2 --paswd hello123 --test_rig CT-US-001 --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "CT-US-001_dataplane_ATH10K(9984)":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta0000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "CT-US-001_wifi_capacity_AX210":{"enabled":"TRUE","load_db":"skip","command":"lf_wifi_capacity_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name cicd-wct --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000 --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.sta2000 --create_stations --radio wiphy2 --ssid asus11ax-5 --security wpa2 --paswd hello123 --test_rig CT-US-001 --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "CT-US-001_dataplane_AX210":{"enabled":"TRUE","load_db":"skip","command":"lf_dataplane_test.py","args":"--mgr 192.168.100.116 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.sta2000 --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --influx_host 192.168.100.201 --influx_port 8086 --influx_org Candela --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== --influx_bucket lanforge_qa_testing --influx_tag testbed CT-US-001 "}, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"--ghost_token 60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742 --ghost_host 192.168.100.153 --authors LANForge --grafana_host 192.168.100.201 --grafana_token eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ== --parent_folder REPORT_PATH --user_push lanforge --password_push lanforge --customer candela --grafana_bucket lanforge_qa_testing --kpi_to_ghost"} + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/lf_check_orig.py b/lanforge/lanforge-scripts/py-scripts/lf_check_orig.py new file mode 100644 index 000000000..079827025 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_check_orig.py @@ -0,0 +1,1219 @@ +#!/usr/bin/python3 + +''' +NAME: +lf_check.py + +PURPOSE: +lf_check.py will tests based on .ini file or .json file. +The config file name can be passed in as a configuraiton parameter. +The json file may be copied from lf_check.json and updated. Currently all the parameters are needed to be set to a value + +The --production flag determine the email list for results + +EXAMPLE: +lf_check.py # this will use the defaults +lf_check.py --json --test_suite +lf_check.py --json --production + +NOTES: +Before using lf_check.py +Using .ini: +1. copy lf_check_config_template.ini to .ini , this will avoid .ini being overwritten on git pull +2. update .ini to enable (TRUE) tests to be run in the test suite, the default suite is the TEST_DICTIONARY +3. update other configuration to specific test bed for example radios + +Using .json: +1. copy lf_check.json to .json this will avoide .json being overwritten on git pull +2. update lf_check.json to enable (TRUE) tests to be run in the test suite, the default TEST_DICTIONARY + +NOTES: getting radio information: +1. (Using Curl) curl -H 'Accept: application/json' http://localhost:8080/radiostatus/all | json_pp | less +2. (using Python) response = self.json_get("/radiostatus/all") + +GENERIC NOTES: +Starting LANforge: + On local or remote system: /home/lanforge/LANforgeGUI/lfclient.bash -cli-socket 3990 -s LF_MGR + On local system the -s LF_MGR will be local_host if not provided + + On LANforge ~lanforge/.config/autostart/LANforge-auto.desktop is used to restart lanforge on boot. + http://www.candelatech.com/cookbook.php?vol=misc&book=Automatically+starting+LANforge+GUI+on+login + +1. add server (telnet localhost 4001) build info, GUI build sha, and Kernel version to the output. + A. for build information on LANforgeGUI : /home/lanforge ./btserver --version + B. for the kernel version uname -r (just verion ), uname -a build date + C. for getting the radio firmware: ethtool -i wlan0 + +# may need to build in a testbed reboot at the beginning of a day's testing... +# seeing some dhcp exhaustion and high latency values for testbeds that have been running +# for a while that appear to clear up once the entire testbed is power cycled + +# issue a shutdown command on the lanforge(s) +# ssh root@lanforge reboot (need to verify) or do a shutdown +# send curl command to remote power switch to reboot testbed +# curl -s http://admin:lanforge@192.168.100.237/outlet?1=CCL -o /dev/null 2>&1 +# + + +''' +import datetime +import sys + +if sys.version_info[0] != 3: + print("This script requires Python3") + exit() + +import os +import socket +import logging +import time +from time import sleep +import argparse +import json +import subprocess +import csv +import shlex +import paramiko +import pandas as pd +import requests + +# lf_report is from the parent of the current file +dir_path = os.path.dirname(os.path.realpath(__file__)) +parent_dir_path = os.path.abspath(os.path.join(dir_path, os.pardir)) +sys.path.insert(0, parent_dir_path) + +from lf_report import lf_report + +sys.path.append('/') + +# setup logging FORMAT +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' + + +# lf_check class contains verificaiton configuration and ocastrates the testing. +class lf_check(): + def __init__(self, + _json_data, + _test_suite, + _production, + _csv_results, + _outfile, + _outfile_name, + _report_path, + _log_path): + self.json_data = _json_data + self.test_suite = _test_suite + self.production_run = _production + self.report_path = _report_path + self.log_path = _log_path + self.radio_dict = {} + self.test_dict = {} + path_parent = os.path.dirname(os.getcwd()) + os.chdir(path_parent) + self.scripts_wd = os.getcwd() + self.results = "" + self.outfile = _outfile + self.outfile_name = _outfile_name + self.test_result = "Failure" + self.results_col_titles = ["Test", "Command", "Result", "STDOUT", "STDERR"] + self.html_results = "" + self.background_green = "background-color:green" + self.background_red = "background-color:red" + self.background_purple = "background-color:purple" + self.background_blue = "background-color:blue" + + self.http_test_ip = "" + self.ftp_test_ip = "" + self.test_ip = "" + + # section TEST_GENERIC + self.radio_lf = "" + self.ssdi = "" + self.ssid_pw = "" + self.security = "" + self.num_sta = "" + self.col_names = "" + self.upstream_port = "" + + self.csv_results = _csv_results + self.csv_results_file = "" + self.csv_results_writer = "" + self.csv_results_column_headers = "" + self.logger = logging.getLogger(__name__) + self.test_timeout = 120 + self.test_timeout_default = 120 + self.use_blank_db = "FALSE" + self.use_factory_default_db = "FALSE" + self.use_custom_db = "FALSE" + self.email_list_production = "" + self.host_ip_production = None + self.email_list_test = "" + self.host_ip_test = None + self.email_title_txt = "" + self.email_txt = "" + + # lanforge configuration + self.lf_mgr_ip = "192.168.0.102" + self.lf_mgr_port = "8080" + self.lf_mgr_user = "lanforge" + self.lf_mgr_pass = "lanforge" + + # dut configuration + self.dut_name = "DUT_NAME_NA" # "ASUSRT-AX88U" note this is not dut_set_name + self.dut_hw = "DUT_HW_NA" + self.dut_sw = "DUT_SW_NA" + self.dut_model = "DUT_MODEL_NA" + self.dut_serial = "DUT_SERIAL_NA" + self.dut_bssid_2g = "BSSID_2G_NA" # "3c:7c:3f:55:4d:64" - this is the mac for the 2.4G radio this may be seen with a scan + self.dut_bssid_5g = "BSSID_5G_NA" # "3c:7c:3f:55:4d:64" - this is the mac for the 5G radio this may be seen with a scan + self.dut_bssid_6g = "BSSID_6G_NA" # "3c:7c:3f:55:4d:64" - this is the mac for the 6G radio this may be seen with a scan + # NOTE: My influx token is unlucky and starts with a '-', but using the syntax below # with '=' right after the argument keyword works as hoped. + # --influx_token= + + # DUT , Test rig must match testbed + self.test_rig = "CT-US-NA" + + # QA report + self.qa_report_html = "NA" + + # database configuration # database + self.database_json = "" + self.database_config = "True" # default to False once testing done + self.database_host = "192.168.100.201" # "c7-grafana.candelatech.com" # influx and grafana have the same host "192.168.100.201" + self.database_port = "8086" + self.database_token = "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==" + self.database_org = "Candela" + self.database_bucket = "lanforge_qa_testing" + self.database_tag = 'testbed CT-US-001' # the test_rig needs to match + self.dut_set_name = 'DUT_NAME ASUSRT-AX88U' # note the name will be set as --set DUT_NAME ASUSRT-AX88U, this is not dut_name (see above) + + # grafana configuration #dashboard + self.dashboard_json = "" + self.dashboard_config = "True" # default to False once testing done + self.dashboard_host = "192.168.100.201" # "c7-grafana.candelatech.com" # 192.168.100.201 + self.dashboard_port = "3000" + self.dashboard_token = "eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ==" + + # ghost configuration + self.blog_json = "" + self.blog_config = False + self.blog_host = "192.168.100.153" + self.blog_port = "2368" + self.blog_token = "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742" + self.blog_authors = "Matthew" + self.blog_customer = "candela" + self.blog_user_push = "lanforge" + self.blog_password_push = "lanforge" + self.blog_flag = "--kpi_to_ghost" + + self.test_run = "" + + def check_if_port_exists(self): + queries = dict() + queries['LANforge Manager'] = 'http://%s:%s' % (self.lf_mgr_ip, self.lf_mgr_port) + queries['Blog Host'] = 'http://%s:%s' % (self.blog_host, self.blog_port) + queries['Influx Host'] = 'http://%s:%s' % (self.database_host, self.database_port) + queries['Grafana Host'] = 'http://%s:%s' % (self.dashboard_host, self.dashboard_port) + results = dict() + for key, value in queries.items(): + try: + ping = requests.get(value).text + results[key] = [str(ping), value] + except: + print('%s not found' % value) + results[key] = [value, None] + return results + + def get_scripts_git_sha(self): + # get git sha + process = subprocess.Popen(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE) + (commit_hash, err) = process.communicate() + exit_code = process.wait() + scripts_git_sha = commit_hash.decode('utf-8', 'ignore') + return scripts_git_sha + + def get_lanforge_node_version(self): + ssh = paramiko.SSHClient() # creating shh client object we use this object to connect to router + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # automatically adds the missing host key + # ssh.connect(self.lf_mgr_ip, port=22, username=self.lf_mgr_user, password=self.lf_mgr_pass, banner_timeout=600) + ssh.connect(hostname=self.lf_mgr_ip, port=22, username=self.lf_mgr_user, password=self.lf_mgr_pass, + allow_agent=False, look_for_keys=False, banner_timeout=600) + stdin, stdout, stderr = ssh.exec_command('uname -n') + lanforge_node_version = stdout.readlines() + # print('\n'.join(output)) + lanforge_node_version = [line.replace('\n', '') for line in lanforge_node_version] + ssh.close() + time.sleep(1) + return lanforge_node_version + + def get_lanforge_kernel_version(self): + ssh = paramiko.SSHClient() # creating shh client object we use this object to connect to router + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # automatically adds the missing host key + # ssh.connect(self.lf_mgr_ip, port=22, username=self.lf_mgr_user, password=self.lf_mgr_pass, banner_timeout=600) + ssh.connect(hostname=self.lf_mgr_ip, port=22, username=self.lf_mgr_user, password=self.lf_mgr_pass, + allow_agent=False, look_for_keys=False, banner_timeout=600) + stdin, stdout, stderr = ssh.exec_command('uname -r') + lanforge_kernel_version = stdout.readlines() + # print('\n'.join(output)) + lanforge_kernel_version = [line.replace('\n', '') for line in lanforge_kernel_version] + ssh.close() + time.sleep(1) + return lanforge_kernel_version + + def get_lanforge_gui_version(self): + output = "" + ssh = paramiko.SSHClient() # creating shh client object we use this object to connect to router + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # automatically adds the missing host key + ssh.connect(hostname=self.lf_mgr_ip, port=22, username=self.lf_mgr_user, password=self.lf_mgr_pass, + allow_agent=False, look_for_keys=False, banner_timeout=600) + stdin, stdout, stderr = ssh.exec_command('./btserver --version | grep Version') + lanforge_gui_version = stdout.readlines() + # print('\n'.join(output)) + lanforge_gui_version = [line.replace('\n', '') for line in lanforge_gui_version] + ssh.close() + time.sleep(1) + return lanforge_gui_version + + def get_radio_status(self): + radio_status = self.json_get("/radiostatus/all") + print("radio status {radio_status}".format(radio_status=radio_status)) + + + # NOT complete : will send the email results + def send_results_email(self, report_file=None): + if (report_file is None): + print("No report file, not sending email.") + return + report_url = report_file.replace('/home/lanforge/', '') + if report_url.startswith('/'): + report_url = report_url[1:] + qa_url = self.qa_report_html.replace('home/lanforge','') + if qa_url.startswith('/'): + qa_url = qa_url[1:] + # following recommendation + # NOTE: https://stackoverflow.com/questions/24196932/how-can-i-get-the-ip-address-from-nic-in-python + # Mail + # command to check if mail running : systemctl status postfix + # command = 'echo "$HOSTNAME mail system works!" | mail -s "Test: $HOSTNAME $(date)" chuck.rekiere@candelatech.com' + hostname = socket.gethostname() + ip = socket.gethostbyname(hostname) + if (self.email_txt != ""): + message_txt = """{email_txt} lanforge target {lf_mgr_ip} +Results from {hostname}: +http://{ip}/{report} +QA Report Dashboard: +http://{ip_qa}/{qa_url} +NOTE: Diagrams are links in dashboard +""".format(hostname=hostname, ip=ip, report=report_url, email_txt=self.email_txt, lf_mgr_ip=self.lf_mgr_ip, + ip_qa=ip,qa_url=qa_url) + + else: + message_txt = """Results from {hostname}: +http://{ip}/{report} +QA Report Dashboard: +QA Report: http://{ip_qa}/{qa_url} +""".format(hostname=hostname, ip=ip,report=report_url,ip_qa=ip,qa_url=qa_url) + + if (self.email_title_txt != ""): + mail_subject = "{} [{hostname}] {date}".format(self.email_title_txt, hostname=hostname, + date=datetime.datetime.now()) + else: + mail_subject = "Regression Test [{hostname}] {date}".format(hostname=hostname, date=datetime.datetime.now()) + try: + if self.production_run == True: + msg = message_txt.format(ip=self.host_ip_production) + # for postfix from command line echo "My message" | mail -s subject user@candelatech.com + command = "echo \"{message}\" | mail -s \"{subject}\" {address}".format( + message=msg, + subject=mail_subject, + ip=self.host_ip_production, + address=self.email_list_production) + else: + msg = message_txt.format(ip=ip) + command = "echo \"{message}\" | mail -s \"{subject}\" {address}".format( + message=msg, + subject=mail_subject, + ip=ip, # self.host_ip_test, + address=self.email_list_test) + + print("running:[{}]".format(command)) + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + # have email on separate timeout + process.wait(timeout=int(self.test_timeout)) + except subprocess.TimeoutExpired: + print("send email timed out") + process.terminate() + + def get_csv_results(self): + return self.csv_file.name + + def start_csv_results(self): + print("self.csv_results") + self.csv_results_file = open(self.csv_results, "w") + self.csv_results_writer = csv.writer(self.csv_results_file, delimiter=",") + self.csv_results_column_headers = ['Test', 'Command', 'Result', 'STDOUT', 'STDERR'] + self.csv_results_writer.writerow(self.csv_results_column_headers) + self.csv_results_file.flush() + + def get_html_results(self): + return self.html_results + + def start_html_results(self): + self.html_results += """ + + + + + + + + + + + + """ + + def finish_html_results(self): + self.html_results += """ + +
    TestCommandResultSTDOUTSTDERR
    +
    +
    +
    + """ + + def read_config(self): + self.read_config_json() + + # there is probably a more efficient way to do this in python + # Keeping it obvious for now, may be refactored later + def read_config_json(self): + # self.logger.info("read_config_json_contents {}".format(self.json_data)) + if "test_parameters" in self.json_data: + self.logger.info("json: read test_parameters") + # self.logger.info("test_parameters {}".format(self.json_data["test_parameters"])) + self.read_test_parameters() + else: + self.logger.info("EXITING test_parameters not in json {}".format(self.json_data)) + exit(1) + + if "test_network" in self.json_data: + self.logger.info("json: read test_network") + # self.logger.info("test_network {}".format(self.json_data["test_network"])) + self.read_test_network() + else: + self.logger.info("EXITING test_network not in json {}".format(self.json_data)) + exit(1) + + if "test_database" in self.json_data: + self.logger.info("json: read test_database") + # self.logger.info("test_database {}".format(self.json_data["test_database"])) + self.read_test_database() + else: + self.logger.info("NOTE: test_database not found in json") + + if "test_dashboard" in self.json_data: + self.logger.info("json: read test_dashboard") + # self.logger.info("test_dashboard {}".format(self.json_data["test_dashboard"])) + self.read_test_dashboard() + else: + self.logger.info("NOTE: test_dashboard not found in json") + + if "test_blog" in self.json_data: + self.logger.info("json: read test_blog") + # self.logger.info("test_blog {}".format(self.json_data["test_blog"])) + self.read_test_blog() + else: + self.logger.info("NOTE: test_blog not found in json") + + if "test_generic" in self.json_data: + self.logger.info("json: read test_generic") + # self.logger.info("test_generic {}".format(self.json_data["test_generic"])) + self.read_test_generic() + else: + self.logger.info("EXITING test_generic not in json {}".format(self.json_data)) + exit(1) + + if "radio_dict" in self.json_data: + self.logger.info("json: read radio_dict") + # self.logger.info("radio_dict {}".format(self.json_data["radio_dict"])) + self.radio_dict = self.json_data["radio_dict"] + self.logger.info("self.radio_dict {}".format(self.radio_dict)) + else: + self.logger.info("EXITING radio_dict not in json {}".format(self.json_data)) + exit(1) + + if "test_suites" in self.json_data: + self.logger.info("json: read test_suites looking for: {}".format(self.test_suite)) + # self.logger.info("test_suites {}".format(self.json_data["test_suites"])) + if self.test_suite in self.json_data["test_suites"]: + self.test_dict = self.json_data["test_suites"][self.test_suite] + # self.logger.info("self.test_dict {}".format(self.test_dict)) + else: + self.logger.info("EXITING test_suite {} Not Present in json test_suites: {}".format(self.test_suite, + self.json_data[ + "test_suites"])) + exit(1) + else: + self.logger.info("EXITING test_suites not in json {}".format(self.json_data)) + exit(1) + + def read_test_parameters(self): + if "test_timeout" in self.json_data["test_parameters"]: + self.test_timeout = self.json_data["test_parameters"]["test_timeout"] + self.test_timeout_default = self.test_timeout + else: + self.logger.info("test_timeout not in test_parameters json") + exit(1) + if "load_blank_db" in self.json_data["test_parameters"]: + self.load_blank_db = self.json_data["test_parameters"]["load_blank_db"] + else: + self.logger.info("load_blank_db not in test_parameters json") + exit(1) + if "load_factory_default_db" in self.json_data["test_parameters"]: + self.load_factory_default_db = self.json_data["test_parameters"]["load_factory_default_db"] + else: + self.logger.info("load_factory_default_db not in test_parameters json") + exit(1) + if "load_custom_db" in self.json_data["test_parameters"]: + self.load_custom_db = self.json_data["test_parameters"]["load_custom_db"] + else: + self.logger.info("load_custom_db not in test_parameters json") + exit(1) + if "custom_db" in self.json_data["test_parameters"]: + self.custom_db = self.json_data["test_parameters"]["custom_db"] + else: + self.logger.info("custom_db not in test_parameters json, if not using custom_db just put in a name") + exit(1) + if "email_list_production" in self.json_data["test_parameters"]: + self.email_list_production = self.json_data["test_parameters"]["email_list_production"] + else: + self.logger.info("email_list_production not in test_parameters json") + exit(1) + if "host_ip_production" in self.json_data["test_parameters"]: + self.host_ip_production = self.json_data["test_parameters"]["host_ip_production"] + else: + self.logger.info("host_ip_production not in test_parameters json") + exit(1) + if "email_list_test" in self.json_data["test_parameters"]: + self.email_list_test = self.json_data["test_parameters"]["email_list_test"] + print(self.email_list_test) + else: + self.logger.info("email_list_test not in test_parameters json") + exit(1) + if "host_ip_test" in self.json_data["test_parameters"]: + self.host_ip_test = self.json_data["test_parameters"]["host_ip_test"] + else: + self.logger.info("host_ip_test not in test_parameters json") + exit(1) + if "email_title_txt" in self.json_data["test_parameters"]: + self.email_title_txt = self.json_data["test_parameters"]["email_title_txt"] + else: + self.logger.info("email_title_txt not in test_parameters json") + if "email_txt" in self.json_data["test_parameters"]: + self.email_txt = self.json_data["test_parameters"]["email_txt"] + else: + self.logger.info("email_txt not in test_parameters json") + if "lf_mgr_ip" in self.json_data["test_parameters"]: + self.lf_mgr_ip = self.json_data["test_parameters"]["lf_mgr_ip"] + else: + self.logger.info("lf_mgr_ip not in test_parameters json") + if "lf_mgr_port" in self.json_data["test_parameters"]: + self.lf_mgr_port = self.json_data["test_parameters"]["lf_mgr_port"] + else: + self.logger.info("lf_mgr_port not in test_parameters json") + if "dut_name" in self.json_data["test_parameters"]: + self.dut_name = self.json_data["test_parameters"]["dut_name"] + else: + self.logger.info("dut_name not in test_parameters json") + if "dut_hw" in self.json_data["test_parameters"]: + self.dut_hw = self.json_data["test_parameters"]["dut_hw"] + else: + self.logger.info("dut_hw not in test_parameters json") + if "dut_sw" in self.json_data["test_parameters"]: + self.dut_sw = self.json_data["test_parameters"]["dut_sw"] + else: + self.logger.info("dut_sw not in test_parameters json") + if "dut_model" in self.json_data["test_parameters"]: + self.dut_model = self.json_data["test_parameters"]["dut_model"] + else: + self.logger.info("dut_model not in test_parameters json") + if "dut_serial" in self.json_data["test_parameters"]: + self.dut_serial = self.json_data["test_parameters"]["dut_serial"] + else: + self.logger.info("dut_serial not in test_parameters json") + if "dut_bssid_2g" in self.json_data["test_parameters"]: + self.dut_bssid_2g = self.json_data["test_parameters"]["dut_bssid_2g"] + else: + self.logger.info("dut_bssid_2G not in test_parameters json") + if "dut_bssid_5g" in self.json_data["test_parameters"]: + self.dut_bssid_5g = self.json_data["test_parameters"]["dut_bssid_5g"] + else: + self.logger.info("dut_bssid_5g not in test_parameters json") + if "dut_bssid_6g" in self.json_data["test_parameters"]: + self.dut_bssid_6g = self.json_data["test_parameters"]["dut_bssid_6g"] + else: + self.logger.info("dut_bssid_6g not in test_parameters json") + + def read_test_network(self): + if "http_test_ip" in self.json_data["test_network"]: + self.http_test_ip = self.json_data["test_network"]["http_test_ip"] + else: + self.logger.info("http_test_ip not in test_network json") + exit(1) + if "ftp_test_ip" in self.json_data["test_network"]: + self.ftp_test_ip = self.json_data["test_network"]["ftp_test_ip"] + else: + self.logger.info("ftp_test_ip not in test_network json") + exit(1) + if "test_ip" in self.json_data["test_network"]: + self.ftp_test_ip = self.json_data["test_network"]["test_ip"] + else: + self.logger.info("test_ip not in test_network json") + exit(1) + + def read_test_database(self): + if "database_config" in self.json_data["test_database"]: + self.database_config = self.json_data["test_database"]["database_config"] + else: + self.logger.info("database_config not in test_database json") + if "database_host" in self.json_data["test_database"]: + self.database_host = self.json_data["test_database"]["database_host"] + else: + self.logger.info("database_host not in test_database json") + if "database_port" in self.json_data["test_database"]: + self.database_port = self.json_data["test_database"]["database_port"] + else: + self.logger.info("database_port not in test_database json") + if "database_token" in self.json_data["test_database"]: + self.database_token = self.json_data["test_database"]["database_token"] + else: + self.logger.info("database_token not in test_database json") + if "database_org" in self.json_data["test_database"]: + self.database_org = self.json_data["test_database"]["database_org"] + else: + self.logger.info("database_org not in test_database json") + if "database_bucket" in self.json_data["test_database"]: + self.database_bucket = self.json_data["test_database"]["database_bucket"] + else: + self.logger.info("database_bucket not in test_database json") + if "database_tag" in self.json_data["test_database"]: + self.database_tag = self.json_data["test_database"]["database_tag"] + else: + self.logger.info("database_tag not in test_database json") + if "test_rig" in self.json_data["test_database"]: + self.test_rig = self.json_data["test_database"]["test_rig"] + else: + self.logger.info("test_rig not in test_database json") + if "dut_set_name" in self.json_data["test_database"]: + self.dut_set_name = self.json_data["test_database"]["dut_set_name"] + else: + self.logger.info("dut_set_name not in test_database json") + + def read_test_dashboard(self): + if "dashboard_config" in self.json_data["test_dashboard"]: + self.dashboard_config = self.json_data["test_dashboard"]["dashboard_config"] + else: + self.logger.info("dashboard_config not in test_dashboard json") + + if "dashboard_host" in self.json_data["test_dashboard"]: + self.dashboard_host = self.json_data["test_dashboard"]["dashboard_host"] + else: + self.logger.info("dashboard_host not in test_dashboard json") + + if "dashboard_token" in self.json_data["test_dashboard"]: + self.dashboard_token = self.json_data["test_dashboard"]["dashboard_token"] + else: + self.logger.info("dashboard_token not in test_dashboard json") + + def read_test_blog(self): + if "blog_config" in self.json_data["test_blog"]: + self.blog_config = self.json_data["test_blog"]["blog_config"] + else: + self.logger.info("blog_config not in test_blog json") + + if "blog_host" in self.json_data["test_blog"]: + self.blog_host = self.json_data["test_blog"]["blog_host"] + else: + self.logger.info("blog_host not in test_blog json") + + if "blog_token" in self.json_data["test_blog"]: + self.blog_token = self.json_data["test_blog"]["blog_token"] + else: + self.logger.info("blog_token not in test_blog json") + + if "blog_authors" in self.json_data["test_blog"]: + self.blog_authors = self.json_data["test_blog"]["blog_authors"] + else: + self.logger.info("blog_authors not in test_blog json") + + if "blog_customer" in self.json_data["test_blog"]: + self.blog_customer = self.json_data["test_blog"]["blog_customer"] + else: + self.logger.info("blog_customer not in test_blog json") + + if "blog_user_push" in self.json_data["test_blog"]: + self.blog_user_push = self.json_data["test_blog"]["blog_user_push"] + else: + self.logger.info("blog_user_push not in test_blog json") + + if "blog_password_push" in self.json_data["test_blog"]: + self.blog_password_push = self.json_data["test_blog"]["blog_password_push"] + else: + self.logger.info("blog_password_push not in test_blog json") + + if "blog_flag" in self.json_data["test_blog"]: + self.blog_flag = self.json_data["test_blog"]["blog_flag"] + else: + self.logger.info("blog_flag not in test_blog json") + + def read_test_generic(self): + if "radio_used" in self.json_data["test_generic"]: + self.radio_lf = self.json_data["test_generic"]["radio_used"] + else: + self.logger.info("radio_used not in test_generic json") + exit(1) + if "ssid_used" in self.json_data["test_generic"]: + self.ssid = self.json_data["test_generic"]["ssid_used"] + else: + self.logger.info("ssid_used not in test_generic json") + exit(1) + if "ssid_pw_used" in self.json_data["test_generic"]: + self.ssid_pw = self.json_data["test_generic"]["ssid_pw_used"] + else: + self.logger.info("ssid_pw_used not in test_generic json") + exit(1) + if "security_used" in self.json_data["test_generic"]: + self.security = self.json_data["test_generic"]["security_used"] + else: + self.logger.info("security_used not in test_generic json") + exit(1) + if "num_sta" in self.json_data["test_generic"]: + self.num_sta = self.json_data["test_generic"]["num_sta"] + else: + self.logger.info("num_sta not in test_generic json") + exit(1) + if "col_names" in self.json_data["test_generic"]: + self.num_sta = self.json_data["test_generic"]["col_names"] + else: + self.logger.info("col_names not in test_generic json") + exit(1) + if "upstream_port" in self.json_data["test_generic"]: + self.upstream_port = self.json_data["test_generic"]["upstream_port"] + else: + self.logger.info("upstream_port not in test_generic json") + exit(1) + + def load_factory_default_db(self): + # self.logger.info("file_wd {}".format(self.scripts_wd)) + try: + os.chdir(self.scripts_wd) + # self.logger.info("Current Working Directory {}".format(os.getcwd())) + except: + self.logger.info("failed to change to {}".format(self.scripts_wd)) + + # no spaces after FACTORY_DFLT + command = "./{} {}".format("scenario.py", "--load FACTORY_DFLT") + process = subprocess.Popen((command).split(' '), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + # wait for the process to terminate + out, err = process.communicate() + errcode = process.returncode + + # not currently used + def load_blank_db(self): + try: + os.chdir(self.scripts_wd) + except: + self.logger.info("failed to change to {}".format(self.scripts_wd)) + + # no spaces after FACTORY_DFLT + command = "./{} {}".format("scenario.py", "--load BLANK") + process = subprocess.Popen((command).split(' '), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + + def load_custom_db(self, custom_db): + try: + os.chdir(self.scripts_wd) + except: + self.logger.info("failed to change to {}".format(self.scripts_wd)) + + # no spaces after FACTORY_DFLT + command = "./{} {}".format("scenario.py", "--load {}".format(custom_db)) + process = subprocess.Popen((command).split(' '), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + # wait for the process to terminate + out, err = process.communicate() + errcode = process.returncode + + def run_script_test(self): + self.start_html_results() + self.start_csv_results() + print(self.test_dict) + + for test in self.test_dict: + if self.test_dict[test]['enabled'] == "FALSE": + self.logger.info("test: {} skipped".format(test)) + # load the default database + elif self.test_dict[test]['enabled'] == "TRUE": + # if args key has a value of an empty string then need to manipulate the args_list to args + # list does not have replace only stings do to args_list will be joined and converted to a string and placed + # in args. Then the replace below will work. + if self.test_dict[test]['args'] == "": + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace(self.test_dict[test]['args'], + ''.join(self.test_dict[test][ + 'args_list'])) + # Configure Tests + # loop through radios + for radio in self.radio_dict: + # replace RADIO, SSID, PASSWD, SECURITY with actual config values (e.g. RADIO_0_CFG to values) + # not "KEY" is just a word to refer to the RADIO define (e.g. RADIO_0_CFG) to get the vlaues + # --num_stations needs to be int not string (no double quotes) + if self.radio_dict[radio]["KEY"] in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace( + self.radio_dict[radio]["KEY"], + '--radio {} --ssid {} --passwd {} --security {} --num_stations {}' + .format(self.radio_dict[radio]['RADIO'], self.radio_dict[radio]['SSID'], + self.radio_dict[radio]['PASSWD'], self.radio_dict[radio]['SECURITY'], + self.radio_dict[radio]['STATIONS'])) + + if 'HTTP_TEST_IP' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('HTTP_TEST_IP', + self.http_test_ip) + if 'FTP_TEST_IP' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('FTP_TEST_IP', self.ftp_test_ip) + if 'TEST_IP' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('TEST_IP', self.test_ip) + + if 'LF_MGR_IP' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('LF_MGR_IP', self.lf_mgr_ip) + if 'LF_MGR_PORT' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('LF_MGR_PORT', self.lf_mgr_port) + + if 'DUT_NAME' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_NAME', self.dut_name) + if 'DUT_HW' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_HW', self.dut_hw) + if 'DUT_SW' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_SW', self.dut_sw) + if 'DUT_MODEL' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_MODEL', self.dut_model) + if 'DUT_SERIAL' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_SERIAL', self.dut_serial) + if 'DUT_BSSID_2G' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_BSSID_2G', + self.dut_bssid_2g) + if 'DUT_BSSID_5G' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_BSSID_5G', + self.dut_bssid_5g) + if 'DUT_BSSID_6G' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_BSSID_6G', + self.dut_bssid_6g) + + if 'RADIO_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('RADIO_USED', self.radio_lf) + if 'SSID_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SSID_USED', self.ssid) + if 'SSID_PW_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SSID_PW_USED', self.ssid_pw) + if 'SECURITY_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SECURITY_USED', self.security) + if 'NUM_STA' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('NUM_STA', self.num_sta) + if 'COL_NAMES' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('COL_NAMES', self.col_names) + if 'UPSTREAM_PORT' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('UPSTREAM_PORT', + self.upstream_port) + + # lf_dataplane_test.py and lf_wifi_capacity_test.py use a parameter --local_path for the location + # of the reports when the reports are pulled. + if 'REPORT_PATH' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('REPORT_PATH', self.report_path) + + # The TEST_BED is the database tag + if 'TEST_BED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('TEST_BED', self.database_tag) + + # database configuration + if 'DATABASE_HOST' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DATABASE_HOST', + self.database_host) + if 'DATABASE_PORT' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DATABASE_PORT', + self.database_port) + if 'DATABASE_TOKEN' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DATABASE_TOKEN', + self.database_token) + if 'DATABASE_ORG' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DATABASE_ORG', + self.database_org) + if 'DATABASE_BUCKET' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DATABASE_BUCKET', + self.database_bucket) + if 'DATABASE_TAG' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DATABASE_TAG', + self.database_tag) + if 'DUT_SET_NAME' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_SET_NAME', + self.dut_set_name) + + if 'TEST_RIG' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('TEST_RIG', self.test_rig) + + # dashboard configuration + if 'DASHBOARD_HOST' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DASHBOARD_HOST', + self.dashboard_host) + if 'DASHBOARD_TOKEN' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DASHBOARD_TOKEN', + self.dashboard_token) + + # blog configuration + if 'BLOG_HOST' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('BLOG_HOST', self.blog_host) + if 'BLOG_TOKEN' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('BLOG_TOKEN', self.blog_token) + if 'BLOG_AUTHORS' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('BLOG_AUTHORS', + self.blog_authors) + if 'BLOG_CUSTOMER' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('BLOG_CUSTOMER', + self.blog_customer) + if 'BLOG_USER_PUSH' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('BLOG_USER_PUSH', + self.blog_user_push) + if 'BLOG_PASSWORD_PUSH' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('BLOG_PASSWORD_PUSH', + self.blog_password_push) + if 'BLOG_FLAG' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('BLOG_FLAG', self.blog_flag) + + if 'timeout' in self.test_dict[test]: + self.logger.info("timeout : {}".format(self.test_dict[test]['timeout'])) + self.test_timeout = int(self.test_dict[test]['timeout']) + else: + self.test_timeout = self.test_timeout_default + + if 'load_db' in self.test_dict[test]: + self.logger.info("load_db : {}".format(self.test_dict[test]['load_db'])) + if str(self.test_dict[test]['load_db']).lower() != "none" and str( + self.test_dict[test]['load_db']).lower() != "skip": + try: + self.load_custom_db(self.test_dict[test]['load_db']) + except: + self.logger.info("custom database failed to load check existance and location: {}".format( + self.test_dict[test]['load_db'])) + else: + self.logger.info("no load_db present in dictionary, load db normally") + if self.use_factory_default_db == "TRUE": + self.load_factory_default_db() + sleep(3) + self.logger.info("FACTORY_DFLT loaded between tests with scenario.py --load FACTORY_DFLT") + if self.use_blank_db == "TRUE": + self.load_blank_db() + sleep(1) + self.logger.info("BLANK loaded between tests with scenario.py --load BLANK") + if self.use_custom_db == "TRUE": + try: + self.load_custom_db(self.custom_db) + sleep(1) + self.logger.info("{} loaded between tests with scenario.py --load {}".format(self.custom_db, + self.custom_db)) + except: + self.logger.info("custom database failed to load check existance and location: {}".format( + self.custom_db)) + else: + self.logger.info("no db loaded between tests: {}".format(self.use_custom_db)) + + sleep(1) # DO NOT REMOVE the sleep is to allow for the database to stablize + try: + os.chdir(self.scripts_wd) + # self.logger.info("Current Working Directory {}".format(os.getcwd())) + except: + self.logger.info("failed to change to {}".format(self.scripts_wd)) + cmd_args = "{}".format(self.test_dict[test]['args']) + command = "./{} {}".format(self.test_dict[test]['command'], cmd_args) + self.logger.info("command: {}".format(command)) + self.logger.info("cmd_args {}".format(cmd_args)) + + if self.outfile_name is not None: + stdout_log_txt = os.path.join(self.log_path, "{}-{}-stdout.txt".format(self.outfile_name,test)) + self.logger.info("stdout_log_txt: {}".format(stdout_log_txt)) + stdout_log = open(stdout_log_txt, 'a') + stderr_log_txt = os.path.join(self.log_path, "{}-{}-stderr.txt".format(self.outfile_name,test)) + self.logger.info("stderr_log_txt: {}".format(stderr_log_txt)) + stderr_log = open(stderr_log_txt, 'a') + + # need to take into account --raw_line parameters thus need to use shlex.split + # need to preserve command to have correct command syntax in command output + command_to_run = command + command_to_run = shlex.split(command_to_run) + print("running {command_to_run}".format(command_to_run=command_to_run)) + try: + process = subprocess.Popen(command_to_run, shell=False, stdout=stdout_log, stderr=stderr_log, + universal_newlines=True) + # if there is a better solution please propose, the TIMEOUT Result is different then FAIL + try: + process.wait(timeout=int(self.test_timeout)) + except subprocess.TimeoutExpired: + process.terminate() + self.test_result = "TIMEOUT" + + except: + print("No such file or directory with command: {}".format(command)) + self.logger.info("No such file or directory with command: {}".format(command)) + + if self.test_result != "TIMEOUT": + stderr_log_size = os.path.getsize(stderr_log_txt) + if stderr_log_size > 0: + self.logger.info("File: {} is not empty: {}".format(stderr_log_txt, str(stderr_log_size))) + text = open(stderr_log_txt).read() + if 'Error' in text: + self.text_result = "Failure" + background = self.background_red + else: + self.test_result = "Success" + background = self.background_green + else: + self.logger.info("File: {} is empty: {}".format(stderr_log_txt, str(stderr_log_size))) + self.test_result = "Success" + background = self.background_green + else: + self.logger.info("TIMEOUT FAILURE, Check LANforge Radios") + self.test_result = "Time Out" + background = self.background_purple + + # Ghost will put data in stderr + if 'ghost' in command or 'lf_qa' in command: + if self.test_result != "TIMEOUT": + text = open(stderr_log_txt).read() + if 'Error' in text: + self.test_result = "Failure" + background = self.background_red + else: + self.test_result = "Success" + background = self.background_blue + if 'lf_qa' in command: + line_list = open(stdout_log_txt).readlines() + for line in line_list: + if 'html report:' in line: + self.qa_report_html = line + print("html_report: {report}".format(report=self.qa_report_html)) + break + + self.qa_report_html = self.qa_report_html.replace('html report: ','') + + + # stdout_log_link is used for the email reporting to have the corrected path + stdout_log_link = str(stdout_log_txt).replace('/home/lanforge', '') + stderr_log_link = str(stderr_log_txt).replace('/home/lanforge', '') + self.html_results += """ + """ + str(test) + """""" + str(command) + """ + """ + str(self.test_result) + """ +
    STDOUT""" + if self.test_result == "Failure": + self.html_results += """STDERR""" + elif self.test_result == "Time Out": + self.html_results += """STDERR""" + else: + self.html_results += """""" + self.html_results += """""" + + row = [test, command, self.test_result, stdout_log_txt, stderr_log_txt] + self.csv_results_writer.writerow(row) + self.csv_results_file.flush() + # self.logger.info("row: {}".format(row)) + self.logger.info("test: {} executed".format(test)) + + else: + self.logger.info( + "enable value {} invalid for test: {}, test skipped".format(self.test_dict[test]['enabled'], test)) + self.finish_html_results() + + +def main(): + # arguments + parser = argparse.ArgumentParser( + prog='lf_check.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + lf_check.py : running scripts listed in .ini or .json + ''', + description='''\ +lf_check.py +----------- + +Summary : +--------- +running scripts listed in .ini or .json + +Example : +./lf_check.py --json lf_check_test.json --suite suite_two +--------- + ''') + + parser.add_argument('--ini', help="--ini default lf_check_config.ini", + default="lf_check_config.ini") + parser.add_argument('--dir', help="--dir ", default="lf_check") + parser.add_argument('--path', help="--path ", default="/home/lanforge/html-results") + parser.add_argument('--json', help="--json ", default="lf_check_config.json") + parser.add_argument('--use_json', help="--use_json FLAG DEPRECATED", action='store_true') + parser.add_argument('--suite', help="--suite default TEST_DICTIONARY", default="TEST_DICTIONARY") + parser.add_argument('--production', help="--production stores true, sends email results to production email list", + action='store_true') + parser.add_argument('--outfile', help="--outfile used as base name for all files generated", + default="") + parser.add_argument('--logfile', help="--logfile logging for output of lf_check.py script", + default="lf_check.log") + + args = parser.parse_args() + + if args.use_json: + print("NOTE: --use_json flag deprecated and unused") + # load test config file information either .json or .ini + json_data = "" + try: + print("args.json {}".format(args.json)) + with open(args.json, 'r') as json_config: + json_data = json.load(json_config) + except: + print("Error reading {}".format(args.json)) + + # Test-rig information information + lanforge_node_version = 'NO_LF_NODE_VER' + scripts_git_sha = 'NO_GIT_SHA' + lanforge_kernel_version = 'NO_KERNEL_VER' + lanforge_gui_version = 'NO_LF_GUI_VER' + + # select test suite + test_suite = args.suite + __dir = args.dir + __path = args.path + + if args.production: + production = True + print("Email to production list") + else: + production = False + print("Email to email list") + + # create report class for reporting + report = lf_report(_path = __path, + _results_dir_name=__dir, + _output_html="lf_check.html", + _output_pdf="lf-check.pdf") + + current_time = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) + csv_results = "lf_check{}-{}.csv".format(args.outfile, current_time) + csv_results = report.file_add_path(csv_results) + outfile_name = "lf_check-{}-{}".format(args.outfile, current_time) + outfile = report.file_add_path(outfile_name) + report_path = report.get_report_path() + log_path = report.get_log_path() + + # lf_check() class created + check = lf_check(_json_data=json_data, + _test_suite=test_suite, + _production=production, + _csv_results=csv_results, + _outfile=outfile, + _outfile_name = outfile_name, + _report_path=report_path, + _log_path=log_path) + + # set up logging + logfile = args.logfile[:-4] + print("logfile: {}".format(logfile)) + logfile = "{}-{}.log".format(logfile, current_time) + logfile = report.file_add_path(logfile) + print("logfile {}".format(logfile)) + formatter = logging.Formatter(FORMAT) + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + file_handler = logging.FileHandler(logfile, "w") + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + logger.addHandler(logging.StreamHandler(sys.stdout)) # allows to logging to file and stdout + + # read config and run tests + check.read_config() + ping_result = check.check_if_port_exists() + for key, value in ping_result.items(): + if value[1] is None: + print(UserWarning('Check your %s IP address, %s is unreachable' % (key, value[0]))) + else: + print('%s IP address %s accessible' % (key, value[1])) + + if ping_result['LANforge Manager'][1] is None: + pass + else: + check.run_script_test() + + # get sha and lanforge information for results + # Need to do this after reading the configuration + try: + scripts_git_sha = check.get_scripts_git_sha() + print("git_sha {sha}".format(sha=scripts_git_sha)) + except: + print("git_sha read exception ") + + try: + lanforge_node_version = check.get_lanforge_node_version() + print("lanforge_node_version {node_ver}".format(node_ver=lanforge_node_version)) + except: + print("lanforge_node_version exception") + + try: + lanforge_kernel_version = check.get_lanforge_kernel_version() + print("lanforge_kernel_version {kernel_ver}".format(kernel_ver=lanforge_kernel_version)) + except: + print("lanforge_kernel_version exception") + + try: + lanforge_gui_version = check.get_lanforge_gui_version() + print("lanforge_gui_version {gui_ver}".format(gui_ver=lanforge_gui_version)) + except: + print("lanforge_gui_version exception") + + #check.get_radio_status() + + # LANforge and scripts config + lf_test_setup = pd.DataFrame() + lf_test_setup['LANforge'] = lanforge_node_version + lf_test_setup['kernel version'] = lanforge_kernel_version + lf_test_setup['GUI version'] = lanforge_gui_version + lf_test_setup['scripts git sha'] = scripts_git_sha + + # generate output reports + report.set_title("LF Check: lf_check.py") + report.build_banner_left() + report.start_content_div2() + report.set_obj_html("Objective", "Run QA Tests") + report.build_objective() + report.set_table_title("LANForge") + report.build_table_title() + report.set_table_dataframe(lf_test_setup) + report.build_table() + report.set_table_title("LF Check Test Results") + report.build_table_title() + html_results = check.get_html_results() + report.set_custom_html(html_results) + report.build_custom() + report.build_footer() + html_report = report.write_html_with_timestamp() + print("html report: {}".format(html_report)) + try: + report.write_pdf_with_timestamp() + except: + print("exception write_pdf_with_timestamp()") + + print("lf_check_html_report: " + html_report) + check.send_results_email(report_file=html_report) + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_csv.py b/lanforge/lanforge-scripts/py-scripts/lf_csv.py new file mode 100644 index 000000000..01a90ad32 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_csv.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +''' +NAME: lf_csv.py + +PURPOSE: +Common Library for generating csv for LANforge output +KPI - Key Performance Indicators + +SETUP: +/lanforge/html-reports directory needs to be present or output generated in local file + +EXAMPLE: +see: /py-scripts/lf_report_test.py for example + +COPYWRITE + Copyright 2021 Candela Technologies Inc + License: Free to distribute and modify. LANforge systems must be licensed. + +INCLUDE_IN_README +''' + +import pandas as pd + +class lf_csv: + def __init__(self, + _columns=['Stations', 'bk', 'be', 'vi', 'vo'], + _rows=[['sta0001', 'sta0002', 'sta0003', 'sta0004', 'sta0005'], + [1, 2, 3, 4, 5], + [11, 22, 33, 44, 55], + [6, 7, 8, 9, 10], + [66, 77, 88, 99, 100]], + _filename='test.csv'): + self.rows = _rows + self.columns = _columns + self.filename = _filename + + def generate_csv(self): + df = {} + for i in range(len(self.columns)): + df[self.columns[i]] = self.rows[i] + csv_df = pd.DataFrame(df) + print(csv_df) + csv_df.to_csv(self.filename, index=False, encoding='utf-8', na_rep='NA', float_format='%.2f') + +# this layout may need to change +''' +kpi.csv : specific file that is used for the database, dashboard and blog post +A blank entry is a valid entry in some cases. + + Date: date of run + test-rig : testbed that the tests are run on for example ct_us_001 + test-tag : test specific information to differenciate the test, LANforge radios used, security modes (wpa2 , open) + dut-hw-version : hardware version of the device under test + dut-sw-version : software version of the device under test + dut-model-num : model number / name of the device under test + test-priority : test-priority is arbitrary number, choosing under 95 means it goes down at bottom of blog report, and higher priority goes at top. + test-id : script or test name , AP Auto, wifi capacity, data plane, dfs + short-description : short description of the test + pass/fail : set blank for performance tests + numeric-score : this is the value for the y-axis (x-axis is a timestamp), numeric value of what was measured + test-details : what was measured in the numeric-score, e.g. bits per second, bytes per second, upload speed, minimum cx time (ms) + Units : units used for the numeric-scort + Graph-Group - For the dashboard the graph / panel to put the resutls in . Currently the dashboard is Grafana + +''' +class lf_kpi_csv: + def __init__(self, + _kpi_headers = ['Date','test-rig','test-tag','dut-hw-version','dut-sw-version','dut-model-num', + 'test-priority','test-id','short-description','pass/fail','numberic-score' + 'test details','Units','Graph-Group','Subtest-Pass','Subtest-Fail'], + _kpi_file='kpi.csv' #Currently this is the only file name accepted + ): + self.kpi_headers = _kpi_headers + self.kpi_rows = "" + self.kpi_filename = _kpi_file + + +if __name__ == "__main__": + test = lf_csv() + test.generate_csv() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_dataplane_test.py b/lanforge/lanforge-scripts/py-scripts/lf_dataplane_test.py new file mode 100755 index 000000000..88431acc9 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_dataplane_test.py @@ -0,0 +1,410 @@ +#!/usr/bin/env python3 +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +This script is used to automate running Dataplane tests. You +may need to view a Dataplane test configured through the GUI to understand +the options and how best to input data. + + ./lf_dataplane_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 \ + --dut linksys-8450 --duration 15s --station 1.1.sta01500 \ + --download_speed 85% --upload_speed 0 \ + --raw_line 'pkts: Custom;60;142;256;512;1024;MTU' \ + --raw_line 'cust_pkt_sz: 88 1200' \ + --raw_line 'directions: DUT Transmit;DUT Receive' \ + --raw_line 'traffic_types: UDP;TCP' \ + --test_rig Testbed-01 --pull_report \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01 + +Note: + --raw_line 'line contents' will add any setting to the test config. This is + useful way to support any options not specifically enabled by the + command options. + --set modifications will be applied after the other config has happened, + so it can be used to override any other config. + +Example of raw text config for Dataplane, to show other possible options: + +show_events: 1 +show_log: 0 +port_sorting: 0 +kpi_id: Dataplane Pkt-Size +notes0: ec5211 in bridge mode, wpa2 auth. +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 0 +skip_2: 0 +skip_5: 0 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +selected_dut: ea8300 +duration: 15000 +traffic_port: 1.1.157 sta01500 +upstream_port: 1.1.2 eth2 +path_loss: 10 +speed: 85% +speed2: 0Kbps +min_rssi_bound: -150 +max_rssi_bound: 0 +channels: AUTO +modes: Auto +pkts: Custom;60;142;256;512;1024;MTU +spatial_streams: AUTO +security_options: AUTO +bandw_options: AUTO +traffic_types: UDP;TCP +directions: DUT Transmit;DUT Receive +txo_preamble: OFDM +txo_mcs: 0 CCK, OFDM, HT, VHT +txo_retries: No Retry +txo_sgi: OFF +txo_txpower: 15 +attenuator: 0 +attenuator2: 0 +attenuator_mod: 255 +attenuator_mod2: 255 +attenuations: 0..+50..950 +attenuations2: 0..+50..950 +chamber: 0 +tt_deg: 0..+45..359 +cust_pkt_sz: 88 1200 +show_bar_labels: 1 +show_prcnt_tput: 0 +show_3s: 0 +show_ll_graphs: 0 +show_gp_graphs: 1 +show_1m: 1 +pause_iter: 0 +outer_loop_atten: 0 +show_realtime: 1 +operator: +mconn: 1 +mpkt: 1000 +tos: 0 +loop_iterations: 1 + +""" +import sys +import os +import importlib +import argparse +import time +import json + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cv_test = cv_test_manager.cv_test +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser + + +class DataplaneTest(cv_test): + def __init__(self, + lf_host="localhost", + lf_port=8080, + lf_user="lanforge", + lf_password="lanforge", + ssh_port=22, + local_lf_report_dir="", + instance_name="dpt_instance", + config_name="dpt_config", + upstream="1.1.eth2", + pull_report=False, + load_old_cfg=False, + upload_speed="0", + download_speed="85%", + duration="15s", + station="1.1.sta01500", + dut="NA", + enables=[], + disables=[], + raw_lines=[], + raw_lines_file="", + sets=[], + graph_groups=None, + report_dir="", + test_rig="" + ): + super().__init__(lfclient_host=lf_host, lfclient_port=lf_port) + + self.lf_host = lf_host + self.lf_port = lf_port + self.lf_user = lf_user + self.lf_password = lf_password + self.instance_name = instance_name + self.config_name = config_name + self.dut = dut + self.duration = duration + self.upstream = upstream + self.station = station + self.pull_report = pull_report + self.load_old_cfg = load_old_cfg + self.test_name = "Dataplane" + self.upload_speed = upload_speed + self.download_speed = download_speed + self.enables = enables + self.disables = disables + self.raw_lines = raw_lines + self.raw_lines_file = raw_lines_file + self.sets = sets + self.graph_groups = graph_groups + self.report_dir = report_dir + self.ssh_port = ssh_port + self.local_lf_report_dir = local_lf_report_dir + self.test_rig = test_rig + + def setup(self): + # Nothing to do at this time. + return + + def run(self): + self.sync_cv() + time.sleep(2) + self.sync_cv() + + blob_test = "dataplane-test-latest-" + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + self.show_text_blob(None, None, False) + + # Test related settings + cfg_options = [] + + ### HERE### + self.apply_cfg_options(cfg_options, self.enables, self.disables, self.raw_lines, self.raw_lines_file) + + # cmd line args take precedence and so come last in the cfg array. + if self.upstream != "": + cfg_options.append("upstream_port: " + self.upstream) + if self.station != "": + cfg_options.append("traffic_port: " + self.station) + if self.download_speed != "": + cfg_options.append("speed: " + self.download_speed) + if self.upload_speed != "": + cfg_options.append("speed2: " + self.upload_speed) + if self.duration != "": + cfg_options.append("duration: " + self.duration) + if self.dut != "": + cfg_options.append("selected_dut: " + self.dut) + if self.test_rig != "": + cfg_options.append("test_rig: " + self.test_rig) + + # We deleted the scenario earlier, now re-build new one line at a time. + + self.build_cfg(self.config_name, blob_test, cfg_options) + + cv_cmds = [] + self.create_and_run_test(self.load_old_cfg, self.test_name, self.instance_name, + self.config_name, self.sets, + self.pull_report, self.lf_host, self.lf_user, self.lf_password, + cv_cmds, ssh_port=self.ssh_port, local_lf_report_dir=self.local_lf_report_dir, + graph_groups_file=self.graph_groups) + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + + +def main(): + parser = argparse.ArgumentParser( + prog='lf_dataplane_test', + formatter_class=argparse.RawTextHelpFormatter, + description=""" + + IMPORTANT: Start lanforge with socket 3990 : ./lfclient.bash -cli-socket 3990 + lfclient.bash is located in the LANforgeGUI_X.X.X directory + + On local or remote system: ./lfclient.bash -cli-socket 3990 -s LF_MGR + On local system the -s LF_MGR will be local_host if not provided + + Open this file in an editor and read the top notes for more details. + Example: + ./lf_dataplane_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth2 \ + --dut linksys-8450 --duration 15s --station 1.1.sta01500 \ + --download_speed 85% --upload_speed 0 \ + --raw_line 'pkts: Custom;60;142;256;512;1024;MTU' \ + --raw_line 'cust_pkt_sz: 88 1200' \ + --raw_line 'directions: DUT Transmit;DUT Receive' \ + --raw_line 'traffic_types: UDP;TCP' \ + --test_rig Testbed-01 --pull_report \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01 + + + Example 2: + ./lf_dataplane_test.py --json .json + + see sample json file: lf_dataplane_config.json + + Sample .json between using eth1 and eth2 + { + "mgr":"192.168.0.101", + "port":"8080", + "lf_user":"lanforge", + "lf_password":"lanforge", + "instance_name":"dataplane-instance", + "config_name":"test_con", + "upstream":"1.1.eth1", + "dut":"asus_5g", + "duration":"15s", + "station":"1.1.eth2", + "download_speed":"85%", + "upload_speed":"0", + "raw_line": ["pkts: Custom;60;MTU", "cust_pkt_sz: 88 1200", "directions: DUT Transmit", "traffic_types: UDP", "bandw_options: 20", "spatial_streams: 1"] + } + + Sample .json between using eth1 and station 1.1.sta0002 + { + "mgr":"192.168.0.101", + "port":"8080", + "lf_user":"lanforge", + "lf_password":"lanforge", + "instance_name":"dataplane-instance", + "config_name":"test_con", + "upstream":"1.1.eth1", + "dut":"asus_5g", + "duration":"15s", + "station":"1.1.sta0002", + "download_speed":"85%", + "upload_speed":"0", + "raw_line": ["pkts: Custom;60;MTU", "cust_pkt_sz: 88 1200", "directions: DUT Transmit", "traffic_types: UDP", "bandw_options: 20", "spatial_streams: 1"] + } + + """ + ) + + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument('--json', help="--json json input file", default="") + parser.add_argument('--influx_json', help="--influx_json influx config json input file", + default="") + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for wifi capacity test ex. 1.1.eth2") + parser.add_argument("--station", type=str, default="", + help="Station to be used in this test, example: 1.1.sta01500") + + parser.add_argument("--dut", default="", + help="Specify DUT used by this test, example: linksys-8450") + parser.add_argument("--download_speed", default="", + help="Specify requested download speed. Percentage of theoretical is also supported. Default: 85%%.") + parser.add_argument("--upload_speed", default="", + help="Specify requested upload speed. Percentage of theoretical is also supported. Default: 0") + parser.add_argument("--duration", default="", + help="Specify duration of each traffic run") + parser.add_argument("--graph_groups", help="File to save graph_groups to", default=None) + parser.add_argument("--report_dir", default="") + parser.add_argument("--local_lf_report_dir", + help="--local_lf_report_dir default '' put where dataplane script run from", + default="") + + args = parser.parse_args() + + # use json config file + if args.json != "": + try: + with open(args.json, 'r') as json_config: + json_data = json.load(json_config) + except: + print("Error reading {}".format(args.json)) + # json configuation takes presidence to command line + if "mgr" in json_data: + args.mgr = json_data["mgr"] + if "port" in json_data: + args.port = json_data["port"] + if "lf_user" in json_data: + args.lf_user = json_data["lf_user"] + if "lf_password" in json_data: + args.lf_password = json_data["lf_password"] + if "instance_name" in json_data: + args.instance_name = json_data["instance_name"] + if "config_name" in json_data: + args.config_name = json_data["config_name"] + if "upstream" in json_data: + args.upstream = json_data["upstream"] + if "dut" in json_data: + args.dut = json_data["dut"] + if "duration" in json_data: + args.duration = json_data["duration"] + if "station" in json_data: + args.station = json_data["station"] + if "download_speed" in json_data: + args.download_speed = json_data["download_speed"] + if "upload_speed" in json_data: + args.upload_speed = json_data["upload_speed"] + if "pull_report" in json_data: + args.pull_report = json_data["pull_report"] + if "raw_line" in json_data: + # the json_data is a list , need to make into a list of lists, to match command line raw_line paramaters + # https://www.tutorialspoint.com/convert-list-into-list-of-lists-in-python + json_data_tmp = [[x] for x in json_data["raw_line"]] + args.raw_line = json_data_tmp + + # use influx json config file + if args.influx_json != "": + try: + with open(args.influx_json, 'r') as influx_json_config: + influx_json_data = json.load(influx_json_config) + except: + print("Error reading {}".format(args.influx_json)) + # json configuation takes presidence to command line + # influx DB configuration + if "influx_host" in influx_json_data: + args.influx_host = influx_json_data["influx_host"] + if "influx_port" in influx_json_data: + args.influx_port = influx_json_data["influx_port"] + if "influx_org" in influx_json_data: + args.influx_org = influx_json_data["influx_org"] + if "influx_token" in influx_json_data: + args.influx_token = influx_json_data["influx_token"] + if "influx_bucket" in influx_json_data: + args.influx_bucket = influx_json_data["influx_bucket"] + + cv_base_adjust_parser(args) + + CV_Test = DataplaneTest(lf_host=args.mgr, + lf_port=args.port, + lf_user=args.lf_user, + lf_password=args.lf_password, + instance_name=args.instance_name, + config_name=args.config_name, + upstream=args.upstream, + pull_report=args.pull_report, + local_lf_report_dir=args.local_lf_report_dir, + load_old_cfg=args.load_old_cfg, + download_speed=args.download_speed, + upload_speed=args.upload_speed, + duration=args.duration, + dut=args.dut, + station=args.station, + enables=args.enable, + disables=args.disable, + raw_lines=args.raw_line, + raw_lines_file=args.raw_lines_file, + sets=args.set, + graph_groups=args.graph_groups, + test_rig=args.test_rig + ) + CV_Test.setup() + CV_Test.run() + + CV_Test.check_influx_kpi(args) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_dfs_test.py b/lanforge/lanforge-scripts/py-scripts/lf_dfs_test.py new file mode 100755 index 000000000..5ed0ff596 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_dfs_test.py @@ -0,0 +1,2845 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import pexpect +import time +import datetime +import subprocess +import re +import csv +import random +import logging + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +import argparse +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + +# Check for the logs channel switching time and radar detected + + +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' + +# see https://stackoverflow.com/a/13306095/11014343 +class FileAdapter(object): + def __init__(self, logger): + self.logger = logger + def write(self, data): + # NOTE: data can be a partial line, multiple lines + data = data.strip() # ignore leading/trailing whitespace + if data: # non-blank + self.logger.info(data) + def flush(self): + pass # leave it to logging to flush properly + + +################################################################################ +# controller class :This class will be left in this file to allow for the +# Scaling and Performance to be self contained and not impact other tests +################################################################################ + +class CreateCtlr(): + def __init__(self, + _scheme, + _port, + _series, + _ctlr, + _prompt, + _user, + _passwd, + _ap, + _band, + _chan_5ghz, + _chan_24ghz, + _chan_width, + _ap_mode, + _tx_power, + _client_density, + _cap_ctl_out): + + self.scheme = _scheme + self.port = _port + self.series = _series + self.ctlr = _ctlr + self.prompt = _prompt + self.user = _user + self.passwd = _passwd + self.ap = _ap + self.band = _band + self.chan_5ghz = _chan_5ghz + self.chan_24ghz = _chan_24ghz + self.chan_width = _chan_width + self.ap_mode = _ap_mode + self.tx_power = _tx_power + self.cap_ctl_out = _cap_ctl_out + self.client_density = 0 + + def verify_controller(self,client_density): + self.client_density = client_density + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"summary")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series,"--action", "summary"], capture_output=True) + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}" + .format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + # Find our station count + searchap = False + for line in pss.splitlines(): + if (line.startswith("---------")): + searchap = True + continue + #TODO need to test with 9800 series to chelck the values + if (searchap): + pat = "%s\s+\S+\s+\S+\s+\S+\s+\S+.* \S+\s+\S+\s+(\S+)\s+\["%(self.ap) + #logg.info("AP line: %s"%(line)) + m = re.search(pat, line) + if (m != None): + sta_count = m.group(1) + logg.info("AP line: %s"%(line)) + logg.info("sta-count: %s"%(sta_count)) + if (int(sta_count) != int(self.client_density)): + logg.info("WARNING: Controller reported %s stations, should be %s"%(sta_count, self.client_density)) + + #show summary (to get AP) (3400/9800) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 --action summary --series 9800 --log stdout + def controller_show_summary(self): + pss = "" + try: + logg.info("\ + scheme: {} \ + ctlr: {} \ + port: {} \ + prompt: {} \ + user: {} \ + passwd: {} \ + AP: {} \ + series: {} \ + band: {} \ + action: {}".format( + self.scheme, + self.ctlr, + self.port, + self.prompt, + self.user, + self.passwd, + self.ap, + self.series, + self.band, + "summary")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", + "--scheme", self.scheme, + "--prompt", self.prompt, + "--port", self.port, + "-d", self.ctlr, + "-u", self.user, + "-p", self.passwd, + "-a", self.ap, + "--series", self.series, + "--band", self.band, + "--action", "summary"], + capture_output=self.cap_ctl_out, + check=True) + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}" + .format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + return pss + + #show ap dot11 5ghz summary (band defaults to 5ghz) --band a + #show ap dot11 24ghz summary use --band b for 2.4 ghz + #action advanced (3400/9800) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 --action advanced --series 9800 --log stdout + def controller_show_ap_summary(self): + pss = "" + try: + logg.info("\ + scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"advanced")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "advanced"], + capture_output=True, check=True) + + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + return pss + + #show wlan summary + def controller_show_wlan_summary(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"show wlan summary")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "show_wlan_summary"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #disable AP + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action disable --series 9800 + def controller_disable_ap(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"disable")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", + self.ctlr, "-u",self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "disable"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + #disable wlan + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action disable_wlan --series 9800 + def controller_disable_wlan(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} wlan: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,self.wlan,"disable_wlan")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band,"--wlan", self.wlan, "--action", "disable_wlan"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + #disable network 5ghz + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action disable_network_5ghz --series 9800 + def controller_disable_network_5ghz(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"disable_network_5ghz")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "disable_network_5ghz"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"cmd","config 802.11a disable network")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "cmd", "--value", "config 802.11a disable network"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + #disable network 24ghz + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action disable_network_24ghz --series 9800 + def controller_disable_network_24ghz(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"disable_network_24ghz")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "disable_network_24ghz"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"cmd","config 802.11b disable network")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "cmd", "--value", "config 802.11b disable network"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + + #set manual mode - Series 9800 must be set to manual mode + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action manual --series 9800 + # ap name dot11 5ghz radio role manual client-serving + def controller_role_manual(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"manual")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "manual"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + logg.info("Check the controller scheme used attemping 9800 series on 3504 controller: {}".format(self.scheme)) + + #set manual mode - Series 9800 must be set to auto mode + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action auto --series 9800 + # ap name dot11 5ghz radio role manual client-serving + def controller_role_auto(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"auto")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "auto"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + logg.info("Check the controller scheme used attemping 9800 series on 3504 controller: {}".format(self.scheme)) + + #test parameters summary (txPower 1-8) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action txPower --value 5 --series 9800 + def controller_set_tx_power(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"txPower", self.tx_power )) # TODO fix txPower to tx_power in wifi_ctl_9800_3504.py + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "txPower","--value", self.tx_power], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #set channel [36, 64, 100] + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action channel --value 36 --series 9800 + # 9800 : ap name dot11 [5ghz | 24ghz] channel + # 3504 : (controller Controller) >config 802.11a channel ap APA453.0E7B.CF9C 52 + def controller_set_channel(self): + try: + if (self.band == "a"): + controller_channel = self.chan_5ghz + else: + controller_channel = self.chan_24ghz + + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"channel", controller_channel )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "channel","--value", controller_channel], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + def controller_set_bandwidth_20(self): + controller_chan_width_20 = "20" + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"channel", controller_chan_width_20 )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "channel","--value", controller_chan_width_20], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + #set bandwidth [20 40 80 160] + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action bandwidth --value 40 --series 9800 + def controller_set_bandwidth(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"channel", self.chan_width )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "channel","--value", self.chan_width], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + #create wlan + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action create_wlan --wlan "open-wlan" --wlanID 1 --series 9800 + def controller_create_wlan(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} wlan {} wlanID {} wlanSSID {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"create_wlan", self.wlan, self.wlanID, self.wlanSSID )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "create_wlan","--wlan", self.wlan, "--wlanID", self.wlanID, "--wlanSSID", self.wlanSSID], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + logg.info("Check the controller_scheme used attemping 9800 series on 3504 controller: {}".format(self.scheme)) + + #create wireless tag policy --9800 series needs to have wireless tag policy set + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action wireless_tag_policy --series 9800 + def controller_set_wireless_tag_policy(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"wireless_tag_policy" )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "wireless_tag_policy"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + logg.info("Check the controller_scheme used attemping 9800 series on 3504 controller: {}".format(self.scheme)) + + + #enable wlan + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action enable_wlan --series 9800 + def controller_enable_wlan(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} wlan: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band, self.wlan,"enable_wlan")) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--wlan", self.wlan, + "--action", "enable_wlan"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + #enable 5ghz + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action enable_network_5ghz --series 9800 + def controller_enable_network_5ghz(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"enable_network_5ghz")) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "enable_network_5ghz"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"cmd","config 802.11a enable network")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "cmd", "--value", "config 802.11a enable network"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + + #enable 24ghz + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action enable_network_24ghz --series 9800 + def controller_enable_network_24ghz(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"enable_network_24ghz")) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "enable_network_24ghz"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"cmd","config 802.11b enable network")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "cmd", "--value", "config 802.11b enable network"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + + #enable (band a) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action enable --series 9800 + def controller_enable_ap(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"enable")) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "enable"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + + #advanced (showes summary) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action advanced --series 9800 + def controller_show_ap_channel(self): + advanced = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--action", "ap_channel"], capture_output=True) + + pss = advanced.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + if self.series == "9800": + if (self.band == "a"): + controller_channel = self.chan_5ghz + else: + controller_channel = self.chan_24ghz + + for line in pss.splitlines(): + search_str = self.ap + logg.info("line {}".format(line)) + element_list = line.lstrip().split() + logg.info("element_list {}".format(element_list)) + if (line.lstrip().startswith(search_str)): + logg.info("line {}".format(line)) + element_list = line.lstrip().split() + logg.info("element_list {}".format(element_list)) + # AP Name (0) mac (1) slot (2) Admin State [enable/disable] (3) Oper State [Up/Down] (4) Width (5) Txpwr (6,7) channel (8) mode (9) + logg.info("ap: {} slof {} channel {} chan_width {}".format(element_list[0],element_list[2],element_list[8],element_list[5])) + if (str(controller_channel) in str(element_list[8])) and (str(self.chan_width) in str(element_list[5])): + logg.info("ap {} configuration successful: channel {} in expected {} chan_width {} in expected {}" + .format(element_list[0],controller_channel,element_list[8],self.chan_width,element_list[5])) + else: + logg.info("WARNING ap {} configuration: channel {} in expected {} chan_width {} in expected {}" + .format(element_list[0],controller_channel,element_list[8],self.chan_width,element_list[5])) + break + else: + logg.info("checking for 802.11{}".format(self.band)) + if (self.band == "a"): + controller_channel = self.chan_5ghz + else: + controller_channel = self.chan_24ghz + + for line in pss.splitlines(): + #logg.info("line {}".format(line)) + search_str = "802.11{}".format(self.band) + if (line.lstrip().startswith(search_str)): + logg.info("line {}".format(line)) + element_list = line.lstrip().split() + logg.info("element_list {}".format(element_list)) + logg.info("ap: {} channel {} chan_width {}".format(self.ap,element_list[4],element_list[5])) + if (str(controller_channel) in str(element_list[4])) and (str(self.chan_width) in str(element_list[5])): + logg.info("ap configuration successful: channel {} in expected {} chan_width {} in expected {}" + .format(controller_channel,element_list[4],self.chan_width,element_list[5])) + else: + logg.info("AP WARNING: channel {} expected {} chan_width {} expected {}" + .format(element_list[4],controller_channel,element_list[5],self.chan_width)) + break + + logg.info("configure ap {} channel {} chan_width {}".format(self.ap,self.channel,self.chan_width)) + # Verify channel and channel width. +########################################## +# End of controller controller class +########################################## + +########################################## +# Traffic Generation Begin +########################################## + +class L3VariableTime(Realm): + def __init__(self, + args, + _dfs, + _dfs_time, + _radar_duration, + _scheme, + _port, + _series, + _ctlr, + _prompt, + _user, + _passwd, + _ap, + _ap_slot, + _band, + _chan_5ghz, + _chan_24ghz, + _chan_width, + _ap_mode, + _tx_power, + _client_density, + _cap_ctl_out, + _ap_dict, + endp_type, + tos, + side_b, + radio_name_list, + number_of_stations_per_radio_list, + ssid_list, + ssid_password_list, + ssid_security_list, + wifimode_list, + station_lists, + name_prefix, + debug_on, + outfile, + results, + test_keys, + test_config, + reset_port_enable_list, + reset_port_time_min_list, + reset_port_time_max_list, + csv_started=False, + side_a_min_bps=560000, + side_a_max_bps=0, + side_a_min_pdu=1518, + side_a_max_pdu=0, + side_b_min_bps=560000, + side_b_max_bps=0, + side_b_min_pdu=1518, + side_b_max_pdu=0, + number_template="00", + test_duration="256s", + polling_interval="60s", + lfclient_host="localhost", + lfclient_port=8080, + debug=False, + wait_timeout=120, + _exit_on_error=False, + _exit_on_fail=False, + _proxy_str=None, + _capture_signal_list=[]): + super().__init__(lfclient_host=lfclient_host, + lfclient_port=lfclient_port, + debug_=debug, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail, + _proxy_str=_proxy_str, + _capture_signal_list=_capture_signal_list) + self.dfs = _dfs + self.dfs_time = _dfs_time + self.radar_duration = _radar_duration + self.radar_duration_seconds = self.duration_time_to_seconds(_radar_duration) + self.dfs_time_seconds = self.duration_time_to_seconds(_dfs_time) + self.scheme = _scheme + self.port = _port + self.series = _series + self.ctlr = _ctlr + self.prompt = _prompt + self.user = _user + self.passwd = _passwd + self.ap = _ap + self.ap_slot = _ap_slot + self.band = _band + self.chan_5ghz = _chan_5ghz + self.chan_24ghz = _chan_24ghz + self.chan_width = _chan_width + self.ap_mode = _ap_mode + self.tx_power = _tx_power + self.cap_ctl_out = _cap_ctl_out + self.ap_dict = _ap_dict + self.client_density = _client_density + self.tos = tos.split() + self.endp_type = endp_type + self.side_b = side_b + self.ssid_list = ssid_list + self.ssid_password_list = ssid_password_list + self.station_lists = station_lists + self.ssid_security_list = ssid_security_list + self.wifimode_list = wifimode_list + self.reset_port_enable_list = reset_port_enable_list + self.reset_port_time_min_list = reset_port_time_min_list + self.reset_port_time_max_list = reset_port_time_max_list + self.number_template = number_template + self.name_prefix = name_prefix + self.test_duration = test_duration + self.radio_name_list = radio_name_list + self.number_of_stations_per_radio_list = number_of_stations_per_radio_list + #self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port, debug_=debug_on) + self.polling_interval_seconds = self.duration_time_to_seconds(polling_interval) + self.cx_profile = self.new_l3_cx_profile() + self.multicast_profile = self.new_multicast_profile() + self.multicast_profile.name_prefix = "MLT-" + self.station_profiles = [] + self.args = args + self.outfile = outfile + self.results = results + self.csv_started = csv_started + self.epoch_time = int(time.time()) + self.dfs_epoch_start = 0 + self.dfs_epoch_detect = 0 + #[*07/07/2020 23:37:48.1460] changed to DFS channel 52, running CAC for 60 seconds. + self.CAC_TIMER = "" + #[*07/07/2020 23:38:48.7240] CAC_EXPIRY_EVT: CAC finished on DFS channel 52 + self.CAC_EXPIRY_EVT = "" + #[*07/07/2020 23:44:27.8060] DOT11_DRV[1]: set_dfs Channel set to 36/20, CSA count 10 + self.CSA_COUNT = "" + self.BLACK_LIST = "" + self.debug = debug_on + self.wait_timeout = wait_timeout + self.test_keys = test_keys + self.test_config = test_config + + self.test_config_dict = dict(map(lambda x: x.split('=='), str(self.test_config).replace('[','').replace(']','').replace("'","").split())) + + + # Full spread-sheet data + if self.outfile is not None: + self.csv_file = open(self.outfile, "a+") + self.csv_writer = csv.writer(self.csv_file, delimiter=",") + + if self.results is not None: + self.csv_results = open(self.results, "a+") + self.csv_results_writer = csv.writer(self.csv_results, delimiter=",") + + for (radio_, ssid_, ssid_password_, ssid_security_, wifimode_,\ + reset_port_enable_, reset_port_time_min_, reset_port_time_max_) \ + in zip(radio_name_list, ssid_list, ssid_password_list, ssid_security_list, wifimode_list,\ + reset_port_enable_list, reset_port_time_min_list, reset_port_time_max_list): + self.station_profile = self.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = ssid_ + self.station_profile.ssid_pass = ssid_password_ + self.station_profile.security = ssid_security_ + self.station_profile.mode = wifimode_ + self.station_profile.number_template = self.number_template + self.station_profile.mode = wifimode_ + self.station_profile.set_reset_extra(reset_port_enable=reset_port_enable_,\ + test_duration=self.duration_time_to_seconds(self.test_duration),\ + reset_port_min_time=self.duration_time_to_seconds(reset_port_time_min_),\ + reset_port_max_time=self.duration_time_to_seconds(reset_port_time_max_)) + self.station_profiles.append(self.station_profile) + + self.multicast_profile.host = self.lfclient_host + self.cx_profile.host = self.lfclient_host + self.cx_profile.port = self.lfclient_port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = side_a_min_bps + self.cx_profile.side_a_max_bps = side_a_min_bps + self.cx_profile.side_a_min_pdu = side_a_min_pdu + self.cx_profile.side_a_max_pdu = side_a_max_pdu + self.cx_profile.side_b_min_bps = side_b_min_bps + self.cx_profile.side_b_max_bps = side_b_min_bps + self.cx_profile.side_b_min_pdu = side_b_min_pdu + self.cx_profile.side_b_max_pdu = side_b_max_pdu + + def __get_rx_values(self): + endp_list = self.json_get("endp?fields=name,rx+bytes,rx+drop+%25", debug_=False) + endp_rx_drop_map = {} + endp_rx_map = {} + our_endps = {} + for e in self.multicast_profile.get_mc_names(): + our_endps[e] = e + for e in self.cx_profile.created_endp.keys(): + our_endps[e] = e + for endp_name in endp_list['endpoint']: + if endp_name != 'uri' and endp_name != 'handler': + for item, value in endp_name.items(): + if item in our_endps: + for value_name, value_rx in value.items(): + if value_name == 'rx bytes': + endp_rx_map[item] = value_rx + for value_name, value_rx_drop in value.items(): + if value_name == 'rx drop %': + endp_rx_drop_map[item] = value_rx_drop + + return endp_rx_map, endp_rx_drop_map + + def time_stamp(self): + return time.strftime('%Y-%m-%d %H %M %S', time.localtime(self.epoch_time)) + + def __record_rx_dropped_percent(self,rx_drop_percent): + csv_rx_drop_percent_data = [] + print("test_keys {}".format(self.test_keys)) + print("self.test_config_dict {}".format(self.test_config_dict)) + for key in self.test_keys: + csv_rx_drop_percent_data.append(self.test_config_dict[key]) + + csv_rx_drop_percent_data.extend([self.epoch_time, self.time_stamp(),'rx_drop_percent']) + # remove multi cast since downstream only if selected + for key in [key for key in rx_drop_percent if "mtx" in key]: del rx_drop_percent[key] + + if "upstream" in self.test_config_dict.values(): + for key in [key for key in rx_drop_percent if "-A" in key]: del rx_drop_percent[key] + elif "downstream" in self.test_config_dict.values(): + for key in [key for key in rx_drop_percent if "-B" in key]: del rx_drop_percent[key] + + + filtered_values = [v for _, v in rx_drop_percent.items() if v !=0] + average_rx_drop_percent = sum(filtered_values) / len(filtered_values) if len(filtered_values) != 0 else 0 + + csv_performance_rx_drop_percent_values=sorted(rx_drop_percent.items(), key=lambda x: (x[1],x[0]), reverse=False) + csv_performance_rx_drop_percent_values=self.csv_validate_list(csv_performance_rx_drop_percent_values,5) + for i in range(5): + csv_rx_drop_percent_data.append(str(csv_performance_rx_drop_percent_values[i]).replace(',',';')) + for i in range(-1,-6,-1): + csv_rx_drop_percent_data.append(str(csv_performance_rx_drop_percent_values[i]).replace(',',';')) + + csv_rx_drop_percent_data.append(average_rx_drop_percent) + + for item, value in rx_drop_percent.items(): + #logg.info(item, "rx drop percent: ", rx_drop_percent[item]) + csv_rx_drop_percent_data.append(rx_drop_percent[item]) + + self.csv_add_row(csv_rx_drop_percent_data,self.csv_writer,self.csv_file) + self.csv_add_row(csv_rx_drop_percent_data,self.csv_results_writer,self.csv_results) + + def __compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + csv_performance_values = [] + csv_rx_headers = [] + csv_rx_row_data = [] + csv_result_row_data = [] + csv_rx_delta_row_data = [] + csv_rx_delta_dict = {} + test_id = "" + + #for key in self.test_keys: + # csv_rx_row_data.append(self.test_config_dict[key]) + # csv_rx_delta_row_data.append(self.test_config_dict[key]) + + + for key in [key for key in old_list if "mtx" in key]: del old_list[key] + for key in [key for key in new_list if "mtx" in key]: del new_list[key] + + filtered_values = [v for _, v in new_list.items() if v !=0] + average_rx= sum(filtered_values) / len(filtered_values) if len(filtered_values) != 0 else 0 + + # only evaluate upstream or downstream + new_evaluate_list = new_list.copy() + print("new_evaluate_list before",new_evaluate_list) + if "upstream" in self.test_config_dict.values(): + for key in [key for key in new_evaluate_list if "-A" in key]: del new_evaluate_list[key] + print("upstream in dictionary values") + elif "downstream" in self.test_config_dict.values(): + for key in [key for key in new_evaluate_list if "-B" in key]: del new_evaluate_list[key] + print("downstream in dictionary values") + #follow code left in for now, provides the best 5 worst 5 + '''print("new_evaluate_list after",new_evaluate_list) + csv_performance_values=sorted(new_evaluate_list.items(), key=lambda x: (x[1],x[0]), reverse=False) + csv_performance_values=self.csv_validate_list(csv_performance_values,5) + for i in range(5): + csv_rx_row_data.append(str(csv_performance_values[i]).replace(',',';')) + for i in range(-1,-6,-1): + csv_rx_row_data.append(str(csv_performance_values[i]).replace(',',';')) + + csv_rx_row_data.append(average_rx)''' + + old_evaluate_list = old_list.copy() + if "upstream" in self.test_config_dict.values(): + for key in [key for key in old_evaluate_list if "-A" in key]: del old_evaluate_list[key] + print("upstream in dictionary values") + elif "downstream" in self.test_config_dict.values(): + for key in [key for key in old_evaluate_list if "-B" in key]: del old_evaluate_list[key] + print("downstream in dictionary values") + + if len(old_evaluate_list) == len(new_evaluate_list): + for item, value in old_evaluate_list.items(): + expected_passes +=1 + print("ITEM: {} VALUE: {}".format(item, value)) + if new_evaluate_list[item] > old_evaluate_list[item]: + passes += 1 + #if self.debug: logg.info(item, new_evaluate_list[item], old_evaluate_list[item], " Difference: ", new_evaluate_list[item] - old_evaluate_list[item]) + print(item, new_evaluate_list[item], old_evaluate_list[item], " Difference: ", new_evaluate_list[item] - old_evaluate_list[item]) + else: + print("Failed to increase rx data: ", item, new_evaluate_list[item], old_evaluate_list[item]) + if not self.csv_started: + csv_rx_headers.append(item) + csv_rx_delta_dict.update({item:(new_evaluate_list[item] - old_evaluate_list[item])}) + + + if not self.csv_started: + csv_header = self.csv_generate_column_headers() + csv_header += csv_rx_headers + logg.info(csv_header) + self.csv_add_column_headers(csv_header) + csv_results = self.csv_generate_column_results_headers() + #csv_results += csv_rx_headers + self.csv_add_column_headers_results(csv_results) + print("###################################") + print(csv_results) + print("###################################") + + self.csv_started = True + + # need to generate list first to determine worst and best + filtered_values = [v for _, v in csv_rx_delta_dict.items() if v !=0] + #average_rx_delta= sum(filtered_values) / len(filtered_values) if len(filtered_values) != 0 else 0 + for key in self.test_keys: + csv_rx_row_data.append(self.test_config_dict[key]) + csv_result_row_data.append(self.test_config_dict[key]) + csv_rx_delta_row_data.append(self.test_config_dict[key]) + + max_tp_mbps = sum(filtered_values) + csv_rx_row_data.append(max_tp_mbps) + csv_result_row_data.append(max_tp_mbps) + + #To do needs to be read or passed in based on test type + expected_tp_mbps = max_tp_mbps + csv_rx_row_data.append(expected_tp_mbps) + csv_result_row_data.append(expected_tp_mbps) + + + #Generate TestID + for key in self.test_keys: + test_id = test_id + "_" + self.test_config_dict[key] + + print("test_id: {}".format(test_id)) + csv_rx_row_data.append(test_id) + csv_result_row_data.append(test_id) + + # Todo pass or fail + # Todo have a pass_fail for channel + # have pass_fail for data + '''if max_tp_mbps == expected_tp_mbps: + csv_rx_row_data.append("pass") + csv_result_row_data.append("pass") + else: + csv_rx_row_data.append("fail") + csv_result_row_data.append("fail")''' + + csv_rx_row_data.extend([self.epoch_time, self.time_stamp(),'rx_delta']) + csv_result_row_data.extend([self.epoch_time, self.time_stamp()]) + + print("csv_rx_row_data {}".format(csv_rx_row_data)) + #TODO: may want to pass in the information that needs to be in the csv file into the class + ''' + csv_rx_row_data.extend([self.epoch_time, self.time_stamp(),'rx']) + csv_rx_delta_row_data.extend([self.epoch_time, self.time_stamp(),'rx_delta']) + + csv_performance_delta_values=sorted(csv_rx_delta_dict.items(), key=lambda x: (x[1],x[0]), reverse=False) + csv_performance_delta_values=self.csv_validate_list(csv_performance_delta_values,5) + for i in range(5): + csv_rx_delta_row_data.append(str(csv_performance_delta_values[i]).replace(',',';')) + for i in range(-1,-6,-1): + csv_rx_delta_row_data.append(str(csv_performance_delta_values[i]).replace(',',';')) + + csv_rx_delta_row_data.append(average_rx_delta)''' + + for item, value in old_evaluate_list.items(): + expected_passes +=1 + if new_evaluate_list[item] > old_evaluate_list[item]: + passes += 1 + #if self.debug: logg.info(item, new_evaluate_list[item], old_evaluate_list[item], " Difference: ", new_evaluate_list[item] - old_evaluate_list[item]) + print(item, new_evaluate_list[item], old_evaluate_list[item], " Difference: ", new_evaluate_list[item] - old_evaluate_list[item]) + else: + print("Failed to increase rx data: ", item, new_evaluate_list[item], old_evaluate_list[item]) + if not self.csv_started: + csv_rx_headers.append(item) + # note need to have all upstream and downstream in the csv table thus new_list and old_list + #csv_rx_row_data.append(new_list[item]) + # provide delta + csv_rx_row_data.append(new_list[item] - old_list[item]) + + self.csv_add_row(csv_rx_row_data,self.csv_writer,self.csv_file) + #self.csv_add_row(csv_rx_row_data,self.csv_results_writer,self.csv_results) + + #self.csv_add_row(csv_rx_delta_row_data,self.csv_writer,self.csv_file) + + if passes == expected_passes: + return True, max_tp_mbps, csv_result_row_data + else: + return False, max_tp_mbps, csv_result_row_data + else: + print("Old-list length: %i new: %i does not match in compare-vals."%(len(old_list), len(new_list))) + print("old-list:",old_list) + print("new-list:",new_list) + return False, None, None # check to see if this is valid + + def reset_port_check(self): + for station_profile in self.station_profiles: + if station_profile.reset_port_extra_data['reset_port_enable']: + if station_profile.reset_port_extra_data['reset_port_timer_started'] == False: + logg.info("reset_port_time_min: {}".format(station_profile.reset_port_extra_data['reset_port_time_min'])) + logg.info("reset_port_time_max: {}".format(station_profile.reset_port_extra_data['reset_port_time_max'])) + station_profile.reset_port_extra_data['seconds_till_reset'] = \ + random.randint(station_profile.reset_port_extra_data['reset_port_time_min'],\ + station_profile.reset_port_extra_data['reset_port_time_max']) + station_profile.reset_port_extra_data['reset_port_timer_started'] = True + logg.info("on radio {} seconds_till_reset {}".format(station_profile.add_sta_data['radio'],station_profile.reset_port_extra_data['seconds_till_reset'])) + else: + station_profile.reset_port_extra_data['seconds_till_reset'] = station_profile.reset_port_extra_data['seconds_till_reset'] - 1 + if self.debug: logg.info("radio: {} countdown seconds_till_reset {}".format(station_profile.add_sta_data['radio'] ,station_profile.reset_port_extra_data['seconds_till_reset'])) + if ((station_profile.reset_port_extra_data['seconds_till_reset'] <= 0)): + station_profile.reset_port_extra_data['reset_port_timer_started'] = False + port_to_reset = random.randint(0,len(station_profile.station_names)-1) + logg.info("reset on radio {} station: {}".format(station_profile.add_sta_data['radio'],station_profile.station_names[port_to_reset])) + self.reset_port(station_profile.station_names[port_to_reset]) + + def pre_cleanup(self): + self.cx_profile.cleanup_prefix() + self.multicast_profile.cleanup_prefix() + self.total_stas = 0 + for station_list in self.station_lists: + for sta in station_list: + self.rm_port(sta, check_exists=True) + self.total_stas += 1 + + # Make sure they are gone + count = 0 + while (count < 10): + more = False + for station_list in self.station_lists: + for sta in station_list: + rv = self.rm_port(sta, check_exists=True) + if (rv): + more = True + if not more: + break + count += 1 + time.sleep(5) + + def build(self): + index = 0 + for station_profile in self.station_profiles: + station_profile.use_security(station_profile.security, station_profile.ssid, station_profile.ssid_pass) + station_profile.set_number_template(station_profile.number_template) + logg.info("Creating stations") + + station_profile.create(radio=self.radio_name_list[index], sta_names_=self.station_lists[index], debug=self.debug, sleep_time=0) + index += 1 + + # 12/4/2020 put back in multi cast + #for etype in self.endp_types: + # if etype == "mc_udp" or etype == "mc_udp6": + # logg.info("Creating Multicast connections for endpoint type: %s"%(etype)) + # self.multicast_profile.create_mc_tx(etype, self.side_b, etype) + # self.multicast_profile.create_mc_rx(etype, side_rx=station_profile.station_names) + + for _tos in self.tos: + logg.info("Creating connections for endpoint type: {} TOS: {} stations_names {}".format(self.endp_type, _tos, station_profile.station_names)) + self.cx_profile.create(endp_type=self.endp_type, side_a=station_profile.station_names, side_b=self.side_b, sleep_time=0, tos=_tos) + self._pass("PASS: Stations build finished") + + def station_bringup(self): + client_density = 0 + logg.info("Bringing up stations") + self.admin_up(self.side_b) + for station_profile in self.station_profiles: + for sta in station_profile.station_names: + logg.info("Bringing up station %s"%(sta)) + self.admin_up(sta) + client_density += 1 + + + temp_stations_list = [] + temp_stations_list.append(self.side_b) + for station_profile in self.station_profiles: + temp_stations_list.extend(station_profile.station_names.copy()) + # need algorithm for setting time default + if self.wait_for_ip(temp_stations_list, timeout_sec=self.wait_timeout, debug=self.debug): + logg.info("ip's acquired") + else: + logg.info("print failed to get IP's") + exit(1) # why continue + + return client_density + + def read_channel(self): + + logg.info("read_channel: wifi_ctl_9800_3504.py action advanced") + pss = "" + try: + logg.info("\ + scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"advanced")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "advanced"], + capture_output=True, check=True) + + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + logg.info("controller_show_ap_summary::: pss {}".format(pss)) + if self.series == "9800": + searchap = False + cc_mac = "" + cc_ch = "" + cc_bw = "" + cc_power = "" + cc_dbm = "" + for line in pss.splitlines(): + if (line.startswith("---------")): + searchap = True + continue + # if the pattern changes save the output of the advanced command and re parse https://regex101.com + if (searchap): + pat = "%s\s+(\S+)\s+(%s)\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)\s+dBm\)+\s+(\S+)+\s"%(self.ap,self.ap_slot) + m = re.search(pat, line) + if (m != None): + if(m.group(2) == self.ap_slot): + cc_mac = m.group(1) + cc_slot = m.group(2) + cc_ch = m.group(6); # (132,136,140,144) + cc_power = m.group(4) + cc_power = cc_power.replace("/", " of ") # spread-sheets turn 1/8 into a date + cc_dbm = m.group(5) + cc_dbm = cc_dbm.replace("(","") + cc_ch_count = cc_ch.count(",") + 1 + cc_bw = m.group(3) + logg.info("group 1: {} 2: {} 3: {} 4: {} 5: {} 6: {}".format(m.group(1),m.group(2),m.group(3),m.group(4),m.group(5),m.group(6))) + logg.info("9800 test_parameters cc_mac: read : {}".format(cc_mac)) + logg.info("9800 test_parameters cc_slot: read : {}".format(cc_slot)) + logg.info("9800 test_parameters cc_count: read : {}".format(cc_ch_count)) + logg.info("9800 test_parameters cc_bw: read : {}".format(cc_bw)) + logg.info("9800 test_parameters cc_power: read : {}".format(cc_power)) + logg.info("9800 test_parameters cc_dbm: read : {}".format(cc_dbm)) + logg.info("9800 test_parameters cc_ch: read : {}".format(cc_ch)) + break + else: + searchap = False + cc_mac = "" + cc_ch = "" + cc_bw = "" + cc_power = "" + cc_dbm = "" + ch_count = "" + for line in pss.splitlines(): + if (line.startswith("---------")): + searchap = True + continue + + if (searchap): + pat = "%s\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+\(\s*(\S+)\s+dBm"%(self.ap) + m = re.search(pat, line) + if (m != None): + cc_mac = m.group(1) + cc_ch = m.group(2); # (132,136,140,144) + cc_power = m.group(3) + cc_power = cc_power.replace("/", " of ", 1) # spread-sheets turn 1/8 into a date + cc_dbm = m.group(4) + ch_count = cc_ch.count(",") + cc_bw = 20 * (ch_count + 1) + + logg.info("3504 test_parameters cc_mac: read : {}".format(cc_mac)) + logg.info("3504 test_parameters cc_count: read : {}".format(ch_count)) + logg.info("3504 test_parameters cc_bw: read : {}".format(cc_bw)) + logg.info("3504 test_parameters cc_power: read : {}".format(cc_power)) + logg.info("3504 test_parameters cc_dbm: read : {}".format(cc_dbm)) + logg.info("3504 test_parameters cc_ch: read : {}".format(cc_ch)) + + return cc_ch + + def read_auto_rf(self): + + logg.info("read_channel: wifi_ctl_9800_3504.py action auto-rf") + pss = "" + try: + logg.info("\ + scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"advanced")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "auto_rf"], + capture_output=True, check=True) + + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + blacklist_time = "" + for line in pss.splitlines(): + pat = 'Channel\s+%s\S+\s+(\S+)\s+\S+\s+remaining'%(self.chan_5ghz) + m = re.search(pat, line) + if ( m != None ): + blacklist_time = m.group(1) + logg.info("dfs_channel: {} blacklist_time: {}".format(self.chan_5ghz,blacklist_time)) + + return blacklist_time + + def dfs_waveforms(self,waveform): + # 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16 + # "FCCO" , "FCC1" , "FCC2" , "FCC3", "FCC4", "FCC5", "ETSI1", "ETSI2", "ETSI3", "ETSI4", "ETSI5", "ETSI6" + if waveform == "FCCO": + width = "1" + interval = "1428" + count = "18" + elif waveform == "FCC1": + width = "1" + interval = "1163" + count = "18" + elif waveform == "FCC2": + width = "2" + interval = "208" + count = "28" + elif waveform == "FCC3": + width = "7" + interval = "365" + count = "16" + elif waveform == "FCC4": + width = "16" + interval = "271" + count = "12" + elif waveform == "FCC5": + width = "70" + interval = "1975" + count = "3" + elif waveform == "ETSI1": + width = "5" + interval = "342" + count = "10" + elif waveform == "ETSI2": + width = "2" + interval = "1271" + count = "15" + elif waveform == "ETSI3": + width = "15" + interval = "3280" + count = "25" + elif waveform == "ETSI4": + width = "24" + interval = "2477" + count = "20" + elif waveform == "ETSI5": + width = "1" + interval = "356" + count = "10" + elif waveform == "ETSI6": + width = "2" + interval = "1091" + count = "15" + + return width, interval, count + + def dfs_get_frequency(self,channel): + # possibly have a dictionary + + if channel == "36": + frequency = "5180000" + elif channel == "38": + frequency = "5190000" + elif channel == "40": + frequency = "5200000" + elif channel == "42": + frequency = "5210000" + elif channel == "44": + frequency = "5220000" + elif channel == "46": + frequency = "5230000" + elif channel == "48": + frequency = "5240000" + # DFS Channels US + elif channel == "52": + frequency = "5260000" + elif channel == "56": + frequency = "5280000" + elif channel == "60": + frequency = "5300000" + elif channel == "64": + frequency = "5320000" + elif channel == "100": + frequency = "5500000" + elif channel == "104": + frequency = "5520000" + elif channel == "108": + frequency = "5540000" + elif channel == "112": + frequency = "5560000" + elif channel == "116": + frequency = "5580000" + elif channel == "120": + frequency = "5600000" + elif channel == "124": + frequency = "5620000" + elif channel == "128": + frequency = "5640000" + elif channel == "132": + frequency = "5660000" + elif channel == "136": + frequency = "5680000" + elif channel == "140": + frequency = "5700000" + elif channel == "144": + frequency = "5720000" + elif channel == "149": + frequency = "5745000" + elif channel == "153": + frequency = "5765000" + elif channel == "157": + frequency = "5785000" + elif channel == "161": + frequency = "5805000" + elif channel == "165": + frequency = "5825000" + elif channel == "169": + frequency = "5845000" + elif channel == "173": + frequency = "5865000" + else: + logg.info("Invalid Channel") + exit(1) + return frequency + + + def dfs_send_radar(self,channel): + # Hard coded to FCC0 - need to support others + width_ = "1" + interval_ = "1428" + count_ = "18" + frequency_ = "5260000" # channel 52 + #sweep_time_ = "1000" + sweep_time_ = "0" + if_gain_ = "40" + bb_gain_ = "20" + gain_ = "0" + + frequency_ = self.dfs_get_frequency(channel) + if frequency_ == None: + logg.info("frequency_ is : {}".format(frequency_)) + exit(1) + + logg.info("dfs_send_radar channel: {} frequency: {}".format(channel, frequency_)) + + # spawn bash for lf_hackrf.py + child = pexpect.spawn('bash') + time.sleep(0.4) + + # for testing bash + i = child.expect([r'\$',pexpect.TIMEOUT],timeout=2) + if i == 0: + logg.info("i: {} received bash prompt for hackrf command".format(i)) + if i == 1: + logg.info("i: {} TIMEOUT in bash prompt".format(i)) + ''' + child.sendline('ls -lrt') + child.expect([pexpect.TIMEOUT], timeout=1) # do not delete this for it allows for subprocess to see output + print(child.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + + child.expect(r'\$') + ''' + + ################################# + # No timeouts - this would hang + ################################# + '''command_hackRF = "sudo python lf_hackrf.py --pulse_width {} --pulse_interval {} --pulse_count {} --sweep_time {} --freq {} --if_gain {} --bb_gain {} --gain {}".format(width_,interval_,count_,sweep_time_,frequency_,if_gain_,bb_gain_,gain_) + print("hackrf command {}".format(command_hackRF)) + child.sendline(command_hackRF) + child.expect([pexpect.TIMEOUT], timeout=1) # do not delete this for it allows for subprocess to see output + print(child.before.decode('utf-8', 'ignore')) # do not delete this for it allows for subprocess to see output + + child.expect('lanforge:') + print(child.before.decode('utf-8', 'ignore')) + child.sendline('lanforge') + time.sleep(5) + child.expect('>>>') + print(child.before.decode('utf-8', 'ignore')) + child.sendline('s') + child.expect('>>>') + print(child.before.decode('utf-8', 'ignore')) + child.sendline('q') + time.sleep(1)''' + + ################################# + # With timeouts + ################################# + command_hackRF = "sudo python lf_hackrf.py --pulse_width {} --pulse_interval {} --pulse_count {} --sweep_time {} --freq {} --if_gain {} --bb_gain {} --gain {}".format(width_,interval_,count_,sweep_time_,frequency_,if_gain_,bb_gain_,gain_) + logg.info("hackrf command {}".format(command_hackRF)) + child.sendline(command_hackRF) + time.sleep(0.4) + i = child.expect(['lanforge:',pexpect.TIMEOUT], timeout=2) + if i == 0: + logg.info("lanforge prompt received i: {} before {} after {}".format(i,child.before.decode('utf-8', 'ignore'),child.after.decode('utf-8', 'ignore'))) + child.sendline('lanforge') + time.sleep(0.4) + self.dfs_epoch_start = int(time.time()) + j = child.expect(['>>>',pexpect.TIMEOUT], timeout=5) + if j == 0: + logg.info(">>> prompt received i: {} j: {} before {} after {}".format(i,j,child.before.decode('utf-8', 'ignore'),child.after.decode('utf-8', 'ignore'))) + logg.info("Let the radar run for {}".format(self.radar_duration_seconds)) + time.sleep(self.radar_duration_seconds) + child.sendline('s') # stop + time.sleep(0.4) + k = child.expect(['>>>',pexpect.TIMEOUT], timeout=2) + if k == 0: + logg.info(">>> prompt received i: {} j: {} k: {} before {} after {}".format(i,j,k,child.before.decode('utf-8', 'ignore'),child.after.decode('utf-8', 'ignore'))) + logg.info("send q - for quit") + child.sendline('q') + time.sleep(1) + if k == 1: + logg.info("TIMEOUT hackrf >>> prompt i: {} j: {} k: {} before {} after {}".format(i,j,k,child.before.decode('utf-8', 'ignore'),child.after)) + if j == 1: + logg.info("TIMEOUT hackrf >>> prompt i: {} j: {} before {} after {}".format(i,j,child.before.decode('utf-8', 'ignore'),child.after)) + if i == 1: + logg.info("TIMEOUT lanforge password prompt i: {} before {} after {}".format(i,child.before.decode('utf-8', 'ignore'),child.after)) + + time.sleep(2) + + def ap_cac_verify(self): + if(bool(self.ap_dict)): + pss = "" + # will need to verify that timer has timed out on AP - need in results + logg.info("DFS channel 5ghz {} done waiting CAC time, 2.4 ghz: {}".format(self.chan_5ghz, self.chan_24ghz)) + logg.info("##################################################################") + logg.info("# READ changed to DFS channel {}, running CAC for 60 seconds.".format(self.chan_5ghz)) + logg.info("# READ AP CAC_EXPIRY_EVT: CAC finished on DFS channel ") + logg.info("##################################################################") + logg.info("ap_dict {}".format(self.ap_dict)) + logg.info("Read AP action: {} ap_scheme: {} ap_ip: {} ap_port: {} ap_user: {} ap_pw: {} ap_tty: {} ap_baud: {}".format("show_log",self.ap_dict['ap_scheme'],self.ap_dict['ap_ip'],self.ap_dict["ap_port"], + self.ap_dict['ap_user'],self.ap_dict['ap_pw'],self.ap_dict['ap_tty'],self.ap_dict['ap_baud'])) + try: + logg.info("ap_ctl.py: read for CAC timer and CAC_EXPIRY_EVT") + # TODO remove position dependence if in tree + ap_info= subprocess.run(["./../ap_ctl.py", "--scheme", self.ap_dict['ap_scheme'], "--prompt", self.ap_dict['ap_prompt'],"--dest", self.ap_dict['ap_ip'], "--port", self.ap_dict["ap_port"], + "--user", self.ap_dict['ap_user'], "--passwd", self.ap_dict['ap_pw'],"--tty", self.ap_dict['ap_tty'],"--baud", self.ap_dict['ap_baud'],"--action", "show_log"],capture_output=True, check=True) + try: + pss = ap_info.stdout.decode('utf-8', 'ignore') + except: + logg.info("ap_info was of type NoneType will set pss empty") + + except subprocess.CalledProcessError as process_error: + logg.info("###################################################") + logg.info("# CHECK IF AP HAS CONNECTION ALREADY ACTIVE") + logg.info("###################################################") + logg.info("# Unable to commicate to AP error code: {} output {}".format(process_error.returncode, process_error.output)) + logg.info("###################################################") + logg.info(pss) + # fine CAC_TIMER + for line in pss.splitlines(): + logg.info("ap: CAC_EXPIRY_EVT {}".format(line)) + pat = 'changed to DFS channel\s+(\S+),\s+\S+\s+\S+\s+\S+\s+(\S+)' + m = re.search(pat, line) + if (m != None): + dfs_channel = m.group(1) + cac_time = m.group(2) + logg.info("dfs_channel: {} cac_time: {}".format(dfs_channel,cac_time)) + logg.info("dfs_cac line: {}".format(line)) + self.CAC_TIMER = line + break + + # find CAC_EXPIRY_EVT + for line in pss.splitlines(): + logg.info("ap: CAC_EXPIRY_EVT {}".format(line)) + pat = 'CAC_EXPIRY_EVT:\s+\S+\s+\S+\s+\S+\s\S+\s\S+\s(\S+)' + m = re.search(pat, line) + if (m != None): + dfs_channel = m.group(1) + logg.info("dfs_channel: {}".format(dfs_channel)) + logg.info("dfs_channel line: {}".format(line)) + self.CAC_EXPIRY_EVT = line + else: + logg.info("ap_dict not set") + + def start(self, print_pass=False, print_fail=False): + best_max_tp_mbps = 0 + best_csv_rx_row_data = " " + max_tp_mbps = 0 + csv_rx_row_data = " " + Result = False + + + + # verify the AP CAC timer and experation + self.ap_cac_verify() + + # verify controller channel , see if a DFS channel + initial_channel = self.read_channel() + + logg.info("###########################################") + logg.info("# INITIAL CHANNEL: {}".format(initial_channel)) + logg.info("###########################################") + + if (initial_channel != self.chan_5ghz): + logg.info("##################################################################") + logg.info("# DFS LOCKOUT? COMMAND LINE CHANNEL: {} NOT EQUAL INITIAL CONTROLLER CHANNEL: {}".format(self.chan_5ghz,initial_channel)) + logg.info("##################################################################") + + time.sleep(30) + + logg.info("Starting multicast traffic (if any configured)") + self.multicast_profile.start_mc(debug_=self.debug) + self.multicast_profile.refresh_mc(debug_=self.debug) + logg.info("Starting layer-3 traffic (if any configured)") + self.cx_profile.start_cx() + self.cx_profile.refresh_cx() + + cur_time = datetime.datetime.now() + logg.info("Getting initial values.") + old_rx_values, rx_drop_percent = self.__get_rx_values() + + end_time = self.parse_time(self.test_duration) + cur_time + + logg.info("Monitoring throughput for duration: %s"%(self.test_duration)) + + passes = 0 + expected_passes = 0 + logg.info("polling_interval_seconds {}".format(self.polling_interval_seconds)) + + logg.info("dfs_time_seconds {}".format(self.dfs_time_seconds)) + dfs_time = cur_time + datetime.timedelta(seconds=self.dfs_time_seconds) + dfs_radar_sent = False + while cur_time < end_time: + interval_time = cur_time + datetime.timedelta(seconds=self.polling_interval_seconds) + + while cur_time < interval_time: + cur_time = datetime.datetime.now() + self.reset_port_check() + if((cur_time > dfs_time) and dfs_radar_sent == False): + if(self.dfs): + self.dfs_send_radar(initial_channel) + dfs_radar_sent = True + else: + logg.info("################################################################") + logg.info("# DFS IS NOT ENABLED FROM THE COMMAND LINE NO RADAR SENT") + logg.info("################################################################") + + time.sleep(1) + + self.epoch_time = int(time.time()) + new_rx_values, rx_drop_percent = self.__get_rx_values() + + expected_passes += 1 + ''' + #self.csv_add_row(csv_rx_row_data,self.csv_results_writer,self.csv_results) + + + if passes == expected_passes: + return True, max_tp_mbps, csv_rx_row_data + else: + return False, max_tp_mbps, csv_rx_row_data + ''' + # __compare_vals - does the calculations + Result, max_tp_mbps, csv_rx_row_data = self.__compare_vals(old_rx_values, new_rx_values) + if max_tp_mbps > best_max_tp_mbps: + best_max_tp_mbps = max_tp_mbps + best_csv_rx_row_data = csv_rx_row_data + + # need to check the expected max_tp_mbps + if Result: + passes += 1 + else: + self._fail("FAIL: Not all stations increased traffic", print_fail) + old_rx_values = new_rx_values + + #percentage dropped not needed for scaling and performance , needed for longevity + #self.__record_rx_dropped_percent(rx_drop_percent) + + cur_time = datetime.datetime.now() + + + final_channel = self.read_channel() + + logg.info("###########################################") + logg.info("# FINAL CHANNEL : {}".format(final_channel)) + logg.info("###########################################") + + dfs_channel_bw20_values = [52, 56, 60, 64, 68, 96, 100, 104, 108, 112, 116, 120, 124 ,128, 132, 136, 140, 144] + + pass_fail = "pass" + if int(final_channel) in dfs_channel_bw20_values: + logg.info("FAIL: The DFS channel did not change or initial channel was not DFS") + pass_fail = "fail" + + if (initial_channel != self.chan_5ghz): + logg.info("FAIL: channel set on command line: {} not configured in controller: {} is there a DFS lockout condition".format(self.chan_5ghz,initial_channel)) + pass_fail = "fail" + + blacklist_time = self.read_auto_rf() + + + best_csv_rx_row_data.append(initial_channel) + best_csv_rx_row_data.append(final_channel) + best_csv_rx_row_data.append(pass_fail) + best_csv_rx_row_data.append(self.CAC_TIMER) + best_csv_rx_row_data.append(self.CAC_EXPIRY_EVT) + best_csv_rx_row_data.append(blacklist_time) + self.csv_add_row(best_csv_rx_row_data,self.csv_results_writer,self.csv_results) + + # TO DO check to see if the data is still being transmitted + if passes == expected_passes: + self._pass("PASS: All tests passed", print_pass) + + def stop(self): + self.cx_profile.stop_cx() + self.multicast_profile.stop_mc() + for station_list in self.station_lists: + for station_name in station_list: + self.admin_down(station_name) + + def cleanup(self): + self.cx_profile.cleanup() + self.multicast_profile.cleanup() + for station_profile in self.station_profiles: + station_profile.cleanup() + + + def csv_generate_column_headers(self): + csv_rx_headers = self.test_keys.copy() + csv_rx_headers.extend + csv_rx_headers.extend(['max_tp_mbps','expected_tp','test_id','epoch_time','time','monitor','pass_fail']) + '''for i in range(1,6): + csv_rx_headers.append("least_rx_data {}".format(i)) + for i in range(1,6): + csv_rx_headers.append("most_rx_data_{}".format(i)) + csv_rx_headers.append("average_rx_data")''' + return csv_rx_headers + + def csv_generate_column_results_headers(self): + csv_rx_headers = self.test_keys.copy() + csv_rx_headers.extend + csv_rx_headers.extend(['max_tp_mbps','expected_tp','test_id','epoch_time','time','initial_channel','final_channel','pass_fail','cac_timer','cac_expiry_evt','blacklist_time_sec_remaining']) + '''for i in range(1,6): + csv_rx_headers.append("least_rx_data {}".format(i)) + for i in range(1,6): + csv_rx_headers.append("most_rx_data_{}".format(i)) + csv_rx_headers.append("average_rx_data")''' + return csv_rx_headers + + + def csv_add_column_headers(self,headers): + if self.csv_file is not None: + self.csv_writer.writerow(headers) + self.csv_file.flush() + + def csv_add_column_headers_results(self,headers): + if self.csv_results is not None: + self.csv_results_writer.writerow(headers) + self.csv_results.flush() + + def csv_validate_list(self, csv_list, length): + if len(csv_list) < length: + csv_list = csv_list + [('no data','no data')] * (length - len(csv_list)) + return csv_list + + def csv_add_row(self,row,writer,csv_file): # can make two calls eventually + if csv_file is not None: + writer.writerow(row) + csv_file.flush() + +######################################### +# +# AP helper functions +# +# ####################################### + +def valid_endp_types(_endp_type): + etypes = _endp_type.split() + for endp_type in etypes: + valid_endp_type=['lf_udp','lf_udp6','lf_tcp','lf_tcp6','mc_udp','mc_udp6'] + if not (str(endp_type) in valid_endp_type): + logg.info('invalid endp_type: %s. Valid types lf_udp, lf_udp6, lf_tcp, lf_tcp6, mc_udp, mc_udp6' % endp_type) + exit(1) + return _endp_type + +########################################## +# Traffic Generation End +########################################## + + +def main(): + global logg + lfjson_host = "localhost" + lfjson_port = 8080 + endp_types = "lf_udp" + debug_on = False + + parser = argparse.ArgumentParser( + prog='lf_dfs_test.py', + #formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Scaling and Performance + ''', + + description='''\ +lf_dfs_test.py: +-------------------- + +################################################################################## +Task Description: +################################################################################## +----------------- + +DFS Testing can do multiple clients at once and various radios + +Our ultimate aim is to achieve the following things: + +1. 1 to 200 client DFS on 11ac (1, 50 and 200 client count tests) + 1. 5 Ghz with different channel widths + 2. Data encryption enabled/disabled + 3. Local/central switching and authentication combinations +2. 1 to 37 client DFS on 11ax (1, 10 and 37 client count tests) eventually 200 clients + 1. Different channel widths + 2. Data encryption enabled/disabled + 3. Local/central switching and authentication combinations + 4. MU-MIMO and OFDMA enabled/disabled combination +3. CI/CD implementation + 1. Download latest WLC images and upload them to the controller + 2. Start the test suite + 3. Generate a report per release + 4. Display and graph all result data according to each release along with each testcase historical graph + 5. Review overall AP performance across multiple AP platforms + + +Summary : +---------- +create stations, create traffic between upstream port and stations, run traffic. +The traffic on the stations will be checked once per minute to verify that traffic is transmitted +and recieved. + +Generic command layout: +----------------------- +python .\\lf_dfs_test.py --test_duration --endp_type --upstream_port + --radio "radio== stations== ssid== ssid_pw== security== wifimode==AUTO" --debug + +Multiple radios may be entered with individual --radio switches + +generiic command with controller setting channel and channel width test duration 30 sec +python3 lf_dfs_test.py --controller_ip --controller_dfs True/False --mgr + --controller_chan_5ghz --controller_chan_width <20,40,80,120> --endp_type 'lf_udp lf_tcp mc_udp' --upstream_port <1.ethX> + --radio "radio== stations== ssid== ssid_pw== security== wifimode==" + --radio "radio== stations== ssid== ssid_pw== security== wifimode==" + --duration 5m + +wifimode: + : number followed by one of the following +d - days +h - hours +m - minutes +s - seconds + +: +lf_udp : IPv4 UDP traffic +lf_tcp : IPv4 TCP traffic +lf_udp6 : IPv6 UDP traffic +lf_tcp6 : IPv6 TCP traffic +mc_udp : IPv4 multi cast UDP traffic +mc_udp6 : IPv6 multi cast UDP traffic + +: +BK, BE, VI, VO: Optional wifi related Tos Settings. Or, use your preferred numeric values. + +################################# +#Command switches +################################# +--controller_ip ',default=None +--controller_user ' +--controller_passwd ' +--controller_prompt ',default="(controller Controller) > +--controller_ap ',default="APA453.0E7B.CF9C" + +--controller_dfs ',default=False +--controller_chan_5ghz ',default=None , no change +--controller_chan_width <20 40 80 160>',default="20",choices=["20","40","80","160"] +--controller_band ',default="a",choices=["a", "b", "abgn"] + +--mgr ',default='localhost' +-d / --test_duration example --time 5d (5 days) default: 3m options: number followed by d, h, m or s',default='3m' +--tos: Support different ToS settings: BK | BE | VI | VO | numeric',default="BE" +--debug: Enable debugging',default=False +-t / --endp_type example --endp_type \"lf_udp lf_tcp mc_udp\" Default: lf_udp , options: lf_udp, lf_udp6, lf_tcp, lf_tcp6, mc_udp, mc_udp6', + default='lf_udp', type=valid_endp_types +-u / --upstream_port example: --upstream_port eth1',default='eth1') +-o / --outfile ", default='dfs' + +######################################### +# Examples +# ####################################### +Example #1 running traffic with two radios +1. Test duration 4 minutes +2. Traffic IPv4 TCP +3. Upstream-port eth1 +4. Radio #0 wiphy0 has 32 stations, ssid = candelaTech-wpa2-x2048-4-1, ssid password = candelaTech-wpa2-x2048-4-1 +5. Radio #1 wiphy1 has 64 stations, ssid = candelaTech-wpa2-x2048-5-3, ssid password = candelaTech-wpa2-x2048-5-3 +6. Create connections with TOS of BK and VI + +Command: (remove carriage returns) +python3 .\\lf_controller_dfs.py --test_duration 4m --endp_type \"lf_tcp lf_udp mc_udp\" --tos \"BK VI\" --upstream_port eth1 +--radio "radio==wiphy0 stations==32 ssid==candelaTech-wpa2-x2048-4-1 ssid_pw==candelaTech-wpa2-x2048-4-1 security==wpa2" +--radio "radio==wiphy1 stations==64 ssid==candelaTech-wpa2-x2048-5-3 ssid_pw==candelaTech-wpa2-x2048-5-3 security==wpa2" + +Example #2 using controller controller +1. controller controller at 192.168.100.112 +2. controller dfs True +3. controller channel 52 +4. controller channel width 20 +5. traffic 'lf_udp lf_tcp mc_udp' +6. upstream port eth3 +7. radio #0 wiphy0 stations 3 ssid test_candela ssid_pw [BLANK] secruity Open +8. radio #1 wiphy1 stations 16 ssid test_candela ssid_pw [BLANK] security Open +9. lanforge manager at 192.168.100.178 +10. duration 5m + +Command: +python3 lf_controller_dfs.py --controller_ip 192.168.100.112 --controller_dfs True --mgr 192.168.100.178 + --controller_chan_5ghz 52 --controller_chan_width 20 --endp_type 'lf_udp lf_tcp mc_udp' --upstream_port 1.eth3 + --radio "radio==1.wiphy0 stations==3 ssid==test_candela ssid_pw==[BLANK] security==open" + --radio "radio==1.wiphy1 stations==16 ssid==test_candela ssid_pw==[BLANK] security==open" + --test_duration 5m + +############################################################################## +Detailed test loop description 10/9/2020 - Karthik Recommendation +############################################################################## +Script logic loops: + +AP {Axel, Vanc} Dynamic + frequency {24ghz, 5ghz} Common (band) : 24ghz == b , 5ghz == a + wifimode{11ax (2.4 ghz or 5 ghz), 11ac (5 ghz only), 11n (2.4 ghz or 5ghz), 11bg(2.4 ghz)} Common (an anAX anAC abgn bg) + Bandwidth {20, 40, 80, 160} + data-encryption {enable/disable} Common + AP-mode {local/flexconnect} Common + client-density {1, 10, 20, 50, 100, 200} Dynamic + Packet-type {TCP, UDP} Common + Direction {Downstream, Upstream} + Packet-size { 88, 512, 1370, 1518} Common + Time (4 iterations of 30 sec and get the best average TP out of it) + +Notes: +############################################# +CandelaTech Radios and what supports +############################################# + +Radio descriptions: +ax200: so if AP is /n, then ax200 will connect at /n. But if AP is /AX, we have no way to force ax200 to act like /n +ax200: is dual band, supporting at least /b/g/n/AX on 2.4Ghz, and /a/n/ac/AX on 5Ghz. 2.4Ghz doesn't officially support /AC, but often chips will do /AC there anyway + +ath10K: if they want /AC or /n or /abg stations, then our ath10k radios can support that need (and ath9k if they have any, can do /n and /abg) +ath10K(998x) - wave -1 , dual band card it can be ac, n , a/b/g modes, up to 3x3 spacial streams +ath10K(9884) - wave-2 supports 4x4 802.11an-AC 5ghz (can act as ac , an) + +Note: wave-2 radios can act as ac, an, (802.11an-AC) or legacy a/b/g (802.11bgn-AC) + +############################################# +wifimodes needed to support +############################################# +11ax (2.4 ghz or 5 ghz), 11ac (5 ghz only), 11n (2.4ghz or 5 ghz), 11bg (2.4 ghz) (controller) + +############################################# +5 Ghz +############################################# +Wifi mode: 11ax - 5ghz +Radios : ax200 : 802.11 /a/n/ac/AX + +Wifi mode: 11ac - 5ghz +Radios : ath10K(9984) 802.11an-AC (9984 are single band) + +Wifi mode: 11n - 5ghz +Radios : ath10K(9984) 802.11an-AC (9984 are single band) + +############################################# +24 Ghz +############################################# +Wifi mode: 11ax - 24ghz +Radios : ax200 - 802.11 /b/g/n/AX + +Wifi mode: 11ac - 24ghz +Radios : ax200 802.11 /b/g/n/AX (2.4Ghz doesn't officially support /AC, but often chips will do /AC there anyway) (invalid) + +Wifi mode: 11n - 24ghz +Radios : ax200 802.11 /b/g/n/AX + +Wifi mode: 11bg - 24ghz +Radios : ax200 802.11 /b/g/n/AX + +############################################ +Radio support for specific Modes +############################################ +controller_wifimode == "anAX" or controller_wifimode == "abgn" or controller_wifimode == "bg": + radios = radio_AX200_abgn_ax_dict[controller_client_density] + +controller_wifimode == "an" or controller_wifimode == "anAC": + radios = radio_ath10K_9984_an_AC_dict[controller_client_density] + + + +Sample script 2/11/2021 + + ./lf_dfs_test.py -cc 192.168.100.112 -cu admin -cpw controller123 -cca APA453.0E7B.CF9C -ccf "a" -cwm "auto" -cc5 "52" -ccw "20" -ccd "1" -cs "3504" --endp_type 'lf_udp' --upstream_port eth2 --controller_wlan "test_candela" --controller_wlanID 1 --controller_wlanSSID "test_candela" --controller_directions "upstream" --controller_prompt "(controller Controller)" --radio "radio==1.wiphy0 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto" --ap_info "ap_scheme==serial ap_prompt==APA453.0E7B.CF9C ap_ip==0 ap_port==0 ap_user==admin ap_pw==Admin123 ap_tty==/dev/ttyUSB2 ap_baud==9600" + + + ''') + + # reorder to follow looping + + parser.add_argument('-cca' ,'--controller_ap', help='--controller_ap List of APs to test default: Axel',default="APA453.0E7B.CF9C") + parser.add_argument('-ccf' ,'--controller_band', help='--controller_band default a',default="a") + # controller wanted 11ax , 11ac, 11n, 11gb + parser.add_argument('-cwm' ,'--controller_wifimode', help='List of of wifi mode to test <11ax 11ac 11n 11gb> default: an',default="an", + choices=[ "auto", "a", "b", "g", "abg", "abgn", "bgn", "bg", "abgnAC", "anAC", "an", "bgnAC", "abgnAX", "bgnAX", "anAX"]) + + parser.add_argument('-cc5','--controller_chan_5ghz', help='--controller_chan_5ghz <36 40 ...> default 36',default="36") + parser.add_argument('-cc2','--controller_chan_24ghz', help='--controller_chan_24ghz <1 2 ...> default 1',default="1") + parser.add_argument('-ccw','--controller_chan_width', help='--controller_chan_width <20 40 80 160> default: 20',default="20") + parser.add_argument('-cam','--controller_ap_mode', help='--controller_ap_mode default local',default="local") + parser.add_argument('-cps','--controller_packet_size', help='--controller_packet_size List of packet sizes <88 512 1370 1518> default 1518 ',default="1518" ) + parser.add_argument('-ctd','--controller_directions', help='--controller_directions default: upstream downstream ',default="upstream downstream" ) + parser.add_argument('-ccd','--controller_client_density', help='--controller_client_density List of client densities <1 10 20 50 100 200> default 1 ', + default="1" ) + #TODO set str for ones that are str + parser.add_argument('-cde','--controller_data_encryption', help='--controller_data_encryption \"enable disable\"',default="disable" ) + parser.add_argument('-cs' ,'--controller_series', help='--controller_series <9800 | 3504>',default="3504",choices=["9800","3504"]) + parser.add_argument('-ccp','--controller_prompt', type=str,help="controller prompt default WLC",default="WLC") + parser.add_argument('-cas','--controller_ap_slot', type=str,help="AP slot, default 1",default="1") + + parser.add_argument('-cc' ,'--controller_ip', help='--controller_ip default 192.168.100.178',default="192.168.100.178") + parser.add_argument('-cp' ,'--controller_port', help='--controller_port ssh default 22',default="22") + parser.add_argument('-cu' ,'--controller_user', help='--controller_user ',default="admin") + parser.add_argument('-cpw','--controller_passwd', help='--controller_passwd ',default="controller123") + parser.add_argument('-ccs','--controller_scheme', help='--controller_scheme (serial|telnet|ssh): connect via serial, ssh or telnet',default="ssh",choices=["serial","telnet","ssh"]) + parser.add_argument('-cw' ,'--controller_wlan', help='--controller_wlan ',required=True) + parser.add_argument('-cwi','--controller_wlanID', help='--controller_wlanID ',required=True) + parser.add_argument('-cws' ,'--controller_wlanSSID', help='--controller_wlanSSID ',required=True) + + parser.add_argument('-ctp','--controller_tx_power', help='--controller_tx_power <1 | 2 | 3 | 4 | 5 | 6 | 7 | 8> 1 is highest power default NA NA means no change',default="NA" + ,choices=["1","2","3","4","5","6","7","8","NA"]) + parser.add_argument('-dfs','--controller_dfs', help='--controller_dfs, switch to enable dfs testing', action='store_true') + parser.add_argument('-dft','--controller_dfs_time', help='--controller_dfs_time, time to wait prior to sending radar signal default 30s', default='30s') + parser.add_argument('-hrd','--radar_duration', help='--radar_duration, hack rf radar duration default 5s', default='5s') + parser.add_argument('-cco','--cap_ctl_out', help='--cap_ctl_out , switch the controller controller output will be captured', action='store_true') + + + parser.add_argument('-apr','--amount_ports_to_reset', help='--amount_ports_to_reset \" \" ', default=None) + parser.add_argument('-prs','--port_reset_seconds', help='--ports_reset_seconds \" \" ', default="10 30") + + parser.add_argument('-lm','--mgr', help='--mgr ',default='localhost') + parser.add_argument('-d','--test_duration', help='--test_duration example --time 5d (5 days) default: 2m options: number followed by d, h, m or s',default='2m') + parser.add_argument('-pi','--polling_interval', help="--polling_interval ", default='30s') + parser.add_argument('--tos', help='--tos: Support different ToS settings: BK | BE | VI | VO | numeric',default="BE") + parser.add_argument('-db','--debug', help='--debug: Enable debugging',action='store_true') + parser.add_argument('-t', '--endp_type', help='--endp_type example --endp_type \"lf_udp lf_tcp mc_udp\" Default: lf_tcp, options: lf_udp, lf_udp6, lf_tcp, lf_tcp6, mc_udp, mc_udp6', + default='lf_tcp', type=valid_endp_types) + parser.add_argument('-u', '--upstream_port', help='--upstream_port example: --upstream_port eth1',default='eth1') + parser.add_argument('-o','--csv_outfile', help="--csv_outfile ", default='dfs') + parser.add_argument("-l", "--log", action='store_true', help="create logfile for messages, default stdout") + parser.add_argument('-c','--csv_output', help="Generate csv output", default=True) + + #to do add wifimode + parser.add_argument('-r','--radio', action='append', nargs=1, help='--radio \ + \"radio== ssid== ssid_pw== security== wifimode==\" '\ + , required=False) + parser.add_argument('-amr','--side_a_min_bps', help='--side_a_min_bps, station min tx bits per second default 9600', default=9600) + parser.add_argument('-amp','--side_a_min_pdu', help='--side_a_min_pdu , station ipdu size default 1518', default=1518) + parser.add_argument('-bmr','--side_b_min_bps', help='--side_b_min_bps , upstream min tx rate default 256000', default=9600) + parser.add_argument('-bmp','--side_b_min_pdu', help='--side_b_min_pdu , upstream pdu size default 1518', default=1518) + + # AP parameters + parser.add_argument('-api','--ap_info', action='append', nargs=1, type=str, \ + help='(enter 0 if does not apply) --ap_info \"ap_scheme== ap_prompt== ap_ip== ap_port== ap_user== ap_pw== ap_tty==\" ') + #--ap_info "ap_scheme==serial ap_prompt==APA53.0E7B.CF9C ap_ip==0 ap_port==0 ap_user==admin ap_pw==Admin123 ap_tty==/dev/ttyUSB2" + + '''./lf_dfs_test.py -cc 192.168.100.112 -cu admin -cpw controller123 -cca APA453.0E7B.CF9C -ccf "a" -cwm "auto" -cc5 "36" \ + -ccw "20" -ccd "1" -cs "3504" --endp_type 'lf_udp' --upstream_port eth2 --controller_wlan "test_candela" --controller_wlanID 1 \ + --controller_wlanSSID "test_candela" --controller_directions "upstream" --controller_prompt "(controller Controller)" \ + --radio "radio==1.wiphy0 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto" \ + --ap_info "ap_scheme==serial ap_prompt--APA53.0E7B.EF9C ap_ip==0 ap_port==0 ap_baud==9600 ap_user==admin ap_pw==Admin123 ap_tty==/dev/ttyUSB2" ''' + + + # Parameters that allow for testing + parser.add_argument('-noc','--no_controller', help='--no_controller no configuration of the controller', action='store_true') + parser.add_argument('-nos','--no_stations', help='--no_stations , no stations', action='store_true') + parser.add_argument('-wto','--wait_timeout', help='--wait_timeout , time to wait for stations to get IP ', default="120") + + args = parser.parse_args() + + controller_args = args + + #logg.info("args: {}".format(args)) + debug_on = args.debug + + ################################################################## + # Gather Test Data + ################################################################# + + if args.test_duration: + test_duration = args.test_duration + + if args.polling_interval: + polling_interval = args.polling_interval + + if args.endp_type: + endp_types = args.endp_type + + if args.mgr: + lfjson_host = args.mgr + + if args.upstream_port: + side_b = args.upstream_port + + if args.radio: + radios = args.radio + + if args.csv_outfile != None: + current_time = time.strftime("%m_%d_%Y_%H_%M_%S", time.localtime()) + csv_outfile = "{}_{}.csv".format(args.csv_outfile,current_time) + csv_results = "results_{}_{}.csv".format(args.csv_outfile,current_time) + print("csv output file : {}".format(csv_outfile)) + + if args.log: + outfile_log = "{}_{}_output_log.log".format(args.outfile,current_time) + print("output file log: {}".format(outfile_log)) + else: + outfile_log = "stdout" + print("output file log: {}".format(outfile_log)) + + if args.wait_timeout: + wait_timeout = int(args.wait_timeout) + + if args.controller_scheme: + __scheme = args.controller_scheme + + if args.controller_port: + __port = args.controller_port + + if args.controller_ip: + __ctlr = args.controller_ip + + if args.controller_prompt: + __prompt = args.controller_prompt + + if args.controller_series: + __series = args.controller_series + + if args.controller_user: + __user = args.controller_user + + if args.controller_passwd: + __passwd = args.controller_passwd + + if args.cap_ctl_out: + __cap_ctl_out = args.cap_ctl_out + else: + __cap_ctl_out = False + + if args.controller_ap_slot: + __ap_slot = args.controller_ap_slot + + if args.controller_dfs: + __dfs = args.controller_dfs + else: + __dfs = False + + if args.controller_dfs_time: + __dfs_time = args.controller_dfs_time + + if args.radar_duration: + __radar_duration = args.radar_duration + + ap_dict = [] + if args.ap_info: + ap_info = args.ap_info + for _ap_info in ap_info: + print("ap_info {}".format(_ap_info)) + ap_keys = ['ap_scheme','ap_prompt','ap_ip','ap_port','ap_user','ap_pw', 'ap_tty', 'ap_baud'] + ap_dict = dict(map(lambda x: x.split('=='), str(_ap_info).replace('[','').replace(']','').replace("'","").split())) + for key in ap_keys: + if key not in ap_dict: + print("missing ap config, for the {}, all these need to be set {} ".format(key,ap_keys)) + exit(1) + print("ap_dict: {}".format(ap_dict)) + + + console_handler = logging.StreamHandler() + formatter = logging.Formatter(FORMAT) + logg = logging.getLogger(__name__) + logg.setLevel(logging.DEBUG) + file_handler = None + if (args.log): + file_handler = logging.FileHandler(outfile_log, "w") + + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + logg.addHandler(file_handler) + logg.addHandler(logging.StreamHandler(sys.stdout)) # allows to logging to file and stderr + # if loggin.basicConfig is called this will result in duplicating log entries + # logging.basicConfig(format=FORMAT, handlers=[file_handler]) + else: + # stdout logging + logging.basicConfig(format=FORMAT, handlers=[console_handler]) + + MAX_NUMBER_OF_STATIONS = 200 + + radio_name_list = [] + number_of_stations_per_radio_list = [] + ssid_list = [] + ssid_password_list = [] + ssid_security_list = [] + wifimode_list = [] + + #optional radio configuration + reset_port_enable_list = [] + reset_port_time_min_list = [] + reset_port_time_max_list = [] + + wifi_mode_dict = { + "auto" : "0", + "a" : "1", + "b" : "2", + "g" : "3", + "abg" : "4", + "abgn" : "5", + "bgn" : "6", + "bg" : "7", + "abgnAC" : "8", + "anAC" : "9", + "an" : "10", + "bgnAC" : "11", + "abgnAX" : "12", + "bgnAX" : "13", + "anAX" : "14" + } + + dfs_channel_bw20_values = [52, 56, 60, 64, 68, 96, 100, 104, 108, 112, 116, 120, 124 ,128, 132, 136, 140, 144] + + + + + controller_aps = args.controller_ap.split() + controller_bands = args.controller_band.split() + controller_wifimodes = args.controller_wifimode.split() + for mode in controller_wifimodes: + if mode in wifi_mode_dict.keys(): + pass + else: + logg.info("wifimode [{}] not recognised. Please use: auto, a, b, g, abg, abgn, bgn, bg, abgnAC, anAC, an, bgnAC, abgnAX, bgnAX, anAX".format(mode)) + exit(1) + controller_tx_powers = "3".split() + controller_chan_5ghzs = args.controller_chan_5ghz.split() + controller_chan_24ghzs = args.controller_chan_24ghz.split() + controller_chan_widths = args.controller_chan_width.split() + controller_ap_modes = args.controller_ap_mode.split() + controller_client_densities = args.controller_client_density.split() + controller_packet_types = args.endp_type.split() + controller_directions = args.controller_directions.split() + #controller_directions = "upstream".split() + controller_packet_sizes = args.controller_packet_size.split() + controller_data_encryptions = args.controller_data_encryption.split() + controller_side_a_min_bps = args.side_a_min_bps + controller_side_b_min_bps = args.side_b_min_bps + + + logg.info(controller_aps) + logg.info(controller_bands) + logg.info(controller_wifimodes) + logg.info(controller_tx_powers) + logg.info(controller_chan_5ghzs) + logg.info(controller_chan_24ghzs) + logg.info(controller_chan_widths) + logg.info(controller_ap_modes) + logg.info(controller_packet_types) + logg.info(controller_packet_sizes) + logg.info(controller_client_densities) + logg.info(controller_data_encryptions) + if bool(ap_dict): + logg.info("ap_dict {}".format(ap_dict)) + else: + logg.info("AP NO login information") + + + __ap_set = None + __band_set = None + __chan_width_set = None + __ap_mode_set = None + __tx_power_set = None + __chan_5ghz_set = None + __chan_24ghz_set = None + __csv_started = False + + __dfs_channel = None + __cac_timer_time = 0 + __dfs_chan_switch_to = None + + for controller_ap in controller_aps: + for controller_band in controller_bands: # frequency + for controller_wifimode in controller_wifimodes: + # check for valid frequency and wifi_mode combination put here to simplify logic since all radios do not support all modes + # "an anAX anAC abgn bg" + if((controller_band == "a" and controller_wifimode == "bg") or (controller_band == "b" and controller_wifimode == "anAC")): + logg.info("#######################################################################") + logg.info("# Skipping combination controller_band {} controller_wifimode {}".format(controller_band,controller_wifimode)) + logg.info("#######################################################################") + pass # invalid combination continue + else: + # TODO the following + #[(x, y, z) for x in [1,2,3] for y in [4,5,6] for z in [7,8,9] if x != z]: + for controller_tx_power in controller_tx_powers: + for controller_chan_5ghz in controller_chan_5ghzs: + for controller_chan_24ghz in controller_chan_24ghzs: + for controller_chan_width in controller_chan_widths: #bandwidth + for controller_data_encryption in controller_data_encryptions: + for controller_ap_mode in controller_ap_modes: + for controller_client_density in controller_client_densities: + for controller_packet_type in controller_packet_types: + for controller_direction in controller_directions: + for controller_packet_size in controller_packet_sizes: + logg.info("#####################################################") + logg.info("# TEST RUNNING , TEST RUNNING ######################") + logg.info("#####################################################") + test_config = "AP=={} Band=={} chan_5ghz=={} chan_24ghz=={} wifi_mode=={} BW=={} encryption=={} ap_mode=={} clients=={} packet_type=={} direction=={} packet_size=={}".format( + controller_ap,controller_band,controller_chan_5ghz,controller_chan_24ghz,controller_wifimode,controller_chan_width,controller_data_encryption,controller_ap_mode,controller_client_density, + controller_packet_type,controller_direction,controller_packet_size) + test_keys = ['AP','Band','wifi_mode','chan_5ghz','chan_24ghz','BW','encryption','ap_mode','clients','packet_type','direction','packet_size'] + logg.info("# controller run settings: {}".format(test_config)) + if(args.no_controller): + logg.info("################################################") + logg.info("# NO CONTROLLER SET , TEST MODE") + logg.info("################################################") + else: + if( controller_ap != __ap_set or + controller_band != __band_set or + controller_chan_width != __chan_width_set or + controller_ap_mode != __ap_mode_set or + controller_tx_power != __tx_power_set or + controller_chan_5ghz != __chan_5ghz_set or + controller_chan_24ghz != __chan_24ghz_set + ): + logg.info("###############################################") + logg.info("# NEW CONTROLLER CONFIG") + logg.info("###############################################") + __ap_set = controller_ap + __band_set = controller_band + __chan_width_set = controller_chan_width + __ap_mode_set = controller_ap_mode + __tx_power_set = controller_tx_power + __chan_5ghz_set = controller_chan_5ghz + __chan_24ghz_set = controller_chan_24ghz + __client_density = controller_client_density + controller = CreateCtlr( + _scheme=__scheme, + _port=__port, + _series=__series, + _ctlr=__ctlr, + _prompt=__prompt, + _user=__user, + _passwd=__passwd, + _ap=__ap_set, + _band=__band_set, + _chan_5ghz=__chan_5ghz_set, + _chan_24ghz=__chan_24ghz_set, + _chan_width=__chan_width_set, + _ap_mode=__ap_mode_set, + _tx_power=__tx_power_set, + _client_density=__client_density, + _cap_ctl_out=__cap_ctl_out + ) + #Disable AP + # + # Controller Configuration + # + #if controller_args.controller_series == "9800": + # controller_controller_no_loggin_console() + pss = controller.controller_show_ap_summary() + logg.info("pss {}".format(pss)) + controller.controller_disable_ap() + if controller_args.controller_series == "9800": + controller.controller_disable_wlan() + controller.controller_disable_network_5ghz() + controller.controller_disable_network_24ghz() + controller.controller_role_manual() + else: + controller.controller_disable_network_5ghz() + controller.controller_disable_network_24ghz() + controller.controller_set_tx_power() + controller.controller_set_bandwidth_20() + controller.controller_set_channel() + controller.controller_set_bandwidth() + if controller_args.controller_series == "9800": + controller.controller_create_wlan() + controller.controller_set_wireless_tag_policy() + controller.controller_enable_wlan() + if controller_band == "a": + controller.controller_enable_network_5ghz() + else: + controller.controller_enable_network_24ghz() + # clear logs on AP /dev/ttyUSB2 - candelatech + if(bool(ap_dict)): + logg.info("ap_dict {}".format(ap_dict)) + logg.info("Read AP action: {} ap_scheme: {} ap_ip: {} ap_port: {} ap_user: {} ap_pw: {} ap_tty: {} ap_baud: {}".format("show_log",ap_dict['ap_scheme'],ap_dict['ap_ip'],ap_dict["ap_port"], + ap_dict['ap_user'],ap_dict['ap_pw'],ap_dict['ap_tty'],ap_dict['ap_baud'])) + + # clear log (AP) + try: + logg.info("ap_ctl.py: clear log") + # TODO remove position dependence if in tree + ap_info= subprocess.run(["./../ap_ctl.py", "--scheme", ap_dict['ap_scheme'], "--prompt", ap_dict['ap_prompt'],"--dest", ap_dict['ap_ip'], "--port", ap_dict["ap_port"], + "--user", ap_dict['ap_user'], "--passwd", ap_dict['ap_pw'],"--tty", ap_dict['ap_tty'],"--baud", ap_dict['ap_baud'],"--action", "clear_log"],capture_output=True, check=True)#stdout=subprocess.PIPE) + try: + pss = ap_info.stdout.decode('utf-8', 'ignore') + except: + logg.info("ap_info was of type NoneType will set pss empty") + pss = "empty" + except subprocess.CalledProcessError as process_error: + logg.info("####################################################################################################") + logg.info("# CHECK IF AP HAS CONNECTION ALREADY ACTIVE") + logg.info("####################################################################################################") + logg.info("####################################################################################################") + logg.info("# Unable to commicate to AP error code: {} output {}".format(process_error.returncode, process_error.output)) + logg.info("####################################################################################################") + logg.info(pss) + + # show log (AP) + try: + logg.info("ap_ctl.py: show log") + # TODO remove position dependence if in tree + ap_info= subprocess.run(["./../ap_ctl.py", "--scheme", ap_dict['ap_scheme'], "--prompt", ap_dict['ap_prompt'],"--dest", ap_dict['ap_ip'], "--port", ap_dict["ap_port"], + "--user", ap_dict['ap_user'], "--passwd", ap_dict['ap_pw'],"--tty", ap_dict['ap_tty'],"--baud", ap_dict['ap_baud'],"--action", "show_log"],capture_output=True, check=True) #stdout=subprocess.PIPE + try: + pss = ap_info.stdout.decode('utf-8', 'ignore') + except: + logg.info("ap_info was of type NoneType will set pss empty") + pss = "empty" + except subprocess.CalledProcessError as process_error: + logg.info("####################################################################################################") + logg.info("# CHECK IF AP HAS CONNECTION ALREADY ACTIVE") + logg.info("####################################################################################################") + logg.info("####################################################################################################") + logg.info("# Unable to commicate to AP error code: {} output {}".format(process_error.returncode, process_error.output)) + logg.info("####################################################################################################") + logg.info(pss) + + controller.controller_enable_ap() + # need to actually check the CAC timer + time.sleep(10) + # When the AP moves to another DFS channel, the wait time is 60 second + # the CAC (Channel Avaiability Check Time) + if (int(__chan_5ghz_set) in dfs_channel_bw20_values): + logg.info("DFS 5ghz channel {} being set wait CAC time 60, 2.4 ghz: {} : ".format(__chan_5ghz_set, __chan_24ghz_set)) + # read AP to verify CAC timer set + # will need to use time to verify CAC from AP - need in results + cac_sleeptime = "65" # 65 + logg.info("CAC start sleeptime: {}".format(cac_sleeptime)) + time.sleep(int(cac_sleeptime)) + logg.info("CAC done sleeptime: {}".format(cac_sleeptime)) + if(bool(ap_dict)): + # will need to verify that timer has timed out on AP - need in results + logg.info("DFS channel 5ghz {} done waiting CAC time, 2.4 ghz: {}".format(__chan_5ghz_set, __chan_24ghz_set)) + logg.info("####################################################################################################") + logg.info("# READ changed to DFS channel {}, running CAC for 60 seconds.".format(__chan_5ghz_set)) + logg.info("# READ AP CAC_EXPIRY_EVT: CAC finished on DFS channel ") + logg.info("####################################################################################################") + + logg.info("ap_dict {}".format(ap_dict)) + logg.info("Read AP action: {} ap_scheme: {} ap_ip: {} ap_port: {} ap_user: {} ap_pw: {} ap_tty: {} ap_baud: {}".format("show_log",ap_dict['ap_scheme'],ap_dict['ap_ip'],ap_dict["ap_port"], + ap_dict['ap_user'],ap_dict['ap_pw'],ap_dict['ap_tty'],ap_dict['ap_baud'],)) + + try: + logg.info("ap_ctl.py: read for CAC timer and CAC_EXPIRY_EVT") + # TODO remove position dependence if in tree + #ap_info= subprocess.run(["./../ap_ctl.py", "--scheme", ap_dict['ap_scheme'], "--prompt", ap_dict['ap_prompt'],"--dest", ap_dict['ap_ip'], "--port", ap_dict["ap_port"], + # "--user", ap_dict['ap_user'], "--passwd", ap_dict['ap_pw'],"--tty", ap_dict['ap_tty'],"--baud", ap_dict['ap_baud'],"--action", "cac_expiry_evt"],capture_output=True, check=True) + ap_info= subprocess.run(["./../ap_ctl.py", "--scheme", ap_dict['ap_scheme'], "--prompt", ap_dict['ap_prompt'],"--dest", ap_dict['ap_ip'], "--port", ap_dict["ap_port"], + "--user", ap_dict['ap_user'], "--passwd", ap_dict['ap_pw'],"--tty", ap_dict['ap_tty'],"--baud", ap_dict['ap_baud'],"--action", "show_log"],capture_output=True, check=True) + + try: + pss = ap_info.stdout.decode('utf-8', 'ignore') + except: + logg.info("ap_info was of type NoneType will set pss empty") + pss = "empty" + + except subprocess.CalledProcessError as process_error: + logg.info("####################################################################################################") + logg.info("# CHECK IF AP HAS CONNECTION ALREADY ACTIVE") + logg.info("####################################################################################################") + + logg.info("####################################################################################################") + logg.info("# Unable to commicate to AP error code: {} output {}".format(process_error.returncode, process_error.output)) + logg.info("####################################################################################################") + + logg.info(pss) + # find the DFS Channel + for line in pss.splitlines(): + logg.info("ap: {}".format(line)) + pat = 'CAC_EXPIRY_EVT:\s+\S+\s+\S+\s+\S+\s\S+\s\S+\s(\S+)' + m = re.search(pat, line) + if (m != None): + __dfs_channel = m.group(1) + logg.info("__dfs_channel: {}".format(__dfs_channel)) + logg.info("__dfs_channel line: {}".format(line)) + break + else: + logg.info("Non-DFS 5ghz channel {} being set sleep 30, 2.4 ghz: {} ".format(__chan_5ghz_set, __chan_24ghz_set)) + time.sleep(30) + ########################################## + # end of controller controller code + ########################################## + + else: + logg.info("###############################################") + logg.info("# NO CHANGE TO CONTROLLER CONFIG") + logg.info("###############################################") + logg.info("controller_ap: {} controller_band: {} controller_chan_width: {} controller_ap_mode: {} controller_tx_power: {} controller_chan_5ghz: {} controller_chan_24ghz: {}" + .format(controller_ap,controller_band, controller_chan_width, controller_ap_mode, controller_tx_power, controller_chan_5ghz, controller_chan_24ghz)) + logg.info("__ap_set: {} __band_set: {} __chan_width_set: {} __ap_mode_set: {} __tx_power_set: {} __chan_5ghz_set: {} __chan_24ghz_set: {}" + .format(__ap_set,__band_set, __chan_width_set, __ap_mode_set, __tx_power_set, __chan_5ghz_set, __chan_24ghz_set)) + logg.info("controller_wifi_mode {}".format(controller_wifimode)) + pss = controller.controller_show_ap_summary() + logg.info("controller_show_ap_summary::: pss {}".format(pss)) + if args.controller_series == "9800": + searchap = False + cc_mac = "" + cc_ch = "" + cc_bw = "" + cc_power = "" + cc_dbm = "" + for line in pss.splitlines(): + if (line.startswith("---------")): + searchap = True + continue + # if the pattern changes save the output of the advanced command and re parse https://regex101.com + if (searchap): + pat = "%s\s+(\S+)\s+(%s)\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+(\S+)\s+dBm\)+\s+(\S+)+\s"%(__ap_set,__ap_slot) + m = re.search(pat, line) + if (m != None): + if(m.group(2) == __ap_slot): + cc_mac = m.group(1) + cc_slot = m.group(2) + cc_ch = m.group(6); # (132,136,140,144) + cc_power = m.group(4) + cc_power = cc_power.replace("/", " of ") # spread-sheets turn 1/8 into a date + cc_dbm = m.group(5) + cc_dbm = cc_dbm.replace("(","") + + cc_ch_count = cc_ch.count(",") + 1 + cc_bw = m.group(3) + logg.info("group 1: {} 2: {} 3: {} 4: {} 5: {} 6: {}".format(m.group(1),m.group(2),m.group(3),m.group(4),m.group(5),m.group(6))) + + logg.info("9800 test_parameters cc_mac: read : {}".format(cc_mac)) + logg.info("9800 test_parameters cc_slot: read : {}".format(cc_slot)) + logg.info("9800 test_parameters cc_count: read : {}".format(cc_ch_count)) + logg.info("9800 test_parameters cc_bw: read : {}".format(cc_bw)) + logg.info("9800 test_parameters cc_power: read : {}".format(cc_power)) + logg.info("9800 test_parameters cc_dbm: read : {}".format(cc_dbm)) + logg.info("9800 test_parameters cc_ch: read : {}".format(cc_ch)) + break + else: + searchap = False + cc_mac = "" + cc_ch = "" + cc_bw = "" + cc_power = "" + cc_dbm = "" + ch_count = "" + for line in pss.splitlines(): + if (line.startswith("---------")): + searchap = True + continue + + if (searchap): + pat = "%s\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\S+)\s+(\S+)\s+\(\s*(\S+)\s+dBm"%(__ap_set) + m = re.search(pat, line) + if (m != None): + cc_mac = m.group(1) + cc_ch = m.group(2); # (132,136,140,144) + cc_power = m.group(3) + cc_power = cc_power.replace("/", " of ", 1) # spread-sheets turn 1/8 into a date + cc_dbm = m.group(4) + + ch_count = cc_ch.count(",") + cc_bw = 20 * (ch_count + 1) + + logg.info("3504 test_parameters cc_mac: read : {}".format(cc_mac)) + logg.info("3504 test_parameters cc_count: read : {}".format(ch_count)) + logg.info("3504 test_parameters cc_bw: read : {}".format(cc_bw)) + logg.info("3504 test_parameters cc_power: read : {}".format(cc_power)) + logg.info("3504 test_parameters cc_dbm: read : {}".format(cc_dbm)) + logg.info("3504 test_parameters cc_ch: read : {}".format(cc_ch)) + break + + if(cc_ch != controller_chan_5ghz): + logg.info("configured channel {} not equal controller channel {}".format(controller_chan_5ghz,cc_ch)) + ###################################################### + # end of controller controller code no change to controller + ###################################################### + if args.radio: + radios = args.radio + logg.info("radios {}".format(radios)) + for radio_ in radios: + radio_keys = ['radio','stations','ssid','ssid_pw','security','wifimode'] + radio_info_dict = dict(map(lambda x: x.split('=='), str(radio_).replace('[','').replace(']','').replace("'","").split())) + logg.info("radio_dict {}".format(radio_info_dict)) + for key in radio_keys: + if key not in radio_info_dict: + logg.info("missing config, for the {}, all of the following need to be present {} ".format(key,radio_keys)) + exit(1) + radio_name_list.append(radio_info_dict['radio']) + ssid_list.append(radio_info_dict['ssid']) + ssid_password_list.append(radio_info_dict['ssid_pw']) + ssid_security_list.append(radio_info_dict['security']) + if args.radio: + number_of_stations_per_radio_list.append(radio_info_dict['stations']) + wifimode_list.append(int(wifi_mode_dict[radio_info_dict['wifimode']])) + else: + number_of_stations_per_radio_list.append(radio_info_dict['stations']) + wifimode_list.append(int(wifi_mode_dict[radio_info_dict['wifimode']])) + optional_radio_reset_keys = ['reset_port_enable'] + radio_reset_found = True + for key in optional_radio_reset_keys: + if key not in radio_info_dict: + #logg.info("port reset test not enabled") + radio_reset_found = False + break + + if radio_reset_found: + reset_port_enable_list.append(True) + reset_port_time_min_list.append(radio_info_dict['reset_port_time_min']) + reset_port_time_max_list.append(radio_info_dict['reset_port_time_max']) + else: + reset_port_enable_list.append(False) + reset_port_time_min_list.append('0s') + reset_port_time_max_list.append('0s') + # no stations for testing reconfiguration of the controller - + if(args.no_stations): + logg.info("##################################") + logg.info("# NO STATIONS") + logg.info("##################################") + else: + index = 0 + station_lists = [] + for (radio_name_, number_of_stations_per_radio_) in zip(radio_name_list,number_of_stations_per_radio_list): + number_of_stations = int(number_of_stations_per_radio_) + if number_of_stations > MAX_NUMBER_OF_STATIONS: + logg.info("number of stations per radio exceeded max of : {}".format(MAX_NUMBER_OF_STATIONS)) + quit(1) + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_= 1 + index*1000, end_id_= number_of_stations + index*1000, + padding_number_=10000, radio=radio_name_) + station_lists.append(station_list) + index += 1 + # Run Traffic Upstream (STA to AP) + if(controller_direction == "upstream"): + side_a_min_bps = controller_side_a_min_bps + side_b_min_bps = 0 + # Run Traffic Downstream (AP to STA) + else: + side_a_min_bps = 0 + side_b_min_bps = controller_side_b_min_bps + # current default is to have a values + ip_var_test = L3VariableTime( + args=args, + _dfs=__dfs, + _dfs_time=__dfs_time, + _radar_duration=__radar_duration, + _scheme=__scheme, + _port=__port, + _series=__series, + _ctlr=__ctlr, + _prompt=__prompt, + _user=__user, + _passwd=__passwd, + _ap=__ap_set, + _ap_slot=__ap_slot, + _band=__band_set, + _chan_5ghz=__chan_5ghz_set, + _chan_24ghz=__chan_24ghz_set, + _chan_width=__chan_width_set, + _ap_mode=__ap_mode_set, + _tx_power=__tx_power_set, + _client_density=__client_density, + _cap_ctl_out=__cap_ctl_out, + _ap_dict = ap_dict, + endp_type=controller_packet_type, + tos=args.tos, + side_b=side_b, + radio_name_list=radio_name_list, + number_of_stations_per_radio_list=number_of_stations_per_radio_list, + ssid_list=ssid_list, + ssid_password_list=ssid_password_list, + ssid_security_list=ssid_security_list, + wifimode_list=wifimode_list, + station_lists= station_lists, + name_prefix="LT-", + debug_on=debug_on, + wait_timeout=wait_timeout, + outfile=csv_outfile, + results=csv_results, + test_keys=test_keys, + test_config=test_config, + reset_port_enable_list=reset_port_enable_list, + reset_port_time_min_list=reset_port_time_min_list, + reset_port_time_max_list=reset_port_time_max_list, + csv_started=__csv_started, + side_a_min_bps =side_a_min_bps, + side_a_max_bps =0, + side_a_min_pdu =controller_packet_size, + side_a_max_pdu =0, + side_b_min_bps =side_b_min_bps, + side_b_max_bps =0, + side_b_min_pdu =controller_packet_size, + side_b_max_pdu = 0, + number_template="00", + test_duration=test_duration, + polling_interval= polling_interval, + lfclient_host=lfjson_host, + lfclient_port=lfjson_port) + __csv_started = True + ip_var_test.pre_cleanup() + ip_var_test.build() + if not ip_var_test.passes(): + logg.info("build step failed.") + logg.info(ip_var_test.get_fail_message()) + exit(1) + client_density = ip_var_test.station_bringup() + #controller.verify_controller(client_density) + ip_var_test.start(False, False) + ip_var_test.stop() + if not ip_var_test.passes(): + logg.info("stop test failed") + logg.info(ip_var_test.get_fail_message()) + # clean up + radio_name_list = [] + number_of_stations_per_radio_list = [] + ssid_list = [] + ssid_password_list = [] + ssid_security_list = [] + wifimode_list = [] + ip_var_test.cleanup() + if ( args.no_stations): + pass + else: + ip_var_test.passes() + logg.info("Test Complete") + +if __name__ == "__main__": + main() + + +''' +SAMPLE Command 2/15/2021 +./lf_dfs_test.py -cc 192.168.100.112 -cu admin -cpw Controller123 -cca APA453.0E7B.CF9C -ccf "a" -cwm "auto" -cc5 "52 56 60 64 68 96 100 104 108 112 116 120 124 128 132 136 140 144" -ccw "20" -ccd "1" -cs "3504" --endp_type 'lf_udp' --upstream_port eth2 --controller_wlan "test_candela" --controller_wlanID 1 --controller_wlanSSID "test_candela" --controller_directions "upstream" --controller_prompt "(controller Controller)" --radio "radio==1.wiphy0 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto" + +SAMPLE Command with AP (need root if using serial) +sudo ./lf_dfs_test.py -cc 192.168.100.112 -cu admin -cpw Controller123 -cca APA453.0E7B.CF9C -ccf "a" -cwm "auto" -cc5 "56" -ccw "20" -ccd "1" -cs "3504" --endp_type 'lf_udp' --upstream_port eth2 --controller_wlan "test_candela" --controller_wlanID 1 --controller_wlanSSID "test_candela" --controller_directions "upstream" --controller_prompt "(controller Controller)" --radio "radio==1.wiphy0 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto" --ap_info "ap_scheme==serial ap_prompt==APA453.0E7B.CF9C ap_ip==0 ap_port==0 ap_user==admin ap_pw==Admin123 ap_tty==/dev/ttyUSB2 ap_baud==9600" --controller_dfs +''' + diff --git a/lanforge/lanforge-scripts/py-scripts/lf_dut_sta_vap_test.py b/lanforge/lanforge-scripts/py-scripts/lf_dut_sta_vap_test.py new file mode 100755 index 000000000..3b56a9a19 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_dut_sta_vap_test.py @@ -0,0 +1,481 @@ +#!/usr/bin/env python3 +''' + This Script has two classes : + 1. LoadScenario : It will load the existing saved scenario to the Lanforge (Here used for Loading Bridged VAP) + 2. CreateSTA_CX : It will create stations and L3 Cross connects and start them + 3. Login_DUT : This class is specifically used to test the Linux based DUT that has SSH Server. It is used to read the CPU Core temperature during testing + In this example, Another Lanforge is used as DUT + It also have a function : GenerateReport that generates the report in xlsx format as well as it plots the Graph of throughput over time with temperature + It also have Plot function that generates a html page that contains the plot + + + Prerequisite + Start the Lanforge Manager both Sides + + Installation + pip install paramiko + pip install bokeh + pip install XlsxWriter + + Example + .\Lexus_Final.py --lf_host 192.168.200.15 --dut_host 192.168.200.18 --dut_radio wiphy1 --lf_radio wiphy1 --num_sta 1 --sta_id 1 --lf_ssid lanforge_ap --dut_ssid lexusap --security open --dut_upstream eth2 --lf_upstream eth1 --protocol lf_udp --min_bps 1000 --max_bps 10000 --time 1 + This Script is intended to automate the testing of DUT that has stations as well as AP. + To automate the simultaenous testing and check the DUT Temperature +''' +import sys +import os +import importlib +import argparse +import time +import logging +import paramiko as pm +from paramiko.ssh_exception import NoValidConnectionsError as exception +import xlsxwriter +from bokeh.io import show +from bokeh.plotting import figure +from bokeh.models import LinearAxis, Range1d + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +# Specifically for Measuring CPU Core Temperatures +class Login_DUT: + + def __init__(self, threadID, name, HOST): + self.threadID = threadID + self.name = name + self.host=HOST + self.USERNAME = "lanforge" + self.PASSWORD = "lanforge" + self.CLIENT= pm.SSHClient() + self.LF1= self.Connect() + self.data_core1=[] + self.data_core2=[] + if self.CLIENT == 0: + exit() + print("Connected to " +HOST+" DUT to Measure the Core Temperature") + def run(self): + stdin, stdout, stderr= self.CLIENT.exec_command("sensors") + out_lines = stdout.readlines() + err_lines = stderr.readlines() + print(out_lines[len(out_lines)-3], out_lines[len(out_lines)-2]) + self.data_core1.append(out_lines[len(out_lines)-3]) + self.data_core2.append(out_lines[len(out_lines)-2]) + + + def Connect(self): + self.CLIENT.load_system_host_keys() + self.CLIENT.set_missing_host_key_policy(pm.AutoAddPolicy()) + try: + self.CLIENT.connect(self.host, username=self.USERNAME, password=self.PASSWORD,timeout=10) + return None + except exception as error: + self.CLIENT = 0; + return None + + +# Class to Load a Scenario that has been Created in Chamber View saved under DB/[Database_Name] +class LoadScenario(LFCliBase): + def __init__(self, host, port, db_name, security_debug_on=False, _exit_on_error=False,_exit_on_fail=False): + super().__init__(host, port, _debug=security_debug_on, _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.json_post("/cli-json/load", { "name": db_name, "action": 'overwrite' }) + print(host+ " : Scenario Loaded...") + time.sleep(2) + +# Class to create stations and run L3 Cross connects and run them for given time. It also stores the endpoint names for measuring throughput +class CreateSTA_CX(LFCliBase): + + def __init__(self, host, port, radio, num_sta, sta_id, ssid, security, password, upstream, protocol, min_bps, max_bps, security_debug_on=True, _exit_on_error=True, _exit_on_fail=True): + super().__init__(host, port, _debug=security_debug_on, _exit_on_fail=_exit_on_fail) + + self.host = host + self.port = port + self.radio = radio + + self.num_sta = num_sta + self.sta_id = sta_id + + self.ssid = ssid + + self.security = security + self.password = password + + self.upstream = upstream + self.protocol = protocol + + self.min_bps =min_bps + self.max_bps =max_bps + + #Creating a Realm Object + self.local_realm = Realm(lfclient_host=host, lfclient_port=port) + + #Creating Profile Objects + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_l3_cx_profile() + + #Setting CX Name + self.cx_profile.name_prefix_="Connection" + self.cx_names = [] + self.sta_list = [] + self.endp=[] + for i in range(sta_id,sta_id+num_sta): + self.sta_list.append("sta00") + + #portDhcpUpRequest + ''' + upstream_dhcp = LFRequest.LFRequest("http://"+str(host)+":"+str(port)+"/"+"/cli-form/set_port") + upstream_dhcp.addPostData( LFUtils.portSetDhcpDownRequest(1, upstream)) + upstream_dhcp.formPost() + time.sleep(2) + upstream_dhcp.addPostData( LFUtils.portUpRequest(1, upstream)) + upstream_dhcp.formPost() + print(upstream + "Set to DHCP For Cross Connects") + ''' + + def build(self): + + #Creating Stations of Given Profile Settings + self.station_profile.use_security(self.security, self.ssid, passwd=self.password) + self.station_profile.create(self.radio, num_stations=self.num_sta, sta_names_=self.sta_list) + self.station_profile.admin_up() + #Wait for a while + time.sleep(15) + + #Setting up the Parameters for CX + self.cx_profile.side_a_min_bps = self.min_bps + self.cx_profile.side_b_min_bps = self.min_bps + self.cx_profile.side_a_max_bps = self.max_bps + self.cx_profile.side_b_max_bps = self.max_bps + + self.cx_profile.side_a_min_pdu = 'Auto' + self.cx_profile.side_b_min_pdu = 'Auto' + self.cx_profile.report_timer = 1000 + self.cx_profile.side_a_min_pkt='Same' + self.cx_profile.side_a_max_pkt='Same' + + #Create Connections of Given Parameters + self.cx_profile.create(self.protocol, side_a="1.1."+self.upstream, side_b=list(self.local_realm.find_ports_like("sta0+"))) + time.sleep(15) + + # Getting all the Endpoint Names for measuring Throughput Later + for i in self.cx_profile.get_cx_names(): + self.cx_names.append(i) + for j in self.cx_names: + x=self.local_realm.json_get("/cx/"+j) + self.endp.append(x.get(j).get('endpoints')[1]) + #print(self.endp) + return 0 + + + def start(self): + #self.station_profile.admin_up() + + self.cx_profile.start_cx() + time.sleep(5) + return 0 + + def stop(self): + self.cx_profile.stop_cx() + time.sleep(5) + self.lf_stations.admin_down() + time.sleep(5) + return 0 + + def cleanup(self): + # Removing Connections + self.local_realm.cleanup_cxe_prefix(self.cx_profile.name_prefix) + + vap = self.local_realm.find_ports_like("vap+") + bridges = self.local_realm.find_ports_like("br+") + station_map = self.local_realm.find_ports_like("sta+") + #Removing Bridges + for eid,record in bridges.items(): + self.local_realm.remove_vlan_by_eid(eid) + time.sleep(0.03) + #Removing VAP + for eid,record in vap.items(): + self.local_realm.remove_vlan_by_eid(eid) + time.sleep(0.03) + + #Removing stations + for eid,record in station_map.items(): + self.local_realm.remove_vlan_by_eid(eid) + time.sleep(0.03) + del_sta_names = [] + try: + for eid,value in station_map.items(): + tname = eid[eid.rfind('.'):] + del_sta_names.append(tname) + except Exception as x: + self.local_realm.error(x) + try: + LFUtils.waitUntilPortsDisappear(base_url=self.local_realm.lfclient_url, port_list=del_sta_names, debug=True) + print("Ports Successfully Cleaned up") + return 0 + except: + print("Ports Successfully Cleaned up") + time.sleep(5) + return 0 + + +# Generates XLSX Report +def GenerateReport(throughput_sta, throughput_vap, core1_temp, core2_temp, duration,name): + workbook = xlsxwriter.Workbook(name) + worksheet = workbook.add_worksheet() + worksheet.write('A1', 'THROUGHPUT OVER TIME STA CX ') + worksheet.write('B1', 'THROUGHPUT OVER TIME VAP ') + worksheet.write('C1', 'CORE 0 TEMP') + worksheet.write('D1', 'CORE 1 TEMP') + core1=[] + core2=[] + sta_throu=[] + vap_throu=[] + j=2 + for i in throughput_sta: + sta_throu.append(i/1000000) + worksheet.write('A'+str(j), str(i/1000000)+" Mbps") + j=j+1 + j=2 + for i in throughput_vap: + vap_throu.append(i/1000000) + worksheet.write('B'+str(j), str(i/1000000)+" Mbps") + j=j+1 + j=2 + for i in core1_temp: + core1.append(int(str(i).split(':')[1].split('(')[0].split('.')[0].split('+')[1])) + worksheet.write('C'+str(j),str(i).split(':')[1].split('(')[0] ) + j=j+1 + j=2 + for i in core2_temp: + core2.append(int(str(i).split(':')[1].split('(')[0].split('.')[0].split('+')[1])) + worksheet.write('D'+str(j), str(i).split(':')[1].split('(')[0]) + j=j+1 + + Time =[] + for i in range(0,int(duration)*5): + Time.append(i) + plot(sta_throu, vap_throu, core1, core2, Time) + workbook.close() + + +# Plotting Function for Parameters +def plot(throughput_sta, throughput_vap, core1_temp, core2_temp, Time): + + + s1 = figure() + s1.title.text = "WIFI Throughput vs Temperature Plot" + s1.xaxis.axis_label = "Time in Seconds" + s1.yaxis.axis_label = "Throughput in Mbps" + + s1.line( Time, throughput_sta, color='black') + #s1.circle(Time, throughput_sta, color='red') + + s1.line( Time, throughput_vap, color='blue') + #s1.circle(Time, throughput_vap, color='blue') + + s1.extra_y_ranges = {"Temperature": Range1d(start=0, end=150)} + s1.add_layout(LinearAxis(y_range_name="Temperature", axis_label="Temperature in Degree Celsius"), 'right') + + s1.line(Time, core1_temp, y_range_name='Temperature', color='red') + #s1.circle(Time, core1_temp, y_range_name='Temperature', color='red') + + s1.line(Time, core2_temp, y_range_name='Temperature', color='green') + #s1.circle(Time, core2_temp, y_range_name='Temperature', color='blue') + + show(s1) + + +# Creates the Instance for LFCliBase +class VAP_Measure(LFCliBase): + def __init__(self, lfclient_host, lfclient_port): + super().__init__(lfclient_host, lfclient_port) + + + +# main method +def main(): + + parser = argparse.ArgumentParser( + prog='lf_dut_sta_vap_test.py', + formatter_class=argparse.RawTextHelpFormatter, + description="Test Scenario of DUT Temperature measurement along with simultaneous throughput on VAP as well as stations") + + parser.add_argument("-m", "--manager", type=str, help="Enter the address of Lanforge Manager (By default localhost)") + parser.add_argument("-sc", "--scenario", type=str, help="Enter the Name of the Scenario you want to load (by Default DFLT)") + parser.add_argument("-r", "--radio", type=str, help="Enter the radio on which you want to create a station/s on ") + parser.add_argument("-n", "--num_sta", type=int, help="Enter the Number of Stations You want to create") + parser.add_argument("-i", "--sta_id", type=int, help="Enter Station id [for sta001, enter 1]") + parser.add_argument("-ss", "--ssid", type=str, help="Enter the ssid, with which you want to associate your stations (Enter the SSID of DUT AP)") + parser.add_argument("-up", "--upstream", type=str, help="Enter the upstream ethernet port") + parser.add_argument("-sec", "--security", type=str, help="Enter the security type [open, wep, wpa, wpa2]") + parser.add_argument("-p", "--password", type=str, help="Enter the password if security is not open") + parser.add_argument("-pr", "--protocol", type=str, help="Enter the protocol on which you want to run your connections [lf_udp, lf_tcp]") + parser.add_argument("-mn", "--min_mbps", type=str, help="Enter the Minimum Rate") + parser.add_argument("-mx", "--max_mbps", type=str, help="Enter the Maximum Rate") + parser.add_argument("-t", "--duration", type=int, help="Enter the Time for which you want to run test (In Minutes)") + parser.add_argument("-o", "--report_name", type=str, help="Enter the Name of the Output file ('Report.xlsx')") + args = None + + try: + args = parser.parse_args() + # Lanforge Manager IP Address + if (args.manager is None): + manager = "localhost" + if (args.manager is not None): + manager = args.manager + + # Scenario Name + if (args.scenario is not None): + scenario = args.scenario + # Radio Name + if (args.radio is not None): + radio = args.radio + + # Number of Stations + if (args.num_sta is None): + num_sta = 0 + if (args.num_sta is not None): + num_sta = args.num_sta + + # Station ID + if (args.sta_id is None): + sta_id = '0' + if (args.sta_id is not None): + sta_id = args.sta_id + + # SSID + if (args.ssid is not None): + ssid = args.ssid + if (args.ssid is not None): + ssid = args.ssid + + # Security (Open by Default) + if (args.security is None): + security = 'open' + if (args.security is not None): + security = args.security + + # Password (if Security is not Open) + if (args.password is not None): + password = args.password + if (args.password == 'open'): + password = "[Blank]" + if (args.password is None): + password = "[Blank]" + + # Upstream Port (By default br0000) + if (args.upstream is None): + upstream = 'br0000' + if (args.upstream is not None): + upstream = args.upstream + + # Protocol (By Default lf_udp) + if (args.protocol is not None): + protocol = args.protocol + if (args.protocol is None): + protocol = 'lf_udp' + + #Min BPS + if (args.min_mbps is not None): + min_bps = int(args.min_mbps)*1000000 + if (args.min_mbps is None): + min_bps = int(1000)*1000000 + if (args.max_mbps is None ): + max_bps = int(1000)*1000000 + + if (args.min_mbps is not None): + min_bps = int(args.min_mbps)*1000000 + if (args.max_mbps is not None and args.max_mbps != "same"): + max_bps = int(args.max_mbps)*1000000 + if (args.max_mbps is not None and args.max_mbps == "same"): + max_bps = args.min_mbps + if (args.duration is not None): + duration = (args.duration * 60)/5 + if (args.report_name is not None): + report_name = args.report_name + if (args.duration is None): + duration = (1 * 60)/5 + if (args.report_name is None): + report_name = "report.xlsx" + except Exception as e: + logging.exception(e) + + exit(2) + + + + # Start DUT + + + #Loading the Scenario on Lanforge_1 (Here Considered as DUT) [Created VAP With SSID 'lexusap' on wiphy0 with eth1 as backhaul] + Scenario_1 = LoadScenario("192.168.200.18", 8080, "Lexus_DUT") + + dut_traffic_profile = CreateSTA_CX("192.168.200.18", 8080, "wiphy1", 1, 0, 'lanforge_ap', 'open', password, 'br0000', 'lf_udp', min_bps, max_bps) + dut_traffic_profile.build() + + print("DUT All Set... Lets setup Lanforge") + + + #Loading the Scenario on Lanforge_2 (Here Considered as LANFORGE Test) [Created VAP With SSID 'lanforge_ap' on wiphy0 with eth2 as backhaul] + + DB_Lanforge_2 = "LANforge_TEST" + Scenario_2 = LoadScenario(manager, 8080, scenario) + + + lf_traffic_profile = CreateSTA_CX(manager, 8080, radio, num_sta, sta_id, ssid, security, password, upstream, protocol, min_bps, max_bps) + lf_traffic_profile.build() + + print("Lanforge System is All set... Lets start and Measure") + + lf_traffic_profile.start() + dut_traffic_profile.start() + + time.sleep(10) + + print("Collecting Throughput Values...") + + # Object to Measure Throughput at VAP Side + vap_measure_obj = VAP_Measure(manager, 8080) + + # + dut_temp_obj = Login_DUT(1, "Thread-1", "192.168.200.18") + + #List for Storing the Total Throughput + throughput_sta =[] + throughput_vap =[] + + # This loop will get the Data from All the endpoints and sum up to give total Throughput over time + for i in range(0,int(duration)): + temp=0 + for j in lf_traffic_profile.endp: + y=lf_traffic_profile.local_realm.json_get("/endp/"+j).get('endpoint').get('rx rate') + temp=temp+y + throughput_sta.append(temp) + throughput_vap.append(vap_measure_obj.json_get("/port/1/1/vap0000/").get('interface').get('bps rx')) + dut_temp_obj.run() + print("Throughput Stations side: ", throughput_sta) + print("\nThroughput VAP side: ", throughput_vap) + time.sleep(5) + print(throughput_sta) + dut_traffic_profile.cleanup() + lf_traffic_profile.cleanup() + GenerateReport(throughput_sta, throughput_vap, dut_temp_obj.data_core1, dut_temp_obj.data_core2, duration, report_name) + + + +if __name__ == '__main__': + main() + diff --git a/lanforge/lanforge-scripts/py-scripts/lf_ftp.py b/lanforge/lanforge-scripts/py-scripts/lf_ftp.py new file mode 100755 index 000000000..e097b5277 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_ftp.py @@ -0,0 +1,859 @@ +""" lf_ftp.py will verify that N clients connected on specified band and can simultaneously download/upload some amount of file from FTP server and measuring the time taken by client to download/upload the file. + cli- python3 lf_ftp.py --mgr localhost --mgr_port 8080 --upstream_port eth1 --ssid FTP --security open --passwd BLANK --ap_name WAC505 --ap_ip 192.168.213.90 --bands Both --directions Download --twog_radio wiphy1 --fiveg_radio wiphy0 --file_size 2MB --num_stations 40 --Both_duration 1 --traffic_duration 2 --ssh_port 22_ + Copyright 2021 Candela Technologies Inc + License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import importlib +import paramiko +import argparse +from datetime import datetime +import time +import os +import matplotlib.patches as mpatches + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +lf_report = importlib.import_module("py-scripts.lf_report") +lf_graph = importlib.import_module("py-scripts.lf_graph") + + +class FtpTest(LFCliBase): + def __init__(self, lfclient_host="localhost", lfclient_port=8080, sta_prefix="sta", start_id=0, num_sta=None, + dut_ssid=None, dut_security=None, dut_passwd=None, file_size=None, band=None, twog_radio=None, + fiveg_radio=None, upstream="eth1", _debug_on=False, _exit_on_error=False, _exit_on_fail=False, + direction=None, duration=None, traffic_duration=None, ssh_port=None): + super().__init__(lfclient_host, lfclient_port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + print("Test is about to start") + self.host = lfclient_host + self.port = lfclient_port + # self.radio = radio + self.upstream = upstream + self.sta_prefix = sta_prefix + self.sta_start_id = start_id + self.num_sta = num_sta + self.ssid = dut_ssid + self.security = dut_security + self.password = dut_passwd + self.requests_per_ten = 1 + self.band = band + self.file_size = file_size + self.direction = direction + self.twog_radio = twog_radio + self.fiveg_radio = fiveg_radio + self.duration = duration + self.traffic_duration = traffic_duration + self.ssh_port = ssh_port + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_http_profile() + self.port_util = realm.PortUtils(self.local_realm) + self.cx_profile.requests_per_ten = self.requests_per_ten + + print("Test is Initialized") + + def set_values(self): + '''This method will set values according user input''' + + if self.band == "5G": + self.radio = [self.fiveg_radio] + elif self.band == "2.4G": + self.radio = [self.twog_radio] + elif self.band == "Both": + self.radio = [self.fiveg_radio, self.twog_radio] + + # if Both then number of stations are half for 2.4G and half for 5G + self.num_sta = self.num_sta // 2 + + # converting minutes into time stamp + self.pass_fail_duration = self.duration + self.duration = self.convert_min_in_time(self.duration) + self.traffic_duration = self.convert_min_in_time(self.traffic_duration) + + # file size in Bytes + self.file_size_bytes = int(self.convert_file_size_in_Bytes(self.file_size)) + + def precleanup(self): + self.count = 0 + + # delete everything in the GUI before starting the script + '''try: + self.local_realm.load("BLANK") + except: + print("Couldn't load 'BLANK' Test configurations")''' + + for rad in self.radio: + if rad == self.fiveg_radio: + + # select mode(All stations will connects to 5G) + self.station_profile.mode = 9 + self.count = self.count + 1 + + elif rad == self.twog_radio: + + # select mode(All stations will connects to 2.4G) + self.station_profile.mode = 6 + self.count = self.count + 1 + + # check Both band if both band then for 2G station id start with 20 + if self.count == 2: + self.sta_start_id = self.num_sta + self.num_sta = 2 * (self.num_sta) + + # if Both band then first 20 stations will connects to 5G + self.station_profile.mode = 9 + + self.cx_profile.cleanup() + + # create station list with sta_id 20 + self.station_list1 = LFUtils.portNameSeries(prefix_=self.sta_prefix, start_id_=self.sta_start_id, + end_id_=self.num_sta - 1, padding_number_=10000, + radio=rad) + + # cleanup station list which started sta_id 20 + self.station_profile.cleanup(self.station_list1, debug_=self.debug) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=self.station_list, + debug=self.debug) + return + # clean layer4 ftp traffic + self.cx_profile.cleanup() + self.station_list = LFUtils.portNameSeries(prefix_=self.sta_prefix, start_id_=self.sta_start_id, + end_id_=self.num_sta - 1, padding_number_=10000, + radio=rad) + + # cleans stations + self.station_profile.cleanup(self.station_list, delay=1.5, debug_=self.debug) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=self.station_list, + debug=self.debug) + time.sleep(1) + + print("precleanup done") + + def build(self): + # set ftp + self.port_util.set_ftp(port_name=self.local_realm.name_to_eid(self.upstream)[2], resource=1, on=True) + + for rad in self.radio: + + # station build + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template("00") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=rad, sta_names_=self.station_list, debug=self.debug) + self.local_realm.wait_until_ports_appear(sta_list=self.station_list) + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(self.station_list): + self._pass("All stations got IPs") + else: + self._fail("Stations failed to get IPs") + # exit(1) + + # building layer4 + self.cx_profile.direction = "dl" + self.cx_profile.dest = "/dev/null" + print('Direction:', self.direction) + + if self.direction == "Download": + + # data from GUI for find out ip addr of upstream port + data = self.json_get("ports/list?fields=IP") + + for i in data["interfaces"]: + for j in i: + if "1.1." + self.upstream == j: + ip_upstream = i["1.1." + self.upstream]['ip'] + + self.cx_profile.create(ports=self.station_profile.station_names, ftp_ip=ip_upstream + "/ftp_test.txt", + sleep_time=.5, debug_=self.debug, suppress_related_commands_=True, ftp=True, + user="lanforge", + passwd="lanforge", source="") + + + elif self.direction == "Upload": + dict_sta_and_ip = {} + + # data from GUI for find out ip addr of each station + data = self.json_get("ports/list?fields=IP") + + # This loop for find out proper ip addr and station name + for i in self.station_list: + for j in data['interfaces']: + for k in j: + if i == k: + dict_sta_and_ip[k] = j[i]['ip'] + + # list of ip addr of all stations + ip = list(dict_sta_and_ip.values()) + + eth_list = [] + client_list = [] + + # list of all stations + for i in range(len(self.station_list)): + client_list.append(self.station_list[i][4:]) + + # list of upstream port + eth_list.append(self.upstream) + + # create layer for connection for upload + for client_num in range(len(self.station_list)): + self.cx_profile.create(ports=eth_list, ftp_ip=ip[client_num] + "/ftp_test.txt", sleep_time=.5, + debug_=self.debug, suppress_related_commands_=True, ftp=True, + user="lanforge", passwd="lanforge", + source="", upload_name=client_list[client_num]) + + # check Both band present then build stations with another station list + if self.count == 2: + self.station_list = self.station_list1 + + # if Both band then another 20 stations will connects to 2.4G + self.station_profile.mode = 6 + print("Test Build done") + + def start(self, print_pass=False, print_fail=False): + for rad in self.radio: + self.cx_profile.start_cx() + + print("Test Started") + + def stop(self): + self.cx_profile.stop_cx() + self.station_profile.admin_down() + + def postcleanup(self): + self.cx_profile.cleanup() + # self.local_realm.load("BLANK") + self.station_profile.cleanup(self.station_profile.station_names, delay=1.5, debug_=self.debug) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + + def file_create(self): + '''This method will Create file for given file size''' + + ip = self.host + user = "root" + pswd = "lanforge" + port = self.ssh_port + ssh = paramiko.SSHClient() # creating shh client object we use this object to connect to router + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # automatically adds the missing host key + ssh.connect(ip, port=port, username=user, password=pswd, banner_timeout=600) + cmd = '[ -f /home/lanforge/ftp_test.txt ] && echo "True" || echo "False"' + stdin, stdout, stderr = ssh.exec_command(str(cmd)) + output = stdout.readlines() + print(output) + if output == ["True\n"]: + cmd1 = "rm /home/lanforge/ftp_test.txt" + stdin, stdout, stderr = ssh.exec_command(str(cmd1)) + output = stdout.readlines() + time.sleep(10) + cmd2 = "sudo fallocate -l " + self.file_size + " /home/lanforge/ftp_test.txt" + stdin, stdout, stderr = ssh.exec_command(str(cmd2)) + print("File creation done %s" % self.file_size) + output = stdout.readlines() + else: + cmd2 = "sudo fallocate -l " + self.file_size + " /home/lanforge/ftp_test.txt" + stdin, stdout, stderr = ssh.exec_command(str(cmd2)) + print("File creation done %s" % self.file_size) + output = stdout.readlines() + ssh.close() + time.sleep(1) + return output + + def convert_file_size_in_Bytes(self, size): + '''convert file size MB or GB into Bytes''' + + if (size.endswith("MB")) or (size.endswith("Mb")) or (size.endswith("GB")) or (size.endswith("Gb")): + if (size.endswith("MB")) or (size.endswith("Mb")): + return float(size[:-2]) * 10 ** 6 + elif (size.endswith("GB")) or (size.endswith("Gb")): + return float(size[:-2]) * 10 ** 9 + + def my_monitor(self, time1): + # data in json format + data = self.json_get("layer4/list?fields=bytes-rd") + + # list of layer 4 connections name + self.data1 = [] + + for i in range(self.num_sta): + self.data1.append((str(list(data['endpoint'][i].keys())))[2:-2]) + + data2 = self.data1 + list_of_time = [] + list1 = [] + list2 = [] + counter = 0 + + for i in range(self.num_sta): + list_of_time.append(0) + #running layer 4 traffic upto user given time + while str(datetime.datetime.now() - time1) <= self.traffic_duration: + if list_of_time.count(0) == 0: + break + + while list_of_time.count(0) != 0: + + # run script upto given time + if counter == 0: + if str(datetime.datetime.now() - time1) >= self.duration: + counter = counter + 1 + break + else: + if str(datetime.datetime.now() - time1) >= self.traffic_duration: + break + + for i in range(self.num_sta): + data = self.json_get("layer4/list?fields=bytes-rd") + + # reading uc-avg data in json format + uc_avg = self.json_get("layer4/list?fields=uc-avg") + if data['endpoint'][i][data2[i]]['bytes-rd'] <= self.file_size_bytes: + data = self.json_get("layer4/list?fields=bytes-rd") + if data['endpoint'][i][data2[i]]['bytes-rd'] >= self.file_size_bytes: + list1.append(i) + if list1.count(i) == 1: + list2.append(i) + list1 = list2 + + # stop station after download or upload file with particular size + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": "CX_" + data2[i], + "cx_state": "STOPPED" + }, debug_=self.debug) + + list_of_time[i] = round(int(uc_avg['endpoint'][i][data2[i]]['uc-avg']) / 1000, 1) + time.sleep(0.5) + + # method calling for throughput calculation + self.throughput_calculation() + + # return list of download/upload time in seconds + return list_of_time + + def throughput_calculation(self): + '''Method for calculate throughput of each station''' + + self.list_of_throughput = [] + data = self.json_get("layer4/list?fields=bytes-rd") + for i in range(self.num_sta): + throughput = data['endpoint'][i][self.data1[i]]['bytes-rd'] / 10 ** 6 + if isinstance(throughput, float): + self.list_of_throughput.append(round(throughput, 2)) + else: + self.list_of_throughput.append(throughput) + + def ftp_test_data(self, list_time, pass_fail, bands, file_sizes, directions, num_stations): + '''Method for arrange ftp download/upload time data in dictionary''' + + # creating dictionary for single iteration + create_dict = {} + + create_dict["band"] = self.band + create_dict["direction"] = self.direction + create_dict["file_size"] = self.file_size + create_dict["time"] = list_time + create_dict["duration"] = self.pass_fail_duration + create_dict["result"] = pass_fail + create_dict["bands"] = bands + create_dict["file_sizes"] = file_sizes + create_dict["directions"] = directions + create_dict["num_stations"] = num_stations + create_dict["throughput"] = self.list_of_throughput + + return create_dict + + def convert_min_in_time(self, total_minutes): + # Get hours with floor division + hours = total_minutes // 60 + + # Get additional minutes with modulus + minutes = total_minutes % 60 + + # Create time as a string + time_string = str("%d:%02d" % (divmod(total_minutes, 60))) + ":00" + ":000000" + + return time_string + + def pass_fail_check(self, time_list): + if max(time_list) < (self.pass_fail_duration * 60): + return "Pass" + else: + return "Fail" + + def add_pass_fail_table(self, result_data): + '''Method for create dict for pass/fail table for report''' + + self.column_head = [] + self.rows_head = [] + self.bands = result_data[1]["bands"] + self.file_sizes = result_data[1]["file_sizes"] + self.directions = result_data[1]["directions"] + self.num_stations = result_data[1]["num_stations"] + + for size in self.file_sizes: + for direction in self.directions: + self.column_head.append(size + " " + direction) + for band in self.bands: + if band != "Both": + self.rows_head.append(str(self.num_stations) + " Clients-" + band) + else: + self.rows_head.append(str(self.num_stations // 2) + "+" + str(self.num_stations // 2) + " Clients-2.4G+5G") + + #creating dict for a table + table_dict_pass_fail = {} + i = 0 + table_dict_pass_fail[""] = self.rows_head + for size in self.file_sizes: + for d in self.directions: + list_data = [] + for b in self.bands: + for data in result_data.values(): + if data["band"] == b and data["direction"] == d and data["file_size"] == size: + list_data.append(data["result"]) + + table_dict_pass_fail[self.column_head[i]] = list_data + i = i + 1 + + return table_dict_pass_fail + + def download_upload_time_table(self,result_data): + '''Method for create dict for download/upload table for report''' + + table_dict_time = {} + string_data = "" + i = 0 + table_dict_time[""] = self.rows_head + + for size in self.file_sizes: + for d in self.directions: + list_data = [] + for b in self.bands: + for data in result_data.values(): + data_time = data['time'] + if data_time.count(0) == 0: + Min = min(data_time) + Max = max(data_time) + Sum = int(sum(data_time)) + Len = len(data_time) + Avg = round(Sum / Len, 2) + elif data_time.count(0) == len(data_time): + Min = "-" + Max = "-" + Avg = "-" + else: + data_time = [i for i in data_time if i != 0] + Min = min(data_time) + Max = max(data_time) + Sum = int(sum(data_time)) + Len = len(data_time) + Avg = round(Sum / Len, 2) + string_data = "Min=" + str(Min) + ",Max=" + str(Max) + ",Avg=" + str(Avg) + " (sec)" + + if data["band"] == b and data["direction"] == d and data["file_size"] == size: + list_data.append(string_data) + + table_dict_time[self.column_head[i]] = list_data + i = i + 1 + + return table_dict_time + + def generate_graph_time(self,result_data, x_axis, band, size): + '''Method for generating graph for time''' + + num_stations = result_data[1]["num_stations"] + dataset = [] + labels = [] + color = [] + graph_name = "" + graph_description = "" + count = 0 + handles = [] + for data in result_data.values(): + if data["band"] == band and data["file_size"] == size and data["direction"] == "Download": + dataset.append(data["time"]) + labels.append("Download") + + #Adding red bar if client unable to download/upload file + color_list = [] + #converting minutes in seconds + duration = data["duration"] * 60 + for i in data["time"]: + if i < duration: + color_list.append("orange") + else: + color_list.append("red") + if color_list.count("red") == 0: + handles.append(mpatches.Patch(color='orange', label='Download <= threshold time')) + num_col = 1 + box = (1, 1.15) + else: + handles.append(mpatches.Patch(color='orange', label='Download <= threshold time')) + handles.append(mpatches.Patch(color='red', label='Download > threshold time')) + num_col = 2 + box = (1, 1.15) + color.append(color_list) + graph_name = "File size " + size + " " + str( + num_stations) + " Clients " + band + "-File Download Times(secs)" + fail_count = len([i for i in data["time"] if i > (data["duration"] * 60)]) + graph_description = "Out of " + str(data["num_stations"]) + " clients, " + str( + data["num_stations"] - fail_count) + " are able to download " + "within " + str( + data["duration"]) + " min." + count = count + 1 + if data["band"] == band and data["file_size"] == size and data["direction"] == "Upload": + dataset.append(data["time"]) + labels.append("Upload") + + # Adding red bar if client unable to download/upload file + color_list = [] + duration = data["duration"] * 60 + for i in data["time"]: + if i < duration: + color_list.append("blue") + else: + color_list.append("red") + if color_list.count("red") == 0: + handles.append(mpatches.Patch(color='blue', label='Upload <= threshold time')) + num_col = 1 + box = (1, 1.15) + else: + handles.append(mpatches.Patch(color='blue', label='Upload <= threshold time')) + handles.append(mpatches.Patch(color='red', label='Upload < threshold time')) + num_col = 2 + box = (1, 1.15) + color.append(color_list) + + graph_name = "File size " + size + " " + str( + num_stations) + " Clients " + band + "-File Upload Times(secs)" + fail_count = len([i for i in data["time"] if i > (data["duration"] * 60)]) + graph_description = graph_description + "Out of " + str(data["num_stations"]) + " clients, " + str( + data["num_stations"] - fail_count) + " are able to upload " + "within " + str( + data["duration"]) + " min." + count = count + 1 + if count == 2: + graph_name = "File size " + size + " " + str( + num_stations) + " Clients " + band + "-File Download and Upload Times(secs)" + handles = [] + for i in labels: + if i == "Upload": + c = "blue" + else: + c = "orange" + handles.append(mpatches.Patch(color=c, label=i + " <= threshold time")) + num_col = 2 + box = (1, 1.15) + if (color[0].count("red") >= 1) or (color[1].count("red") >= 1): + num_col = 3 + box = (1, 1.15) + if labels[0] == "Download": + handles.append(mpatches.Patch(color='red', label='Download/Upload > threshold time')) + else: + handles.append(mpatches.Patch(color='red', label='Upload/Download > threshold time')) + + + self.report.set_obj_html(graph_name, graph_description) + self.report.build_objective() + image_name = "image"+band+size + x_axis_name = "Stations" + y_axis_name = "Time in seconds" + self.bar_graph(x_axis, image_name, dataset, color, labels, x_axis_name, y_axis_name, handles, ncol=num_col, box=box, fontsize=12) + + def generate_graph_throughput(self, result_data, x_axis, band, size): + '''Method for generating graph for time''' + + num_stations = result_data[1]["num_stations"] + dataset = [] + labels = [] + color = [] + graph_name = "" + graph_description = "" + count = 0 + for data in result_data.values(): + if data["band"] == band and data["file_size"] == size and data["direction"] == "Download": + dataset.append(data["throughput"]) + labels.append("Download") + color.append("Orange") + graph_name = "File size " + size + " " + str( + num_stations) + " Clients " + band + "-File Download Throughput(MB)" + graph_description = str(data["num_stations"] - data["time"].count(0)) + " clients are able to download " + data["file_size"] + " file." + count = count + 1 + if data["band"] == band and data["file_size"] == size and data["direction"] == "Upload": + dataset.append(data["throughput"]) + labels.append("Upload") + color.append("Blue") + graph_name = "File size " + size + " " + str( + num_stations) + " Clients " + band + "-File Upload Throughput(MB)" + graph_description = graph_description + str(data["num_stations"] - data["time"].count(0)) + " clients are able to upload " + data["file_size"] + " file." + count = count + 1 + if count == 2: + graph_name = "File size " + size + " " + str( + num_stations) + " Clients " + band + "-File Download and Upload Throughput(MB)" + + self.report.set_obj_html(graph_name, graph_description) + self.report.build_objective() + image_name = "image" + band + size + "throughput" + x_axis_name = "Stations" + y_axis_name = "Throughput in MB" + box = (1.1, 1.05) + self.bar_graph(x_axis, image_name, dataset, color, labels, x_axis_name, y_axis_name, handles=None, ncol=1, box=box, fontsize=None) + + def bar_graph(self, x_axis, image_name, dataset, color, labels, x_axis_name, y_axis_name,handles, ncol, box, fontsize): + '''This Method will plot bar graph''' + + graph = lf_bar_graph(_data_set=dataset, + _xaxis_name=x_axis_name, + _yaxis_name=y_axis_name, + _xaxis_categories=x_axis, + _label=labels, + _graph_image_name=image_name, + _figsize=(18, 6), + _color=color, + _show_bar_value=False, + _xaxis_step=None, + _legend_handles=handles, + _color_edge=None, + _text_rotation=40, + _legend_loc="upper right", + _legend_box=box, + _legend_ncol=ncol, + _legend_fontsize=fontsize, + _enable_csv=True) + + graph_png = graph.build_bar_graph() + + print("graph name {}".format(graph_png)) + + self.report.set_graph_image(graph_png) + # need to move the graph image to the results + self.report.move_graph_image() + self.report.set_csv_filename(graph_png) + self.report.move_csv_file() + + self.report.build_graph() + + def generate_graph(self, result_data): + '''This method will generate bar graph of time and throughput''' + + x_axis = [] + for i in range(1, self.num_stations + 1, 1): + x_axis.append(i) + + for b in self.bands: + for size in self.file_sizes: + self.generate_graph_time(result_data, x_axis, b, size) + self.generate_graph_throughput(result_data, x_axis, b, size) + + def generate_report(self, ftp_data, date,test_setup_info, input_setup_info): + '''Method for generate the report''' + + self.report = lf_report(_results_dir_name="ftp_test", _output_html="ftp_test.html", _output_pdf="ftp_test.pdf") + self.report.set_title("FTP Test") + self.report.set_date(date) + self.report.build_banner() + self.report.set_table_title("Test Setup Information") + self.report.build_table_title() + self.report.test_setup_table(value="Device under test", test_setup_data=test_setup_info) + + self.report.set_obj_html("Objective", + "This FTP Test is used to Verify that N clients connected on Specified band and can simultaneously download/upload some amount of file from FTP server and measuring the time taken by client to Download/Upload the file.") + self.report.build_objective() + self.report.set_obj_html("PASS/FAIL Results", + "This Table will give Pass/Fail results.") + self.report.build_objective() + dataframe1 = pd.DataFrame(self.add_pass_fail_table(ftp_data)) + self.report.set_table_dataframe(dataframe1) + self.report.build_table() + self.report.set_obj_html("File Download/Upload Time (sec)", + "This Table will give FTP Download/Upload Time of Clients.") + self.report.build_objective() + dataframe2 = pd.DataFrame(self.download_upload_time_table(ftp_data)) + self.report.set_table_dataframe(dataframe2) + self.report.build_table() + self.generate_graph(ftp_data) + self.report.set_table_title("Test input Information") + self.report.build_table_title() + self.report.test_setup_table(value="Information", test_setup_data=input_setup_info) + self.report.build_footer() + html_file = self.report.write_html() + print("returned file {}".format(html_file)) + print(html_file) + self.report.write_pdf() + + + + + + + +def main(): + parser = argparse.ArgumentParser( + prog='lf_ftp.py', + formatter_class=argparse.RawTextHelpFormatter, + description="FTP Test Script") + parser.add_argument('--mgr', help='hostname for where LANforge GUI is running', default='localhost') + parser.add_argument('--mgr_port', help='port LANforge GUI HTTP service is running on', default=8080) + parser.add_argument('--upstream_port', help='non-station port that generates traffic: eg: eth1', default='eth1') + parser.add_argument('--ssid', type=str, help='--ssid') + parser.add_argument('--passwd', type=str, help='--passwd') + parser.add_argument('--security', type=str, help='--security') + parser.add_argument('--ap_name', type=str, help='--ap_name') + parser.add_argument('--ap_ip', type=str, help='--ap_ip') + parser.add_argument('--twog_radio', type=str, help='specify radio for 2.4G clients', default='wiphy1') + parser.add_argument('--fiveg_radio', type=str, help='specify radio for 5G client', default='wiphy0') + parser.add_argument('--twog_duration', nargs="+", help='Pass and Fail duration for 2.4G band in minutes') + parser.add_argument('--fiveg_duration', nargs="+", help='Pass and Fail duration for 5G band in minutes') + parser.add_argument('--Both_duration', nargs="+", help='Pass and Fail duration for Both band in minutes') + parser.add_argument('--traffic_duration', type=int, help='duration for layer 4 traffic running') + parser.add_argument('--ssh_port', type=int, help="specify the shh port eg 22", default=22) + + # Test variables + parser.add_argument('--bands', nargs="+", help='--bands defaults ["5G","2.4G","Both"]', + default=["5G", "2.4G", "Both"]) + parser.add_argument('--directions', nargs="+", help='--directions defaults ["Download","Upload"]', + default=["Download", "Upload"]) + parser.add_argument('--file_sizes', nargs="+", help='--File Size defaults ["2MB","500MB","1000MB"]', + default=["2MB", "500MB", "1000MB"]) + parser.add_argument('--num_stations', type=int, help='--num_stations is number of stations', default=40) + + args = parser.parse_args() + + # 1st time stamp for test duration + time_stamp1 = datetime.datetime.now() + + # use for creating ftp_test dictionary + iteraration_num = 0 + + # empty dictionary for whole test data + ftp_data = {} + + def pass_fail_duration(band, file_size): + '''Method for set duration according file size and band which are given by user''' + + if band == "2.4G": + if len(args.file_sizes) is not len(args.twog_duration): + raise Exception("Give proper Pass or Fail duration for 2.4G band") + + for size in args.file_sizes: + if size == file_size: + index = list(args.file_sizes).index(size) + duration = args.twog_duration[index] + elif band == "5G": + if len(args.file_sizes) is not len(args.fiveg_duration): + raise Exception("Give proper Pass or Fail duration for 5G band") + for size in args.file_sizes: + if size == file_size: + index = list(args.file_sizes).index(size) + duration = args.fiveg_duration[index] + else: + if len(args.file_sizes) is not len(args.Both_duration): + raise Exception("Give proper Pass or Fail duration for 5G band") + for size in args.file_sizes: + if size == file_size: + index = list(args.file_sizes).index(size) + duration = args.Both_duration[index] + if duration.isdigit(): + duration = int(duration) + else: + duration = float(duration) + + return duration + + # For all combinations ftp_data of directions, file size and client counts, run the test + for band in args.bands: + for direction in args.directions: + for file_size in args.file_sizes: + # Start Test + obj = FtpTest(lfclient_host=args.mgr, + lfclient_port=args.mgr_port, + upstream=args.upstream_port, + dut_ssid=args.ssid, + dut_passwd=args.passwd, + dut_security=args.security, + num_sta=args.num_stations, + band=band, + file_size=file_size, + direction=direction, + twog_radio=args.twog_radio, + fiveg_radio=args.fiveg_radio, + duration=pass_fail_duration(band, file_size), + traffic_duration=args.traffic_duration, + ssh_port=args.ssh_port + ) + + iteraration_num = iteraration_num + 1 + obj.file_create() + obj.set_values() + obj.precleanup() + obj.build() + if not obj.passes(): + print(obj.get_fail_message()) + exit(1) + + # First time stamp + time1 = datetime.datetime.now() + + obj.start(False, False) + + # return list of download/upload completed time stamp + time_list = obj.my_monitor(time1) + + # check pass or fail + pass_fail = obj.pass_fail_check(time_list) + + # dictionary of whole data + ftp_data[iteraration_num] = obj.ftp_test_data(time_list, pass_fail, args.bands, args.file_sizes, + args.directions, args.num_stations) + + obj.stop() + obj.postcleanup() + + # 2nd time stamp for test duration + time_stamp2 = datetime.datetime.now() + + # total time for test duration + test_duration = str(time_stamp2 - time_stamp1)[:-7] + + date = str(datetime.datetime.now()).split(",")[0].replace(" ", "-").split(".")[0] + + #print(ftp_data) + + test_setup_info = { + "AP Name": args.ap_name, + "SSID": args.ssid, + "Test Duration": test_duration + } + + input_setup_info = { + "AP IP": args.ap_ip, + "File Size": args.file_sizes, + "Bands": args.bands, + "Direction": args.directions, + "Stations": args.num_stations, + "Upstream": args.upstream_port, + "SSID": args.ssid, + "Security": args.security, + "Contact": "support@candelatech.com" + } + obj.generate_report(ftp_data, + date, + test_setup_info, + input_setup_info) + + +if __name__ == '__main__': + main() + diff --git a/lanforge/lanforge-scripts/py-scripts/lf_ftp_test.py b/lanforge/lanforge-scripts/py-scripts/lf_ftp_test.py new file mode 100755 index 000000000..2b0444f6d --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_ftp_test.py @@ -0,0 +1,557 @@ +#!/usr/bin/env python3 + +""" +NAME: ftp_test.py + +PURPOSE: + will create stations and endpoints to generate and verify layer-4 traffic over an ftp connection. + find out download/upload time of each client according to file size. + This script will monitor the bytes-rd attribute of the endpoints. + +SETUP: + + Create a file to be downloaded linux: fallocate -l example fallocate -l 2M ftp_test.txt + + +EXAMPLE: + './lf_ftp_test.py --ssid "jedway-wap2-x2048-5-3" --passwd "jedway-wpa2-x2048-5-3" --security wpa2 --bands "5G" --direction "Download" \ + --file_size "2MB" --num_stations 2 + +INCLUDE_IN_README + + + -Jitendrakumar Kushavah + Copyright 2021 Candela Technologies Inc + License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import paramiko +import argparse +from datetime import datetime +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +ftp_html = importlib.import_module("py-scripts.ftp_html") + + +class ftp_test(LFCliBase): + def __init__(self, lfclient_host="localhost", lfclient_port=8080, radio = "wiphy0", sta_prefix="sta", start_id=0, num_sta= None, + dut_ssid=None,dut_security=None, dut_passwd=None, file_size=None, band=None, + upstream="eth1",_debug_on=False, _exit_on_error=False, _exit_on_fail=False, direction= None,user='lanforge'): + super().__init__(lfclient_host, lfclient_port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + print("Test is about to start") + self.host = lfclient_host + self.port = lfclient_port + self.radio = radio + self.upstream = upstream + self.sta_prefix = sta_prefix + self.sta_start_id = start_id + self.num_sta = num_sta + self.ssid = dut_ssid + self.security = dut_security + self.password = dut_passwd + self.requests_per_ten = 1 + self.band=band + self.file_size=file_size + self.direction=direction + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_http_profile() + self.port_util = realm.PortUtils(self.local_realm) + self.cx_profile.requests_per_ten = self.requests_per_ten + self.user = user + + print("Test is Initialized") + + def set_values(self): + #This method will set values according user input + + if self.band == "5G": + self.radio = ["wiphy0"] # need to pass in the radios + if self.file_size == "2MB": + + #providing time duration for Pass or fail criteria + self.duration = self.convert_min_in_time(1) + elif self.file_size == "500MB": + self.duration = self.convert_min_in_time(1) # 30 + elif self.file_size == "1000MB": + self.duration = self.convert_min_in_time(1) # 50 + else: + self.duration = self.convert_min_in_time(10) # 10 + elif self.band == "2.4G": + self.radio = ["wiphy0"] # need to pass in the radios + if self.file_size == "2MB": + self.duration = self.convert_min_in_time(1) # 2 + elif self.file_size == "500MB": + self.duration = self.convert_min_in_time(1) # 60 + elif self.file_size == "1000MB": + self.duration = self.convert_min_in_time(1) # 80 + else: + self.duration = self.convert_min_in_time(10) # 10 + elif self.band == "Both": + self.radio = ["wiphy0", "wiphy0"] # need to pass in the radios + + #if Both then number of stations are half for 2.4G and half for 5G + self.num_sta = self.num_sta // 2 + print(self.num_sta) + if self.file_size == "2MB": + self.duration = self.convert_min_in_time(1) # 2 + elif self.file_size == "500MB": + self.duration = self.convert_min_in_time(1) # 60 + elif self.file_size == "1000MB": + self.duration = self.convert_min_in_time(1) # 80 + else: + self.duration = self.convert_min_in_time(10) # 10 + + self.file_size_bytes=int(self.convert_file_size_in_Bytes(self.file_size)) + + + def precleanup(self): + self.count=0 + + #delete everything in the GUI before starting the script + '''try: + self.local_realm.load("BLANK") + except: + print("Couldn't load 'BLANK' Test configurations") + ''' + + for rad in self.radio: + if rad == "wiphy0": + + #select mode(All stations will connects to 5G) + self.station_profile.mode = 10 + self.count=self.count+1 + + elif rad == "wiphy0": # This probably is not the best selection mode + + # select mode(All stations will connects to 2.4G) + self.station_profile.mode = 6 + self.count = self.count + 1 + + #check Both band if both band then for 2.4G station id start with 20 + if self.count == 2: + self.sta_start_id = self.num_sta + self.num_sta = 2 * (self.num_sta) + + #if Both band then first 20 stations will connects to 5G + self.station_profile.mode = 10 + self.cx_profile.cleanup() + + #create station list with sta_id 20 + self.station_list1 = LFUtils.portNameSeries(prefix_=self.sta_prefix, start_id_=self.sta_start_id, + end_id_=self.num_sta - 1, padding_number_=10000, + radio=rad) + + #cleanup station list which started sta_id 20 + self.station_profile.cleanup(self.station_list1, debug_=self.debug) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=self.station_list, + debug=self.debug) + return + #clean layer4 ftp traffic + self.cx_profile.cleanup() + self.station_list = LFUtils.portNameSeries(prefix_=self.sta_prefix, start_id_=self.sta_start_id, + end_id_=self.num_sta - 1, padding_number_=10000, + radio=rad) + + #cleans stations + self.station_profile.cleanup(self.station_list , delay=1, debug_=self.debug) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=self.station_list, + debug=self.debug) + time.sleep(1) + + + print("precleanup done") + + def build(self): + + #set ftp + self.port_util.set_ftp(port_name=self.local_realm.name_to_eid(self.upstream)[2], resource=1, on=True) + + for rad in self.radio: + + #station build + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template("00") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=rad, sta_names_=self.station_list, debug=self.debug) + self.local_realm.wait_until_ports_appear(sta_list=self.station_list) + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(self.station_list): + self._pass("All stations got IPs") + else: + self._fail("Stations failed to get IPs") + exit(1) + #building layer4 + self.cx_profile.direction ="dl" + self.cx_profile.dest = "/dev/null" + print('DIRECTION',self.direction) + + if self.direction == "Download": + self.cx_profile.create(ports=self.station_profile.station_names, ftp_ip="10.40.0.1/ftp_test.txt", + sleep_time=.5,debug_=self.debug,suppress_related_commands_=True, ftp=True, user="lanforge", + passwd="lanforge", source="") + + + elif self.direction == "Upload": + dict_sta_and_ip = {} + + #data from GUI for find out ip addr of each station + data = self.json_get("ports/list?fields=IP") + + # This loop for find out proper ip addr and station name + for i in self.station_list: + for j in data['interfaces']: + for k in j: + if i == k: + dict_sta_and_ip[k] = j[i]['ip'] + + + + #list of ip addr of all stations + ip = list(dict_sta_and_ip.values()) + + eth_list = [] + client_list = [] + + #list of all stations + for i in range(len(self.station_list)): + client_list.append(self.station_list[i][4:]) + + #list of upstream port + eth_list.append(self.upstream) + + #create layer for connection for upload + for client_num in range(len(self.station_list)): + self.cx_profile.create(ports=eth_list, ftp_ip=ip[client_num] + "/ftp_test_upload.txt", sleep_time=.5, + debug_=self.debug, suppress_related_commands_=True, ftp=True, + user="lanforge", passwd="lanforge", + source="", upload_name=client_list[client_num]) + + + #check Both band present then build stations with another station list + if self.count == 2: + self.station_list = self.station_list1 + + # if Both band then another 20 stations will connects to 2.4G + self.station_profile.mode = 6 + print("Test Build done") + + def start(self, print_pass=False, print_fail=False): + for rad in self.radio: + self.cx_profile.start_cx() + + print("Test Started") + + + def stop(self): + self.cx_profile.stop_cx() + self.station_profile.admin_down() + + def postcleanup(self): + self.cx_profile.cleanup() + self.local_realm.load("BLANK") + self.station_profile.cleanup(self.station_profile.station_names, delay=1, debug_=self.debug) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + + #Create file for given file size + def file_create(self): + if os.path.isfile("/home/lanforge/ftp_test.txt"): + os.remove("/home/lanforge/ftp_test.txt") + os.system("fallocate -l " +self.file_size +" /home/lanforge/ftp_test.txt") + print("File creation done", self.file_size) + + #convert file size MB or GB into Bytes + def convert_file_size_in_Bytes(self,size): + if (size.endswith("MB")) or (size.endswith("Mb")) or (size.endswith("GB")) or (size.endswith("Gb")): + if (size.endswith("MB")) or (size.endswith("Mb")): + return float(size[:-2]) * 10**6 + elif (size.endswith("GB")) or (size.endswith("Gb")): + return float(size[:-2]) * 10**9 + + + def my_monitor(self,time1): + #data in json format + data = self.json_get("layer4/list?fields=bytes-rd") + + print("layer4/list?fields=bytes-read: {}".format(data)) + + + #list of layer 4 connections name + self.data1 = [] + for i in range(self.num_sta): + if self.num_sta == 1: + # the station num is 1 , yet the station is 0 + print("i: {} self.num_sta: {}".format(i,self.num_sta)) + print("data['endpoint'][{}]: {}".format(i,data['endpoint'])) + print("data list: {}".format((str(list(data['endpoint'].keys())))[2:-2])) + print("data list: {}".format((str(list(data['endpoint'].keys())))[2:-2])) + #self.data1.append((str(list(data['endpoint']['name'])))) + self.data1.append((str((data['endpoint']['name'])))) + + else: + # the station num is 1 , yet the station is 0 + print("i: {} self.num_sta: {}".format(i,self.num_sta)) + print("data['endpoint'][{}]: {}".format(i,data['endpoint'][i])) + print("data list: {}".format((str(list(data['endpoint'][i].keys())))[2:-2])) + self.data1.append((str(list(data['endpoint'][i].keys())))[2:-2]) + + data2 = self.data1 + print("data1: {}".format(self.data1)) + print("data2: {}".format(data2)) + list_of_time = [] + list1 = [] + list2 = [] + + for i in range(self.num_sta): + list_of_time.append(0) + + print("list_of_time: {}".format(list_of_time)) + + num_sta_finished = 0 + while list_of_time.count(0) != 0: + + #run script upto given time + if str(datetime.now()- time1) >= self.duration: + break + + for i in range(self.num_sta): + data = self.json_get("layer4/list?fields=bytes-rd") + #print("data from bytes-rd: {}".format(data)) + + + if self.num_sta == 1: + #reading uc-avg data in json format + uc_avg= self.json_get("layer4/list?fields=uc-avg") + #print("layer4/list?fields=uc-avg: {}".format(uc_avg)) + if data['endpoint']['bytes-rd'] <= self.file_size_bytes: + data = self.json_get("layer4/list?fields=bytes-rd") + if data['endpoint']['bytes-rd'] >= self.file_size_bytes: + list1.append(i) + print("list1: {} list2: {}".format(list1,list2)) + if list1.count(i) == 1: + list2.append(i) + list1 = list2 + print("CX_{} list1: {} list2: {}".format(data2[0],list1,list2)) + + #stop station after download or upload file with particular size + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": "CX_" + data2[0], + "cx_state": "STOPPED" + }, debug_=self.debug) + list_of_time[i] = round(int(uc_avg['endpoint']['uc-avg'])/1000,1) + num_sta_finished += 1 + if num_sta_finished >= self.num_sta: + break + else: + #reading uc-avg data in json format + uc_avg= self.json_get("layer4/list?fields=uc-avg") + if data['endpoint'][i][data2[i]]['bytes-rd'] <= self.file_size_bytes: + data = self.json_get("layer4/list?fields=bytes-rd") + if data['endpoint'][i][data2[i]]['bytes-rd'] >= self.file_size_bytes: + list1.append(i) + print("list1: {} list2: {}".format(list1,list2)) + + if list1.count(i) == 1: + list2.append(i) + list1 = list2 + print("CX_{} list1: {} list2: {}".format(data2[i],list1,list2)) + + #stop station after download or upload file with particular size + self.json_post("/cli-json/set_cx_state", { + "test_mgr": "default_tm", + "cx_name": "CX_" + data2[i], + "cx_state": "STOPPED" + }, debug_=self.debug) + + list_of_time[i] = round(int(uc_avg['endpoint'][i][data2[i]]['uc-avg'])/1000,1) + time.sleep(0.5) + # print(".", end='') + + + #return list of download/upload time in seconds + return list_of_time + + #Method for arrange ftp download/upload time data in dictionary + def ftp_test_data(self, list_time, pass_fail, bands, file_sizes, directions, num_stations): + + #creating dictionary for single iteration + create_dict={} + + create_dict["band"] = self.band + create_dict["direction"] = self.direction + create_dict["file_size"] = self.file_size + create_dict["time"] = list_time + create_dict["duration"] = self.time_test + create_dict["result"] = pass_fail + create_dict["bands"] = bands + create_dict["file_sizes"] = file_sizes + create_dict["directions"] = directions + create_dict["num_stations"] = num_stations + + return create_dict + + #Method for AP reboot + def ap_reboot(self, ip, user, pswd): + + print("starting AP reboot") + + # creating shh client object we use this object to connect to router + ssh = paramiko.SSHClient() + + # automatically adds the missing host key + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(ip, port=22, username=user, password=pswd, banner_timeout=600) + stdin, stdout, stderr = ssh.exec_command('reboot') + output = stdout.readlines() + ssh.close() + # print('\n'.join(output)) + + time.sleep(180) + print("AP rebooted") + + def convert_min_in_time(self,total_minutes): + + #saving time in minutus + self.time_test = total_minutes + + # Get hours with floor division + hours = total_minutes // 60 + + # Get additional minutes with modulus + minutes = total_minutes % 60 + + # Create time as a string + time_string = str("%d:%02d" % (divmod(total_minutes, 60))) + ":00" + ":000000" + + return time_string + + def pass_fail_check(self,time_list): + if time_list.count(0) == 0: + return "Pass" + else: + return "Fail" + +def main(): + # This has --mgr, --mgr_port and --debug + parser = LFCliBase.create_bare_argparse(prog="netgear-ftp", formatter_class=argparse.RawTextHelpFormatter, epilog="About This Script") + # Adding More Arguments for custom use + parser.add_argument('--ssid',type=str, help='--ssid', default="TestAP-Jitendra") + parser.add_argument('--passwd',type=str, help='--passwd', default="BLANK") + parser.add_argument('--security', type=str, help='--security', default="open") + parser.add_argument('--radios',nargs="+",help='--radio to use on LANforge for 5G and 2G', default=["wiphy0"]) + + # Test variables + parser.add_argument('--bands', nargs="+", help='--bands defaults ["5G","2.4G","Both"]', default=["5G","2.4G","Both"]) + parser.add_argument('--directions', nargs="+", help='--directions defaults ["Download","Upload"]', default=["Download","Upload"]) + parser.add_argument('--file_sizes', nargs="+", help='--File Size defaults ["2MB","500MB","1000MB"]', default=["2MB","500MB","1000MB"]) + parser.add_argument('--num_stations', type=int, help='--num_client is number of stations', default=40) + parser.add_argument('--user', default='lanforge') + + args = parser.parse_args() + + # 1st time stamp for test duration + time_stamp1 = datetime.now() + + #use for creating ftp_test dictionary + iteraration_num=0 + + #empty dictionary for whole test data + ftp_data={} + + #For all combinations ftp_data of directions, file size and client counts, run the test + for band in args.bands: + for direction in args.directions: + for file_size in args.file_sizes: + # Start Test + obj = ftp_test(lfclient_host=args.mgr, + lfclient_port=args.mgr_port, + dut_ssid=args.ssid, + dut_passwd=args.passwd, + dut_security=args.security, + num_sta= args.num_stations, + band=band, + file_size=file_size, + direction=direction, + user=args.user + ) + + iteraration_num=iteraration_num+1 + obj.file_create() + obj.set_values() + obj.precleanup() + #if file_size != "2MB": + #obj.ap_reboot("192.168.213.190","root","Password@123xzsawq@!") + obj.build() + if not obj.passes(): + print(obj.get_fail_message()) + exit(1) + + #First time stamp + time1 = datetime.now() + + obj.start(False, False) + + #return list of download/upload completed time stamp + time_list = obj.my_monitor(time1) + + # check pass or fail + pass_fail = obj.pass_fail_check(time_list) + + #dictionary of whole data + ftp_data[iteraration_num] = obj.ftp_test_data(time_list,pass_fail,args.bands,args.file_sizes,args.directions,args.num_stations) + + obj.stop() + obj.postcleanup() + + #2nd time stamp for test duration + time_stamp2 = datetime.now() + + #total time for test duration + test_duration = str(time_stamp2 - time_stamp1)[:-7] + + print("FTP Test Data", ftp_data) + + date = str(datetime.now()).split(",")[0].replace(" ", "-").split(".")[0] + + test_setup_info = { + "AP Name": "vap5", + "SSID": args.ssid, + "Number of Stations": args.num_stations, + "Test Duration": test_duration + } + + input_setup_info = { + "IP": "192.168.213.190" , + "user": "root", + "Contact": "support@candelatech.com" + } + ftp_html.generate_report(ftp_data, + date, + test_setup_info, + input_setup_info, + graph_path="/home/%s/html-reports/FTP-Test" % args.user) + + +if __name__ == '__main__': + main() + diff --git a/lanforge/lanforge-scripts/py-scripts/lf_graph.py b/lanforge/lanforge-scripts/py-scripts/lf_graph.py new file mode 100755 index 000000000..9704c3059 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_graph.py @@ -0,0 +1,469 @@ +#!/usr/bin/env python3 +""" +NAME: lf_graph.py + +PURPOSE: +Common Library for generating graphs for LANforge output + +SETUP: +/lanforge/html-reports directory needs to be present or output generated in local file + +EXAMPLE: +see: /py-scritps/lf_report_test.py for example + +COPYWRITE + Copyright 2021 Candela Technologies Inc + License: Free to distribute and modify. LANforge systems must be licensed. + +INCLUDE_IN_README +""" +import sys +import os +import importlib +import matplotlib.pyplot as plt +import numpy as np +import pdfkit +from matplotlib.colors import ListedColormap + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lf_csv = importlib.import_module("py-scripts.lf_csv") +lf_csv = lf_csv.lf_csv + +# internal candela references included during intial phases, to be deleted at future date + +# graph reporting classes +class lf_bar_graph(): + def __init__(self, _data_set=[[30.4, 55.3, 69.2, 37.1], [45.1, 67.2, 34.3, 22.4], [22.5, 45.6, 12.7, 34.8]], + _xaxis_name="x-axis", + _yaxis_name="y-axis", + _xaxis_categories=[1, 2, 3, 4], + _xaxis_label=["a", "b", "c", "d"], + _graph_title="", + _title_size=16, + _graph_image_name="image_name", + _label=["bi-downlink", "bi-uplink", 'uplink'], + _color=None, + _bar_width=0.25, + _color_edge='grey', + _font_weight='bold', + _color_name=['lightcoral', 'darkgrey', 'r', 'g', 'b', 'y'], + _figsize=(10, 5), + _show_bar_value=False, + _xaxis_step=1, + _xticks_font = None, + _xaxis_value_location = 0, + _text_font=None, + _text_rotation=None, + _grp_title = "", + _legend_handles=None, + _legend_loc="best", + _legend_box=None, + _legend_ncol=1, + _legend_fontsize=None, + _dpi=96, + _enable_csv=False): + + self.data_set = _data_set + self.xaxis_name = _xaxis_name + self.yaxis_name = _yaxis_name + self.xaxis_categories = _xaxis_categories + self.xaxis_label = _xaxis_label + self.title = _graph_title + self.title_size = _title_size + self.graph_image_name = _graph_image_name + self.label = _label + self.color = _color + self.bar_width = _bar_width + self.color_edge = _color_edge + self.font_weight = _font_weight + self.color_name = _color_name + self.figsize = _figsize + self.show_bar_value = _show_bar_value + self.xaxis_step = _xaxis_step + self.xticks_font = _xticks_font + self._xaxis_value_location = _xaxis_value_location + self.text_font = _text_font + self.text_rotation = _text_rotation + self.grp_title = _grp_title + self.enable_csv = _enable_csv + self.lf_csv = lf_csv() + self.legend_handles = _legend_handles + self.legend_loc = _legend_loc + self.legend_box = _legend_box + self.legend_ncol = _legend_ncol + self.legend_fontsize = _legend_fontsize + + def build_bar_graph(self): + if self.color is None: + i = 0 + self.color = [] + for col in self.data_set: + self.color.append(self.color_name[i]) + i = i + 1 + + fig = plt.subplots(figsize=self.figsize) + i = 0 + + def show_value(rects): + for rect in rects: + h = rect.get_height() + plt.text(rect.get_x() + rect.get_width() / 2., h, h, + ha='center', va='bottom', rotation=self.text_rotation, fontsize=self.text_font) + + for data in self.data_set: + if i > 0: + br = br1 + br2 = [x + self.bar_width for x in br] + rects = plt.bar(br2, self.data_set[i], color=self.color[i], width=self.bar_width, + edgecolor=self.color_edge, label=self.label[i]) + if self.show_bar_value: + show_value(rects) + br1 = br2 + i = i + 1 + else: + br1 = np.arange(len(self.data_set[i])) + rects = plt.bar(br1, self.data_set[i], color=self.color[i], width=self.bar_width, + edgecolor=self.color_edge, label=self.label[i]) + if self.show_bar_value: + show_value(rects) + i = i + 1 + plt.xlabel(self.xaxis_name, fontweight='bold', fontsize=15) + plt.ylabel(self.yaxis_name, fontweight='bold', fontsize=15) + if self.xaxis_categories[0] == 0: + plt.xticks(np.arange(0, len(self.xaxis_categories), step=self.xaxis_step),fontsize = self.xticks_font) + else: + plt.xticks([i + self._xaxis_value_location for i in np.arange(0, len(self.data_set[0]), step=self.xaxis_step)], + self.xaxis_categories, fontsize=self.xticks_font) + plt.legend(handles=self.legend_handles, loc=self.legend_loc, bbox_to_anchor=self.legend_box, ncol=self.legend_ncol, fontsize=self.legend_fontsize) + plt.suptitle(self.title, fontsize=self.title_size) + plt.title(self.grp_title) + fig = plt.gcf() + plt.savefig("%s.png" % self.graph_image_name, dpi=96) + plt.close() + print("{}.png".format(self.graph_image_name)) + if self.enable_csv: + if self.data_set is not None and self.xaxis_categories is not None: + if len(self.xaxis_categories) == len(self.data_set[0]): + self.lf_csv.columns = [] + self.lf_csv.rows = [] + self.lf_csv.columns.append(self.xaxis_name) + self.lf_csv.columns.extend(self.label) + self.lf_csv.rows.append(self.xaxis_categories) + self.lf_csv.rows.extend(self.data_set) + self.lf_csv.filename = f"{self.graph_image_name}.csv" + self.lf_csv.generate_csv() + else: + raise ValueError("Length and x-axis values and y-axis values should be same.") + else: + print("No Dataset Found") + print("{}.csv".format(self.graph_image_name)) + return "%s.png" % self.graph_image_name + + +class lf_scatter_graph(): + def __init__(self, + _x_data_set=["sta0 ", "sta1", "sta2", "sta3"], + _y_data_set=[[30, 55, 69, 37]], + _values=None, + _xaxis_name="x-axis", + _yaxis_name="y-axis", + _label=["num1", "num2"], + _graph_image_name="image_name1", + _color=["r", "y"], + _figsize=(9, 4), + _enable_csv=True): + self.x_data_set = _x_data_set + self.y_data_set = _y_data_set + self.xaxis_name = _xaxis_name + self.yaxis_name = _yaxis_name + self.figsize = _figsize + self.graph_image_name = _graph_image_name + self.color = _color + self.label = _label + self.values = _values + self.enable_csv = _enable_csv + self.lf_csv = lf_csv() + + def build_scatter_graph(self): + if self.color is None: + self.color = ["orchid", "lime", "aquamarine", "royalblue", "darkgray", "maroon"] + fig = plt.subplots(figsize=self.figsize) + if self.values is None: + plt.scatter(self.x_data_set, self.y_data_set[0], color=self.color[0], label=self.label[0]) + if len(self.y_data_set) > 1: + for i in range(1, len(self.y_data_set)): + plt.scatter(self.x_data_set, self.y_data_set[i], color=self.color[i], label=self.label[i]) + plt.xlabel(self.xaxis_name, fontweight='bold', fontsize=15) + plt.ylabel(self.yaxis_name, fontweight='bold', fontsize=15) + plt.gcf().autofmt_xdate() + plt.legend() + else: + colours = ListedColormap(self.color) + scatter = plt.scatter(self.x_data_set, self.y_data_set, c=self.values, cmap=colours) + plt.xlabel(self.xaxis_name, fontweight='bold', fontsize=15) + plt.ylabel(self.yaxis_name, fontweight='bold', fontsize=15) + plt.gcf().autofmt_xdate() + plt.legend(handles=scatter.legend_elements()[0], labels=self.label) + plt.savefig("%s.png" % self.graph_image_name, dpi=96) + plt.close() + print("{}.png".format(self.graph_image_name)) + if self.enable_csv: + self.lf_csv.columns = self.label + self.lf_csv.rows = self.y_data_set + self.lf_csv.filename = f"{self.graph_image_name}.csv" + self.lf_csv.generate_csv() + + return "%s.png" % self.graph_image_name + + +class lf_stacked_graph(): + def __init__(self, + _data_set=[[1, 2, 3, 4], [1, 1, 1, 1], [1, 1, 1, 1]], + _xaxis_name="Stations", + _yaxis_name="Numbers", + _label=['Success', 'Fail'], + _graph_image_name="image_name2", + _color=["b", "g"], + _figsize=(9, 4), + _enable_csv=True): + self.data_set = _data_set # [x_axis,y1_axis,y2_axis] + self.xaxis_name = _xaxis_name + self.yaxis_name = _yaxis_name + self.figsize = _figsize + self.graph_image_name = _graph_image_name + self.label = _label + self.color = _color + self.enable_csv = _enable_csv + self.lf_csv = lf_csv() + + def build_stacked_graph(self): + fig = plt.subplots(figsize=self.figsize) + if self.color is None: + self.color = ["darkred", "tomato", "springgreen", "skyblue", "indigo", "plum"] + plt.bar(self.data_set[0], self.data_set[1], color=self.color[0]) + plt.bar(self.data_set[0], self.data_set[2], bottom=self.data_set[1], color=self.color[1]) + if len(self.data_set) > 3: + for i in range(3, len(self.data_set)): + plt.bar(self.data_set[0], self.data_set[i], + bottom=np.array(self.data_set[i - 2]) + np.array(self.data_set[i - 1]), color=self.color[i - 1]) + plt.xlabel(self.xaxis_name) + plt.ylabel(self.yaxis_name) + plt.legend(self.label) + plt.savefig("%s.png" % (self.graph_image_name), dpi=96) + plt.close() + print("{}.png".format(self.graph_image_name)) + if self.enable_csv: + self.lf_csv.columns = self.label + self.lf_csv.rows = self.data_set + self.lf_csv.filename = f"{self.graph_image_name}.csv" + self.lf_csv.generate_csv() + return "%s.png" % (self.graph_image_name) + + +class lf_horizontal_stacked_graph(): + def __init__(self, + _seg=2, + _yaxis_set=('A', 'B'), + _xaxis_set1=[12, 0, 0, 16, 15], + _xaxis_set2=[23, 34, 23, 0], + _unit="%", + _xaxis_name="Stations", + _label=['Success', 'Fail'], + _graph_image_name="image_name3", + _color=["success", "Fail"], + _figsize=(9, 4), + _disable_xaxis=False, + _enable_csv=True): + self.unit = _unit + self.seg = _seg + self.xaxis_set1 = _xaxis_set1 + self.xaxis_set2 = _xaxis_set2 + self.yaxis_set = _yaxis_set + self.xaxis_name = _xaxis_name + self.figsize = _figsize + self.graph_image_name = _graph_image_name + self.label = _label + self.color = _color + self.disable_xaxis = _disable_xaxis + self.enable_csv = _enable_csv + self.lf_csv = lf_csv() + + def build_horizontal_stacked_graph(self): + def sumzip(items): + return [sum(values) for values in zip(items)] + + fig, ax = plt.subplots(figsize=self.figsize) + + n = self.seg + values1 = self.xaxis_set1 + values2 = self.xaxis_set2 + + ind = np.arange(n) + .15 + width = 0.3 + + rects1 = plt.barh(ind, values1, width, color=self.color[0], label=self.label[0]) + rects2 = plt.barh(ind, values2, width, left=sumzip(values1), color=self.color[1], label=self.label[1]) + + extra_space = 0.15 + ax.set_yticks(ind + width - extra_space) + ax.set_yticklabels(self.yaxis_set) + ax.yaxis.set_tick_params(length=0, labelbottom=True) + + for i, v in enumerate(values1): + if v != 0: + plt.text(v * 0.45, i + .145, "%s%s" % (v, self.unit), color='white', fontweight='bold', fontsize=10, + ha='center', va='center') + + for i, v in enumerate(values2): + if v != 0: + plt.text(v * 0.45 + values1[i], i + .145, "%s%s" % (v, self.unit), color='white', fontweight='bold', + fontsize=10, + ha='center', va='center') + + ax.spines['right'].set_visible(False) + ax.spines['top'].set_visible(False) + ax.legend(loc='upper right') + if self.disable_xaxis: + plt.tick_params(axis='x', which='both', bottom=False, top=False, labelbottom=False) # disable x-axis + plt.savefig("%s.png" % self.graph_image_name, dpi=96) + plt.close() + print("{}.png".format(self.graph_image_name)) + if self.enable_csv: + self.lf_csv.columns = self.label + self.lf_csv.rows = self.data_set + self.lf_csv.filename = f"{self.graph_image_name}.csv" + self.lf_csv.generate_csv() + return "%s.png" % self.graph_image_name + + +class lf_line_graph(): + def __init__(self,_data_set=[[30.4, 55.3, 69.2, 37.1], [45.1, 67.2, 34.3, 22.4], [22.5, 45.6, 12.7, 34.8]], + _xaxis_name="x-axis", + _yaxis_name="y-axis", + _xaxis_categories=[1, 2, 3, 4, 5], + _xaxis_label=["a", "b", "c", "d", "e"], + _graph_title="", + _title_size=16, + _graph_image_name="image_name", + _label=["bi-downlink", "bi-uplink", 'uplink'], + _font_weight='bold', + _color=['forestgreen', 'c', 'r', 'g', 'b', 'p'], + _figsize=(10, 5), + _xaxis_step = 5, + _xticks_font = None, + _text_font=None, + _legend_handles=None, + _legend_loc="best", + _legend_box=None, + _legend_ncol=1, + _legend_fontsize=None, + _marker=None, + _dpi=96, + _enable_csv=False): + self.data_set = _data_set + self.xaxis_name = _xaxis_name + self.yaxis_name = _yaxis_name + self.xaxis_categories = _xaxis_categories + self.xaxis_label = _xaxis_label + self.grp_title = _graph_title + self.title_size = _title_size + self.graph_image_name = _graph_image_name + self.label = _label + self.color = _color + self.font_weight = _font_weight + self.figsize = _figsize + self.xaxis_step = _xaxis_step + self.xticks_font = _xticks_font + self.text_font = _text_font + self.marker = _marker + self.enable_csv = _enable_csv + self.lf_csv = lf_csv() + self.legend_handles = _legend_handles + self.legend_loc = _legend_loc + self.legend_box = _legend_box + self.legend_ncol = _legend_ncol + self.legend_fontsize = _legend_fontsize + + def build_line_graph(self): + fig = plt.subplots(figsize=self.figsize) + i = 0 + for data in self.data_set: + plt.plot(self.xaxis_categories, data, color=self.color[i], label=self.label[i], marker = self.marker) + i += 1 + + plt.xlabel(self.xaxis_name, fontweight='bold', fontsize=15) + plt.ylabel(self.yaxis_name, fontweight='bold', fontsize=15) + plt.legend(handles=self.legend_handles, loc=self.legend_loc, bbox_to_anchor=self.legend_box, ncol=self.legend_ncol, fontsize=self.legend_fontsize) + plt.suptitle(self.grp_title, fontsize=self.title_size) + fig = plt.gcf() + plt.savefig("%s.png" % self.graph_image_name, dpi=96) + plt.close() + print("{}.png".format(self.graph_image_name)) + if self.enable_csv: + if self.data_set is not None: + self.lf_csv.columns = self.label + self.lf_csv.rows = self.data_set + self.lf_csv.filename = f"{self.graph_image_name}.csv" + self.lf_csv.generate_csv() + else: + print("No Dataset Found") + print("{}.csv".format(self.graph_image_name)) + return "%s.png" % self.graph_image_name + +# Unit Test +if __name__ == "__main__": + output_html_1 = "graph_1.html" + output_pdf_1 = "graph_1.pdf" + + # test build_bar_graph with defaults + graph = lf_bar_graph() + graph_html_obj = """ + +

    + """ + # + test_file = open(output_html_1, "w") + test_file.write(graph_html_obj) + test_file.close() + + # write to pdf + # write logic to generate pdf here + # wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb + # sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb + options = {"enable-local-file-access": None} # prevent eerror Blocked access to file + pdfkit.from_file(output_html_1, output_pdf_1, options=options) + + # test build_bar_graph setting values + dataset = [[45, 67, 34, 22], [22, 45, 12, 34], [30, 55, 69, 37]] + x_axis_values = [1, 2, 3, 4] + + output_html_2 = "graph_2.html" + output_pdf_2 = "graph_2.pdf" + + # test build_bar_graph with defaults + graph = lf_bar_graph(_data_set=dataset, + _xaxis_name="stations", + _yaxis_name="Throughput 2 (Mbps)", + _xaxis_categories=x_axis_values, + _graph_image_name="Bi-single_radio_2.4GHz", + _label=["bi-downlink", "bi-uplink", 'uplink'], + _color=None, + _color_edge='red', + _enable_csv=True) + graph_html_obj = """ + +

    + """ + # + test_file = open(output_html_2, "w") + test_file.write(graph_html_obj) + test_file.close() + + # write to pdf + # write logic to generate pdf here + # wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb + # sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb + options = {"enable-local-file-access": None} # prevent eerror Blocked access to file + pdfkit.from_file(output_html_2, output_pdf_2, options=options) diff --git a/lanforge/lanforge-scripts/py-scripts/lf_header_template.py b/lanforge/lanforge-scripts/py-scripts/lf_header_template.py new file mode 100644 index 000000000..4aeda4bdb --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_header_template.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +''' +NAME: + +PURPOSE: + + +EXAMPLE: + + +SETUP: + + +NOTES: + + +COPYRIGHT: +Copyright 2021 Candela Technologies Inc + +INCLUDE_IN_README +''' \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/lf_influx_db.json b/lanforge/lanforge-scripts/py-scripts/lf_influx_db.json new file mode 100644 index 000000000..3aee6cfa9 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_influx_db.json @@ -0,0 +1,12 @@ +{ + "influx_host":"192.168.100.201", + "influx_port": "8086", + "influx_org": "Candela", + "influx_token": "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==", + "influx_bucket": "ben", + "influx_tag": "testbed Ferndale-01" +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/lf_mesh_test.py b/lanforge/lanforge-scripts/py-scripts/lf_mesh_test.py new file mode 100755 index 000000000..68924e85e --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_mesh_test.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 + +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +This script is used to automate running Mesh tests. You +may need to view a Mesh test configured through the GUI to understand +the options and how best to input data. + + ./lf_mesh_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name mesh-instance --config_name test_con --upstream 1.1.eth1 \ + --raw_line 'selected_dut2: RootAP wactest 08:36:c9:19:47:40 (1)' \ + --raw_line 'selected_dut5: RootAP wactest 08:36:c9:19:47:50 (2)' \ + --duration 15s \ + --download_speed 85% --upload_speed 56Kbps \ + --raw_line 'velocity: 100' \ + --raw_lines_file example-configs/mesh-ferndale-cfg.txt \ + --test_rig Ferndale-Mesh-01 --pull_report + +Note: + --raw_line 'line contents' will add any setting to the test config. This is + useful way to support any options not specifically enabled by the + command options. + --set modifications will be applied after the other config has happened, + so it can be used to override any other config. + +Example of raw text config for Mesh, to show other possible options: + +show_events: 1 +show_log: 0 +port_sorting: 0 +kpi_id: Mesh +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 1 +skip_2: 0 +skip_5: 0 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +selected_dut5: RootAP wactest 08:36:c9:19:47:50 (2) +selected_dut2: RootAP wactest 08:36:c9:19:47:40 (1) +upstream_port: 1.1.1 eth1 +operator: +mconn: 5 +tos: 0 +dur: 60 +speed: 100% +speed2: 56Kbps +velocity: 100 +path_loops: 1 +bgscan_mod: simple +bgscan_short: 30 +bgscan_long: 300 +bgscan_rssi: -60 +skip_2: 0 +skip_5: 0 +skip_dhcp: 0 +show_tx_mcs: 1 +show_rx_mcs: 1 +chamber-0: RootAP +chamber-1: Node1 +chamber-2: Node2 +chamber-3: +chamber-4: MobileStations +sta_amount-0: 1 +sta_amount-1: 1 +sta_amount-2: 1 +sta_amount-3: 1 +sta_amount-4: 1 +radios-0-0: 1.2.2 wiphy0 +radios-0-1: +radios-0-2: +radios-0-3: 1.2.3 wiphy1 +radios-0-4: +radios-0-5: +radios-1-0: 1.3.2 wiphy0 +radios-1-1: +radios-1-2: +radios-1-3: 1.3.3 wiphy1 +radios-1-4: +radios-1-5: +radios-2-0: 1.4.2 wiphy0 +radios-2-1: +radios-2-2: +radios-2-3: 1.4.3 wiphy1 +radios-2-4: +radios-2-5: +radios-3-0: +radios-3-1: +radios-3-2: +radios-3-3: +radios-3-4: +radios-3-5: +radios-4-0: 1.1.2 wiphy0 +radios-4-1: +radios-4-2: +radios-4-3: 1.1.3 wiphy1 +radios-4-4: +radios-4-5: +ap_arrangements: Current Position +tests: Roam +traf_combo: STA +sta_position: Current Position +traffic_types: UDP +direction: Download +path: Orbit Current +traf_use_sta: 0 + +""" +import sys +import os +import importlib +import argparse +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cvtest = cv_test_manager.cv_test +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser + + +class MeshTest(cvtest): + def __init__(self, + lf_host="localhost", + lf_port=8080, + lf_user="lanforge", + lf_password="lanforge", + instance_name="dpt_instance", + config_name="dpt_config", + upstream="1.1.eth1", + pull_report=False, + load_old_cfg=False, + upload_speed="56Kbps", + download_speed="85%", + duration="60s", + enables=[], + disables=[], + raw_lines=[], + raw_lines_file="", + sets=[], + ): + super().__init__(lfclient_host=lf_host, lfclient_port=lf_port) + + self.lf_host = lf_host + self.lf_port = lf_port + self.lf_user = lf_user + self.lf_password =lf_password + self.instance_name = instance_name + self.config_name = config_name + self.duration = duration + self.upstream = upstream + self.pull_report = pull_report + self.load_old_cfg = load_old_cfg + self.test_name = "Mesh" + self.upload_speed = upload_speed + self.download_speed = download_speed + self.enables = enables + self.disables = disables + self.raw_lines = raw_lines + self.raw_lines_file = raw_lines_file + self.sets = sets + + def setup(self): + # Nothing to do at this time. + return + + + def run(self): + self.sync_cv() + time.sleep(2) + self.sync_cv() + + blob_test = "Mesh-" + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + self.show_text_blob(None, None, False) + + # Test related settings + cfg_options = [] + + ### HERE### + self.apply_cfg_options(cfg_options, self.enables, self.disables, self.raw_lines, self.raw_lines_file) + + # cmd line args take precedence and so come last in the cfg array. + if self.upstream != "": + cfg_options.append("upstream_port: " + self.upstream) + if self.download_speed != "": + cfg_options.append("speed: " + self.download_speed) + if self.upload_speed != "": + cfg_options.append("speed2: " + self.upload_speed) + if self.duration != "": + cfg_options.append("duration: " + self.duration) + + # We deleted the scenario earlier, now re-build new one line at a time. + + self.build_cfg(self.config_name, blob_test, cfg_options) + + cv_cmds = [] + self.create_and_run_test(self.load_old_cfg, self.test_name, self.instance_name, + self.config_name, self.sets, + self.pull_report, self.lf_host, self.lf_user, self.lf_password, + cv_cmds) + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + + +def main(): + + parser = argparse.ArgumentParser( + prog="lf_mesh_test.py", + formatter_class=argparse.RawTextHelpFormatter, + description=""" + Open this file in an editor and read the top notes for more details. + + Example: + ./lf_mesh_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name mesh-instance --config_name test_con --upstream 1.1.eth1 \ + --raw_line 'selected_dut2: RootAP wactest 08:36:c9:19:47:40 (1)' \ + --raw_line 'selected_dut5: RootAP wactest 08:36:c9:19:47:50 (2)' \ + --duration 15s \ + --download_speed 85% --upload_speed 56Kbps \ + --raw_line 'velocity: 100' \ + --raw_lines_file example-configs/mesh-ferndale-cfg.txt \ + --test_rig Ferndale-Mesh-01 --pull_report + + NOTE: There is quite a lot of config needed, see example-configs/mesh-ferndale-cfg.txt + Suggestion is to configure the test through the GUI, make sure it works, then view + the config and paste it into your own cfg.txt file. + + """ + ) + + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for wifi capacity test ex. 1.1.eth2") + + parser.add_argument("--download_speed", default="", + help="Specify requested download speed. Percentage of theoretical is also supported. Default: 85%") + parser.add_argument("--upload_speed", default="", + help="Specify requested upload speed. Percentage of theoretical is also supported. Default: 0") + parser.add_argument("--duration", default="", + help="Specify duration of each traffic run") + + args = parser.parse_args() + + cv_base_adjust_parser(args) + + CV_Test = MeshTest(lf_host = args.mgr, + lf_port = args.port, + lf_user = args.lf_user, + lf_password = args.lf_password, + instance_name = args.instance_name, + config_name = args.config_name, + upstream = args.upstream, + pull_report = args.pull_report, + load_old_cfg = args.load_old_cfg, + download_speed = args.download_speed, + upload_speed = args.upload_speed, + duration = args.duration, + enables = args.enable, + disables = args.disable, + raw_lines = args.raw_line, + raw_lines_file = args.raw_lines_file, + sets = args.set + ) + CV_Test.setup() + CV_Test.run() + + # Mesh does not do KPI currently. + #CV_Test.check_influx_kpi(args) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_multipsk.py b/lanforge/lanforge-scripts/py-scripts/lf_multipsk.py new file mode 100755 index 000000000..298247e8b --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_multipsk.py @@ -0,0 +1,403 @@ +#!/usr/bin/env python3 + +""" +NAME: lf_multipsk.py + +PURPOSE: + to test the multipsk feature in access point. Multipsk feature states connecting clients using same ssid but different passwords , + here we will create two or 3 passwords with different vlan id on single ssid and try to connect client with different passwords. + +DESCRIPTION: + The script will follow basic functionality as:- + 1- create station on input parameters provided + 2- the input parameters consist of dictionary of passwords,upstream,mac address, number of clients and radio as input + 3- will create layer3 cx for tcp and udp + 3- verify layer3 cx + 4- verify the ip for each station is comming from respective vlan id or not. +example :- + python3 lf_multipsk.py --mgr localhost --mgr_port 8802 --ssid "MDU Wi-Fi" --security wpa2 + +INCLUDE_IN_README + -Nikita Yadav + Copyright 2021 Candela Technologies Inc + License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import argparse +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class MultiPsk(Realm): + def __init__(self, + host=None, + port=None, + ssid=None, + input=None, + security=None, + passwd=None, + radio=None, + num_sta=None, + start_id=0, + resource=1, + sta_prefix="sta", + debug_=False, + ): + self.host = host + self.port = port + self.ssid = ssid + self.input = input + self.security = security + self.passwd = passwd + self.radio = radio + self.num_sta = num_sta + self.start_id = start_id + self.resource = resource + self.sta_prefix = sta_prefix + self.debug = debug_ + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + + def build(self): + station_list = [] + for idex, input in enumerate(self.input): + # print(input) + if "." in input['upstream']: + num = input['upstream'].split('.')[1] + sta_name = "1." + str(self.resource) + ".sta" + str(num) + # print(sta_name) + station_list.append(sta_name) + else: + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=self.start_id, + end_id_=input['num_station'] - 1, padding_number_=100, + radio=input['radio']) + # implementation for non vlan pending **** + print("creating stations") + self.station_profile.use_security(self.security, self.ssid, str(input['password'])) + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=input['radio'], sta_names_=station_list, debug=self.local_realm.debug) + self.local_realm.wait_until_ports_appear(sta_list=station_list) + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(station_list, timeout_sec=120): + print("All stations got IPs") + else: + print("Stations failed to get IPs") + + print("create udp endp") + self.cx_profile_udp = self.local_realm.new_l3_cx_profile() + self.cx_profile_udp.side_a_min_bps = 128000 + self.cx_profile_udp.side_b_min_bps = 128000 + self.cx_profile_udp.side_a_min_pdu = 1200 + self.cx_profile_udp.side_b_min_pdu = 1500 + self.cx_profile_udp.report_timer = 1000 + self.cx_profile_udp.name_prefix = "udp" + port_list = list(self.local_realm.find_ports_like("%s+" % self.sta_prefix)) + # print("port list", port_list) + if (port_list is None) or (len(port_list) < 1): + raise ValueError("Unable to find ports named '%s'+" % self.sta_prefix) + self.cx_profile_udp.create(endp_type="lf_udp", + side_a=port_list, + side_b="%d.%s" % (self.resource, input['upstream']), + suppress_related_commands=True) + + # Create TCP endpoints + print("create tcp endp") + self.l3_tcp_profile = self.local_realm.new_l3_cx_profile() + self.l3_tcp_profile.side_a_min_bps = 128000 + self.l3_tcp_profile.side_b_min_bps = 56000 + self.l3_tcp_profile.name_prefix = "tcp" + self.l3_tcp_profile.report_timer = 1000 + self.l3_tcp_profile.create(endp_type="lf_tcp", + side_a=list(self.local_realm.find_ports_like("%s+" % self.sta_prefix)), + side_b="%d.%s" % (self.resource, input['upstream']), + suppress_related_commands=True) + + def start(self): + self.cx_profile_udp.start_cx() + self.l3_tcp_profile.start_cx() + + def monitor_vlan_ip(self): + # this function gives vlan ip dict eg{'eth2.100': '172.17.0.1'} + vlan_ips = {} + for i in self.input: + # print(i) + if "." in i['upstream']: + # print(str(i['upstream']) + " is a vlan upstream port") + print("checking its ip ..") + data = self.local_realm.json_get("ports/list?fields=IP") + for val in data["interfaces"]: + for j in val: + if "1." + str(self.resource) + "." + str(i['upstream']) == j: + ip_upstream = val["1." + str(self.resource) + "." + str(i['upstream'])]['ip'] + vlan_ips[i['upstream']] = ip_upstream + # print(ip_upstream) + # print(vlan_ips) + return vlan_ips + # {'eth2.100': '172.17.0.1', 'eth2.200': '172.18.0.1'} + + def monitor_non_vlan_ip(self): + non_vlan_ips = {} + for i in self.input: + if "." not in i['upstream']: + # print(str(i['upstream']) + " is not an vlan upstream port") + print("checking its ip ..") + data = self.local_realm.json_get("ports/list?fields=IP") + for val in data["interfaces"]: + for j in val: + if "1." + str(self.resource) + "." + str(i['upstream']) == j: + ip_upstream = val["1." + str(self.resource) + "." + str(i['upstream'])]['ip'] + non_vlan_ips[i['upstream']] = ip_upstream + # print(ip_upstream) + # print(non_vlan_ips) + return non_vlan_ips + + def get_sta_ip(self): + # this function gives station ip dict eg{'eth2.100': '172.17.0.100'} + # self.input = [{'password': 'lanforge1', 'upstream': 'eth2.100', 'mac': '', 'num_station': 1, 'radio': 'wiphy4'}, {'password': 'lanforge2', 'upstream': 'eth2.200', 'mac': '', 'num_station': 1, 'radio': 'wiphy4'}, {'password': 'lanforge3', 'upstream': 'eth2', 'mac': '', 'num_station': 1, 'radio': 'wiphy0'}] + # port_list = ['1.1.sta200', '1.1.sta00', '1.1.sta100'] + station_ip = {} + port_list = list(self.local_realm.find_ports_like("%s+" % self.sta_prefix)) + # print("port list", port_list) + # port list ['1.1.sta200', '1.1.sta00', '1.1.sta100'] + for name, id in zip(port_list, self.input): + # print(name) + # print(type(name)) + x = id['upstream'].split('.')[1] + # print(x) + + if name == "1." + str(self.resource) + ".sta" + str(x): + data = self.local_realm.json_get("ports/list?fields=IP") + for i in data["interfaces"]: + # print(i) + for j in i: + if j == name: + sta_ip = i[name]['ip'] + # print(sta_ip) + station_ip[id['upstream']] = sta_ip + # print(station_ip) + return station_ip + + def get_sta_ip_for_more_vlan(self): + input = [{'password': 'lanforge1', 'upstream': 'eth2.100', 'mac': '', 'num_station': 1, 'radio': 'wiphy4'}, + {'password': 'lanforge2', 'upstream': 'eth2.200', 'mac': '', 'num_station': 1, 'radio': 'wiphy4'}, + {'password': 'lanforge3', 'upstream': 'eth2', 'mac': '', 'num_station': 1, 'radio': 'wiphy0'}] + port_list = ['1.1.sta200', '1.1.sta00', '1.1.sta100'] + upstream_list = [] + id_num = [] + station_ip = {} + for i in input: + if "." in i['upstream']: + # print(i['upstream']) + upstream_list.append(i['upstream']) + x = i['upstream'].split('.')[1] + id_num.append(x) + + # print(upstream_list) + # print(id_num) + # print(port_list) + port = [] + + for i in port_list: + # print(i.split(".")[2]) + for num in id_num: + if i.split(".")[2] == "sta" + str(num): + port.append(i) + sorted_port = sorted(port) + + for name, id in zip(sorted_port, self.input): + # print(name) + # print(type(name)) + x = id['upstream'].split('.')[1] + # print(x) + + if name == "1." + str(self.resource) + ".sta" + str(x): + data = self.local_realm.json_get("ports/list?fields=IP") + for i in data["interfaces"]: + # print(i) + for j in i: + if j == name: + sta_ip = i[name]['ip'] + # print(sta_ip) + station_ip[id['upstream']] = sta_ip + # print(station_ip) + return station_ip + + def get_non_vlan_sta_ip(self): + station_nonvlan_ip = {} + x = "" + port_list = list(self.local_realm.find_ports_like("%s+" % self.sta_prefix)) + # print("port list", port_list) + for id in self.input: + if "." not in id['upstream']: + x = id['upstream'] + # print(x) + for name in port_list: + if name == "1.1.sta00": + data = self.local_realm.json_get("ports/list?fields=IP") + for i in data["interfaces"]: + # print(i) + for j in i: + if j == name: + sta_ip = i[name]['ip'] + # print(sta_ip) + station_nonvlan_ip[x] = sta_ip + return station_nonvlan_ip + + def compare_ip(self): + vlan_ip = self.monitor_vlan_ip() + station_ip = self.get_sta_ip() + # vlan_ip = {'eth2.100': '172.17.0.1', 'eth2.200': '172.18.0.1'} + # station_ip = {'eth2.100': '172.17.0.237', 'eth2.200': '172.18.100.222'} + for i, j in zip(vlan_ip, station_ip): + if i == j: + x = vlan_ip[i].split('.') + y = station_ip[j].split('.') + if x[0] == y[0] and x[1] == y[1]: + print("station got ip from vlan") + x = "Pass" + else: + print("station did not got ip from vlan") + x = "Fail" + return x + + def compare_nonvlan_ip_nat(self): + non_vlan_sta_ip = self.get_non_vlan_sta_ip() + # print(non_vlan_sta_ip) + for id in self.input: + if "." not in id['upstream']: + x = id['upstream'] + # print(non_vlan_sta_ip[x]) + non_vlan = non_vlan_sta_ip[x].split(".") + if non_vlan[0] == "192" and non_vlan[1] == "168": + # print("Pass") + x = 'Pass' + else: + x = "Fail" + return x + + def compare_nonvlan_ip_bridge(self): + upstream_ip = self.monitor_non_vlan_ip() + non_vlan_sta_ip = self.get_non_vlan_sta_ip() + + for i, j in zip(upstream_ip, non_vlan_sta_ip): + # print(i) + if i == j: + x = upstream_ip[i].split('.') + y = non_vlan_sta_ip[j].split('.') + if x[0] == y[0] and x[1] == y[1]: + print("station got ip from upstream") + result1 = "Pass" + else: + print("station did not got ip from upstream") + result1 = "Fail" + return result1 + + def postcleanup(self): + self.cx_profile_udp.cleanup() + self.l3_tcp_profile.cleanup() + self.station_profile.cleanup() + LFUtils.wait_until_ports_disappear(base_url=self.local_realm.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + print("Test Completed") + + +def main(): + parser = argparse.ArgumentParser( + prog="lf_multipsk.py", + formatter_class=argparse.RawTextHelpFormatter, + description="lanforge webpage download Test Script") + parser.add_argument('--mgr', help='hostname for where LANforge GUI is running', default='localhost') + parser.add_argument('--mgr_port', help='port LANforge GUI HTTP service is running on', default=8080) + parser.add_argument('--ssid', help='WiFi SSID for client to associate to') + parser.add_argument('--security', help='WiFi Security protocol: {open|wep|wpa2|wpa3', default="wpa2") + parser.add_argument('--mode', help="specify mode of ap eg BRIDGE or NAT", default="BRIDGE") + parser.add_argument('--n_vlan', help="type number of vlan using in test eg 1 or 2", default=1) + # parser.add_argument('--input', nargs="+", help="specify list of parameters like passwords,upstream,mac address, number of clients and radio as input, eg password@123,eth2.100,"",1,wiphy0 lanforge@123,eth2.100,"",1,wiphy1") + args = parser.parse_args() + + input_data = [{ + "password": "lanforge1", + "upstream": "eth2.100", + "mac": "", + "num_station": 1, + "radio": "wiphy4" + }, + { + "password": "lanforge2", + "upstream": "eth2.200", + "mac": "", + "num_station": 1, + "radio": "wiphy4" + }, + { + "password": "lanforge2", + "upstream": "eth2.300", + "mac": "", + "num_station": 1, + "radio": "wiphy4" + }, + { + "password": "lanforge", + "upstream": "eth2", + "mac": "", + "num_station": 1, + "radio": "wiphy0" + }, + + ] + multi_obj = MultiPsk(host=args.mgr, + port=args.mgr_port, + ssid=args.ssid, + input=input_data, + security=args.security) + + multi_obj.build() + multi_obj.start() + time.sleep(60) + multi_obj.monitor_vlan_ip() + if args.n_vlan == "1": + multi_obj.get_sta_ip() + else: + multi_obj.get_sta_ip_for_more_vlan() + + result = multi_obj.compare_ip() + print("checking for vlan ips") + if result == "Pass": + print("Test pass") + else: + print("Test Fail") + print("now checking ip for non vlan port") + multi_obj.monitor_non_vlan_ip() + multi_obj.get_non_vlan_sta_ip() + if args.mode == "BRIDGE": + result1 = multi_obj.compare_nonvlan_ip_bridge() + else: + result1 = multi_obj.compare_nonvlan_ip_nat() + if result1 == "Pass": + print("Test passed for non vlan ip ") + else: + print("Test failed for non vlan ip") + print("all result gathered") + print("clean up") + multi_obj.postcleanup() + + +if __name__ == '__main__': + main() + diff --git a/lanforge/lanforge-scripts/py-scripts/lf_report.py b/lanforge/lanforge-scripts/py-scripts/lf_report.py new file mode 100755 index 000000000..96ceadd3c --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_report.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python3 + +''' +NAME: lf_report.py + +PURPOSE: + +This program is a helper class for reporting results for a lanforge python script. +The class will generate an output directory based on date and time in the /home/lanforge/html-reports/ . +If the reports-data is not present then the date and time directory will be created in the current directory. +The banner and Candela Technology logo will be copied in the results directory. +The results directory may be over written and many of the other paramaters during construction. +Creating the date time directory on construction was a design choice. + +EXAMPLE: + +This is a helper class, a unit test is included at the bottom of the file. +To test lf_report.py and lf_graph.py together use the lf_report_test.py file + +LICENSE: + Free to distribute and modify. LANforge systems must be licensed. + Copyright 2021 Candela Technologies Inc + + +INCLUDE_IN_README +''' +# CAUTION: adding imports to this file which are not in update_dependencies.py is not advised +import os +import shutil +import datetime + +import pandas as pd +import pdfkit + + +# internal candela references included during intial phases, to be deleted at future date +# https://candelatech.atlassian.net/wiki/spaces/LANFORGE/pages/372703360/Scripting+Data+Collection+March+2021 +# base report class +class lf_report(): + def __init__(self, + # _path the report directory under which the report directories will be created. + _path="/home/lanforge/html-reports", + _alt_path="", + _date="", + _title="LANForge Test Run Heading", + _table_title="LANForge Table Heading", + _graph_title="LANForge Graph Title", + _obj="", + _obj_title="", + _output_html="outfile.html", + _output_pdf="outfile.pdf", + _results_dir_name="LANforge_Test_Results", + _output_format='html', # pass in on the write functionality, current not used + _dataframe="", + _path_date_time="", + _custom_css='custom-example.css'): # this is where the final report is placed. + # other report paths, + + # _path is where the directory with the data time will be created + if _path == "local" or _path == "here": + self.path = os.path.abspath(__file__) + print("path set to file path: {}".format(self.path)) + elif _alt_path != "": + self.path = _alt_path + print("path set to alt path: {}".format(self.path)) + else: + self.path = _path + print("path set: {}".format(self.path)) + + self.dataframe = _dataframe + self.text = "" + self.title = _title + self.table_title = _table_title + self.graph_title = _graph_title + self.date = _date + self.output_html = _output_html + self.path_date_time = _path_date_time + self.write_output_html = "" + self.output_pdf = _output_pdf + self.write_output_pdf = "" + self.banner_html = "" + self.footer_html = "" + self.graph_titles = "" + self.graph_image = "" + self.csv_file_name = "" + self.html = "" + self.custom_html = "" + self.pdf_link_html = "" + self.objective = _obj + self.obj_title = _obj_title + # self.systeminfopath = "" + self.date_time_directory = "" + self.log_directory = "" + + self.banner_directory = "artifacts" + self.banner_file_name = "banner.png" # does this need to be configurable + self.logo_directory = "artifacts" + self.logo_file_name = "CandelaLogo2-90dpi-200x90-trans.png" # does this need to be configurable. + self.logo_footer_file_name = "candela_swirl_small-72h.png" # does this need to be configurable. + self.current_path = os.path.dirname(os.path.abspath(__file__)) + self.custom_css = _custom_css + # note: the following 3 calls must be in order + self.set_date_time_directory(_date, _results_dir_name) + self.build_date_time_directory() + self.build_log_directory() + + self.font_file = "CenturyGothic.woff" + # move the banners and candela images to report path + self.copy_banner() + self.copy_css() + self.copy_logo() + self.copy_logo_footer() + + def copy_banner(self): + banner_src_file = str(self.current_path) + '/' + str(self.banner_directory) + '/' + str(self.banner_file_name) + banner_dst_file = str(self.path_date_time) + '/' + str(self.banner_file_name) + # print("banner src_file: {}".format(banner_src_file)) + # print("dst_file: {}".format(banner_dst_file)) + shutil.copy(banner_src_file, banner_dst_file) + + def copy_css(self): + reportcss_src_file = str(self.current_path) + '/' + str(self.banner_directory) + '/report.css' + # print("copy_css: source file is: "+reportcss_src_file) + reportcss_dest_file = str(self.path_date_time) + '/report.css' + + customcss_src_file = str(self.current_path) + '/' + str(self.banner_directory) + '/' + str(self.custom_css) + customcss_dest_file = str(self.path_date_time) + '/custom.css' + + font_src_file = str(self.current_path) + '/' + str(self.banner_directory) + '/' + str(self.font_file) + font_dest_file = str(self.path_date_time) + '/' + str(self.font_file) + + shutil.copy(reportcss_src_file, reportcss_dest_file) + shutil.copy(customcss_src_file, customcss_dest_file) + shutil.copy(font_src_file, font_dest_file) + + def copy_logo(self): + logo_src_file = str(self.current_path) + '/' + str(self.logo_directory) + '/' + str(self.logo_file_name) + logo_dst_file = str(self.path_date_time) + '/' + str(self.logo_file_name) + # print("logo_src_file: {}".format(logo_src_file)) + # print("logo_dst_file: {}".format(logo_dst_file)) + shutil.copy(logo_src_file, logo_dst_file) + + def copy_logo_footer(self): + logo_footer_src_file = str(self.current_path) + '/' + str(self.logo_directory) + '/' + str( + self.logo_footer_file_name) + logo_footer_dst_file = str(self.path_date_time) + '/' + str(self.logo_footer_file_name) + # print("logo_footer_src_file: {}".format(logo_footer_src_file)) + # print("logo_footer_dst_file: {}".format(logo_footer_dst_file)) + shutil.copy(logo_footer_src_file, logo_footer_dst_file) + + def move_graph_image(self, ): + graph_src_file = str(self.graph_image) + graph_dst_file = str(self.path_date_time) + '/' + str(self.graph_image) + print("graph_src_file: {}".format(graph_src_file)) + print("graph_dst_file: {}".format(graph_dst_file)) + shutil.move(graph_src_file, graph_dst_file) + + def move_csv_file(self): + csv_src_file = str(self.csv_file_name) + csv_dst_file = str(self.path_date_time) + '/' + str(self.csv_file_name) + print("csv_src_file: {}".format(csv_src_file)) + print("csv_dst_file: {}".format(csv_dst_file)) + shutil.move(csv_src_file, csv_dst_file) + + def set_path(self, _path): + self.path = _path + + def set_date_time_directory(self, _date, _results_dir_name): + self.date = _date + self.results_dir_name = _results_dir_name + if self.date != "": + self.date_time_directory = str(self.date) + str("_") + str(self.results_dir_name) + else: + self.date = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")).replace(':', '-') + self.date_time_directory = self.date + str("_") + str(self.results_dir_name) + + def build_date_time_directory(self): + if self.date_time_directory == "": + self.set_date_time_directory() + self.path_date_time = os.path.join(self.path, self.date_time_directory) + print("path_date_time {}".format(self.path_date_time)) + try: + if not os.path.exists(self.path_date_time): + os.mkdir(self.path_date_time) + except: + self.path_date_time = os.path.join(self.current_path, self.date_time_directory) + if not os.path.exists(self.path_date_time): + os.mkdir(self.path_date_time) + print("report path : {}".format(self.path_date_time)) + + def build_log_directory(self): + if self.log_directory == "": + self.log_directory = os.path.join(self.path_date_time, "log") + try: + if not os.path.exists(self.log_directory): + os.mkdir(self.log_directory) + except: + print("exception making {}".format(self.log_directory)) + exit(1) + + def set_text(self, _text): + self.text = _text + + def set_title(self, _title): + self.title = _title + + def set_table_title(self, _table_title): + self.table_title = _table_title + + def set_graph_title(self, _graph_title): + self.graph_title = _graph_title + + # sets the csv file name as graph title + def set_csv_filename(self, _graph_title): + fname, ext = os.path.splitext(_graph_title) + self.csv_file_name = fname + ".csv" + + # The _date is set when class is enstanciated / created so this set_date should be used with caution, used to synchronize results + def set_date(self, _date): + self.date = _date + + def set_table_dataframe(self, _dataframe): + self.dataframe = _dataframe + + def set_table_dataframe_from_csv(self, _csv): + self.dataframe = pd.read_csv(_csv) + + def set_custom_html(self, _custom_html): + self.custom_html = _custom_html + + def set_obj_html(self, _obj_title, _obj): + self.objective = _obj + self.obj_title = _obj_title + + def set_graph_image(self, _graph_image): + self.graph_image = _graph_image + + def get_date(self): + return self.date + + def get_path(self): + return self.path + + def get_parent_path(self): + parent_path = os.path.dirname(self.path) + return parent_path + + # get_path_date_time, get_report_path and need to be the same + def get_path_date_time(self): + return self.path_date_time + + def get_report_path(self): + return self.path_date_time + + def get_log_path(self): + return self.log_directory + + def file_add_path(self, file): + output_file = str(self.path_date_time) + '/' + str(file) + print("output file {}".format(output_file)) + return output_file + + def write_html(self): + self.write_output_html = str(self.path_date_time) + '/' + str(self.output_html) + print("write_output_html: {}".format(self.write_output_html)) + try: + test_file = open(self.write_output_html, "w") + test_file.write(self.html) + test_file.close() + except: + print("write_html failed") + return self.write_output_html + + def write_html_with_timestamp(self): + self.write_output_html = "{}/{}-{}".format(self.path_date_time, self.date, self.output_html) + print("write_output_html: {}".format(self.write_output_html)) + try: + test_file = open(self.write_output_html, "w") + test_file.write(self.html) + test_file.close() + except: + print("write_html failed") + return self.write_output_html + + # https://wkhtmltopdf.org/usage/wkhtmltopdf.txt + # page_size A4, A3, Letter, Legal + # orientation Portrait , Landscape + def write_pdf(self, _page_size='A4', _orientation='Portrait'): + # write logic to generate pdf here + # wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb + # sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb + + options = {"enable-local-file-access": None, + 'orientation': _orientation, + 'page-size': _page_size} # prevent error Blocked access to file + self.write_output_pdf = str(self.path_date_time) + '/' + str(self.output_pdf) + pdfkit.from_file(self.write_output_html, self.write_output_pdf, options=options) + + # https://wkhtmltopdf.org/usage/wkhtmltopdf.txt + # page_size A4, A3, Letter, Legal + # orientation Portrait , Landscape + def write_pdf_with_timestamp(self, _page_size='A4', _orientation='Portrait'): + # write logic to generate pdf here + # wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb + # sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb + + options = {"enable-local-file-access": None, + 'orientation': _orientation, + 'page-size': _page_size} # prevent error Blocked access to file + self.write_output_pdf = "{}/{}-{}".format(self.path_date_time, self.date, self.output_pdf) + pdfkit.from_file(self.write_output_html, self.write_output_pdf, options=options) + + def get_pdf_path(self): + pdf_link_path = "{}/{}-{}".format(self.path_date_time, self.date, self.output_pdf) + return pdf_link_path + + def build_pdf_link(self, _pdf_link_name, _pdf_link_path): + self.pdf_link_html = """ + +
    {pdf_link_name} +
    + """.format(pdf_link_path=_pdf_link_path, pdf_link_name=_pdf_link_name) + self.html += self.pdf_link_html + + def build_link(self, _link_name, _link_path): + self.link = """ + + {link_name} +
    + """.format(link_path=_link_path, link_name=_link_name) + self.html += self.link + + def generate_report(self): + self.write_html() + self.write_pdf() + + def build_all(self): + self.build_banner() + self.start_content_div() + self.build_table_title() + self.build_table() + self.end_content_div() + + def get_html_head(self, title='Untitled'): + return """ + + + + + + {title} + """.format(title=title) + + def build_banner(self): + # NOTE: {{ }} are the ESCAPED curly braces + # JBR removed deep indentation of html tag because it makes browser view-source is hard to debug + # JBR suggests rename method to start_html_doc() + self.banner_html = """ + + {head_tag} + +
    + +
    + """.format( + head_tag=self.get_html_head(title=self.title), + title=self.title, + date=self.date, + ) + self.html += self.banner_html + + def build_banner_left(self): + # NOTE: {{ }} are the ESCAPED curly braces + # JBR suggests rename method to start_html_doc() + # This method violates DRY, if the ID of the body/div#BannerBack/div element is actually necessary + # to specify, this needs to be made a parameter for build_banner() or start_html_doc() + self.banner_html = """ + + {head_tag} + +
    +
    +
    + +
    +
    +

    {title}

    +

    {date}

    +
    +
    +
    + """.format( + head_tag=self.get_html_head(title=self.title), + title=self.title, + date=self.date, + ) + self.html += self.banner_html + + def build_table_title(self): + self.table_title_html = """ + +

    {title}

    + """.format(title=self.table_title) + self.html += self.table_title_html + + def start_content_div2(self): + self.html += "\n
    \n" + + def start_content_div(self): + self.html += "\n
    \n" + + def build_text(self): + # please do not use 'style=' tags unless you cannot override a class + self.text_html = """ +
    +

    {text}

    \n +
    """.format(text=self.text) + self.html += self.text_html + + def build_date_time(self): + self.date_time = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-h-%m-m-%S-s")).replace(':', '-') + return self.date_time + + def build_path_date_time(self): + try: + self.path_date_time = os.path.join(self.path, self.date_time) + os.mkdir(self.path_date_time) + except: + curr_dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + self.path_date_time = os.path.join(curr_dir_path, self.date_time) + os.mkdir(self.path_date_time) + + def build_table(self): + self.dataframe_html = self.dataframe.to_html(index=False, + justify='center') # have the index be able to be passed in. + self.html += self.dataframe_html + + def test_setup_table(self, test_setup_data, value): + if test_setup_data is None: + return None + else: + var = "" + for i in test_setup_data: + var = var + "" + i + "" + str(test_setup_data[i]) + "" + + setup_information = """ + + + + + + + +
    """ + str(value) + """ + + """ + var + """ +
    +
    + +
    + """ + self.html += setup_information + + def build_footer(self): + self.footer_html = """ + + """ + self.html += self.footer_html + + def build_footer_no_png(self): + self.footer_html = """ + """ + self.html += self.footer_html + + def copy_js(self): + self.html += """ + + """ + + def build_custom(self): + self.html += self.custom_html + + def build_objective(self): + self.obj_html = """ + +

    {title}

    +

    {objective}

    + """.format(title=self.obj_title, + objective=self.objective) + self.html += self.obj_html + + def build_graph_title(self): + self.table_graph_html = """ +
    +

    {title}

    + """.format(title=self.graph_title) + self.html += self.table_graph_html + + def build_graph(self): + self.graph_html_obj = """ + + """.format(image=self.graph_image) + self.html += self.graph_html_obj + + def end_content_div(self): + self.html += "\n
    \n" + + +# Unit Test +if __name__ == "__main__": + # Testing: generate data frame + dataframe = pd.DataFrame({ + 'product': ['CT521a-264-1ac-1n', 'CT521a-1ac-1ax', 'CT522-264-1ac2-1n', 'CT523c-2ac2-db-10g-cu', + 'CT523c-3ac2-db-10g-cu', 'CT523c-8ax-ac10g-cu', 'CT523c-192-2ac2-1ac-10g'], + 'radios': [1, 1, 2, 2, 6, 9, 3], + 'MIMO': ['N', 'N', 'N', 'Y', 'Y', 'Y', 'Y'], + 'stations': [200, 64, 200, 128, 384, 72, 192], + 'mbps': [300, 300, 300, 10000, 10000, 10000, 10000] + }) + + print(dataframe) + + # Testing: generate data frame + dataframe2 = pd.DataFrame({ + 'station': [1, 2, 3, 4, 5, 6, 7], + 'time_seconds': [23, 78, 22, 19, 45, 22, 25] + }) + + report = lf_report() + report.set_title("Banner Title One") + report.build_banner() + + report.set_table_title("Title One") + report.build_table_title() + + report.set_table_dataframe(dataframe) + report.build_table() + + report.set_table_title("Title Two") + report.build_table_title() + + report.set_table_dataframe(dataframe2) + report.build_table() + + # report.build_all() + # report.build_footer() + report.build_footer_no_png() + + html_file = report.write_html() + print("returned file ") + print(html_file) + report.write_pdf() + + print("report path {}".format(report.get_path())) + diff --git a/lanforge/lanforge-scripts/py-scripts/lf_report_test.py b/lanforge/lanforge-scripts/py-scripts/lf_report_test.py new file mode 100755 index 000000000..0923f1e9d --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_report_test.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +''' +NAME: lf_report_test.py + +PURPOSE: +Common file for testing lf_report and lf_graph Library generates html and pdf output + +SETUP: +/lanforge/html-reports directory needs to be present or output generated in local file + +EXAMPLE: +./lf_report_test.py : currently script does not accept input + +COPYWRITE + Copyright 2021 Candela Technologies Inc + License: Free to distribute and modify. LANforge systems must be licensed. + +INCLUDE_IN_README +''' +import sys +import os +import importlib +import matplotlib.pyplot as plt +import matplotlib as mpl +import numpy as np +import pandas as pd +import pdfkit +import random + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lf_report = importlib.import_module("py-scripts.lf_report") +lf_report = lf_report.lf_report +lf_graph = importlib.import_module("py-scripts.lf_graph") +lf_bar_graph = lf_graph.lf_bar_graph +lf_scatter_graph = lf_graph.lf_scatter_graph +lf_stacked_graph = lf_graph.lf_stacked_graph +lf_horizontal_stacked_graph = lf_graph.lf_horizontal_stacked_graph + + +# Unit Test +if __name__ == "__main__": + # Testing: generate data frame + dataframe = pd.DataFrame({ + 'product': ['CT521a-264-1ac-1n', 'CT521a-1ac-1ax', 'CT522-264-1ac2-1n', 'CT523c-2ac2-db-10g-cu', + 'CT523c-3ac2-db-10g-cu', 'CT523c-8ax-ac10g-cu', 'CT523c-192-2ac2-1ac-10g'], + 'radios': [1, 1, 2, 2, 6, 9, 3], + 'MIMO': ['N', 'N', 'N', 'Y', 'Y', 'Y', 'Y'], + 'stations': [200, 64, 200, 128, 384, 72, 192], + '1 mbps': [300, 300, 300, 10000, 10000, 10000, 10000] + }) + + print(dataframe) + + # Testing: generate data frame + dataframe2 = pd.DataFrame({ + 'station': [1, 2, 3, 4, 5, 6, 7], + 'time_seconds': [23, 78, 22, 19, 45, 22, 25] + }) + + # report = lf_report(_dataframe=dataframe) + report = lf_report() + + report_path = report.get_path() + report_path_date_time = report.get_path_date_time() + + print("path: {}".format(report_path)) + print("path_date_time: {}".format(report_path_date_time)) + + report.set_title("Banner Title One") + report.build_banner() + + # report.set_title("Banner Title Two") + # report.build_banner() + + report.set_table_title("Title One") + report.build_table_title() + + report.set_table_dataframe(dataframe) + report.build_table() + + report.set_table_title("Title Two") + report.build_table_title() + + report.set_table_dataframe(dataframe2) + report.build_table() + set_xaxis = [] + y_set1 = [] + y_set2 = [] + y_set3 = [] + for i in range(0, 30): + set_xaxis.append(i) + y_set1.append(random.randint(1, 50)) + y_set2.append(random.randint(1, 50)) + y_set3.append(random.randint(1, 50)) + + # test lf_graph in report + dataset = [y_set1, y_set2, y_set3] + x_axis_values = set_xaxis + + report.set_graph_title("Graph Title") + report.build_graph_title() + + graph = lf_bar_graph(_data_set=dataset, + _xaxis_name="stations", + _yaxis_name="Throughput 2 (Mbps)", + _xaxis_categories=x_axis_values, + _graph_image_name="Bi-single_radio_2.4GHz", + _label=["bi-downlink", "bi-uplink", 'uplink'], + _color=['darkorange', 'forestgreen','blueviolet'], + _color_edge='red', + _grp_title="Throughput for each clients", + _xaxis_step=5, + _show_bar_value=True, + _text_font=7, + _text_rotation=45, + _xticks_font=7, + _legend_loc="best", + _legend_box=(1,1), + _legend_ncol=1, + _legend_fontsize=None, + _enable_csv=True) + + graph_png = graph.build_bar_graph() + + print("graph name {}".format(graph_png)) + + report.set_graph_image(graph_png) + # need to move the graph image to the results + report.move_graph_image() + if graph.enable_csv: + report.set_csv_filename(graph_png) + report.move_csv_file() + report.build_graph() + set1 = [1, 2, 3, 4] + set2 = [[45, 67, 45, 34], [34, 56, 45, 34], [45, 78, 23, 45]] + graph2 = lf_scatter_graph(_x_data_set=set1, _y_data_set=set2, _xaxis_name="x-axis", _values=None, + _yaxis_name="y-axis", + _graph_image_name="image_name1", + _color=None, + _label=["s1", "s2", "s3"], + _enable_csv = False) + graph_png = graph2.build_scatter_graph() + + print("graph name {}".format(graph_png)) + + report.set_graph_image(graph_png) + report.move_graph_image() + + report.build_graph() + # this will generate graph which is independent,we can customize the value with different colors + graph2 = lf_scatter_graph(_x_data_set=set1, _y_data_set=[45, 67, 45, 34], _values=[0, 0, 0, 1], + _xaxis_name="x-axis", + _yaxis_name="y-axis", + _graph_image_name="image_name_map", + _color=None, + _label=["s1", "s2"], + _enable_csv = False) + graph_png = graph2.build_scatter_graph() + + print("graph name {}".format(graph_png)) + + report.set_graph_image(graph_png) + report.move_graph_image() + + report.build_graph() + dataset = [["1", "2", "3", "4"], [12, 45, 67, 34], [23, 67, 23, 12], [25, 45, 34, 23]] + graph = lf_stacked_graph(_data_set=dataset, + _xaxis_name="Stations", + _yaxis_name="Login PASS/FAIL", + _label=['Success', 'Fail', 'both'], + _graph_image_name="login_pass_fail1", + _color=None, + _enable_csv = False) + + graph_png = graph.build_stacked_graph() + + print("graph name {}".format(graph_png)) + + report.set_graph_image(graph_png) + report.move_graph_image() + report.build_graph() + + graph = lf_horizontal_stacked_graph(_seg=2, + _yaxis_set=('A', 'B'), + _xaxis_set1=[12, 65], + _xaxis_set2=[23, 34], + _unit="", + _xaxis_name="Stations", + _label=['Success', 'Fail'], + _graph_image_name="image_name_pass_fail", + _color=["r", "g"], + _figsize=(9, 4), + _enable_csv = False) + + graph_png = graph.build_horizontal_stacked_graph() + + print("graph name {}".format(graph_png)) + + report.set_graph_image(graph_png) + report.move_graph_image() + report.build_graph() + # report.build_all() + + html_file = report.write_html() + print("returned file {}".format(html_file)) + print(html_file) + + # try other pdf formats + # report.write_pdf() + # report.write_pdf(_page_size = 'A3', _orientation='Landscape') + # report.write_pdf(_page_size = 'A4', _orientation='Landscape') + report.write_pdf(_page_size='Legal', _orientation='Landscape') + # report.write_pdf(_page_size = 'Legal', _orientation='Portrait') + + # report.generate_report() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_rvr_test.py b/lanforge/lanforge-scripts/py-scripts/lf_rvr_test.py new file mode 100755 index 000000000..628f7720e --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_rvr_test.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +This script is used to automate running Rate-vs-Range tests. You +may need to view a Rate-vs-Range test configured through the GUI to understand +the options and how best to input data. + + ./lf_rvr_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name rvr-instance --config_name test_con --upstream 1.1.eth1 \ + --dut RootAP --duration 15s --station 1.1.wlan0 \ + --download_speed 85% --upload_speed 56Kbps \ + --raw_line 'pkts: MTU' \ + --raw_line 'directions: DUT Transmit' \ + --raw_line 'traffic_types: TCP' \ + --test_rig Ferndale-Mesh-01 --pull_report \ + --raw_line 'attenuator: 1.1.1040' \ + --raw_line 'attenuations: 0..+50..950' \ + --raw_line 'attenuator_mod: 3' \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-Mesh + +Note: + attenuator_mod: selects the attenuator modules, bit-field. + This example uses 3, which is first two attenuator modules on Attenuator ID 1040. + + --raw_line 'line contents' will add any setting to the test config. This is + useful way to support any options not specifically enabled by the + command options. + --set modifications will be applied after the other config has happened, + so it can be used to override any other config. + +Example of raw text config for Rate-vsRange, to show other possible options: + +sel_port-0: 1.1.wlan0 +show_events: 1 +show_log: 0 +port_sorting: 0 +kpi_id: Rate vs Range +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 0 +skip_2: 0 +skip_5: 0 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +selected_dut: RootAP +duration: 15000 +traffic_port: 1.1.6 wlan0 +upstream_port: 1.1.1 eth1 +path_loss: 10 +speed: 85% +speed2: 56Kbps +min_rssi_bound: -150 +max_rssi_bound: 0 +channels: AUTO +modes: Auto +pkts: MTU +spatial_streams: AUTO +security_options: AUTO +bandw_options: AUTO +traffic_types: TCP +directions: DUT Transmit +txo_preamble: OFDM +txo_mcs: 0 CCK, OFDM, HT, VHT +txo_retries: No Retry +txo_sgi: OFF +txo_txpower: 15 +attenuator: 1.1.1040 +attenuator2: 0 +attenuator_mod: 243 +attenuator_mod2: 255 +attenuations: 0..+50..950 +attenuations2: 0..+50..950 +chamber: 0 +tt_deg: 0..+45..359 +cust_pkt_sz: +show_bar_labels: 1 +show_prcnt_tput: 0 +show_3s: 0 +show_ll_graphs: 0 +show_gp_graphs: 1 +show_1m: 1 +pause_iter: 0 +outer_loop_atten: 0 +show_realtime: 1 +operator: +mconn: 1 +mpkt: 1000 +tos: 0 +loop_iterations: 1 +""" +import sys +import os +import importlib +import argparse +import time +import json +from os import path + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cvtest = cv_test_manager.cv_test +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser + + +class RvrTest(cvtest): + def __init__(self, + lf_host="localhost", + lf_port=8080, + ssh_port=22, + local_path="", + graph_groups=None, + lf_user="lanforge", + lf_password="lanforge", + instance_name="rvr_instance", + config_name="rvr_config", + upstream="1.1.eth1", + pull_report=False, + load_old_cfg=False, + upload_speed="0", + download_speed="85%", + duration="15s", + station="1.1.wlan0", + dut="NA", + enables=[], + disables=[], + raw_lines=[], + raw_lines_file="", + sets=[], + ): + super().__init__(lfclient_host=lf_host, lfclient_port=lf_port) + + self.lf_host = lf_host + self.lf_port = lf_port + self.lf_user = lf_user + self.lf_password = lf_password + self.instance_name = instance_name + self.config_name = config_name + self.dut = dut + self.duration = duration + self.upstream = upstream + self.station = station + self.pull_report = pull_report + self.load_old_cfg = load_old_cfg + self.test_name = "Rate vs Range" + self.upload_speed = upload_speed + self.download_speed = download_speed + self.enables = enables + self.disables = disables + self.raw_lines = raw_lines + self.raw_lines_file = raw_lines_file + self.sets = sets + self.ssh_port = ssh_port + self.local_path = local_path + self.graph_groups = graph_groups + + def setup(self): + # Nothing to do at this time. + return + + def run(self): + self.sync_cv() + time.sleep(2) + self.sync_cv() + + blob_test = "rvr-test-latest-" + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + self.show_text_blob(None, None, False) + + # Test related settings + cfg_options = [] + + ### HERE### + self.apply_cfg_options(cfg_options, self.enables, self.disables, self.raw_lines, self.raw_lines_file) + + # cmd line args take precedence and so come last in the cfg array. + if self.upstream != "": + cfg_options.append("upstream_port: " + self.upstream) + if self.station != "": + cfg_options.append("traffic_port: " + self.station) + if self.download_speed != "": + cfg_options.append("speed: " + self.download_speed) + if self.upload_speed != "": + cfg_options.append("speed2: " + self.upload_speed) + if self.duration != "": + cfg_options.append("duration: " + self.duration) + if self.dut != "": + cfg_options.append("selected_dut: " + self.dut) + + # We deleted the scenario earlier, now re-build new one line at a time. + + self.build_cfg(self.config_name, blob_test, cfg_options) + + cv_cmds = [] + self.create_and_run_test(self.load_old_cfg, self.test_name, self.instance_name, + self.config_name, self.sets, + self.pull_report, self.lf_host, self.lf_user, self.lf_password, + cv_cmds, ssh_port=self.ssh_port, local_lf_report_dir=self.local_path, + graph_groups_file=self.graph_groups) + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + + +def main(): + parser = argparse.ArgumentParser( + prog="lf_rvr_test.py", + formatter_class=argparse.RawTextHelpFormatter, + description=""" + Open this file in an editor and read the top notes for more details. + + Example: + + + """ + ) + + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for wifi capacity test ex. 1.1.eth2") + parser.add_argument("--station", type=str, default="", + help="Station to be used in this test, example: 1.1.sta01500") + + parser.add_argument("--dut", default="", + help="Specify DUT used by this test, example: linksys-8450") + parser.add_argument("--download_speed", default="", + help="Specify requested download speed. Percentage of theoretical is also supported. Default: 85%") + parser.add_argument("--upload_speed", default="", + help="Specify requested upload speed. Percentage of theoretical is also supported. Default: 0") + parser.add_argument("--duration", default="", + help="Specify duration of each traffic run") + parser.add_argument("--graph_groups", help="File to save graph_groups to", default=None) + parser.add_argument("--report_dir", default="") + + args = parser.parse_args() + + cv_base_adjust_parser(args) + + CV_Test = RvrTest(lf_host=args.mgr, + lf_port=args.port, + lf_user=args.lf_user, + lf_password=args.lf_password, + instance_name=args.instance_name, + config_name=args.config_name, + upstream=args.upstream, + pull_report=args.pull_report, + load_old_cfg=args.load_old_cfg, + download_speed=args.download_speed, + upload_speed=args.upload_speed, + duration=args.duration, + dut=args.dut, + station=args.station, + enables=args.enable, + disables=args.disable, + raw_lines=args.raw_line, + raw_lines_file=args.raw_lines_file, + sets=args.set, + graph_groups=args.graph_groups + ) + CV_Test.setup() + CV_Test.run() + + CV_Test.check_influx_kpi(args) + + +if __name__ == "__main__": + main() + diff --git a/lanforge/lanforge-scripts/py-scripts/lf_rx_sensitivity_config.json b/lanforge/lanforge-scripts/py-scripts/lf_rx_sensitivity_config.json new file mode 100644 index 000000000..81c370bc2 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_rx_sensitivity_config.json @@ -0,0 +1,19 @@ +{ + "mgr":"192.168.0.101", + "port":"8080", + "lf_user":"lanforge", + "lf_password":"lanforge", + "instance_name":"rx-sensitivity-instance", + "config_name":"test_con", + "upstream":"1.1.eth1", + "dut":"asus_5g", + "duration":"15s", + "station":"1.1.eth2", + "download_speed":"85%", + "upload_speed":"0", + "pull_report": true, + "raw_line": ["txo_preamble: VHT", "txo_mcs: 4 OFDM, HT, VHT;5 OFDM, HT, VHT;6 OFDM, HT, VHT;7 OFDM, HT, VHT", "spatial_streams: 3", "bandw_options: 80", "txo_sgi: ON", "txo_retries: No Retry", "txo_txpower: 17"] +} + + + diff --git a/lanforge/lanforge-scripts/py-scripts/lf_rx_sensitivity_test.py b/lanforge/lanforge-scripts/py-scripts/lf_rx_sensitivity_test.py new file mode 100644 index 000000000..3543c49ae --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_rx_sensitivity_test.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python3 +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +This script is used to automate running RX Sensitivity tests. You +may need to view a RX Sensitivity test configured through the GUI to understand +the options and how best to input data. + + ./lf_rx_sensitivity_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name rx-sensitivity-instance --config_name test_con --upstream 1.1.eth2 \ + --dut linksys-8450 --duration 15s --station 1.1.sta01500 \ + --download_speed 85% --upload_speed 0 \ + --raw_line 'txo_preamble: VHT' \ + --raw_line 'txo_mcs: 4 OFDM, HT, VHT;5 OFDM, HT, VHT;6 OFDM, HT, VHT;7 OFDM, HT, VHT' \ + --raw_line 'spatial_streams: 3' \ + --raw_line 'bandw_options: 80' \ + --raw_line 'txo_sgi: ON' \ + --raw_line 'txo_retries: No Retry' \ + --raw_line 'txo_txpower: 17' \ + --test_rig Testbed-01 --pull_report \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01 + +Note: + --raw_line 'line contents' will add any setting to the test config. This is + useful way to support any options not specifically enabled by the + command options. + --set modifications will be applied after the other config has happened, + so it can be used to override any other config. + +Example of raw text config for RX Sensitivity, to show other possible options: + +show_events: 1 +show_log: 0 +port_sorting: 2 +kpi_id: RX Sensitivity +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 0 +skip_ac: 0 +skip_ax: 0 +skip_2: 0 +skip_5: 0 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +selected_dut: ea8300 +duration: 15000 +settle_time: 1000 +sndbuf: 0 +rcvbuf: 0 +traffic_port: 1.1.157 sta01500 +upstream_port: 1.1.1 eth1 +path_loss: 10 +speed: 85% +speed2: 0Kbps +min_rssi_bound: -150 +max_rssi_bound: 0 +channels: AUTO +modes: Auto +pkts: 1024 +spatial_streams: 1 +security_options: AUTO +bandw_options: 20 +traffic_types: TCP +directions: DUT Transmit +txo_preamble: OFDM +txo_mcs: 0 CCK, OFDM, HT, VHT;1 CCK, OFDM, HT, VHT;2 CCK, OFDM, HT, VHT;3 CCK, OFDM, HT, VHT +txo_retries: No Retry +txo_sgi: ON +txo_txpower: 15 +attenuator: 0 +attenuator2: 0 +attenuator_mod: 255 +attenuator_mod2: 255 +attenuations: 0..+50..100 +attenuations2: 0..+50..950 +chamber: 0 +tt_deg: 0..+45..359 +cust_pkt_sz: +show_bar_labels: 1 +show_prcnt_tput: 0 +show_3s: 0 +show_ll_graphs: 0 +show_gp_graphs: 1 +show_1m: 1 +pause_iter: 0 +outer_loop_atten: 0 +show_realtime: 1 +operator: +mconn: 1 +mpkt: 1000 +tos: 0 +loop_iterations: 1 +""" +import sys +import os +import importlib +import argparse +import time +import json +from os import path + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cv_test = cv_test_manager.cv_test +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser + + +class RxSensitivityTest(cv_test): + def __init__(self, + lf_host="localhost", + lf_port=8080, + lf_user="lanforge", + lf_password="lanforge", + ssh_port=22, + local_path="", + instance_name="dpt_instance", + config_name="dpt_config", + upstream="1.1.eth2", + pull_report=False, + load_old_cfg=False, + upload_speed="0", + download_speed="85%", + duration="15s", + station="1.1.sta01500", + dut="NA", + enables=[], + disables=[], + raw_lines=[], + raw_lines_file="", + sets=[], + graph_groups=None, + report_dir="" + ): + super().__init__(lfclient_host=lf_host, lfclient_port=lf_port) + + self.lf_host = lf_host + self.lf_port = lf_port + self.lf_user = lf_user + self.lf_password = lf_password + self.instance_name = instance_name + self.config_name = config_name + self.dut = dut + self.duration = duration + self.upstream = upstream + self.station = station + self.pull_report = pull_report + self.load_old_cfg = load_old_cfg + self.test_name = "RX Sensitivity" + self.upload_speed = upload_speed + self.download_speed = download_speed + self.enables = enables + self.disables = disables + self.raw_lines = raw_lines + self.raw_lines_file = raw_lines_file + self.sets = sets + self.graph_groups = graph_groups + self.report_dir = report_dir + self.ssh_port = ssh_port + self.local_path = local_path + + def setup(self): + # Nothing to do at this time. + return + + def run(self): + self.sync_cv() + time.sleep(2) + self.sync_cv() + + blob_test = "rxsens-test-latest-" + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + self.show_text_blob(None, None, False) + + # Test related settings + cfg_options = [] + + ### HERE### + self.apply_cfg_options(cfg_options, self.enables, self.disables, self.raw_lines, self.raw_lines_file) + + # cmd line args take precedence and so come last in the cfg array. + if self.upstream != "": + cfg_options.append("upstream_port: " + self.upstream) + if self.station != "": + cfg_options.append("traffic_port: " + self.station) + if self.download_speed != "": + cfg_options.append("speed: " + self.download_speed) + if self.upload_speed != "": + cfg_options.append("speed2: " + self.upload_speed) + if self.duration != "": + cfg_options.append("duration: " + self.duration) + if self.dut != "": + cfg_options.append("selected_dut: " + self.dut) + + # We deleted the scenario earlier, now re-build new one line at a time. + + self.build_cfg(self.config_name, blob_test, cfg_options) + + cv_cmds = [] + self.create_and_run_test(self.load_old_cfg, self.test_name, self.instance_name, + self.config_name, self.sets, + self.pull_report, self.lf_host, self.lf_user, self.lf_password, + cv_cmds, ssh_port=self.ssh_port, local_lf_report_dir=self.local_path, + graph_groups_file=self.graph_groups) + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + + +def main(): + parser = argparse.ArgumentParser( + prog='lf_rx_sensitivity_test.py', + formatter_class=argparse.RawTextHelpFormatter, + description=""" + + IMPORTANT: Start lanforge with socket 3990 : ./lfclient.bash -cli-socket 3990 + lfclient.bash is located in the LANforgeGUI_X.X.X directory + + On local or remote system: ./lfclient.bash -cli-socket 3990 -s LF_MGR + On local system the -s LF_MGR will be local_host if not provided + + Open this file in an editor and read the top notes for more details. + Example: + ./lf_rx_sensitivity_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name rx-sensitivity-instance --config_name test_con --upstream 1.1.eth2 \ + --dut linksys-8450 --duration 15s --station 1.1.sta01500 \ + --download_speed 85% --upload_speed 0 \ + --raw_line 'txo_preamble: VHT' \ + --raw_line 'txo_mcs: 4 OFDM, HT, VHT;5 OFDM, HT, VHT;6 OFDM, HT, VHT;7 OFDM, HT, VHT' \ + --raw_line 'spatial_streams: 3' \ + --raw_line 'bandw_options: 80' \ + --raw_line 'txo_sgi: ON' \ + --raw_line 'txo_retries: No Retry' \ + --raw_line 'txo_txpower: 17' \ + --test_rig Testbed-01 --pull_report \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01 + + + Example 2: + ./lf_rx_sensitivity_test.py --json .json + + see sample json file: lf_rx_sensitivity_config.json + + Sample .json between using eth1 and eth2 + { + "mgr":"192.168.0.101", + "port":"8080", + "lf_user":"lanforge", + "lf_password":"lanforge", + "instance_name":"rx-sensitivity-instance", + "config_name":"test_con", + "upstream":"1.1.eth1", + "dut":"asus_5g", + "duration":"15s", + "station":"1.1.eth2", + "download_speed":"85%", + "upload_speed":"0", + "raw_line": ["txo_preamble: VHT", "txo_mcs: 4 OFDM, HT, VHT;5 OFDM, HT, VHT;6 OFDM, HT, VHT;7 OFDM, HT, VHT", "spatial_streams: 3", "bandw_options: 80", "txo_sgi: ON", "txo_retries: No Retry", "txo_txpower: 17"] + } + + Sample .json between using eth1 and station 1.1.sta0002 + { + "mgr":"192.168.0.101", + "port":"8080", + "lf_user":"lanforge", + "lf_password":"lanforge", + "instance_name":"rx-sensitivity-instance", + "config_name":"test_con", + "upstream":"1.1.eth1", + "dut":"asus_5g", + "duration":"15s", + "station":"1.1.sta0002", + "download_speed":"85%", + "upload_speed":"0", + "raw_line": ["txo_preamble: VHT", "txo_mcs: 4 OFDM, HT, VHT;5 OFDM, HT, VHT;6 OFDM, HT, VHT;7 OFDM, HT, VHT", "spatial_streams: 3", "bandw_options: 80", "txo_sgi: ON", "txo_retries: No Retry", "txo_txpower: 17"] + } + + """ + ) + + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument('--json', help="--json json input file", default="") + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for rx sensitivity test ex. 1.1.eth2") + parser.add_argument("--station", type=str, default="", + help="Station to be used in this test, example: 1.1.sta01500") + + parser.add_argument("--dut", default="", + help="Specify DUT used by this test, example: linksys-8450") + parser.add_argument("--download_speed", default="", + help="Specify requested download speed. Percentage of theoretical is also supported. Default: 85%%.") + parser.add_argument("--upload_speed", default="", + help="Specify requested upload speed. Percentage of theoretical is also supported. Default: 0") + parser.add_argument("--duration", default="", + help="Specify duration of each traffic run") + parser.add_argument("--graph_groups", help="File to save graph_groups to", default=None) + parser.add_argument("--report_dir", default="") + + args = parser.parse_args() + + # use json config file + if args.json != "": + try: + with open(args.json, 'r') as json_config: + json_data = json.load(json_config) + except: + print("Error reading {}".format(args.json)) + # json configuation takes presidence to command line + # TODO see if there is easier way to search presence, look at parser args + if "mgr" in json_data: + args.mgr = json_data["mgr"] + if "port" in json_data: + args.port = json_data["port"] + if "lf_user" in json_data: + args.lf_user = json_data["lf_user"] + if "lf_password" in json_data: + args.lf_password = json_data["lf_password"] + if "instance_name" in json_data: + args.instance_name = json_data["instance_name"] + if "config_name" in json_data: + args.config_name = json_data["config_name"] + if "upstream" in json_data: + args.upstream = json_data["upstream"] + if "dut" in json_data: + args.dut = json_data["dut"] + if "duration" in json_data: + args.duration = json_data["duration"] + if "station" in json_data: + args.station = json_data["station"] + if "download_speed" in json_data: + args.download_speed = json_data["download_speed"] + if "upload_speed" in json_data: + args.upload_speed = json_data["upload_speed"] + if "pull_report" in json_data: + args.pull_report = json_data["pull_report"] + if "raw_line" in json_data: + # the json_data is a list , need to make into a list of lists, to match command line raw_line paramaters + # https://www.tutorialspoint.com/convert-list-into-list-of-lists-in-python + json_data_tmp = [[x] for x in json_data["raw_line"]] + args.raw_line = json_data_tmp + + cv_base_adjust_parser(args) + + CV_Test = RxSensitivityTest(lf_host=args.mgr, + lf_port=args.port, + lf_user=args.lf_user, + lf_password=args.lf_password, + instance_name=args.instance_name, + config_name=args.config_name, + upstream=args.upstream, + pull_report=args.pull_report, + load_old_cfg=args.load_old_cfg, + download_speed=args.download_speed, + upload_speed=args.upload_speed, + duration=args.duration, + dut=args.dut, + station=args.station, + enables=args.enable, + disables=args.disable, + raw_lines=args.raw_line, + raw_lines_file=args.raw_lines_file, + sets=args.set, + graph_groups=args.graph_groups + ) + CV_Test.setup() + CV_Test.run() + + CV_Test.check_influx_kpi(args) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_sniff_radio.py b/lanforge/lanforge-scripts/py-scripts/lf_sniff_radio.py new file mode 100755 index 000000000..34282124d --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_sniff_radio.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" + NAME: lf_sniff_radio.py + PURPOSE: THis script will sniff a Radio after changing the Radio settings. + + Radio settings: channel radio mode AUTO, 802.11a, 802.11b, etc... refer + py-json/LANforge/set_wifi_radio.py for different modes + + EXAMPLE: python3 lf_sniff_radio.py + --mgr localhost + --mgr_port 8080 + --outfile /home/lanforge/test_sniff.pcap + --duration 20 + --channel 52 + --radio_mode AUTO + +""" +import sys +import os +import importlib +import argparse +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class SniffRadio(Realm): + def __init__(self, + lfclient_host="localhost", + lfclient_port=8080, + radio="wiphy0", + outfile="/home/lanforge/test_pcap.pcap", + duration=60, + channel=52, + radio_mode="AUTO", + debug_on_=False): + super().__init__(lfclient_host, lfclient_port) + self.lfclient_host = lfclient_host + self.lfclient_port = lfclient_port + self.debug = debug_on_ + self.local_realm = realm.Realm(lfclient_host=self.lfclient_host, + lfclient_port=self.lfclient_port, + debug_=self.debug) + self.monitor = self.local_realm.new_wifi_monitor_profile() + if channel != "AUTO": + channel = int(channel) + self.channel = channel + self.duration = duration + self.outfile = outfile + self.mode = radio_mode + self.radio = radio + + def setup(self, ht40_value, ht80_value, ht160_value): + self.monitor.set_flag(param_name="disable_ht40", value=ht40_value) + self.monitor.set_flag(param_name="disable_ht80", value=ht80_value) + self.monitor.set_flag(param_name="ht160_enable", value=ht160_value) + self.monitor.create(radio_=self.radio, channel=self.channel, mode=self.mode, name_="moni3a") + + def start(self): + self.monitor.admin_up() + time.sleep(5) + self.monitor.start_sniff(capname=self.outfile, duration_sec=self.duration) + for i in range(0, self.duration): + print("started sniffer, PLease wait,", self.duration - i) + time.sleep(1) + print("Sniffing Completed Success", "Check ", self.outfile) + self.monitor.admin_down() + time.sleep(2) + + def cleanup(self): + self.monitor.cleanup() + + +def main(): + parser = argparse.ArgumentParser( + prog="lf_sniff_radio.py", + formatter_class=argparse.RawTextHelpFormatter, + usage= + """./lf_sniff_radio.py + --mgr localhost + --mgr_port 8080 + --radio wiphy0 + --outfile /home/lanforge/test_sniff.pcap + --duration 1 + --channel 52 + --radio_mode AUTO + """) + + parser.add_argument('--mgr', type=str, help='--mgr: IP Address of LANforge', default="localhost") + parser.add_argument('--mgr_port', type=int, help='--mgr_port: HTTP Port of LANforge', default=8080) + parser.add_argument('--radio', type=str, help='--radio: Radio to sniff', default="wiphy0") + parser.add_argument('--outfile', type=str, help='--outfile: give the filename with path', + default="/home/lanforge/test_pcap.pcap") + parser.add_argument('--duration', type=int, help='--duration duration in sec, for which you want to capture', + default=60) + parser.add_argument('--channel', type=str, help='--channel Set channel pn selected Radio, [52, 56 ...]', + default=0) + parser.add_argument('--radio_mode', type=str, help='--radio_mode select the radio mode [AUTO, 802.11a, 802.11b, ' + '802.11ab ...]', default="AUTO") + parser.add_argument('--disable_ht40', type=str, help='Enable/Disable \"disable_ht40\" [0-disable,1-enable]', default=0) + parser.add_argument('--disable_ht80', type=str, help='Enable/Disable \"disable_ht80\" [0-disable,1-enable]', default=0) + parser.add_argument('--ht160_enable', type=str, help='Enable/Disable \"ht160_enable\ [0-disable,1-enable]" ', default=0) + + args = parser.parse_args() + + obj = SniffRadio(lfclient_host=args.mgr, + lfclient_port=args.mgr_port, + outfile=args.outfile, + duration=args.duration, + channel=args.channel, + radio=args.radio, + radio_mode=args.radio_mode + ) + obj.setup(int(args.disable_ht40), int(args.disable_ht80), int(args.ht160_enable)) + time.sleep(5) + obj.start() + obj.cleanup() + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_snp_test.py b/lanforge/lanforge-scripts/py-scripts/lf_snp_test.py new file mode 100755 index 000000000..d5a54f8f0 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_snp_test.py @@ -0,0 +1,2578 @@ +#!/usr/bin/env python3 +''' +NAME: lf_snp_test.py snp == Scaling and Performance + +PURPOSE: + +This program is to test an AP connected to a controller. + The AP name is configurable. + The controler + with with a specific ap mode, wifi mode (2.4 Ghz or 5 Ghz), Bandwidth (20,40,80,160) and TX power. +The controller will configure the AP. +The Lanforge radios are configured for a specific client dencity, Packet type (TCP, UDP), Direction (download, upload) and Packet-size. +The transmission rate will be recorded and compared against the expected rate to determine pass or fail. +The results will be recorded in CSV file with the following data +AP, Band, wifi_mode, Bandwidth, encryption, ap mode, number of clients, packet type, direction, packet size, measured rx bytes, upload bits per second, +download bits per second. +unique test id, pass / fail, epoch time, and time. + +TECHNICAL UNDERSTANDING: + LANForge Monitored Values Per Polling Interval + 'rx bytes' - bytes transmitted + 'rx rate' - bits per second + + in DL direction: -B tx -> -A rx, (side_b_tx_min_bps) LANforge Eth endpoint transmits bytes (AP/DUT), + station endpoint (Wifi) LANForge receives them. station-end-rx-bps (bits per second) is download rx-bps (bits per second) + + in UL direction: -A tx -> -B rx, (side_a_tx_min_bps) LANforge Eth endpoint receives bytes (AP/DUT), + station endpoint (Wifi) LANForge transmits them. ethernet-end-rx-bps (bits per second) is upload load rx-bps (bits per second) + + configured bps (side_a_tx_min_bps and side_b_tx_min_bps) if lists not same lenght shorter list padded out with 256000 if upload and download selected. + +NOTES: + 1. The controller_client_densities are indpendent of the number of stations on a radio + 2. The --side_a_tx_min_bps (download) and --side_b_tx_min_bps (upload) is used to set the rate + a. default 256000 + +The script is devided into parts: +1. Controller Class : CreateCtlr controller interface. + Configurable by script: + a. Band : a (5ghz) b (2.4ghz) + b. wifi_mode : supported modes based on radio + c. chan_5ghz and chan_24ghz + d. BW: Band width 20 40 80 160, 160 only supports (2x2) spatial streams + d. encryption : enable / disable, * encryption not supported + e. ap_mode : local / flex connect, * ap mode not supported + f. clients : number of clients set by controller_client_densities , radios much create associated number of stations Note: LANforge configuration + g. packet_type: lf_udp lf_tcp + h. traffic direction: upload / download + i. pdu: --side_a_min_pdu, --side_b_min_pdu Note: LANforge configuration + +2. Traffic Generation Class : L3VariableTime + a. Creates and brings up stations/clients on radios + b. Measures connections + c. reports results + +3. Scaling And Performance Main + a. Command parcing + b. Fixed Configuration Coded Into The Script + c. Script Controller Configurations + d. Script LANforge Configurations + e. Parameters Used For Testing + f. report generation + +OUTPUT: + In /home/lanforge/report-data/_Scaling_and_Performance or if not present in script directory under _Scaling_and_Performance + html results , default .html + pdf results , default .pdf + csv results_snp_.csv , results reflected in html and pdf files + csv details_snp_.csv raw data + * radios and con + +EXAMPLE: + +Use --print_test_config at end of command to see test configuration + +Test configurations take presidence to command line parameters + +Using Coded Test Configuration --controller_test_1 + ./lf_snp_test.py --controller_ip 10.195.197.234 --controller_user admin --controller_passwd Milpitas@123 + --controller_aps 'Vanc-e' --controller_series "9800" --endp_types 'lf_udp' --upstream_port eth2 --controller_prompt "Can-SnP-9120" --controller_test_1 + --print_test_config + +Using Coded Test Configuration --controller_test_1 + ./lf_snp_test.py --controller_ip 10.195.197.234 --controller_user admin --controller_passwd Milpitas@123 + --controller_aps 'Vanc-e' --controller_series "9800" --endp_types 'lf_udp' --upstream_port eth2 --controller_prompt "Can-SnP-9120" --controller_test_1 + --print_test_config + +Using Coded Test Configuration: + ./lf_snp_test.py -cc 192.168.100.112 -cu admin -cpw Cisco123 -cca APA453.0E7B.CF9C -cs "3504" --endp_types 'lf_udp' --upstream_port eth2 --controller_test_3 + --controller_prompt "(Cisco Controller)" + --print_test_config + +Using Commandline with defaults: + ./lf_snp_test.py --controller_ip 192.168.100.112 --controller_user admin --controller_passwd Cisco123 --controller_aps APA453.0E7B.CF9C --controller_series "3504" + --controller_prompt "(Cisco Controller)" --radio "radio==1.wiphy0 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto" + --print_test_config + +Using Commandline: + ./lf_snp_test.py --controller_ip 192.168.100.112 --controller_user admin --controller_passwd Cisco123 --controller_aps APA453.0E7B.CF9C + --controller_series "3504" --upstream_port eth2 --controller_prompt "(Cisco Controller)" --controller_wifimode "a" --controller_chan_5ghz "36" + --radio "radio==1.wiphy0 stations==10 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==ac" --controller_client_densities "10" + --print_test_config + +Using Commandline: Setting --test_duration "20s" --polling_interval to 5s -ccd "2" (--controller_client_densities) + ./lf_snp_test.py --controller_ip 192.168.100.112 --controller_user admin --controller_passwd Cisco123 --controller_aps APA453.0E7B.CF9C + --controller_series "3504" --upstream_port eth2 --controller_prompt "(Cisco Controller)" --controller_wifimode "auto" --controller_chan_5ghz "36" + --radio "radio==1.wiphy0 stations==2 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==an" --controller_client_densities "2" + --print_test_config + +SAMPLE TEST CONFIG: --controller_test_1 output from --print_test_config option + +2021-04-21 05:43:25,040 __main__ INFO: USING: controller_test_1 +2021-04-21 05:43:25,040 __main__ INFO: TEST CONFIG: +2021-04-21 05:43:25,040 __main__ INFO: controller_aps ('-cca' ,'--controller_aps'): ['vanc-e'] +2021-04-21 05:43:25,040 __main__ INFO: controller_bands ('-ccf' ,'--controller_bands'): ['a', 'b'] +2021-04-21 05:43:25,040 __main__ INFO: controller_wifimodes ('-cwm' ,'--controller_wifimodes'): ['an', 'anAX', 'anAC', 'abgn', 'bg'] +2021-04-21 05:43:25,040 __main__ INFO: controller_chan_5ghzs ('-cc5','--controller_chan_5ghzs'): ['36'] +2021-04-21 05:43:25,040 __main__ INFO: controller_chan_24ghzs ('-cc2','--controller_chan_24ghzs'): ['1'] +2021-04-21 05:43:25,040 __main__ INFO: controller_chan_widths ('-ccw','--controller_chan_widths'): ['20', '40', '80'] +2021-04-21 05:43:25,040 __main__ INFO: controller_tx_powers ('-ctp','--controller_tx_powers'): ['3'] +2021-04-21 05:43:25,041 __main__ INFO: controller_ap_modes ('-cam','--controller_ap_modes'): ['local'] +2021-04-21 05:43:25,041 __main__ INFO: controller_client_densities ('-ccd','--controller_client_densities'): ['1', '10', '50', '200'] +2021-04-21 05:43:25,041 __main__ INFO: controller_packet_types ('-t', '--endp_types'): ['lf_udp', 'lf_tcp'] +2021-04-21 05:43:25,041 __main__ INFO: controller_pdus ('-cps','--controller_pdus'): ['88', '512', '1370', '1518'] +2021-04-21 05:43:25,041 __main__ INFO: controller_directions ('-cd', '--controller_directions'): ['upload', 'download'] +2021-04-21 05:43:25,041 __main__ INFO: controller_data_encryptions ('-cde','--controller_data_encryptions') ['disable'] +2021-04-21 05:43:25,041 __main__ INFO: controller_side_a_tx_min_bps ('-amr','--side_a_tx_min_bps'): 256000 +2021-04-21 05:43:25,041 __main__ INFO: controller_side_b_tx_min_bps ('-bmr','--side_b_tx_min_bps'): 256000 +2021-04-21 05:43:25,041 __main__ INFO: test duration ('-d','--test_duration'): 20s +2021-04-21 05:43:25,041 __main__ INFO: polling_interval ('-pi','--polling_interval'): 5s +2021-04-21 05:43:25,041 __main__ INFO: radios from coded config used + +INCLUDE_IN_README + +COPYWRITE + Copyright 2021 Candela Technologies Inc + License: Free to distribute and modify. LANforge systems must be licensed. + +''' +import sys +import os +import importlib +import itertools +from pprint import pprint +import argparse +import time +import datetime +import subprocess +import csv +import random +import logging + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +lf_report = importlib.import_module("py-scripts.lf_report") +# lf_graph = importlib.import_module("py-scripts.lf_graph") +# lf_bar_graph = lf_graph.lf_bar_graph + +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' +# see https://stackoverflow.com/a/13306095/11014343 + + +class FileAdapter(object): + def __init__(self, logger): + self.logger = logger + def write(self, data): + # NOTE: data can be a partial line, multiple lines + data = data.strip() # ignore leading/trailing whitespace + if data: # non-blank + self.logger.info(data) + def flush(self): + pass # leave it to logging to flush properly + +################################################################################ +# +# Controller Class : CrateCtlr controller interface +# +################################################################################ +class CreateCtlr(): + def __init__(self, + _scheme, + _port, + _series, + _ctlr, + _prompt, + _user, + _passwd, + _ap, + _band, + _chan_5ghz, + _chan_24ghz, + _chan_width, + _ap_mode, + _tx_power, + _wlan, + _cap_ctl_out): + + self.scheme = _scheme + self.port = _port + self.series = _series + self.ctlr = _ctlr + self.prompt = _prompt + self.user = _user + self.passwd = _passwd + self.ap = _ap + self.band = _band + self.chan_5ghz = _chan_5ghz + self.chan_24ghz = _chan_24ghz + self.chan_width = _chan_width + self.ap_mode = _ap_mode + self.tx_power = _tx_power + self.wlan = _wlan + self.cap_ctl_out = _cap_ctl_out + self.client_density = 0 + + #show summary (to get AP) (3400/9800) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 --action summary --series 9800 --log stdout + def controller_show_summary(self): + pss = "" + try: + logg.info("\ + scheme: {} \ + ctlr: {} \ + port: {} \ + prompt: {} \ + user: {} \ + passwd: {} \ + AP: {} \ + series: {} \ + band: {} \ + action: {}".format( + self.scheme, + self.ctlr, + self.port, + self.prompt, + self.user, + self.passwd, + self.ap, + self.series, + self.band, + "summary")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", + "--scheme", self.scheme, + "--prompt", self.prompt, + "--port", self.port, + "-d", self.ctlr, + "-u", self.user, + "-p", self.passwd, + "-a", self.ap, + "--series", self.series, + "--band", self.band, + "--action", "summary"], + capture_output=self.cap_ctl_out, + check=True) + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}" + .format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + return pss + + #show ap dot11 5ghz summary (band defaults to 5ghz) --band a + #show ap dot11 24ghz summary use --band b for 2.4 ghz + #action advanced (3400/9800) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 --action advanced --series 9800 --log stdout + def controller_show_ap_summary(self): + pss = "" + try: + logg.info("\ + scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"advanced")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "advanced"], + capture_output=True, check=True) + + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + return pss + + #show wlan summary + def controller_show_wlan_summary(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"show wlan summary")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "show_wlan_summary"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #disable AP + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action disable --series 9800 + def controller_disable_ap(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"disable")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", + self.ctlr, "-u",self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "disable"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #disable wlan + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action disable_wlan --series 9800 + def controller_disable_wlan(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} wlan: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,self.wlan,"disable_wlan")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band,"--wlan", self.wlan, "--action", "disable_wlan"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #disable network 5ghz + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action disable_network_5ghz --series 9800 + def controller_disable_network_5ghz(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"disable_network_5ghz")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "disable_network_5ghz"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"cmd","config 802.11a disable network")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "cmd", "--value", "config 802.11a disable network"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #disable network 24ghz + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action disable_network_24ghz --series 9800 + def controller_disable_network_24ghz(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"disable_network_24ghz")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "disable_network_24ghz"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"cmd","config 802.11b disable network")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "cmd", "--value", "config 802.11b disable network"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #set manual mode - Series 9800 must be set to manual mode + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action manual --series 9800 + # ap name dot11 5ghz radio role manual client-serving + def controller_role_manual(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"manual")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "manual"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + logg.info("Check the controller scheme used attemping 9800 series on 3504 controller: {}".format(self.scheme)) + + #set manual mode - Series 9800 must be set to auto mode + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action auto --series 9800 + # ap name dot11 5ghz radio role manual client-serving + def controller_role_auto(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user, + self.passwd,self.ap,self.series,self.band,"auto")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "auto"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + logg.info("Check the controller scheme used attemping 9800 series on 3504 controller: {}".format(self.scheme)) + + #test parameters summary (txPower 1-8) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action txPower --value 5 --series 9800 + def controller_set_tx_power(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"txPower", self.tx_power )) # TODO fix txPower to tx_power in wifi_ctl_9800_3504.py + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "txPower","--value", self.tx_power], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #set channel [36, 64, 100] + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action channel --value 36 --series 9800 + # 9800 : ap name dot11 [5ghz | 24ghz] channel + # 3504 : (controller Controller) >config 802.11a channel ap APA453.0E7B.CF9C 52 + def controller_set_channel(self): + try: + if (self.band == "a"): + controller_channel = self.chan_5ghz + else: + controller_channel = self.chan_24ghz + + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"channel", controller_channel )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "channel","--value", controller_channel], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #set bandwidth [20 40 80 160] + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action bandwidth --value 40 --series 9800 + def controller_set_bandwidth(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"channel", self.chan_width )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "channel","--value", self.chan_width], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #create wlan + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action create_wlan --wlan "open-wlan" --wlanID 1 --series 9800 + def controller_create_wlan(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} wlan {} wlanID {} wlanSSID {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"create_wlan", self.wlan, self.wlanID, self.wlanSSID )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "create_wlan","--wlan", self.wlan, "--wlanID", self.wlanID, "--wlanSSID", self.wlanSSID], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + logg.info("Check the controller_scheme used attemping 9800 series on 3504 controller: {}".format(self.scheme)) + + #create wireless tag policy --9800 series needs to have wireless tag policy set + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action wireless_tag_policy --series 9800 + def controller_set_wireless_tag_policy(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"wireless_tag_policy" )) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "wireless_tag_policy"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + logg.info("Check the controller_scheme used attemping 9800 series on 3504 controller: {}".format(self.scheme)) + + #enable wlan + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action enable_wlan --series 9800 + def controller_enable_wlan(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} wlan: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band, self.wlan,"enable_wlan")) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--wlan", self.wlan, + "--action", "enable_wlan"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #enable 5ghz + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action enable_network_5ghz --series 9800 + def controller_enable_network_5ghz(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"enable_network_5ghz")) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "enable_network_5ghz"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"cmd","config 802.11a enable network")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "cmd", "--value", "config 802.11a enable network"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #enable 24ghz + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action enable_network_24ghz --series 9800 + def controller_enable_network_24ghz(self): + if self.series == "9800": + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"enable_network_24ghz")) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "enable_network_24ghz"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + else: + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {} value: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"cmd","config 802.11b enable network")) + + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, "--action", "cmd", "--value", "config 802.11b enable network"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #enable (band a) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action enable --series 9800 + def controller_enable_ap(self): + try: + logg.info("scheme: {} ctlr: {} port: {} prompt: {} user: {} passwd: {} AP: {} series: {} band: {} action: {}".format(self.scheme, + self.ctlr,self.port,self.prompt,self.user,self.passwd, self.ap, self.series, + self.band,"enable")) + ctl_output = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--band", self.band, + "--action", "enable"], + capture_output=self.cap_ctl_out, check=True) + + if self.cap_ctl_out: + pss = ctl_output.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + except subprocess.CalledProcessError as process_error: + logg.info("Command Error, Controller unable to commicate to AP or unable to communicate to controller error code: {} output {}". + format(process_error.returncode, process_error.output)) + time.sleep(1) + exit(1) + + #advanced (showes summary) + #./wifi_ctl_9800_3504.py --scheme ssh -d 172.19.36.168 -p --port 23 -a "9120-Chamber-1" --band a --action advanced --series 9800 + def controller_show_ap_channel(self): + advanced = subprocess.run(["../wifi_ctl_9800_3504.py", "--scheme", self.scheme, "--prompt", self.prompt, "--port", self.port, "-d", self.ctlr, "-u", + self.user, "-p", self.passwd, + "-a", self.ap,"--series", self.series, "--action", "ap_channel"], capture_output=True) + + pss = advanced.stdout.decode('utf-8', 'ignore') + logg.info(pss) + + if self.series == "9800": + if (self.band == "a"): + controller_channel = self.chan_5ghz + else: + controller_channel = self.chan_24ghz + + for line in pss.splitlines(): + search_str = self.ap + logg.info("line {}".format(line)) + element_list = line.lstrip().split() + logg.info("element_list {}".format(element_list)) + if (line.lstrip().startswith(search_str)): + logg.info("line {}".format(line)) + element_list = line.lstrip().split() + logg.info("element_list {}".format(element_list)) + # AP Name (0) mac (1) slot (2) Admin State [enable/disable] (3) Oper State [Up/Down] (4) Width (5) Txpwr (6,7) channel (8) mode (9) + logg.info("ap: {} slof {} channel {} chan_width {}".format(element_list[0],element_list[2],element_list[8],element_list[5])) + if (str(controller_channel) in str(element_list[8])) and (str(self.chan_width) in str(element_list[5])): + logg.info("ap {} configuration successful: channel {} in expected {} chan_width {} in expected {}" + .format(element_list[0],controller_channel,element_list[8],self.chan_width,element_list[5])) + else: + logg.info("WARNING ap {} configuration: channel {} in expected {} chan_width {} in expected {}" + .format(element_list[0],controller_channel,element_list[8],self.chan_width,element_list[5])) + break + else: + logg.info("checking for 802.11{}".format(self.band)) + if (self.band == "a"): + controller_channel = self.chan_5ghz + else: + controller_channel = self.chan_24ghz + + for line in pss.splitlines(): + #logg.info("line {}".format(line)) + search_str = "802.11{}".format(self.band) + if (line.lstrip().startswith(search_str)): + logg.info("line {}".format(line)) + element_list = line.lstrip().split() + logg.info("element_list {}".format(element_list)) + logg.info("ap: {} channel {} chan_width {}".format(self.ap,element_list[4],element_list[5])) + if (str(controller_channel) in str(element_list[4])) and (str(self.chan_width) in str(element_list[5])): + logg.info("ap configuration successful: channel {} in expected {} chan_width {} in expected {}" + .format(controller_channel,element_list[4],self.chan_width,element_list[5])) + else: + logg.info("AP WARNING: channel {} expected {} chan_width {} expected {}" + .format(element_list[4],controller_channel,element_list[5],self.chan_width)) + break + + logg.info("configure ap {} channel {} chan_width {}".format(self.ap,self.channel,self.chan_width)) + # Verify channel and channel width. +################################################################################ +# +# End of Controller Class : controller interface +# +################################################################################ + +################################################################################ +# +# Traffic Generation Class : L3VariableTime +# +################################################################################ +class L3VariableTime(Realm): + def __init__(self, + args, + _scheme, + _port, + _series, + _ctlr, + _prompt, + _user, + _passwd, + _ap, + _ap_slot, + _band, + _chan_5ghz, + _chan_24ghz, + _chan_width, + _ap_mode, + _tx_power, + _client_density, + _cap_ctl_out, + _ap_dict, + endp_type, + tos, + side_b, + radio_name_list, + number_of_stations_per_radio_list, + ssid_list, + ssid_password_list, + ssid_security_list, + wifimode_list, + station_lists, + name_prefix, + debug_on, + outfile, + results, + test_keys, + test_config, + reset_port_enable_list, + reset_port_time_min_list, + reset_port_time_max_list, + csv_started=False, + side_a_tx_min_bps=560000, + side_a_tx_max_bps=0, # setting to 0 will match min + side_a_min_pdu=1518, + side_a_max_pdu=0, + side_b_tx_min_bps=560000, + side_b_tx_max_bps=0, # setting to 0 will match min + side_b_min_pdu=1518, + side_b_max_pdu=0, + number_template="00", + test_duration="256s", + polling_interval="60s", + lfclient_host="localhost", + lfclient_port=8080, + debug=False, + wait_timeout=120, + _exit_on_error=False, + _exit_on_fail=False, + _proxy_str=None, + _capture_signal_list=[]): + super().__init__(lfclient_host=lfclient_host, + lfclient_port=lfclient_port, + debug_=debug, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail, + _proxy_str=_proxy_str, + _capture_signal_list=_capture_signal_list) + self.scheme = _scheme + self.port = _port + self.series = _series + self.ctlr = _ctlr + self.prompt = _prompt + self.user = _user + self.passwd = _passwd + self.ap = _ap + self.ap_slot = _ap_slot + self.band = _band + self.chan_5ghz = _chan_5ghz + self.chan_24ghz = _chan_24ghz + self.chan_width = _chan_width + self.ap_mode = _ap_mode + self.tx_power = _tx_power + self.cap_ctl_out = _cap_ctl_out + self.ap_dict = _ap_dict + self.client_density = _client_density + self.tos = tos.split() + self.endp_type = endp_type + self.side_b = side_b + self.ssid_list = ssid_list + self.ssid_password_list = ssid_password_list + self.station_lists = station_lists + self.ssid_security_list = ssid_security_list + self.wifimode_list = wifimode_list + self.reset_port_enable_list = reset_port_enable_list + self.reset_port_time_min_list = reset_port_time_min_list + self.reset_port_time_max_list = reset_port_time_max_list + self.number_template = number_template + self.name_prefix = name_prefix + self.test_duration = test_duration + self.radio_name_list = radio_name_list + self.number_of_stations_per_radio_list = number_of_stations_per_radio_list + #self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port, debug_=debug_on) + self.polling_interval_seconds = self.duration_time_to_seconds(polling_interval) + self.cx_profile = self.new_l3_cx_profile() + self.multicast_profile = self.new_multicast_profile() + self.multicast_profile.name_prefix = "MLT-" + self.station_profiles = [] + self.args = args + self.outfile = outfile + self.results = results + self.csv_started = csv_started + + self.side_a_tx_min_bps = side_a_tx_min_bps + self.side_a_tx_max_bps = side_a_tx_max_bps + self.side_a_min_pdu = side_a_min_pdu + self.side_a_max_pdu = side_a_max_pdu + + self.side_b_tx_min_bps = side_b_tx_min_bps + self.side_b_tx_max_bps = side_b_tx_max_bps + self.side_b_min_pdu = side_b_min_pdu + self.side_b_max_pdu = side_b_max_pdu + + self.epoch_time = int(time.time()) + self.debug = debug_on + self.wait_timeout = wait_timeout + self.test_keys = test_keys + self.test_config = test_config + + self.test_config_dict = dict(map(lambda x: x.split('=='), str(self.test_config).replace('[','').replace(']','').replace("'","").split())) + + # Full spread-sheet data + if self.outfile is not None: + self.csv_file_details = open(self.outfile, "a+") + self.csv_writer = csv.writer(self.csv_file_details, delimiter=",") + + if self.results is not None: + self.csv_results = open(self.results, "a+") + self.csv_results_writer = csv.writer(self.csv_results, delimiter=",") + + for (radio_, ssid_, ssid_password_, ssid_security_, wifimode_,\ + reset_port_enable_, reset_port_time_min_, reset_port_time_max_) \ + in zip(radio_name_list, ssid_list, ssid_password_list, ssid_security_list, wifimode_list,\ + reset_port_enable_list, reset_port_time_min_list, reset_port_time_max_list): + self.station_profile = self.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = ssid_ + self.station_profile.ssid_pass = ssid_password_ + self.station_profile.security = ssid_security_ + self.station_profile.mode = wifimode_ + self.station_profile.number_template = self.number_template + self.station_profile.mode = wifimode_ + self.station_profile.set_reset_extra(reset_port_enable=reset_port_enable_,\ + test_duration=self.duration_time_to_seconds(self.test_duration),\ + reset_port_min_time=self.duration_time_to_seconds(reset_port_time_min_),\ + reset_port_max_time=self.duration_time_to_seconds(reset_port_time_max_)) + self.station_profiles.append(self.station_profile) + + self.multicast_profile.host = self.lfclient_host + self.cx_profile.host = self.lfclient_host + self.cx_profile.port = self.lfclient_port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = self.side_a_tx_min_bps # Note: side_a_tx_min_bps is side_a_min_bps in py-json profiles + self.cx_profile.side_a_max_bps = self.side_a_tx_min_bps + self.cx_profile.side_a_min_pdu = self.side_a_min_pdu + self.cx_profile.side_a_max_pdu = self.side_a_max_pdu + self.cx_profile.side_b_min_bps = self.side_b_tx_min_bps # Note: side_b_tx_min_bps is side_b_min_bps in py-json profiles + self.cx_profile.side_b_max_bps = self.side_b_tx_min_bps + self.cx_profile.side_b_min_pdu = self.side_b_min_pdu + self.cx_profile.side_b_max_pdu = self.side_b_max_pdu + + def __get_rx_values(self): + endp_list = self.json_get("endp?fields=name,eid,delay,jitter,rx+rate,rx+bytes,rx+drop+%25", debug_=False) + endp_rx_drop_map = {} + endp_rx_map = {} + our_endps = {} + + endps = [] + # total upload in bits per second across all stations + total_ul = 0 + total_dl = 0 + + for e in self.multicast_profile.get_mc_names(): + our_endps[e] = e + for e in self.cx_profile.created_endp.keys(): + our_endps[e] = e + for endp_name in endp_list['endpoint']: + if endp_name != 'uri' and endp_name != 'handler': + for item, value in endp_name.items(): + if item in our_endps: + endps.append(value) + print("endpoint: ", item, " value:\n") + pprint(value) + + for value_name, value in value.items(): + if value_name == 'rx bytes': + endp_rx_map[item] = value + if value_name == 'rx drop %': + endp_rx_drop_map[item] = value + if value_name == 'rx rate': + # This hack breaks for mcast or if someone names endpoints weirdly. + #print("item: ", item, " rx-bps: ", value_rx_bps) + if item.endswith("-A"): + total_dl += int(value) + else: + total_ul += int(value) + + #print("total-dl: ", total_dl, " total-ul: ", total_ul, "\n") + return endp_rx_map, endp_rx_drop_map, endps, total_dl, total_ul + + def time_stamp(self): + return time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime(self.epoch_time)) + + def __compare_vals(self, old_list, new_list, total_dl_bps, total_ul_bps): + passes = 0 + expected_passes = 0 + csv_rx_headers = [] + csv_rx_row_data = [] + csv_result_row_data = [] + csv_rx_delta_row_data = [] + csv_rx_delta_dict = {} + test_id = "" + + for key in [key for key in old_list if "mtx" in key]: del old_list[key] + for key in [key for key in new_list if "mtx" in key]: del new_list[key] + + filtered_values = [v for _, v in new_list.items() if v !=0] + # Evaluate upload or download + new_evaluate_list = new_list.copy() + print("new_evaluate_list before",new_evaluate_list) + # look at ul and dl + old_evaluate_list = old_list.copy() + + if len(old_evaluate_list) == len(new_evaluate_list): + for item, value in old_evaluate_list.items(): + # check only upload or download - expected passes corresponds to traffic only in observed direction + if "upload" in self.test_config_dict.values() and item.endswith("-B") \ + or "download" in self.test_config_dict.values() and item.endswith("-A"): + expected_passes +=1 + print("ITEM: {} VALUE: {}".format(item, value)) + if new_evaluate_list[item] > old_evaluate_list[item]: + passes += 1 + #if self.debug: logg.info(item, new_evaluate_list[item], old_evaluate_list[item], " Difference: ", new_evaluate_list[item] - old_evaluate_list[item]) + print(item, new_evaluate_list[item], old_evaluate_list[item], " Difference: ", new_evaluate_list[item] - old_evaluate_list[item]) + else: + if "upload" in self.test_config_dict.values() and item.endswith("-B") \ + or "download" in self.test_config_dict.values() and item.endswith("-A"): + # only a failure if expecting traffic in that direction + print("Failed to increase rx bytes: ", item, new_evaluate_list[item], old_evaluate_list[item]) + if not self.csv_started: + # stations that end in -A are dl (download, download), stations that end in -B are ul (upload, upload) + if item.endswith("-A"): + csv_rx_headers.append(item+'-dl-rx-bytes') + else: + csv_rx_headers.append(item+'-ul-rx-bytes') + csv_rx_delta_dict.update({item:(new_evaluate_list[item] - old_evaluate_list[item])}) + + if not self.csv_started: + csv_header = self.csv_generate_column_details_headers() + csv_header += csv_rx_headers + logg.info(csv_header) + self.csv_add_column_headers(csv_header) + csv_results = self.csv_generate_column_results_headers() + #csv_results += csv_rx_headers + self.csv_add_column_headers_results(csv_results) + self.csv_started = True + + # need to generate list of all the values + filtered_values = [v for _, v in csv_rx_delta_dict.items() if v !=0] + # if need the average use average_rx_delta + #average_rx_delta= sum(filtered_values) / len(filtered_values) if len(filtered_values) != 0 else 0 + + # write out the configuraiton for the test + for key in self.test_keys: + csv_rx_row_data.append(self.test_config_dict[key]) + csv_result_row_data.append(self.test_config_dict[key]) + csv_rx_delta_row_data.append(self.test_config_dict[key]) + + csv_rx_row_data.extend([self.epoch_time, self.time_stamp()]) + csv_result_row_data.extend([self.epoch_time, self.time_stamp()]) + + #Generate TestID + for key in self.test_keys: + test_id = test_id + "_" + self.test_config_dict[key] + + print("test_id: {}".format(test_id)) + csv_rx_row_data.append(test_id) + csv_result_row_data.append(test_id) + + csv_rx_row_data.append(self.test_duration) + csv_rx_row_data.append(self.polling_interval_seconds) + + csv_rx_row_data.append(self.side_a_tx_min_bps) + csv_rx_row_data.append(self.side_b_tx_min_bps) + + # Recorde the Total Transmit rate for all stations + rx_bytes = sum(filtered_values) #total + csv_rx_row_data.append(rx_bytes) + + # The total_dl_bps and total_up_bps is for the interval + csv_rx_row_data.append(total_dl_bps) + csv_rx_row_data.append(total_ul_bps) + #csv_result_row_data.append(rx_bytes) + + print("csv_rx_row_data {}".format(csv_rx_row_data)) + #TODO: may want to pass in the information that needs to be in the csv file into the class + + for item, value in old_evaluate_list.items(): + expected_passes +=1 + if new_evaluate_list[item] > old_evaluate_list[item]: + passes += 1 + print(item, new_evaluate_list[item], old_evaluate_list[item], " Difference: ", new_evaluate_list[item] - old_evaluate_list[item]) + else: + print("Failed to increase rx data: ", item, new_evaluate_list[item], old_evaluate_list[item]) + if not self.csv_started: + csv_rx_headers.append(item) + # append the rate for each station + csv_rx_row_data.append(new_list[item] - old_list[item]) + + # data from each station for each iteration + self.csv_add_row(csv_rx_row_data,self.csv_writer,self.csv_file_details) + + if passes == expected_passes: + return True, rx_bytes, csv_result_row_data + else: + return False, rx_bytes, csv_result_row_data + else: + print("Old-list length: %i new: %i does not match in compare-vals."%(len(old_list), len(new_list))) + print("old-list:",old_list) + print("new-list:",new_list) + return False, None, None + + def reset_port_check(self): + for station_profile in self.station_profiles: + if station_profile.reset_port_extra_data['reset_port_enable']: + if station_profile.reset_port_extra_data['reset_port_timer_started'] == False: + logg.info("reset_port_time_min: {}".format(station_profile.reset_port_extra_data['reset_port_time_min'])) + logg.info("reset_port_time_max: {}".format(station_profile.reset_port_extra_data['reset_port_time_max'])) + station_profile.reset_port_extra_data['seconds_till_reset'] = \ + random.randint(station_profile.reset_port_extra_data['reset_port_time_min'],\ + station_profile.reset_port_extra_data['reset_port_time_max']) + station_profile.reset_port_extra_data['reset_port_timer_started'] = True + logg.info("on radio {} seconds_till_reset {}".format(station_profile.add_sta_data['radio'],station_profile.reset_port_extra_data['seconds_till_reset'])) + else: + station_profile.reset_port_extra_data['seconds_till_reset'] = station_profile.reset_port_extra_data['seconds_till_reset'] - 1 + if self.debug: logg.info("radio: {} countdown seconds_till_reset {}".format(station_profile.add_sta_data['radio'] ,station_profile.reset_port_extra_data['seconds_till_reset'])) + if ((station_profile.reset_port_extra_data['seconds_till_reset'] <= 0)): + station_profile.reset_port_extra_data['reset_port_timer_started'] = False + port_to_reset = random.randint(0,len(station_profile.station_names)-1) + logg.info("reset on radio {} station: {}".format(station_profile.add_sta_data['radio'],station_profile.station_names[port_to_reset])) + self.reset_port(station_profile.station_names[port_to_reset]) + + def pre_cleanup(self): + self.cx_profile.cleanup_prefix() + self.multicast_profile.cleanup_prefix() + self.total_stas = 0 + for station_list in self.station_lists: + for sta in station_list: + self.rm_port(sta, check_exists=True) + self.total_stas += 1 + + # Verify Stations are Gone + count = 0 + while (count < 10): + more = False + for station_list in self.station_lists: + for sta in station_list: + rv = self.rm_port(sta, check_exists=True) + if (rv): + more = True + if not more: + break + count += 1 + time.sleep(5) + + def build(self): + index = 0 + for station_profile in self.station_profiles: + station_profile.use_security(station_profile.security, station_profile.ssid, station_profile.ssid_pass) + station_profile.set_number_template(station_profile.number_template) + logg.info("Creating stations") + + station_profile.create(radio=self.radio_name_list[index], sta_names_=self.station_lists[index], debug=self.debug, sleep_time=0) + index += 1 + + for _tos in self.tos: + logg.info("Creating connections for endpoint type: {} TOS: {} stations_names {}".format(self.endp_type, _tos, station_profile.station_names)) + self.cx_profile.create(endp_type=self.endp_type, side_a=station_profile.station_names, side_b=self.side_b, sleep_time=0, tos=_tos) + self._pass("PASS: Stations build finished") + + def start(self, print_pass=False, print_fail=False): + best_rx_bytes = 0 + rx_bytes = 0 + csv_rx_row_data = " " + Result = False + logg.info("Bringing up stations") + self.admin_up(self.side_b) + for station_profile in self.station_profiles: + for sta in station_profile.station_names: + logg.info("Bringing up station %s"%(sta)) + self.admin_up(sta) + + temp_stations_list = [] + temp_stations_list.append(self.side_b) + for station_profile in self.station_profiles: + temp_stations_list.extend(station_profile.station_names.copy()) + if self.wait_for_ip(temp_stations_list, timeout_sec=self.wait_timeout, debug=self.debug): + logg.info("ip's acquired") + else: + logg.info("Stations Failed to get IP's , consider increasing -wto','--wait_timeout' from the command line ") + exit(1) # Exit if cannot receive IP's + time.sleep(30) + logg.info("Starting layer-3 traffic (if any configured)") + self.cx_profile.start_cx() + self.cx_profile.refresh_cx() + + cur_time = datetime.datetime.now() + logg.info("Getting initial values.") + # the total_dl_bps and total_up_bps is for all stations + old_rx_values, rx_drop_percent, endps, total_dl_bps, total_ul_bps = self.__get_rx_values() + + end_time = self.parse_time(self.test_duration) + cur_time + + logg.info("Monitoring throughput for duration: %s"%(self.test_duration)) + + passes = 0 + expected_passes = 0 + logg.info("polling_interval_seconds {}".format(self.polling_interval_seconds)) + + while cur_time < end_time: + interval_time = cur_time + datetime.timedelta(seconds=self.polling_interval_seconds) + while cur_time < interval_time: + cur_time = datetime.datetime.now() + self.reset_port_check() + time.sleep(1) + + self.epoch_time = int(time.time()) + # the total_dl_bps and total_up_bps is for all stations + new_rx_values, rx_drop_percent, endps, total_dl_bps, total_ul_bps = self.__get_rx_values() + + print("main loop, total-dl: ", total_dl_bps, " total-ul: ", total_ul_bps) + + expected_passes += 1 + + # __compare_vals - does the calculations + Result, rx_bytes, csv_rx_row_data = self.__compare_vals(old_rx_values, new_rx_values, total_dl_bps, total_ul_bps) + # save the best rate for the interval + if rx_bytes > best_rx_bytes: + best_rx_bytes = rx_bytes + + if Result: + passes += 1 + else: + fail_msg = "FAIL: TIME: {} EPOCH: {} Not all stations increased traffic".format(cur_time, self.epoch_time) + self._fail(fail_msg, print_fail) + old_rx_values = new_rx_values + + cur_time = datetime.datetime.now() + csv_rx_row_data.append(self.test_duration) + csv_rx_row_data.append(self.polling_interval_seconds) + csv_rx_row_data.append(self.side_a_tx_min_bps) + csv_rx_row_data.append(self.side_b_tx_min_bps) + csv_rx_row_data.append(best_rx_bytes) + csv_rx_row_data.append(total_dl_bps) + csv_rx_row_data.append(total_ul_bps) + self.csv_add_row(csv_rx_row_data,self.csv_results_writer,self.csv_results) + if passes == expected_passes: + self._pass("PASS: All tests passed", print_pass) + + def stop(self): + self.cx_profile.stop_cx() + self.multicast_profile.stop_mc() + for station_list in self.station_lists: + for station_name in station_list: + self.admin_down(station_name) + + def cleanup(self): + self.cx_profile.cleanup() + self.multicast_profile.cleanup() + for station_profile in self.station_profiles: + station_profile.cleanup() + + # for details csv file + def csv_generate_column_details_headers(self): + # test_keys used to generate the test_id + csv_rx_headers = self.test_keys.copy() + csv_rx_headers.extend + # test_keys are the controller configuration + csv_rx_headers.extend(['epoch_time','time','test_id','test_duration','intv_sec','A_to_B_tx_bps_ul','B_to_A_tx_bps_dl','rx_bytes_intv_best','all_sta_dl_bps','all_sta_ul_bps']) + return csv_rx_headers + + def csv_generate_column_results_headers(self): + # test_keys used to generate test_id + csv_rx_headers = self.test_keys.copy() + csv_rx_headers.extend + #test_keys are the controller configuration + csv_rx_headers.extend(['epoch_time','time','test_id','test_duration','intv_sec','A_to_B_tx_bps_ul','B_to_A_tx_bps_dl','rx_bytes_intv_best','all_sta_dl_bps','all_sta_ul_bps']) + return csv_rx_headers + + def csv_add_column_headers(self,headers): + if self.csv_file_details is not None: + self.csv_writer.writerow(headers) + self.csv_file_details.flush() + + def csv_add_column_headers_results(self,headers): + if self.csv_results is not None: + self.csv_results_writer.writerow(headers) + self.csv_results.flush() + + def csv_validate_list(self, csv_list, length): + if len(csv_list) < length: + csv_list = csv_list + [('no data','no data')] * (length - len(csv_list)) + return csv_list + + def csv_add_row(self,row,writer,csv_file_details): # can make two calls eventually + if csv_file_details is not None: + writer.writerow(row) + csv_file_details.flush() + +def valid_endp_types(_endp_type): + etypes = _endp_type.split() + for endp_type in etypes: + valid_endp_type=['lf_udp','lf_udp6','lf_tcp','lf_tcp6','mc_udp','mc_udp6'] + if not (str(endp_type) in valid_endp_type): + print('invalid endp_type: %s. Valid types lf_udp, lf_udp6, lf_tcp, lf_tcp6, mc_udp, mc_udp6' % endp_type) + exit(1) + return _endp_type +################################################################################ +# +# End of Traffic Generation Class +# +################################################################################ + +############################################################ +# +# Scaling And Performance MAIN +# +############################################################ +def main(): + global logg + lfjson_host = "localhost" + lfjson_port = 8080 + debug_on = False + + parser = argparse.ArgumentParser( + prog='lf_controller_snp.py', + #formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Scaling and Performance + ''', + + description='''\ +lf_controller_snp.py: +-------------------- + +################################################################################## +Task Description: Ultimate Aim +################################################################################## +----------------- +Candela Scaling and Performance Test (SNP) + +The Test supports configuraiton of a Controller which configures +An AP and the Configuration of LANforge or Multiple LANforges +configured into a "Realm". + +######################################### +# Examples +# ####################################### +EXAMPLE: + +Use --print_test_config at end of command to see test configuration + +Test configurations take presidence to command line parameters + +Using Coded Test Configuration --controller_test_1 + ./lf_snp_test.py --controller_ip 10.195.197.234 --controller_user admin --controller_passwd Milpitas@123 + --controller_aps 'Vanc-e' --controller_series "9800" --endp_types 'lf_udp' --upstream_port eth2 --controller_prompt "Can-SnP-9120" --controller_test_1 + --print_test_config + +Using Coded Test Configuration --controller_test_1 + ./lf_snp_test.py --controller_ip 10.195.197.234 --controller_user admin --controller_passwd Milpitas@123 + --controller_aps 'Vanc-e' --controller_series "9800" --endp_types 'lf_udp' --upstream_port eth2 --controller_prompt "Can-SnP-9120" --controller_test_1 + --print_test_config + +Using Coded Test Configuration: + ./lf_snp_test.py -cc 192.168.100.112 -cu admin -cpw Cisco123 -cca APA453.0E7B.CF9C -cs "3504" --endp_types 'lf_udp' --upstream_port eth2 --controller_test_3 + --controller_prompt "(Cisco Controller)" + --print_test_config + +Using Commandline with defaults: + ./lf_snp_test.py --controller_ip 192.168.100.112 --controller_user admin --controller_passwd Cisco123 --controller_aps APA453.0E7B.CF9C --controller_series "3504" + --controller_prompt "(Cisco Controller)" --radio "radio==1.wiphy0 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto" + --print_test_config + +Using Commandline: + ./lf_snp_test.py --controller_ip 192.168.100.112 --controller_user admin --controller_passwd Cisco123 --controller_aps APA453.0E7B.CF9C + --controller_series "3504" --upstream_port eth2 --controller_prompt "(Cisco Controller)" --controller_wifimode "a" --controller_chan_5ghz "36" + --radio "radio==1.wiphy0 stations==10 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==ac" --controller_client_densities "10" + --print_test_config + +Using Commandline: Setting --test_duration "20s" --polling_interval to 5s -ccd "2" (--controller_client_densities) + ./lf_snp_test.py --controller_ip 192.168.100.112 --controller_user admin --controller_passwd Cisco123 --controller_aps APA453.0E7B.CF9C + --controller_series "3504" --upstream_port eth2 --controller_prompt "(Cisco Controller)" --controller_wifimode "auto" --controller_chan_5ghz "36" + --radio "radio==1.wiphy0 stations==2 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==an" --controller_client_densities "2" + --print_test_config + +############################################# +############################################# +LANforge Information and General Information +############################################# +############################################# + +############################################# +Radios Description +############################################# + +Radio descriptions: +ax200: so if AP is /n, then ax200 will connect at /n. But if AP is /AX, we have no way to force ax200 to act like /n +ax200: is dual band, supporting at least /b/g/n/AX on 2.4Ghz, and /a/n/ac/AX on 5Ghz. 2.4Ghz doesn't officially support /AC, but often chips will do /AC there anyway + +ath10K: if they want /AC or /n or /abg stations, then our ath10k radios can support that need (and ath9k if they have any, can do /n and /abg) +ath10K(998x) - wave -1 , dual band card it can be ac, n , a/b/g modes, up to 3x3 spacial streams +ath10K(9884) - wave-2 supports 4x4 802.11an-AC 5ghz (can act as ac , an) + +Note: wave-2 radios can act as ac, an, (802.11an-AC) or legacy a/b/g (802.11bgn-AC) + +############################################# +Wifi Modes +############################################# +11ax (2.4 ghz or 5 ghz), 11ac (5 ghz only), 11n (2.4ghz or 5 ghz), 11bg (2.4 ghz) (controller) + +############################################# +5 Ghz Radios and Wifi Modes +############################################# +Wifi mode: 11ax - 5ghz +Radios : ax200 : 802.11 /a/n/ac/AX + +Wifi mode: 11ac - 5ghz +Radios : ath10K(9984) 802.11an-AC (9984 are single band) + +Wifi mode: 11n - 5ghz +Radios : ath10K(9984) 802.11an-AC (9984 are single band) + +############################################# +24 Ghz Radios and Wifi Modes +############################################# +Wifi mode: 11ax - 24ghz +Radios : ax200 - 802.11 /b/g/n/AX + +Wifi mode: 11ac - 24ghz +Radios : ax200 802.11 /b/g/n/AX (2.4Ghz doesn't officially support /AC, but often chips will do /AC there anyway) (invalid) + +Wifi mode: 11n - 24ghz +Radios : ax200 802.11 /b/g/n/AX + +Wifi mode: 11bg - 24ghz +Radios : ax200 802.11 /b/g/n/AX + +############################################ +Radio Mode Configuration +############################################ +controller_wifimode == "anAX" or controller_wifimode == "abgn" or controller_wifimode == "bg": + radios = radio_AX200_abgn_ax_dict[controller_client_density] + +controller_wifimode == "an" or controller_wifimode == "anAC": + radios = radio_ath10K_9984_an_AC_dict[controller_client_density] + +############################################ +LANforge Realm Configuration +############################################ + +1.wiphy0 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +1.wiphy1 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +1.wiphy2 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +1.wiphy3 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +1.wiphy4 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +1.wiphy5 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +1.wiphy6 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +1.wiphy7 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +1.wiphy8 802.11an-AC ath10k(9984) 523 - 64 stations - 5ghz +1.wiphy9 802.11an-AC ath10k(9984) 523 - 64 stations - 5ghz + +2.wiphy0 802.11abgn-ax iwlwifi(AX200) 521 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +2.wiphy1 802.11abgn-ax iwlwifi(AX200) 521 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn + +3.wiphy0 802.11abgn-ax iwlwifi(AX200) 521 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +3.wiphy1 802.11abgn-ax iwlwifi(AX200) 521 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn + +4.wiphy0 802.11abgn-ax iwlwifi(AX200) 521 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +4.wiphy1 802.11abgn-ax iwlwifi(AX200) 521 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn + +5.wiphy0 802.11abgn-ax iwlwifi(AX200) 521 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +5.wiphy1 802.11abgn-ax iwlwifi(AX200) 521 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn + +6.wiphy0 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +6.wiphy1 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +6.wiphy2 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +6.wiphy3 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +6.wiphy4 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +6.wiphy5 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +6.wiphy6 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +6.wiphy7 802.11abgn-ax iwlwifi(AX200) 523 - 1 stations - 5ghz/24ghz use only for 802.11ax - 24gz abgn +6.wiphy8 802.11an-AC ath10k(9984) 523 - 64 stations - 5ghz +6.wiphy9 802.11an-AC ath10k(9984) 523 - 64 stations - 5ghz + +######################################################################################################## +TECHNICAL UNDERSTANDING: LANForge +######################################################################################################## + LANForge Monitored Values Per Polling Interval + 'rx bytes' - bytes transmitted + 'rx rate' - bits per second + + in DL direction: -B tx -> -A rx, (side_b_tx_min_bps) LANforge Eth endpoint transmits bytes (AP/DUT), station endpoint (Wifi) LANForge receives them. station-end-rx-bps (bits per second) is download rx-bps (bits per second) + in UL direction: -A tx -> -B rx, (side_a_tx_min_bps) LANforge Eth endpoint receives bytes (AP/DUT), station endpoint (Wifi) LANForge transmits them. ethernet-end-rx-bps (bits per second) is upload load rx-bps (bits per second) + +######################################################################################################### +LANforge GUI what is displayed in the Column and how to access the value with cli or json +######################################################################################################### +# NOTE: see how rx rate is used in script and can monitor any values in similiar manner + + GUI Column Display Layer3_cols argument to type in (to print in report) + + Name | 'name' + EID | 'eid' + Run | 'run' + Mng | 'mng' + Script | 'script' + Tx Rate | 'tx rate' + Tx Rate (1 min) | 'tx rate (1 min)' + Tx Rate (last) | 'tx rate (last)' + Tx Rate LL | 'tx rate ll' + Rx Rate | 'rx rate' + Rx Rate (1 min) | 'rx rate (1 min)' + Rx Rate (last) | 'rx rate (last)' + Rx Rate LL | 'rx rate ll' + Rx Drop % | 'rx drop %' + Tx PDUs | 'tx pdus' + Tx Pkts LL | 'tx pkts ll' + PDU/s TX | 'pdu/s tx' + Pps TX LL | 'pps tx ll' + Rx PDUs | 'rx pdus' + Rx Pkts LL | 'pps rx ll' + PDU/s RX | 'pdu/s tx' + Pps RX LL | 'pps rx ll' + Delay | 'delay' + Dropped | 'dropped' + Jitter | 'jitter' + Tx Bytes | 'tx bytes' + Rx Bytes | 'rx bytes' + Replays | 'replays' + TCP Rtx | 'tcp rtx' + Dup Pkts | 'dup pkts' + Rx Dup % | 'rx dup %' + OOO Pkts | 'ooo pkts' + Rx OOO % | 'rx ooo %' + RX Wrong Dev | 'rx wrong dev' + CRC Fail | 'crc fail' + RX BER | 'rx ber' + CX Active | 'cx active' + CX Estab/s | 'cx estab/s' + 1st RX | '1st rx' + CX TO | 'cx to' + Pattern | 'pattern' + Min PDU | 'min pdu' + Max PDU | 'max pdu' + Min Rate | 'min rate' + Max Rate | 'max rate' + Send Buf | 'send buf' + Rcv Buf | 'rcv buf' + CWND | 'cwnd' + TCP MSS | 'tcp mss' + Bursty | 'bursty' + A/B | 'a/b' + Elapsed | 'elapsed' + Destination Addr | 'destination addr' + Source Addr | 'source addr' + ''') + ############################################# + # Fixed Configurations Coded Into Script + ############################################# + parser.add_argument('-ct1' ,'--controller_test_1', help='--controller_test_1 LANforge static radio configuration',action="store_true") + parser.add_argument('-ct2' ,'--controller_test_2', help='--controller_test_2 LANforge static radio configuration',action="store_true") + parser.add_argument('-ct3' ,'--controller_test_3', help='--controller_test_3 LANforge static radio configuration',action="store_true") + + ############################################# + # Script Controller Configurations + ############################################# + parser.add_argument('-cca' ,'--controller_aps', help='--controller_aps List of APs to test default: APA453.0E7B.CF9C',default="APA453.0E7B.CF9C") + parser.add_argument('-ccf' ,'--controller_bands', help='--controller_bands default: a',default="a") + parser.add_argument('-cwm' ,'--controller_wifimodes', help='List of of wifi mode to test default: auto',default="auto") + + parser.add_argument('-cc5','--controller_chan_5ghzs', help='--controller_chan_5ghzs <36 40 ...> default 36',default="36") + parser.add_argument('-cc2','--controller_chan_24ghzs', help='--controller_chan_24ghzs <1 2 ...> default 1',default="1") + parser.add_argument('-ccw','--controller_chan_widths', help='--controller_chan_widths <20 40 80 160> default: \"20\"',default="20") + parser.add_argument('-cam','--controller_ap_modes', help='--controller_ap_modes default local',default="local") + parser.add_argument('-pdu','--controller_pdus', help='--controller_pdus List of packet sizes \"88 512 1370 1518\" default 1580',default="1518" ) + + parser.add_argument('-cde','--controller_data_encryptions', help='--controller_data_encryptions \"enable disable\"',default="disable" ) + parser.add_argument('-cs' ,'--controller_series', help='--controller_series <9800 | 3504>',default="9800",choices=["9800","3504"]) + parser.add_argument('-ccp','--controller_prompt', type=str,help="controller prompt default WLC",default="WLC") + parser.add_argument('-cas','--controller_ap_slot', type=str,help="AP slot, default 1",default="1") + parser.add_argument('-cwl','--controller_wlan', type=str,help="--controller_wlan , default wlan",default="wlan") + + parser.add_argument('-cc' ,'--controller_ip', help='--controller_ip default 192.168.100.178',default="192.168.100.178") + parser.add_argument('-cp' ,'--controller_port', help='--controller_port ssh default 22',default="22") + parser.add_argument('-cu' ,'--controller_user', help='--controller_user ',default="admin") + parser.add_argument('-cpw','--controller_passwd', help='--controller_passwd ',default="controller123") + parser.add_argument('-ccs','--controller_scheme', help='--controller_scheme (serial|telnet|ssh): connect via serial, ssh or telnet',default="ssh",choices=["serial","telnet","ssh"]) + parser.add_argument('-ccd','--controller_client_densities', help='--controller_client_densities List of client densities defaults 1', default="1" ) + + parser.add_argument('-ctp','--controller_tx_powers', help='--controller_tx_powers <1 | 2 | 3 | 4 | 5 | 6 | 7 | 8> 1 is highest power default 3',default="3" + ,choices=["1","2","3","4","5","6","7","8"]) + parser.add_argument('-cco','--cap_ctl_out', help='--cap_ctl_out , switch the controller controller output will be captured', action='store_true') + + ################################################################# + # Script AP parameters for reading AP, - not used in this script + ################################################################# + parser.add_argument('-api','--ap_info', action='append', nargs=1, type=str, \ + help='(enter 0 if does not apply) --ap_info \"ap_scheme== ap_prompt== ap_ip== ap_port== ap_user== ap_pw== ap_tty==\" ') + #--ap_info "ap_scheme==serial ap_prompt==APA53.0E7B.CF9C ap_ip==0 ap_port==0 ap_user==admin ap_pw==Admin123 ap_tty==/dev/ttyUSB2" + + ############################################# + # Script LANforge Configurations + ############################################# + parser.add_argument('-lm','--mgr', help='--mgr ',default='localhost') + parser.add_argument('-d','--test_duration', help='--test_duration example --time 5d (5 days) default: 2m options: number followed by d, h, m or s',default='20s') + parser.add_argument('-pi','--polling_interval', help="--polling_interval ", default='5s') + parser.add_argument('--tos', help='--tos: Support different ToS settings: BK | BE | VI | VO | numeric',default="BE") + parser.add_argument('-db','--debug', help='--debug: Enable debugging',action='store_true') + parser.add_argument('-t', '--endp_types', help='--endp_types example --endp_types \"lf_udp lf_tcp\" Default: lf_udp lf_tcp, options: lf_udp, lf_udp6, lf_tcp, lf_tcp6, mc_udp, mc_udp6', + default='lf_udp lf_tcp', type=valid_endp_types) + parser.add_argument('-cd', '--controller_directions', help='--controller_directions example --controller_directions \"upload download\" Default: upload download', default='upload download') + parser.add_argument('-u', '--upstream_port', help='--upstream_port example: --upstream_port eth1',default='eth1') + parser.add_argument('-o','--csv_outfile', help="--csv_outfile ", default='snp') + parser.add_argument("-l", "--log", action='store_true', help="create logfile for messages, default stdout") + parser.add_argument('-c','--csv_output', help="Generate csv output", default=True) + + parser.add_argument('-r','--radio', action='append', nargs=1, help='--radio \ + \"radio== ssid== ssid_pw== security== wifimode==\" '\ + , required=False) + parser.add_argument('-ul_bps','--side_a_tx_min_bps', help='--side_a_tx_min_bps , upload (A side tx) min tx rate bps default 256000 500000000', default="256000 1000000000") + parser.add_argument('-dl_bps','--side_b_tx_min_bps', help='--side_b_tx_min_bps , download(B side tx) min tx rate bps default 1000000000', default="1000000000") + + ############################################## + # Parameters Used For Testing + ############################################## + parser.add_argument('-noc','--no_controller', help='-noc / --no_controller no configuration of the controller', action='store_true') + parser.add_argument('-nos','--no_stations', help='-nos / --no_stations , no stations', action='store_true') + parser.add_argument('-wto','--wait_timeout', help='-wto / --wait_timeout , time to wait for stations to get IP ', default="360") + parser.add_argument('-ptc','--print_test_config', help='-ptc / --print_test_config , print out the test configuration and exit', action='store_true') + + ############################################################## + # + # Scaling and Performance Args Parser + # + ############################################################## + args = parser.parse_args() + controller_args = args # use args. + #logg.info("args: {}".format(args)) + debug_on = args.debug + + ############################################################### + # Gather Test Data + ############################################################### + if args.test_duration: + test_duration = args.test_duration + + if args.polling_interval: + polling_interval = args.polling_interval + + if args.mgr: + lfjson_host = args.mgr + + if args.upstream_port: + side_b = args.upstream_port + + if args.radio: + radios = args.radio + + if args.wait_timeout: + wait_timeout = int(args.wait_timeout) + + if args.controller_scheme: + __scheme = args.controller_scheme + + if args.controller_port: + __port = args.controller_port + + if args.controller_ip: + __ctlr = args.controller_ip + + if args.controller_prompt: + __prompt = args.controller_prompt + + if args.controller_series: + __series = args.controller_series + + if args.controller_user: + __user = args.controller_user + + if args.controller_passwd: + __passwd = args.controller_passwd + + if args.cap_ctl_out: + __cap_ctl_out = args.cap_ctl_out + else: + __cap_ctl_out = False + + if args.controller_ap_slot: + __ap_slot = args.controller_ap_slot + + if args.controller_wlan: + __wlan = args.controller_wlan + + ap_dict = [] + if args.ap_info: + ap_info = args.ap_info + for _ap_info in ap_info: + print("ap_info {}".format(_ap_info)) + ap_keys = ['ap_scheme','ap_prompt','ap_ip','ap_port','ap_user','ap_pw', 'ap_tty', 'ap_baud'] + ap_dict = dict(map(lambda x: x.split('=='), str(_ap_info).replace('[','').replace(']','').replace("'","").split())) + for key in ap_keys: + if key not in ap_dict: + print("missing ap config, for the {}, all these need to be set {} ".format(key,ap_keys)) + exit(1) + print("ap_dict: {}".format(ap_dict)) + + ############################################### + # + # Create a report instanciate a reporting class + # + ############################################### + report = lf_report(_results_dir_name = "Scaling_and_Performance",_output_html="snp.html",_output_pdf="snp.pdf") + + if args.csv_outfile != None: + current_time = time.strftime("%m_%d_%Y_%H_%M_%S", time.localtime()) + csv_outfile = "details_{}_{}.csv".format(args.csv_outfile,current_time) + csv_outfile = report.file_add_path(csv_outfile) + + csv_results = "results_{}_{}.csv".format(args.csv_outfile,current_time) + csv_results = report.file_add_path(csv_results) + print("csv output file : {}".format(csv_outfile)) + print("csv results file : {}".format(csv_results)) + + if args.log: + outfile_log = "{}_{}_output_log.log".format(args.outfile,current_time) + outfile_log = report.file_add_path(outfile_log) + print("output file log: {}".format(outfile_log)) + else: + outfile_log = "stdout" + print("output file log: {}".format(outfile_log)) + + # Set up the log file + console_handler = logging.StreamHandler() + formatter = logging.Formatter(FORMAT) + logg = logging.getLogger(__name__) + logg.setLevel(logging.DEBUG) + file_handler = None + if (args.log): + file_handler = logging.FileHandler(outfile_log, "w") + + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + logg.addHandler(file_handler) + logg.addHandler(logging.StreamHandler(sys.stdout)) # allows to logging to file and stderr + # if loggin.basicConfig is called this will result in duplicating log entries + # logging.basicConfig(format=FORMAT, handlers=[file_handler]) + else: + # stdout logging + logging.basicConfig(format=FORMAT, handlers=[console_handler]) + + ############################################################# + # + # Radio Eictionary for LANforge 1 in LANforge Realm + # Configuration used by command switch --controller_test_1 + # + ############################################################# + radio_AX200_abgn_ax_list_001_one = [['radio==1.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_AX200_abgn_ax_list_008_one = [['radio==1.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy2 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy3 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy4 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy5 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy6 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy7 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_AX200_abgn_ax_dict_one = {'1' : radio_AX200_abgn_ax_list_001_one, + '8' : radio_AX200_abgn_ax_list_008_one} + + radio_ath10K_9984_an_AC_list_001_one = [['radio==1.wiphy8 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_010_one = [['radio==1.wiphy8 stations==10 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_020_one = [['radio==1.wiphy8 stations==20 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_050_one = [['radio==1.wiphy8 stations==50 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_050_one = [['radio==1.wiphy8 stations==64 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_ath10K_9984_an_AC_dict_one = {'1' : radio_ath10K_9984_an_AC_list_001_one, + '10' : radio_ath10K_9984_an_AC_list_010_one, + '20' : radio_ath10K_9984_an_AC_list_020_one, + '50' : radio_ath10K_9984_an_AC_list_020_one, + '65' : radio_ath10K_9984_an_AC_list_020_one} + + #################################################################### + # + # End of Configuration used by command switch --controller_test_1 + # + #################################################################### + + ############################################################ + # + # Radio Dictionary for multiple LANforge in Realm + # Configuration used by command switch --controller_test_2 + # + ############################################################ + radio_AX200_abgn_ax_list_001 = [['radio==1.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_AX200_abgn_ax_list_010 = [['radio==1.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy2 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy3 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy4 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy5 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy6 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy7 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==2.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==2.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + + + radio_AX200_abgn_ax_list_020 = [['radio==1.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy2 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy3 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy4 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy5 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy6 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy7 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==2.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==2.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==3.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==3.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==4.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==4.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==5.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==5.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy2 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy3 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ] + + radio_AX200_abgn_ax_list_024 = [['radio==1.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy2 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy3 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy4 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy5 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy6 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy7 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==2.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==2.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==3.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==3.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==4.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==4.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==5.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==5.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy0 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy1 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy2 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy3 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy4 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy5 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy6 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy7 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ] + + radio_AX200_abgn_ax_dict = {'1' : radio_AX200_abgn_ax_list_001, + '10' : radio_AX200_abgn_ax_list_010, + '24' : radio_AX200_abgn_ax_list_024} + + radio_ath10K_9984_an_AC_list_001 = [['radio==1.wiphy8 stations==1 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_010 = [['radio==1.wiphy8 stations==10 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_020 = [['radio==1.wiphy8 stations==20 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_050 = [['radio==1.wiphy8 stations==50 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_100 = [['radio==1.wiphy8 stations==50 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy9 stations==50 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_200 = [['radio==1.wiphy8 stations==50 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy9 stations==50 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy8 stations==50 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==6.wiphy9 stations==50 ssid==test-can ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_ath10K_9984_an_AC_dict = {'1' : radio_ath10K_9984_an_AC_list_001, + '50' : radio_ath10K_9984_an_AC_list_050, + '200': radio_ath10K_9984_an_AC_list_200} + + #################################################################### + # + # End of Configuration used by command switch --controller_test_2 + # + #################################################################### + + ############################################################# + # + # Static dictionary for radios on 191.168.100.178 + # Static Configuration Candela Tech Realm () + # Configuration used by command switch --controller_test_3 + # + ############################################################# + #iwlwifi(AX200) 521 + radio_AX200_abgn_ax_list_001_wiphy2 = [['radio==1.wiphy2 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_AX200_abgn_ax_list_001 = [['radio==1.wiphy2 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_AX200_abgn_ax_list_004 = [['radio==1.wiphy2 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy3 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy4 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto'], + ['radio==1.wiphy5 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_AX200_abgn_ax_dict_test = {'1' : radio_AX200_abgn_ax_list_001, + '4': radio_AX200_abgn_ax_list_004} + + radio_AX200_abgn_ax_dict_test_wiphy2 = {'1' : radio_AX200_abgn_ax_list_001_wiphy2} + + radio_ath10K_9984_an_AC_list_001 = [['radio==1.wiphy0 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_010 = [['radio==1.wiphy0 stations==10 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_020 = [['radio==1.wiphy0 stations==20 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_050 = [['radio==1.wiphy0 stations==50 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_ath10K_9984_an_AC_list_001_wiphy0 = [['radio==1.wiphy0 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath10K_9984_an_AC_list_010_wiphy0 = [['radio==1.wiphy0 stations==10 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_ath10K_9984_an_AC_dict_test_wiphy0 = {'1' : radio_ath10K_9984_an_AC_list_001_wiphy0, + '10' : radio_ath10K_9984_an_AC_list_010_wiphy0} + + radio_ath10K_9984_an_AC_dict_test = {'1' : radio_ath10K_9984_an_AC_list_001, + '10' : radio_ath10K_9984_an_AC_list_010, + '50' : radio_ath10K_9984_an_AC_list_050} + + #################################################################### + # Test to use ath9K + #################################################################### + + radio_ath9K_9984_abgn_list_001 = [['radio==1.wiphy1 stations==1 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath9K_9984_abgn_list_010 = [['radio==1.wiphy1 stations==10 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath9K_9984_abgn_list_020 = [['radio==1.wiphy1 stations==20 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath9K_9984_abgn_list_050 = [['radio==1.wiphy1 stations==50 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + radio_ath9K_9984_abgn_list_200 = [['radio==1.wiphy1 stations==200 ssid==test_candela ssid_pw==[BLANK] security==open wifimode==auto']] + + radio_ath9K_9984_abgn_dict_test = {'1' : radio_ath9K_9984_abgn_list_001, + '10' : radio_ath9K_9984_abgn_list_010, + '20' : radio_ath9K_9984_abgn_list_020, + '50' : radio_ath9K_9984_abgn_list_050, + '200': radio_ath9K_9984_abgn_list_200 } + #################################################################### + # Test to only use teh ath9K + #################################################################### + # + # End of Configuration used by command switch --controller_test_3 + # + #################################################################### + MAX_NUMBER_OF_STATIONS = 200 + + radio_name_list = [] + number_of_stations_per_radio_list = [] + ssid_list = [] + ssid_password_list = [] + ssid_security_list = [] + wifimode_list = [] + + #optional radio configuration + reset_port_enable_list = [] + reset_port_time_min_list = [] + reset_port_time_max_list = [] + + wifi_mode_dict = { + "auto" : "0", + "a" : "1", + "b" : "2", + "g" : "3", + "abg" : "4", + "abgn" : "5", + "bgn" : "6", + "bg" : "7", + "abgnAC" : "8", + "anAC" : "9", + "an" : "10", + "bgnAC" : "11", + "abgnAX" : "12", + "bgnAX" : "13", + "anAX" : "14" + } + +########################################################################################### +# Test Configurations: Take presidence over command line arguments +# GOAL: help with command line configurations +########################################################################################### + +########################################################################################### +# +# controller_test_1 +# +########################################################################################### + # Test configuration that may be read in , in conjunction with command line arguments + if args.controller_test_1: + logg.info("USING: controller_test_1") + controller_aps = "vanc-e".split() + controller_bands = "a b".split() + controller_wifimodes = "an anAX anAC abgn bg".split() + controller_tx_powers = "3".split() + controller_chan_5ghzs = "36".split() + controller_chan_24ghzs = "1".split() + controller_chan_widths = "20 40 80".split() + controller_ap_modes = "local".split() + controller_data_encryptions = "disable".split() + controller_packet_types = "lf_udp lf_tcp".split() + controller_directions = "upload download".split() + controller_pdus = "88 512 1370 1518".split() + controller_client_densities = "1 10 50 200".split() + controller_side_a_tx_min_bps = "256000".split() + controller_side_b_tx_min_bps = "256000".split() + radio_AX200_abgn_ax_dict = radio_AX200_abgn_ax_dict_one + radio_ath10K_9984_an_AC_dict = radio_ath10K_9984_an_AC_dict_one + +########################################################################################### +# +# controller_test_2 +# +########################################################################################### + elif args.controller_test_2: + logg.info("USING: controller_test_2") + # Note the local system only supports 802.11-abgn , 802.11a + controller_aps = "APA453.0E7B.CF9C".split() + controller_bands = "a".split() + #controller_wifimodes = "an anAX anAC abgn bg".split() + controller_wifimodes = "an".split() + controller_tx_powers = "3".split() + controller_chan_5ghzs = "36".split() + controller_chan_24ghzs = "1".split() + controller_chan_widths = "20".split() + controller_ap_modes = "local".split() + controller_data_encryptions = "disable".split() + #controller_packet_types = "lf_udp lf_tcp".split() + controller_packet_types = "lf_udp".split() + #controller_directions = "upload download".split() + controller_directions = "upload download".split() + #controller_pdus = "88 512 1370 1518".split() + controller_pdus = "1518".split() + controller_client_densities = "10".split() + controller_data_encryptions = "disable".split() + controller_side_a_tx_min_bps = "256000".split() + controller_side_b_tx_min_bps = "256000".split() + radio_AX200_abgn_ax_dict = radio_AX200_abgn_ax_dict_test + radio_ath10K_9984_an_AC_dict = radio_ath10K_9984_an_AC_dict_test + +########################################################################################### +# +# controller_test_3 +# +########################################################################################### + elif args.controller_test_3: #CMR_test_3 + logg.info("USING: controller_test_3") + # Note the local system only supports 802.11-abgn , 802.11a + controller_aps = "APA453.0E7B.CF9C".split() + controller_bands = "a".split() + #controller_wifimodes = "an anAX anAC abgn bg".split() + controller_wifimodes = "an".split() + controller_tx_powers = "3".split() + controller_chan_5ghzs = "36".split() + controller_chan_24ghzs = "1".split() + controller_chan_widths = "20".split() + controller_ap_modes = "local".split() + controller_data_encryptions = "disable".split() + #controller_packet_types = "lf_udp lf_tcp".split() + controller_packet_types = "lf_udp".split() + #controller_directions = "upload download".split() + controller_directions = "upload download".split() + #controller_pdus = "88 512 1370 1518".split() + controller_pdus = "1518".split() + controller_client_densities = "1".split() + controller_data_encryptions = "disable".split() + controller_side_a_tx_min_bps = "256000".split() + controller_side_b_tx_min_bps = "256000".split() + radio_AX200_abgn_ax_dict = radio_AX200_abgn_ax_dict_test_wiphy2 + radio_ath10K_9984_an_AC_dict = radio_ath10K_9984_an_AC_dict_test + #radio_ath10K_9984_an_AC_dict = radio_ath9K_9984_abgn_dict_test + + ########################################################################################### + # Use Command line arguments + ########################################################################################### + else: + + controller_aps = args.controller_aps.split() + controller_bands = args.controller_bands.split() + controller_wifimodes = args.controller_wifimodes.split() + for mode in controller_wifimodes: + if mode in wifi_mode_dict.keys(): + pass + else: + logg.info("wifimode [{}] not recognised. Please use: auto, a, b, g, abg, abgn, bgn, bg, abgnAC, anAC, an, bgnAC, abgnAX, bgnAX, anAX".format(mode)) + exit(1) + controller_tx_powers = args.controller_tx_powers.split() + controller_chan_5ghzs = args.controller_chan_5ghzs.split() + controller_chan_24ghzs = args.controller_chan_24ghzs.split() + controller_chan_widths = args.controller_chan_widths.split() + controller_ap_modes = args.controller_ap_modes.split() + controller_client_densities = args.controller_client_densities.split() + controller_packet_types = args.endp_types.split() + controller_pdus = args.controller_pdus.split() + controller_directions = args.controller_directions.split() + controller_data_encryptions = args.controller_data_encryptions.split() + + controller_side_a_tx_min_bps = args.side_a_tx_min_bps.split() + controller_side_b_tx_min_bps = args.side_b_tx_min_bps.split() + logg.info("TEST CONFIG: ") + logg.info("controller_aps ('-cca' ,'--controller_aps'): {}".format(controller_aps)) + logg.info("controller_bands ('-ccf' ,'--controller_bands'): {}".format(controller_bands)) + logg.info("controller_wifimodes ('-cwm' ,'--controller_wifimodes'): {}".format(controller_wifimodes)) + logg.info("controller_chan_5ghzs ('-cc5','--controller_chan_5ghzs'): {}".format(controller_chan_5ghzs)) + logg.info("controller_chan_24ghzs ('-cc2','--controller_chan_24ghzs'): {}".format(controller_chan_24ghzs)) + logg.info("controller_chan_widths ('-ccw','--controller_chan_widths'): {}".format(controller_chan_widths)) + logg.info("controller_tx_powers ('-ctp','--controller_tx_powers'): {}".format(controller_tx_powers)) + logg.info("controller_ap_modes ('-cam','--controller_ap_modes'): {}".format(controller_ap_modes)) + logg.info("controller_client_densities ('-ccd','--controller_client_densities'): {}".format(controller_client_densities)) + logg.info("controller_packet_types ('-t', '--endp_types'): {}".format(controller_packet_types)) + logg.info("controller_pdus ('-cps','--controller_pdus'): {}".format(controller_pdus)) + logg.info("controller_directions ('-cd', '--controller_directions'): {}".format(controller_directions)) + logg.info("controller_data_encryptions ('-cde','--controller_data_encryptions') {}".format(controller_data_encryptions)) + logg.info("controller_side_a_tx_min_bps ('-amr','--side_a_tx_min_bps'): {}".format(controller_side_a_tx_min_bps)) + logg.info("controller_side_b_tx_min_bps ('-bmr','--side_b_tx_min_bps'): {}".format(controller_side_b_tx_min_bps)) + logg.info("test duration ('-d','--test_duration'): {}".format(test_duration)) + logg.info("polling_interval ('-pi','--polling_interval'): {}".format(polling_interval)) + + if args.radio: + logg.info("radios from command line used") + logg.info(args.radio) + elif args.controller_test_1 or args.controller_test_2 or args.controller_test_3: + logg.info("radios from coded config used") + logg.info("##### AX200 RADIOS #####") + logg.info(radio_AX200_abgn_ax_dict) + logg.info('##### ATH10K_9984 RADIOS ####') + logg.info(radio_ath10K_9984_an_AC_dict) + else: + logg.info("No radios endered EXITING, use --radio or --controller_test_1, --controller_test_2 or --controller_test_3") + exit(1) + + if args.print_test_config: + logg.info("PRINT TEST CONFIG ONLY - exiting remove --print_test_config to run test") + try: + report_path = report.get_report_path() + logg.info("Reports Directory Created: {}".format(report_path)) + except: + logg.info("Reports Directory Created") + exit(1) + + __ap_set = None + __band_set = None + __chan_width_set = None + __ap_mode_set = None + __tx_power_set = None + __chan_5ghz_set = None + __chan_24ghz_set = None + __csv_started = False + + for controller_ap in controller_aps: + for controller_band in controller_bands: # frequency + for controller_wifimode in controller_wifimodes: + # check for valid frequency and wifi_mode combination put here to simplify logic since all radios do not support all modes + # "an anAX anAC abgn bg" + if((controller_band == "a" and controller_wifimode == "bg") or (controller_band == "b" and controller_wifimode == "anAC")): + logg.info("#######################################################################") + logg.info("# Skipping combination controller_band {} controller_wifimode {}".format(controller_band,controller_wifimode)) + logg.info("#######################################################################") + pass # invalid combination continue + else: + for controller_tx_power in controller_tx_powers: + for controller_chan_5ghz in controller_chan_5ghzs: + for controller_chan_24ghz in controller_chan_24ghzs: + for controller_chan_width in controller_chan_widths: #bandwidth + for controller_data_encryption in controller_data_encryptions: + for controller_ap_mode in controller_ap_modes: + for controller_client_density in controller_client_densities: + radios = "" + ######################################################## + # Validate radio configuration + # If controller_client_density is NOT supported continue + ######################################################## + + ######################################################## + # USE command line to configure LANforge radios + ############################################### + if args.radio: + radios = args.radio + logg.info("########################################################") + logg.info("# radios configured from command line {}".format(radios)) + logg.info("# controller_band: {}".format(controller_band)) + logg.info("# controller_wifimode: {}".format(controller_wifimode)) + logg.info("########################################################") + + ################################################################# + # USE radio dictionaies in this file to configure LANforge radios + ################################################################# + elif controller_band == "a": + if controller_wifimode == "anAX" or controller_wifimode == "abgn": + #AX200 dual band + try: + radios = radio_AX200_abgn_ax_dict[controller_client_density] + logg.info("controller_client_density:{} radios: {}".format(controller_client_density,radios)) + except: + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: controller_client_density: {} not supported for configuration".format(controller_client_density)) + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: current dictionary radio_AX200_abgn_ax_dict {}".format(radio_AX200_abgn_ax_dict)) + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: MOVE TO NEXT DENSITY CONTINUING TESTING") + continue + elif controller_wifimode == "an" or controller_wifimode == "anAC" or controller_wifimode =="auto": + #ath10K only supports 5Ghz + try: + radios = radio_ath10K_9984_an_AC_dict[controller_client_density] + logg.info("radios: {}".format(radios)) + except: + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: controller_client_density: {} not supported for configuration".format(controller_client_density)) + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: current dictionary radio_AX200_abgn_ax_dict {}".format(radio_ath10K_9984_an_AC_dict)) + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: MOVE TO NEXT DENSITY CONTINUING TESTING") + continue + else: + logg.info("##################################") + logg.info("# INVALID COMBINATION 5ghz") + #logg.info("# controller run settings: {}".format(test_config)) + logg.info("##################################") + exit(1) + else: # controller_band == "b" + if controller_wifimode == "an" or controller_wifimode == "anAX" or controller_wifimode == "abgn" or controller_wifimode == "bg" or controller_wifimode == "auto": + #AX200 dual band + try: + radios = radio_AX200_abgn_ax_dict[controller_client_density] + logg.info("radios: {}".format(radios)) + except: + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: controller_client_density: {} not supported for configuration".format(controller_client_density)) + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: current dictionary radio_AX200_abgn_ax_dict {}".format(radio_AX200_abgn_ax_dict)) + logg.info("CONTROLLER DENSITY INVALID FOR RADIO DICTIONARY: MOVE TO NEXT DENSITY CONTINUING TESTING") + continue + else: + logg.info("##################################") + logg.info("# INVALID COMBINATION 24 ghz") + #logg.info("# controller run settings: {}".format(test_config)) + logg.info("##################################") + exit(1) + + for controller_packet_type in controller_packet_types: + for controller_direction in controller_directions: + # looping though both A and B together, upload direction will select A, download direction will select B + for side_a_tx_min_bps_ul, side_b_tx_min_bps_dl in itertools.zip_longest(controller_side_a_tx_min_bps, controller_side_b_tx_min_bps, fillvalue = 256000): + for controller_pdu in controller_pdus: + logg.info("###################################################################") + logg.info("# TEST RUNNING direction:{} ul: {} , TEST RUNNING dl: {}" + .format(controller_direction, side_a_tx_min_bps_ul, side_b_tx_min_bps_dl)) + logg.info("####################################################################") + test_config = "AP=={} Band=={} chan_5ghz=={} chan_24ghz=={} wifi_mode=={} BW=={} encryption=={} ap_mode=={} clients=={} packet_type=={} direction=={} pdu=={}".format( + controller_ap,controller_band,controller_chan_5ghz,controller_chan_24ghz,controller_wifimode,controller_chan_width,controller_data_encryption,controller_ap_mode,controller_client_density, + controller_packet_type,controller_direction,controller_pdu) + test_keys = ['AP','Band','wifi_mode','chan_5ghz','chan_24ghz','BW','encryption','ap_mode','clients','packet_type','direction','pdu'] + logg.info("# controller run settings: {}".format(test_config)) + if(args.no_controller): + logg.info("################################################") + logg.info("# TEST MODE : NO CONTROLLER SET : TEST MODE") + logg.info("################################################") + if( controller_ap != __ap_set or + controller_band != __band_set or + controller_chan_width != __chan_width_set or + controller_ap_mode != __ap_mode_set or + controller_tx_power != __tx_power_set or + controller_chan_5ghz != __chan_5ghz_set or + controller_chan_24ghz != __chan_24ghz_set + ): + logg.info("###############################################") + logg.info("# TEST MODE : NEW CONTROLLER CONFIG : TEST MODE") + logg.info("###############################################") + __ap_set = controller_ap + __band_set = controller_band + __chan_width_set = controller_chan_width + __ap_mode_set = controller_ap_mode + __tx_power_set = controller_tx_power + __chan_5ghz_set = controller_chan_5ghz + __chan_24ghz_set = controller_chan_24ghz + __client_density = controller_client_density + else: + logg.info("################################################") + logg.info("# SETUP MODE : SETUP CONTROLLER : SETUP MODE") + logg.info("################################################") + + if( controller_ap != __ap_set or + controller_band != __band_set or + controller_chan_width != __chan_width_set or + controller_ap_mode != __ap_mode_set or + controller_tx_power != __tx_power_set or + controller_chan_5ghz != __chan_5ghz_set or + controller_chan_24ghz != __chan_24ghz_set + ): + logg.info("###############################################") + logg.info("# SETUP MODE : NEW CONTROLLER CONFIG") + logg.info("###############################################") + __ap_set = controller_ap + __band_set = controller_band + __chan_width_set = controller_chan_width + __ap_mode_set = controller_ap_mode + __tx_power_set = controller_tx_power + __chan_5ghz_set = controller_chan_5ghz + __chan_24ghz_set = controller_chan_24ghz + __client_density = controller_client_density + controller = CreateCtlr( + _scheme=__scheme, + _port=__port, + _series=__series, + _ctlr=__ctlr, + _prompt=__prompt, + _user=__user, + _passwd=__passwd, + _ap=__ap_set, + _band=__band_set, + _chan_5ghz=__chan_5ghz_set, + _chan_24ghz=__chan_24ghz_set, + _chan_width=__chan_width_set, + _ap_mode=__ap_mode_set, + _tx_power=__tx_power_set, + _wlan = __wlan, + _cap_ctl_out=__cap_ctl_out + ) + #Disable AP + # + # Controller Configuration + # + #if controller_args.controller_series == "9800": + # controller_controller_no_loggin_console() + pss = controller.controller_show_ap_summary() + logg.info("pss {}".format(pss)) + controller.controller_disable_ap() + if controller_args.controller_series == "9800": + # CISCO 4/27/2021 controller.controller_disable_wlan() + controller.controller_disable_network_5ghz() + controller.controller_disable_network_24ghz() + # CISCO 4/27/2021 controller.controller_role_manual() + else: + controller.controller_disable_network_5ghz() + controller.controller_disable_network_24ghz() + controller.controller_set_tx_power() + controller.controller_set_channel() + controller.controller_set_bandwidth() + #if controller_args.controller_series == "9800": + # CANDELA TODO, not needed controller.controller_create_wlan() + # CANDELA TODO set the policy tag, controller.controller_set_wireless_tag_policy() + # CANDELA controller.controller_enable_wlan() + if controller_band == "a": + controller.controller_enable_network_5ghz() + else: + controller.controller_enable_network_24ghz() + controller.controller_enable_ap() + # need to actually check the CAC timer + time.sleep(30) + #################################### + # end of controller controller code + #################################### + else: + logg.info("###############################################") + logg.info("# SETUP MODE : NO CHANGE TO CONTROLLER CONFIG") + logg.info("###############################################") + logg.info("controller_ap: {} controller_band: {} controller_chan_width: {} controller_ap_mode: {} controller_tx_power: {} controller_chan_5ghz: {} controller_chan_24ghz: {}" + .format(controller_ap,controller_band, controller_chan_width, controller_ap_mode, controller_tx_power, controller_chan_5ghz, controller_chan_24ghz)) + logg.info("__ap_set: {} __band_set: {} __chan_width_set: {} __ap_mode_set: {} __tx_power_set: {} __chan_5ghz_set: {} __chan_24ghz_set: {}" + .format(__ap_set,__band_set, __chan_width_set, __ap_mode_set, __tx_power_set, __chan_5ghz_set, __chan_24ghz_set)) + logg.info("controller_wifi_mode {}".format(controller_wifimode)) + logg.info("radios {}".format(radios)) + for radio_ in radios: + radio_keys = ['radio','stations','ssid','ssid_pw','security','wifimode'] + radio_info_dict = dict(map(lambda x: x.split('=='), str(radio_).replace('[','').replace(']','').replace("'","").split())) + logg.info("radio_dict {}".format(radio_info_dict)) + for key in radio_keys: + if key not in radio_info_dict: + logg.info("missing config, for the {}, all of the following need to be present {} ".format(key,radio_keys)) + exit(1) + radio_name_list.append(radio_info_dict['radio']) + ssid_list.append(radio_info_dict['ssid']) + ssid_password_list.append(radio_info_dict['ssid_pw']) + ssid_security_list.append(radio_info_dict['security']) + if args.radio: + number_of_stations_per_radio_list.append(radio_info_dict['stations']) + wifimode_list.append(int(wifi_mode_dict[radio_info_dict['wifimode']])) + else: + number_of_stations_per_radio_list.append(radio_info_dict['stations']) + wifimode_list.append(int(wifi_mode_dict[radio_info_dict['wifimode']])) + optional_radio_reset_keys = ['reset_port_enable'] + radio_reset_found = True + for key in optional_radio_reset_keys: + if key not in radio_info_dict: + #logg.info("port reset test not enabled") + radio_reset_found = False + break + + if radio_reset_found: + reset_port_enable_list.append(True) + reset_port_time_min_list.append(radio_info_dict['reset_port_time_min']) + reset_port_time_max_list.append(radio_info_dict['reset_port_time_max']) + else: + reset_port_enable_list.append(False) + reset_port_time_min_list.append('0s') + reset_port_time_max_list.append('0s') + # no stations for testing reconfiguration of the controller - + if(args.no_stations): + logg.info("#################################################") + logg.info("# TEST MODE : NO STATIONS TEST MODE") + logg.info("##################################################") + else: + logg.info("#################################################") + logg.info("# SETUP MODE : CREATING STATIONS SETUP MODE") + logg.info("##################################################") + + index = 0 + station_lists = [] + for (radio_name_, number_of_stations_per_radio_) in zip(radio_name_list,number_of_stations_per_radio_list): + number_of_stations = int(number_of_stations_per_radio_) + if number_of_stations > MAX_NUMBER_OF_STATIONS: + logg.info("number of stations per radio exceeded max of : {}".format(MAX_NUMBER_OF_STATIONS)) + quit(1) + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_= 1 + index*1000, end_id_= number_of_stations + index*1000, + padding_number_=10000, radio=radio_name_) + station_lists.append(station_list) + index += 1 + # Run Traffic upload (STA to AP) + if(controller_direction == "upload"): + side_a_tx_min_bps = side_a_tx_min_bps_ul + side_b_tx_min_bps = 0 + # Run Traffic download (AP to STA) + else: + side_a_tx_min_bps = 0 + side_b_tx_min_bps = side_b_tx_min_bps_dl + # current default is to have a values + ip_var_test = L3VariableTime( + args=args, + _scheme=__scheme, + _port=__port, + _series=__series, + _ctlr=__ctlr, + _prompt=__prompt, + _user=__user, + _passwd=__passwd, + _ap=__ap_set, + _ap_slot=__ap_slot, + _band=__band_set, + _chan_5ghz=__chan_5ghz_set, + _chan_24ghz=__chan_24ghz_set, + _chan_width=__chan_width_set, + _ap_mode=__ap_mode_set, + _tx_power=__tx_power_set, + _client_density=__client_density, + _cap_ctl_out=__cap_ctl_out, + _ap_dict = ap_dict, + endp_type=controller_packet_type, + tos=args.tos, + side_b=side_b, + radio_name_list=radio_name_list, + number_of_stations_per_radio_list=number_of_stations_per_radio_list, + ssid_list=ssid_list, + ssid_password_list=ssid_password_list, + ssid_security_list=ssid_security_list, + wifimode_list=wifimode_list, + station_lists= station_lists, + name_prefix="LT-", + debug_on=debug_on, + wait_timeout=wait_timeout, + outfile=csv_outfile, + results=csv_results, + test_keys=test_keys, + test_config=test_config, + reset_port_enable_list=reset_port_enable_list, + reset_port_time_min_list=reset_port_time_min_list, + reset_port_time_max_list=reset_port_time_max_list, + csv_started=__csv_started, + side_a_tx_min_bps = side_a_tx_min_bps, + side_a_tx_max_bps =0, + side_a_min_pdu =controller_pdu, + side_a_max_pdu =0, + side_b_tx_min_bps =side_b_tx_min_bps, + side_b_tx_max_bps =0, + side_b_min_pdu =controller_pdu, + side_b_max_pdu = 0, + number_template="00", + test_duration=test_duration, + polling_interval= polling_interval, + lfclient_host=lfjson_host, + lfclient_port=lfjson_port) + __csv_started = True + ip_var_test.pre_cleanup() + ip_var_test.build() + if not ip_var_test.passes(): + logg.info("build step failed.") + logg.info(ip_var_test.get_fail_message()) + exit(1) + ip_var_test.start(False, False) + ip_var_test.stop() + if not ip_var_test.passes(): + logg.info("stop test failed") + logg.info(ip_var_test.get_fail_message()) + # clean up + radio_name_list = [] + number_of_stations_per_radio_list = [] + ssid_list = [] + ssid_password_list = [] + ssid_security_list = [] + wifimode_list = [] + ip_var_test.cleanup() + if ( args.no_stations): + pass + else: + ip_var_test.passes() + logg.info("Full test passed, all connections increased rx rate") + ########################################## + # + # Build Results + # + ########################################## + if args.csv_outfile != None: + logg.info("Report CSV Details: {}".format(csv_outfile)) + logg.info("Report CSV Results: {}".format(csv_results)) + report.set_title("Scaling And Performance") + report.build_banner() + report.set_table_title("Scaling And Performance Throughput") + report.build_table_title() + report.set_table_dataframe_from_csv(csv_results) + report.build_table() + report.write_html() + report.write_pdf(_page_size = 'A3', _orientation='Landscape') + + if args.log: + logg.info("output_log: {}".format(outfile_log)) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_tr398_test.py b/lanforge/lanforge-scripts/py-scripts/lf_tr398_test.py new file mode 100755 index 000000000..88fe9b2d9 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_tr398_test.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +This script is used to automate running TR398 tests. You +may need to view a TR398 test configured through the GUI to understand +the options and how best to input data. + + ./lf_tr398_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name tr398-instance --config_name test_con \ + --upstream 1.2.eth2 \ + --test_rig Testbed-01 --pull_report \ + --local_lf_report_dir=/tmp/my_report \ + --dut5 'TR398-DUT ruckus750-5 4c:b1:cd:18:e8:ec (1)' \ + --dut2 'TR398-DUT ruckus750-2 4c:b1:cd:18:e8:e8 (2)' \ + --raw_lines_file example-configs/tr398-ferndale-ac-cfg.txt \ + --set 'Calibrate Attenuators' 0 \ + --set 'Receiver Sensitivity' 0 \ + --set 'Maximum Connection' 1 \ + --set 'Maximum Throughput' 1 \ + --set 'Airtime Fairness' 0 \ + --set 'Range Versus Rate' 0 \ + --set 'Spatial Consistency' 0 \ + --set 'Multiple STAs Performance' 0 \ + --set 'Multiple Assoc Stability' 0 \ + --set 'Downlink MU-MIMO' 0 \ + --set 'AP Coexistence' 0 \ + --set 'Long Term Stability' 0 + +Note: + --raw_line 'line contents' will add any setting to the test config. This is + useful way to support any options not specifically enabled by the + command options. + --set modifications will be applied after the other config has happened, + so it can be used to override any other config. Above, we are disabling many + of the subtests, and enablign just Maximum Connection and Maximum Throughput + tests. + + The RSSI values are calibrated, so you will need to run the calibration step and + call with appropriate values for your particular testbed. This is loaded from + example-configs/tr398-ferndale-ac-cfg.txt in this example. + Contents of that file is a list of raw lines, for instance: + +rssi_0_2-0: -26 +rssi_0_2-1: -26 +rssi_0_2-2: -26 +.... + +Example of raw text config for TR-398, to show other possible options: + +show_events: 1 +show_log: 0 +port_sorting: 0 +kpi_id: TR_398 +notes0: Standard LANforge TR-398 automation setup, DUT is in large chamber CT840a, LANforge test system is in +notes1: smaller CT810a chamber. CT704b and CT714 4-module attenuators are used. Directional antennas +notes2: mounted on the sides of the DUT chamber are used to communicate to the DUT. DUT is facing forward at +notes3: the zero-rotation angle. +bg: 0xE0ECF8 +show_scan: 1 +auto_helper: 1 +skip_2: 0 +skip_5: 0 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +selected_dut5: TR398-DUT ruckus750-5 4c:b1:cd:18:e8:ec (1) +selected_dut2: TR398-DUT ruckus750-2 4c:b1:cd:18:e8:e8 (2) +upstream_port: 1.2.2 eth2 +operator: +mconn: 5 +band2_freq: 2437 +band5_freq: 5180 +tos: 0 +speed: 65% +speed_max_cx_2: 2000000 +speed_max_cx_5: 8000000 +max_tput_speed_2: 100000000 +max_tput_speed_5: 560000000 +rxsens_deg_rot: 45 +rxsens_pre_steps: 8 +stability_udp_dur: 3600 +stability_iter: 288 +calibrate_mode: 4 +calibrate_nss: 1 +dur120: 120 +dur180: 180 +i_5g_80: 195000000 +i_5g_40: 90000000 +i_2g_20: 32000000 +spatial_deg_rot: 30 +spatial_retry: 0 +reset_pp: 99 +rxsens_stop_at_pass: 0 +auto_coex: 1 +rvr_adj: 0 +rssi_2m_2: -20 +rssi_2m_5: -32 +extra_dl_path_loss: 3 +dur60: 60 +turn_table: TR-398 +radio-0: 1.1.2 wiphy0 +radio-1: 1.1.3 wiphy1 +radio-2: 1.1.4 wiphy2 +radio-3: 1.1.5 wiphy3 +radio-4: 1.1.6 wiphy4 +radio-5: 1.1.7 wiphy5 +rssi_0_2-0: -26 +rssi_0_2-1: -26 +rssi_0_2-2: -26 +rssi_0_2-3: -26 +rssi_0_2-4: -27 +rssi_0_2-5: -27 +rssi_0_2-6: -27 +rssi_0_2-7: -27 +rssi_0_2-8: -25 +rssi_0_2-9: -25 +rssi_0_2-10: -25 +rssi_0_2-11: -25 +rssi_0_5-0: -38 +rssi_0_5-1: -38 +rssi_0_5-2: -38 +rssi_0_5-3: -38 +rssi_0_5-4: -38 +rssi_0_5-5: -38 +rssi_0_5-6: -38 +rssi_0_5-7: -38 +rssi_0_5-8: -47 +rssi_0_5-9: -47 +rssi_0_5-10: -47 +rssi_0_5-11: -47 +atten-0: 1.1.85.0 +atten-1: 1.1.85.1 +atten-2: 1.1.85.2 +atten-3: 1.1.85.3 +atten-4: 1.1.1002.0 +atten-5: 1.1.1002.1 +atten-8: 1.1.1002.2 +atten-9: 1.1.1002.3 +atten_cal: 1 +rxsens: 0 +max_cx: 0 +max_tput: 0 +atf: 0 +rvr: 0 +spatial: 0 +multi_sta: 0 +reset: 0 +mu_mimo: 0 +stability: 0 +ap_coex: 0 +""" +import sys +import os +import importlib +import argparse +import time +import json +from os import path + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cvtest = cv_test_manager.cv_test +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser + + +class TR398Test(cvtest): + def __init__(self, + lf_host="localhost", + lf_port=8080, + lf_user="lanforge", + lf_password="lanforge", + instance_name="tr398_instance", + config_name="tr398_config", + upstream="1.2.eth2", + test_rig="", + local_lf_report_dir="", + pull_report=False, + load_old_cfg=False, + raw_lines_file="", + dut5="", + dut2="", + enables=[], + disables=[], + raw_lines=[], + sets=[], + ): + super().__init__(lfclient_host=lf_host, lfclient_port=lf_port) + + self.lf_host = lf_host + self.lf_port = lf_port + self.lf_user = lf_user + self.lf_password =lf_password + self.instance_name = instance_name + self.config_name = config_name + self.dut5 = dut5 + self.dut2 = dut2 + self.raw_lines_file = raw_lines_file + self.upstream = upstream + self.pull_report = pull_report + self.load_old_cfg = load_old_cfg + self.test_name = "TR-398" + self.enables = enables + self.disables = disables + self.raw_lines = raw_lines + self.sets = sets + self.local_lf_report_dir = local_lf_report_dir + self.test_rig = test_rig + + def setup(self): + # Nothing to do at this time. + return + + + def run(self): + self.sync_cv() + time.sleep(2) + self.sync_cv() + + blob_test = "%s-"%(self.test_name) + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + self.show_text_blob(None, None, False) + + # Test related settings + cfg_options = [] + + self.apply_cfg_options(cfg_options, self.enables, self.disables, self.raw_lines, self.raw_lines_file) + + # cmd line args take precedence + if self.upstream != "": + cfg_options.append("upstream_port: " + self.upstream) + if self.dut5 != "": + cfg_options.append("selected_dut5: " + self.dut5) + if self.dut2 != "": + cfg_options.append("selected_dut2: " + self.dut2) + if self.test_rig != "": + cfg_options.append("test_rig: " + self.test_rig) + + # We deleted the scenario earlier, now re-build new one line at a time. + + self.build_cfg(self.config_name, blob_test, cfg_options) + + cv_cmds = [] + self.create_and_run_test(self.load_old_cfg, self.test_name, self.instance_name, + self.config_name, self.sets, + self.pull_report, self.lf_host, self.lf_user, self.lf_password, + cv_cmds, local_lf_report_dir=self.local_lf_report_dir) + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + + +def main(): + + parser = argparse.ArgumentParser( + prog="lf_tr398_test.py", + formatter_class=argparse.RawTextHelpFormatter, + description=""" + Open this file in an editor and read the top notes for more details. + + Example: + + ./lf_tr398_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \\ + --instance_name tr398-instance --config_name test_con \\ + --upstream 1.2.eth2 \\ + --test_rig Testbed-01 --pull_report \\ + --local_lf_report_dir /tmp/my-report \\ + --dut5 'TR398-DUT-r750 ruckus-r750-5g 4c:b1:cd:18:e8:ec (1)' \\ + --dut2 'TR398-DUT-r750 ruckus-r750-2g 4c:b1:cd:18:e8:e8 (2)' \\ + --raw_lines_file example-configs/tr398-ferndale-ac-cfg.txt \\ + --set 'Calibrate Attenuators' 0 \\ + --set 'Receiver Sensitivity' 0 \\ + --set 'Maximum Connection' 1 \\ + --set 'Maximum Throughput' 1 \\ + --set 'Airtime Fairness' 0 \\ + --set 'Range Versus Rate' 0 \\ + --set 'Spatial Consistency' 0 \\ + --set 'Multiple STAs Performance' 0 \\ + --set 'Multiple Assoc Stability' 0 \\ + --set 'Downlink MU-MIMO' 0 \\ + --set 'AP Coexistence' 0 \\ + --set 'Long Term Stability' 0 + + The contents of the 'raw_lines_file' argument can be obtained by manually configuring the + TR398 test in the LANforge GUI, then select 'Show Config' on the Advanced configuration tab, + select that config text, and paste it into a file. That file is the argument to the + --raw_lines_file argument. + + """ + ) + + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for wifi capacity test ex. 1.1.eth2") + + parser.add_argument("--dut2", default="", + help="Specify 2Ghz DUT used by this test, example: 'TR398-DUT-r750 ruckus-r750-2g 4c:b1:cd:18:e8:e8 (2)'") + parser.add_argument("--dut5", default="", + help="Specify 5Ghz DUT used by this test, example: 'TR398-DUT-r750 ruckus-r750-5g 4c:b1:cd:18:e8:ec (1)'") + parser.add_argument("--local_lf_report_dir", + help="--local_lf_report_dir default '' means put in current working directory", + default="") + + args = parser.parse_args() + + cv_base_adjust_parser(args) + + CV_Test = TR398Test(lf_host = args.mgr, + lf_port = args.port, + lf_user = args.lf_user, + lf_password = args.lf_password, + instance_name = args.instance_name, + config_name = args.config_name, + upstream = args.upstream, + pull_report = args.pull_report, + local_lf_report_dir = args.local_lf_report_dir, + load_old_cfg = args.load_old_cfg, + dut2 = args.dut2, + dut5 = args.dut5, + raw_lines_file = args.raw_lines_file, + enables = args.enable, + disables = args.disable, + raw_lines = args.raw_line, + sets = args.set, + test_rig=args.test_rig + ) + CV_Test.setup() + CV_Test.run() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_tr398v2_test.py b/lanforge/lanforge-scripts/py-scripts/lf_tr398v2_test.py new file mode 100755 index 000000000..3be1b939e --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_tr398v2_test.py @@ -0,0 +1,446 @@ +#!/usr/bin/env python3 +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.4 (5.4.4 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.4) + ./lfclient.bash -cli-socket 3990 + +This script is used to automate running TR398v2 tests. You +may need to view a TR398v2 test configured through the GUI to understand +the options and how best to input data. + + ./lf_tr398v2_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name tr398-instance --config_name test_con \ + --upstream 1.2.eth2 \ + --test_rig Testbed-01 --pull_report \ + --local_lf_report_dir=/tmp/my_report \ + --dut5 'TR398-DUT ruckus750-5 4c:b1:cd:18:e8:ec (1)' \ + --dut2 'TR398-DUT ruckus750-2 4c:b1:cd:18:e8:e8 (2)' \ + --raw_lines_file example-configs/tr398v2-ferndale-ac-cfg.txt \ + --set 'Calibrate 802.11AX Attenuators' 0 \ + --set 'Calibrate 802.11AC Attenuators' 0 \ + --set '6.1.1 Receiver Sensitivity' 0 \ + --set '6.2.1 Maximum Connection' 0 \ + --set '6.2.2 Maximum Throughput' 1 \ + --set '6.2.3 Airtime Fairness' 0 \ + --set '6.2.3 Airtime Fairness' 0 \ + --set '6.2.4 Dual-Band Throughput' 0 \ + --set '6.2.5 Bi-Directional Throughput' 0 \ + --set '6.3.1 Range Versus Rate' 0 \ + --set '6.3.2 Spatial Consistency' 0 \ + --set '6.3.3 AX Peak Performance' 0 \ + --set '6.4.1 Multiple STAs Performance' 0 \ + --set '6.4.2 Multiple Assoc Stability' 0 \ + --set '6.4.3 Downlink MU-MIMO' 0 \ + --set '6.5.2 AP Coexistence' 0 \ + --set '6.5.1 Long Term Stability' 0 + +Note: + --raw_line 'line contents' will add any setting to the test config. This is + useful way to support any options not specifically enabled by the + command options. + --set modifications will be applied after the other config has happened, + so it can be used to override any other config. Above, we are disabling many + of the subtests, and enablign just Maximum Connection and Maximum Throughput + tests. + + The RSSI values are calibrated, so you will need to run the calibration step and + call with appropriate values for your particular testbed. This is loaded from + example-configs/tr398v2-ferndale-ac-cfg.txt in this example. + Contents of that file is a list of raw lines, for instance: + +rssi_0_2-0: -26 +rssi_0_2-1: -26 +rssi_0_2-2: -26 +.... + +Example of raw text config for TR-398v2, to show other possible options. You +can configure the TR398v2 test in the LANforge GUI and use the 'Show Config' option +on the 'Advanced Configuration' tab to show this config info: + +show_events: 1 +show_log: 1 +port_sorting: 2 +kpi_id: TR_398v2 +bg: 0xE0ECF8 +dut_info_override: Anonymous Enterprise AX AP +test_rig: +test_tag: +show_scan: 1 +auto_helper: 1 +skip_ac: 0 +skip_ax: 0 +skip_2: 0 +skip_5: 0 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +selected_dut5: TR398-DUT-r750 ruckus-r750-5g 4c:b1:cd:18:e8:ec (1) +selected_dut2: TR398-DUT-r750 ruckus-r750-2g 4c:b1:cd:18:e8:e8 (2) +upstream_port: 1.2.2 eth2 +operator: +mconn: 5 +txpower: 20 +band2_freq: 2437 +band5_freq: 5180 +tos: 0 +speed: 65% +ospeed: 20000 +max_cx_random: 0 +speed_max_cx_adjust: 1000000 +speed_max_cx_2: 2000000 +speed_max_cx_ax_2: 3000000 +speed_max_cx_5: 8000000 +speed_max_cx_ax_5: 10000000 +max_tput_speed_2: 100000000 +max_tput_speed_5: 560000000 +max_tput_speed_ax_2: 200000000 +max_tput_speed_ax_5: 720000000 +max_peak_tput_speed_ax_2: 300000000 +max_peak_tput_speed_ax_5: 1100000000 +max_peak_tput_speed_ax_5_4: 1100000000 +atf_max_nss: 2 +atf_extra_2m_atten: 0 +rxsens_deg_rot: 180 +rxsens_pre_steps: 4 +stability_udp_dur: 900 +stability_iter: 16 +calibrate_mode: 4 +calibrate_nss: 1 +dur120: 30 +dur180: 180 +i_5g_80: 195000000 +i_5g_40: 90000000 +i_2g_20: 32000000 +i_5g_80_ax: 195000000 +i_5g_40_ax: 90000000 +i_2g_20_ax: 32000000 +spatial_deg_rot: 30 +spatial_retry: 0 +reset_pp: 99 +bidir_dp_prcnt: 0.05 +rxsens_stop_at_pass: 0 +spatial_pause_on_zero_tput: 0 +auto_coex: 0 +use_virtual_ax_sta: 0 +rvr_adj: 0 +rssi_2m_2: -26 +rssi_2m_5: -30 +extra_dl_path_loss: 0 +dur60: 20 +turn_table: TR-398 +radio-0: 1.1.2 wiphy0 +radio-1: 1.1.3 wiphy1 +radio-2: 1.1.4 wiphy2 +radio-3: 1.1.5 wiphy3 +radio-4: 1.1.6 wiphy4 +radio-5: 1.1.7 wiphy5 +ax_radio-0: 1.2.wiphy0 +ax_radio-1: 1.2.wiphy1 +ax_radio-2: 1.2.wiphy2 +ax_radio-3: 1.2.wiphy3 +ax_radio-4: 1.2.wiphy4 +ax_radio-5: 1.2.wiphy5 +ax_radio-6: 1.2.wiphy6 +ax_radio-7: 1.2.wiphy7 +ax_radio-8: 1.2.wiphy8 +ax_radio-9: 1.2.wiphy9 +ax_radio-10: 1.2.wiphy10 +ax_radio-11: 1.2.wiphy11 +ax_radio-12: 1.3.wiphy0 +ax_radio-13: 1.3.wiphy5 +ax_radio-14: 1.3.wiphy10 +ax_radio-15: 1.3.wiphy15 +ax_radio-16: 1.3.wiphy1 +ax_radio-17: 1.3.wiphy6 +ax_radio-18: 1.3.wiphy11 +ax_radio-19: 1.3.wiphy16 +ax_radio-20: 1.3.wiphy2 +ax_radio-21: 1.3.wiphy7 +ax_radio-22: 1.3.wiphy12 +ax_radio-23: 1.3.wiphy17 +ax_radio-24: 1.3.wiphy3 +ax_radio-25: 1.3.wiphy8 +ax_radio-26: 1.3.wiphy13 +ax_radio-27: 1.3.wiphy18 +ax_radio-28: 1.3.wiphy4 +ax_radio-29: 1.3.wiphy9 +ax_radio-30: 1.3.wiphy14 +ax_radio-31: 1.3.wiphy19 +rssi_0_2-0: -28 +rssi_0_2-1: -28 +rssi_0_2-2: -28 +rssi_0_2-3: -28 +rssi_0_2-4: -22 +rssi_0_2-5: -22 +rssi_0_2-6: -22 +rssi_0_2-7: -22 +rssi_0_2-8: -25 +rssi_0_2-9: -25 +rssi_0_2-10: -25 +rssi_0_2-11: -25 +ax_rssi_0_2-0: -29 +ax_rssi_0_2-1: -29 +ax_rssi_0_2-2: -29 +ax_rssi_0_2-3: -29 +ax_rssi_0_2-4: -23 +ax_rssi_0_2-5: -23 +ax_rssi_0_2-6: -23 +ax_rssi_0_2-7: -23 +ax_rssi_0_2-8: -26 +ax_rssi_0_2-9: -26 +ax_rssi_0_2-10: -26 +ax_rssi_0_2-11: -26 +rssi_0_5-0: -35 +rssi_0_5-1: -35 +rssi_0_5-2: -35 +rssi_0_5-3: -35 +rssi_0_5-4: -33 +rssi_0_5-5: -33 +rssi_0_5-6: -33 +rssi_0_5-7: -33 +rssi_0_5-8: -39 +rssi_0_5-9: -39 +rssi_0_5-10: -39 +rssi_0_5-11: -39 +ax_rssi_0_5-0: -35 +ax_rssi_0_5-1: -35 +ax_rssi_0_5-2: -35 +ax_rssi_0_5-3: -35 +ax_rssi_0_5-4: -32 +ax_rssi_0_5-5: -32 +ax_rssi_0_5-6: -32 +ax_rssi_0_5-7: -32 +ax_rssi_0_5-8: -39 +ax_rssi_0_5-9: -39 +ax_rssi_0_5-10: -39 +ax_rssi_0_5-11: -39 +atten-0: 1.1.3094.0 +atten-1: 1.1.3094.1 +atten-2: 1.1.3094.2 +atten-3: 1.1.3094.3 +atten-4: 1.1.3102.0 +atten-5: 1.1.3102.1 +atten-6: 1.1.3099.0 +atten-7: 1.1.3099.1 +atten-8: 1.1.3102.2 +atten-9: 1.1.3102.3 +ax_atten-0: 1.1.3100.3 +ax_atten-1: 1.1.3100.2 +ax_atten-2: NA +ax_atten-3: NA +ax_atten-4: 1.1.3100.1 +ax_atten-5: 1.1.3100.0 +ax_atten-8: 1.1.3099.3 +ax_atten-9: 1.1.3099.2 +atten_cal_ac: 0 +atten_cal_ax: 0 +rxsens: 0 +max_cx: 0 +max_tput: 1 +peak_perf: 0 +max_tput_bi: 0 +dual_band_tput: 0 +atf: 0 +atf3: 0 +qos3: 0 +rvr: 0 +spatial: 0 +multi_sta: 0 +reset: 0 +mu_mimo: 0 +stability: 0 +ap_coex: 0 + +""" +import sys +import os +import importlib +import argparse +import time +import json +from os import path + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cvtest = cv_test_manager.cv_test +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser + + +class TR398v2Test(cvtest): + def __init__(self, + lf_host="localhost", + lf_port=8080, + lf_user="lanforge", + lf_password="lanforge", + instance_name="tr398_instance", + config_name="tr398_config", + upstream="1.2.eth2", + test_rig="", + local_lf_report_dir="", + pull_report=False, + load_old_cfg=False, + raw_lines_file="", + dut5="", + dut2="", + enables=[], + disables=[], + raw_lines=[], + sets=[], + ): + super().__init__(lfclient_host=lf_host, lfclient_port=lf_port) + + self.lf_host = lf_host + self.lf_port = lf_port + self.lf_user = lf_user + self.lf_password =lf_password + self.instance_name = instance_name + self.config_name = config_name + self.dut5 = dut5 + self.dut2 = dut2 + self.raw_lines_file = raw_lines_file + self.upstream = upstream + self.pull_report = pull_report + self.load_old_cfg = load_old_cfg + self.test_name = "TR-398 Issue 2" + self.enables = enables + self.disables = disables + self.raw_lines = raw_lines + self.sets = sets + self.local_lf_report_dir = local_lf_report_dir + self.test_rig = test_rig + + def setup(self): + # Nothing to do at this time. + return + + + def run(self): + self.sync_cv() + time.sleep(2) + self.sync_cv() + + blob_test = "TR398v2-"; + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + self.show_text_blob(None, None, False) + + # Test related settings + cfg_options = [] + + self.apply_cfg_options(cfg_options, self.enables, self.disables, self.raw_lines, self.raw_lines_file) + + # cmd line args take precedence + if self.upstream != "": + cfg_options.append("upstream_port: " + self.upstream) + if self.dut5 != "": + cfg_options.append("selected_dut5: " + self.dut5) + if self.dut2 != "": + cfg_options.append("selected_dut2: " + self.dut2) + if self.test_rig != "": + cfg_options.append("test_rig: " + self.test_rig) + + # We deleted the scenario earlier, now re-build new one line at a time. + + self.build_cfg(self.config_name, blob_test, cfg_options) + + cv_cmds = [] + self.create_and_run_test(self.load_old_cfg, self.test_name, self.instance_name, + self.config_name, self.sets, + self.pull_report, self.lf_host, self.lf_user, self.lf_password, + cv_cmds, local_lf_report_dir=self.local_lf_report_dir) + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + + +def main(): + + parser = argparse.ArgumentParser(""" + Open this file in an editor and read the top notes for more details. + + Example: + + ./lf_tr398v2_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \\ + --instance_name tr398-instance --config_name test_con \\ + --upstream 1.2.eth2 \\ + --test_rig Testbed-01 --pull_report \\ + --local_lf_report_dir /tmp/my-report \\ + --dut5 'TR398-DUT-r750 ruckus-r750-5g 4c:b1:cd:18:e8:ec (1)' \\ + --dut2 'TR398-DUT-r750 ruckus-r750-2g 4c:b1:cd:18:e8:e8 (2)' \\ + --raw_lines_file example-configs/tr398v2-ferndale-ac-cfg.txt \\ + --set 'Calibrate 802.11AX Attenuators' 0 \\ + --set 'Calibrate 802.11AC Attenuators' 0 \\ + --set '6.1.1 Receiver Sensitivity' 0 \\ + --set '6.2.1 Maximum Connection' 0 \\ + --set '6.2.2 Maximum Throughput' 1 \\ + --set '6.2.3 Airtime Fairness' 0 \\ + --set '6.2.3 Airtime Fairness' 0 \\ + --set '6.2.4 Dual-Band Throughput' 0 \\ + --set '6.2.5 Bi-Directional Throughput' 0 \\ + --set '6.3.1 Range Versus Rate' 0 \\ + --set '6.3.2 Spatial Consistency' 0 \\ + --set '6.3.3 AX Peak Performance' 0 \\ + --set '6.4.1 Multiple STAs Performance' 0 \\ + --set '6.4.2 Multiple Assoc Stability' 0 \\ + --set '6.4.3 Downlink MU-MIMO' 0 \\ + --set '6.5.2 AP Coexistence' 0 \\ + --set '6.5.1 Long Term Stability' 0 + + The contents of the 'raw_lines_file' argument can be obtained by manually configuring the + TR398 issue 2 test in the LANforge GUI, then select 'Show Config' on the Advanced configuration tab, + select that config text, and paste it into a file. That file is the argument to the + --raw_lines_file argument. + + """ + ) + + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for wifi capacity test ex. 1.1.eth2") + + parser.add_argument("--dut2", default="", + help="Specify 2Ghz DUT used by this test, example: 'TR398-DUT-r750 ruckus-r750-2g 4c:b1:cd:18:e8:e8 (2)'") + parser.add_argument("--dut5", default="", + help="Specify 5Ghz DUT used by this test, example: 'TR398-DUT-r750 ruckus-r750-5g 4c:b1:cd:18:e8:ec (1)'") + parser.add_argument("--local_lf_report_dir", + help="--local_lf_report_dir default '' means put in current working directory", + default="") + + args = parser.parse_args() + + cv_base_adjust_parser(args) + + CV_Test = TR398v2Test(lf_host = args.mgr, + lf_port = args.port, + lf_user = args.lf_user, + lf_password = args.lf_password, + instance_name = args.instance_name, + config_name = args.config_name, + upstream = args.upstream, + pull_report = args.pull_report, + local_lf_report_dir = args.local_lf_report_dir, + load_old_cfg = args.load_old_cfg, + dut2 = args.dut2, + dut5 = args.dut5, + raw_lines_file = args.raw_lines_file, + enables = args.enable, + disables = args.disable, + raw_lines = args.raw_line, + sets = args.set, + test_rig=args.test_rig + ) + CV_Test.setup() + CV_Test.run() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_webpage.py b/lanforge/lanforge-scripts/py-scripts/lf_webpage.py new file mode 100755 index 000000000..79a27a621 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_webpage.py @@ -0,0 +1,802 @@ +""" +This script will create 40 clients on 5Ghz , 2.4Ghz and Both and generate layer4 traffic on LANforge ,The Webpage Download Test is designed to test the performance of the Access Point.The goal is to check whether the +webpage loading time meets the expectation when clients connected on single radio as well as dual radio. + +how to run - +python3 lf_webpage.py --mgr 192.168.200.29 --mgr_port 8080 --upstream_port eth1 --num_stations 10 --security open --ssid testap210 --passwd [BLANK] --target_per_ten 1 --bands 5G --file_size 10MB --fiveg_radio wiphy0 --twog_radio wiphy1 --duration 1 +Copyright 2021 Candela Technologies Inc +04 - April - 2021 +""" +import sys +import os +import importlib +import time +import argparse +import paramiko + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +PortUtils = realm.PortUtils +lf_report = importlib.import_module("py-scripts.lf_report") +lf_graph = importlib.import_module("py-scripts.lf_graph") + + +class HttpDownload(Realm): + def __init__(self, lfclient_host, lfclient_port, upstream, num_sta, security, ssid, password, + target_per_ten, file_size, bands, start_id=0, twog_radio=None, fiveg_radio=None, _debug_on=False, _exit_on_error=False, + _exit_on_fail=False): + self.host = lfclient_host + self.port = lfclient_port + self.upstream = upstream + self.num_sta = num_sta + self.security = security + self.ssid = ssid + self.sta_start_id = start_id + self.password = password + self.twog_radio = twog_radio + self.fiveg_radio = fiveg_radio + self.target_per_ten = target_per_ten + self.file_size = file_size + self.bands = bands + self.debug = _debug_on + print(bands) + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.http_profile = self.local_realm.new_http_profile() + self.http_profile.requests_per_ten = self.target_per_ten + #self.http_profile.url = self.url + self.port_util = PortUtils(self.local_realm) + self.http_profile.debug = _debug_on + self.created_cx = {} + + def set_values(self): + # This method will set values according user input + if self.bands == "5G": + self.radio = [self.fiveg_radio] + elif self.bands == "2.4G": + self.radio = [self.twog_radio] + elif self.bands == "Both": + self.radio = [self.fiveg_radio, self.twog_radio] + print( self.radio) + self.num_sta = self.num_sta // 2 + + def precleanup(self): + self.count = 0 + for rad in self.radio: + print("radio", rad) + if rad == self.fiveg_radio: + # select an mode + self.station_profile.mode = 10 + self.count = self.count + 1 + elif rad == self.twog_radio: + # select an mode + self.station_profile.mode = 6 + self.count = self.count + 1 + + if self.count == 2: + self.sta_start_id = self.num_sta + self.num_sta = 2 * (self.num_sta) + self.station_profile.mode = 10 + self.http_profile.cleanup() + self.station_list1 = LFUtils.portNameSeries(prefix_="sta", start_id_=self.sta_start_id, + end_id_=self.num_sta - 1, padding_number_=10000, + radio=rad) + # cleanup station list which started sta_id 20 + self.station_profile.cleanup(self.station_list1, debug_=self.local_realm.debug) + LFUtils.wait_until_ports_disappear(base_url=self.local_realm.lfclient_url, + port_list=self.station_list, + debug=self.local_realm.debug) + return + # clean dlayer4 ftp traffic + self.http_profile.cleanup() + self.station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=self.sta_start_id, + end_id_=self.num_sta - 1, padding_number_=10000, + radio=rad) + # cleans stations + self.station_profile.cleanup(self.station_list, delay=1, debug_=self.local_realm.debug) + LFUtils.wait_until_ports_disappear(base_url=self.local_realm.lfclient_url, + port_list=self.station_list, + debug=self.local_realm.debug) + time.sleep(1) + print("precleanup done") + + def build(self): + # enable http on ethernet + self.port_util.set_http(port_name=self.local_realm.name_to_eid(self.upstream)[2], resource=1, on=True) + for rad in self.radio: + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=rad, sta_names_=self.station_list, debug=self.local_realm.debug) + self.local_realm.wait_until_ports_appear(sta_list=self.station_list) + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(self.station_list,timeout_sec=60): + self.local_realm._pass("All stations got IPs") + else: + self.local_realm._fail("Stations failed to get IPs") + # building layer4 + self.http_profile.direction = 'dl' + self.http_profile.dest = '/dev/null' + data = self.local_realm.json_get("ports/list?fields=IP") + + # getting eth ip + for i in data["interfaces"]: + for j in i: + if "1.1." + self.upstream == j: + ip_upstream = i["1.1." + self.upstream]['ip'] + + # create http profile + self.http_profile.create(ports=self.station_profile.station_names, sleep_time=.5, + suppress_related_commands_=None, http=True, + http_ip=ip_upstream + "/webpage.html") + if self.count == 2: + self.station_list = self.station_list1 + self.station_profile.mode = 6 + print("Test Build done") + + def start(self): + print("Test Started") + self.http_profile.start_cx() + try: + for i in self.http_profile.created_cx.keys(): + while self.local_realm.json_get("/cx/" + i).get(i).get('state') != 'Run': + continue + except Exception as e: + pass + + def stop(self): + self.http_profile.stop_cx() + + def my_monitor(self): + # data in json format + data = self.local_realm.json_get("layer4/list?fields=uc-avg") + data1 = [] + for i in range(len(data['endpoint'])): + data1.append(str(list(data['endpoint'][i]))[2:-2]) + data2 = [] + for i in range(self.num_sta): + data = self.local_realm.json_get("layer4/list?fields=uc-avg") + # print(type(data['endpoint'][i][data1[i]]['uc-avg'])) + data2.append((data['endpoint'][i][data1[i]]['uc-avg'])) + return data2 + + def monitor_bytes(self): + # data in json format + data = self.local_realm.json_get("layer4/list?fields=bytes-rd") + data1 = [] + for i in range(len(data['endpoint'])): + data1.append(str(list(data['endpoint'][i]))[2:-2]) + data2 = [] + for i in range(self.num_sta): + data = self.local_realm.json_get("layer4/list?fields=bytes-rd") + # print(type(data['endpoint'][i][data1[i]]['uc-avg'])) + data2.append((data['endpoint'][i][data1[i]]['bytes-rd'])) + return data2 + + def monitor_rx(self): + # data in json format + data = self.local_realm.json_get("layer4/list?fields=rx rate") + data1 = [] + for i in range(len(data['endpoint'])): + data1.append(str(list(data['endpoint'][i]))[2:-2]) + data2 = [] + for i in range(self.num_sta): + data = self.local_realm.json_get("layer4/list?fields=rx rate") + # print(type(data['endpoint'][i][data1[i]]['uc-avg'])) + data2.append((data['endpoint'][i][data1[i]]['rx rate'])) + return data2 + + def postcleanup(self): + self.http_profile.cleanup() + self.station_profile.cleanup() + LFUtils.wait_until_ports_disappear(base_url=self.local_realm.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + + def file_create(self,ssh_port): + ip = self.host + user = "root" + pswd = "lanforge" + port = ssh_port + ssh = paramiko.SSHClient() # creating shh client object we use this object to connect to router + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # automatically adds the missing host key + ssh.connect(ip, port=port, username=user, password=pswd, banner_timeout=600) + cmd = '[ -f /usr/local/lanforge/nginx/html/webpage.html ] && echo "True" || echo "False"' + stdin, stdout, stderr = ssh.exec_command(str(cmd)) + output = stdout.readlines() + print(output) + if output == ["True\n"]: + cmd1 = "rm /usr/local/lanforge/nginx/html/webpage.html" + stdin, stdout, stderr = ssh.exec_command(str(cmd1)) + output = stdout.readlines() + time.sleep(10) + cmd2 = "sudo fallocate -l " + self.file_size + " /usr/local/lanforge/nginx/html/webpage.html" + stdin, stdout, stderr = ssh.exec_command(str(cmd2)) + print("File creation done", self.file_size) + output = stdout.readlines() + else: + cmd2 = "sudo fallocate -l " + self.file_size + " /usr/local/lanforge/nginx/html/webpage.html" + stdin, stdout, stderr = ssh.exec_command(str(cmd2)) + print("File creation done", self.file_size) + output = stdout.readlines() + ssh.close() + time.sleep(1) + return output + + def download_time_in_sec(self,result_data): + self.resullt_data = result_data + download_time = dict.fromkeys(result_data.keys()) + for i in download_time: + try: + download_time[i] = result_data[i]['dl_time'] + except: + download_time[i] = [] + print(download_time) + lst = [] + lst1 = [] + lst2 = [] + dwnld_time = dict.fromkeys(result_data.keys()) + dataset = [] + for i in download_time: + if i == "5G": + for j in download_time[i]: + x = (j / 1000) + y = round(x, 1) + lst.append(y) + print(lst) + dwnld_time["5G"] = lst + dataset.append(dwnld_time["5G"]) + if i == "2.4G": + for j in download_time[i]: + x = (j / 1000) + y = round(x, 1) + lst1.append(y) + print(lst) + dwnld_time["2.4G"] = lst1 + dataset.append(dwnld_time["2.4G"]) + if i == "Both": + # print("yes", download_time[i]) + for j in download_time[i]: + x = (j / 1000) + y = round(x, 1) + lst2.append(y) + # print(lst2) + dwnld_time["Both"] = lst2 + dataset.append(dwnld_time["Both"]) + return dataset + + def speed_in_Mbps(self,result_data): + self.resullt_data = result_data + speed = dict.fromkeys(result_data.keys()) + for i in speed: + try: + speed[i] = result_data[i]['speed'] + except: + speed[i] = [] + print(speed) + lst = [] + lst1 = [] + lst2 = [] + speed_ = dict.fromkeys(result_data.keys()) + dataset = [] + for i in speed: + if i == "5G": + for j in speed[i]: + x = (j / 1000000) + y = round(x, 1) + lst.append(y) + print("hi", lst) + speed_["5G"] = lst + dataset.append(speed_["5G"]) + if i == "2.4G": + for j in speed[i]: + x = (j / 1000000) + y = round(x, 1) + lst1.append(y) + print("yes", lst1) + speed_["2.4G"] = lst1 + dataset.append(speed_["2.4G"]) + if i == "Both": + # print("yes", speed[i]) + for j in speed[i]: + x = (j / 1000000) + y = round(x, 1) + lst2.append(y) + print(lst2) + speed_["Both"] = lst2 + dataset.append(speed_["Both"]) + return dataset + + def summary_calculation(self, result_data, bands, threshold_5g, threshold_2g, threshold_both): + self.resullt_data = result_data + x1 = [] + html_struct = dict.fromkeys(list(result_data.keys())) + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["avg"] + print(fcc_type) + for i in fcc_type: + x1.append(i) + # print(x) + y11 = [] + for i in x1: + i = i / 1000 + y11.append(i) + # print(y) + z11 = [] + for i in y11: + i = str(round(i, 1)) + z11.append(i) + print(z11) + pass_fail_list = [] + # print(list(result_data.keys())) + sumry2 = [] + sumry5 = [] + sumryB = [] + data = [] + if bands == ["5G", "2.4G", "Both"]: + print("yes") + # 5G + if float(z11[0]) == 0.0 or float(z11[0]) > float(threshold_5g): + print("FAIL") + pass_fail_list.append("FAIL") + sumry5.append("FAIL") + elif float(z11[0]) < float(threshold_5g): + print("PASS") + pass_fail_list.append("PASS") + sumry5.append("PASS") + # 2.4g + if float(z11[1]) == 0.0 or float(z11[1]) > float(threshold_2g): + var = "FAIL" + pass_fail_list.append(var) + sumry2.append("FAIL") + elif float(z11[1]) < float(threshold_2g): + pass_fail_list.append("PASS") + sumry2.append("PASS") + # BOTH + if float(z11[2]) == 0.0 or float(z11[2]) > float(threshold_both) : + var = "FAIL" + pass_fail_list.append(var) + sumryB.append("FAIL") + elif float(z11[2]) < float(threshold_both): + pass_fail_list.append("PASS") + sumryB.append("PASS") + + data.append(','.join(sumry5)) + data.append(','.join(sumry2)) + data.append(','.join(sumryB)) + + elif bands == ['5G']: + if float(z11[0]) == 0.0 or float(z11[0]) > float(threshold_5g): + print("FAIL") + pass_fail_list.append("FAIL") + sumry5.append("FAIL") + elif float(z11[0]) < float(threshold_5g): + print("PASS") + pass_fail_list.append("PASS") + sumry5.append("PASS") + data.append(','.join(sumry5)) + + elif bands == ['2.4G']: + if float(z11[0]) == 0.0 or float(z11[0]) > float(threshold_2g): + var = "FAIL" + pass_fail_list.append(var) + sumry2.append("FAIL") + elif float(z11[0]) < float(threshold_2g): + pass_fail_list.append("PASS") + sumry2.append("PASS") + data.append(','.join(sumry2)) + elif bands == ["Both"]: + if float(z11[0]) == 0.0 or float(z11[0]) > float(threshold_both): + var = "FAIL" + pass_fail_list.append(var) + sumryB.append("FAIL") + elif float(z11[0]) < float(threshold_both): + pass_fail_list.append("PASS") + sumryB.append("PASS") + data.append(','.join(sumryB)) + elif bands == ['5G', '2.4G']: + if float(z11[0]) == 0.0 or float(z11[0]) > float(threshold_5g): + print("FAIL") + pass_fail_list.append("FAIL") + sumry5.append("FAIL") + elif float(z11[0]) < float(threshold_5g): + print("PASS") + pass_fail_list.append("PASS") + sumry5.append("PASS") + # 2.4g + if float(z11[1]) == 0.0 or float(z11[1]) > float(threshold_2g): + var = "FAIL" + pass_fail_list.append(var) + sumry2.append("FAIL") + elif float(z11[1]) < float(threshold_2g): + pass_fail_list.append("PASS") + sumry2.append("PASS") + data.append(','.join(sumry5)) + data.append(','.join(sumry2)) + + elif bands == ['5G', 'Both']: + if float(z11[0]) == 0.0 or float(z11[0]) > float(threshold_5g): + print("FAIL") + pass_fail_list.append("FAIL") + sumry5.append("FAIL") + elif float(z11[0]) < float(threshold_5g): + print("PASS") + pass_fail_list.append("PASS") + sumry5.append("PASS") + if float(z11[1]) == 0.0 or float(z11[1]) > float(threshold_both): + var = "FAIL" + pass_fail_list.append(var) + sumryB.append("FAIL") + elif float(z11[1]) < float(threshold_both): + pass_fail_list.append("PASS") + sumryB.append("PASS") + + data.append(','.join(sumry5)) + data.append(','.join(sumryB)) + + elif bands == ['2.4G', 'Both']: + if float(z11[0]) == 0.0 or float(z11[0]) > float(threshold_2g): + var = "FAIL" + pass_fail_list.append(var) + sumry2.append("FAIL") + elif float(z11[0]) < float(threshold_2g): + pass_fail_list.append("PASS") + sumry2.append("PASS") + if float(z11[1]) == 0.0 or float(z11[1]) > float(threshold_both): + var = "FAIL" + pass_fail_list.append(var) + sumryB.append("FAIL") + elif float(z11[1]) < float(threshold_both): + pass_fail_list.append("PASS") + sumryB.append("PASS") + data.append(','.join(sumry2)) + data.append(','.join(sumryB)) + return data + + def check_station_ip(self): + pass + + def generate_graph(self, dataset, lis, bands): + graph = lf_bar_graph(_data_set=dataset, _xaxis_name="Stations", _yaxis_name="Time in Seconds", + _xaxis_categories=lis, _label=bands, _xticks_font=8, + _graph_image_name="webpage download time graph", + _color=['forestgreen', 'darkorange', 'blueviolet'], _color_edge='black', _figsize=(14, 5), + _grp_title="Download time taken by each client", _xaxis_step=1, _show_bar_value=True, + _text_font=6, _text_rotation=60, + _legend_loc="upper right", + _legend_box=(1, 1.15), + _enable_csv=True + ) + graph_png = graph.build_bar_graph() + print("graph name {}".format(graph_png)) + return graph_png + + def graph_2(self,dataset2, lis, bands): + graph_2 = lf_bar_graph(_data_set=dataset2, _xaxis_name="Stations", _yaxis_name="Download Rate in Mbps", + _xaxis_categories=lis, _label=bands, _xticks_font=8, + _graph_image_name="webpage_speed_graph", + _color=['forestgreen', 'darkorange', 'blueviolet'], _color_edge='black', + _figsize=(14, 5), + _grp_title="Download rate for each client (Mbps)", _xaxis_step=1, _show_bar_value=True, + _text_font=6, _text_rotation=60, + _legend_loc="upper right", + _legend_box=(1, 1.15), + _enable_csv=True + ) + graph_png = graph_2.build_bar_graph() + return graph_png + + def generate_report(self,date, num_stations,duration, test_setup_info,dataset,lis,bands,threshold_2g,threshold_5g,threshold_both,dataset2,summary_table_value,result_data,test_input_infor): + report = lf_report(_results_dir_name="webpage_test", _output_html="Webpage.html", _output_pdf="Webpage.pdf") + report.set_title("WEBPAGE DOWNLOAD TEST") + report.set_date(date) + report.build_banner() + report.set_table_title("Test Setup Information") + report.build_table_title() + + report.test_setup_table(value="Device under test", test_setup_data=test_setup_info) + + report.set_obj_html("Objective", + "The Webpage Download Test is designed to test the performance of the Access Point.The goal is to check whether the webpage loading time of all the " + str( + num_stations) + " clients which are downloading at the same time meets the expectation when clients connected on single radio as well as dual radio") + report.build_objective() + report.set_obj_html("Download Time Graph", + "The below graph provides information about the download time taken by each client to download webpage for test duration of " + str( + duration) + " min") + report.build_objective() + graph = self.generate_graph(dataset=dataset, lis=lis, bands=bands) + report.set_graph_image(graph) + report.set_csv_filename(graph) + report.move_csv_file() + report.move_graph_image() + report.build_graph() + report.set_obj_html("Download Rate Graph", + "The below graph provides information about the download rate in Mbps of each client to download the webpage for test duration of " + str( + duration) + " min") + report.build_objective() + graph2 = self.graph_2(dataset2, lis=lis, bands=bands) + print("graph name {}".format(graph2)) + report.set_graph_image(graph2) + report.set_csv_filename(graph2) + report.move_csv_file() + report.move_graph_image() + report.build_graph() + report.set_obj_html("Summary Table Description", + "This Table shows you the summary result of Webpage Download Test as PASS or FAIL criteria. If the average time taken by " + str( + num_stations) + " clients to access the webpage is less than " + str( + threshold_2g) + "s it's a PASS criteria for 2.4 ghz clients, If the average time taken by " + "" + str( + num_stations) + " clients to access the webpage is less than " + str( + threshold_5g) + "s it's a PASS criteria for 5 ghz clients and If the average time taken by " + str( + num_stations) + " clients to access the webpage is less than " + str( + threshold_both) + "s it's a PASS criteria for 2.4 ghz and 5ghz clients") + + report.build_objective() + test_setup1 = pd.DataFrame(summary_table_value) + report.set_table_dataframe(test_setup1) + report.build_table() + + report.set_obj_html("Download Time Table Description", + "This Table will provide you information of the minimum, maximum and the average time taken by clients to download a webpage in seconds") + + report.build_objective() + x = [] + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["min"] + # print(fcc_type) + for i in fcc_type: + x.append(i) + # print(x) + y = [] + for i in x: + i = i / 1000 + y.append(i) + z = [] + for i in y: + i = str(round(i, 1)) + z.append(i) + # rint(z) + x1 = [] + + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["max"] + # print(fcc_type) + for i in fcc_type: + x1.append(i) + # print(x1) + y1 = [] + for i in x1: + i = i / 1000 + y1.append(i) + z1 = [] + for i in y1: + i = str(round(i, 1)) + z1.append(i) + # print(z1) + x2 = [] + + for fcc in list(result_data.keys()): + fcc_type = result_data[fcc]["avg"] + # print(fcc_type) + for i in fcc_type: + x2.append(i) + # print(x2) + y2 = [] + for i in x2: + i = i / 1000 + y2.append(i) + z2 = [] + for i in y2: + i = str(round(i, 1)) + z2.append(i) + + download_table_value = { + "": bands, + "Minimum": z, + "Maximum": z1, + "Average": z2 + + } + test_setup = pd.DataFrame(download_table_value) + report.set_table_dataframe(test_setup) + report.build_table() + report.set_table_title("Test input Information") + report.build_table_title() + report.test_setup_table(value="Information", test_setup_data=test_input_infor) + report.build_footer() + html_file = report.write_html() + print("returned file {}".format(html_file)) + print(html_file) + report.write_pdf() + +def main(): + parser = argparse.ArgumentParser( + prog="lf_webpage.py", + formatter_class=argparse.RawTextHelpFormatter, + description="lanforge webpage download Test Script") + parser.add_argument('--mgr', help='hostname for where LANforge GUI is running', default='localhost') + parser.add_argument('--mgr_port', help='port LANforge GUI HTTP service is running on', default=8080) + parser.add_argument('--upstream_port', help='non-station port that generates traffic: eg: eth1', default='eth2') + parser.add_argument('--num_stations', type=int, help='number of stations to create', default=1) + parser.add_argument('--twog_radio', help='specify radio for 2.4G clients', default='wiphy3') + parser.add_argument('--fiveg_radio', help='specify radio for 5 GHz client', default='wiphy0') + parser.add_argument('--security', help='WiFi Security protocol: {open|wep|wpa2|wpa3') + parser.add_argument('--ssid', help='WiFi SSID for script object to associate to') + parser.add_argument('--passwd', help='WiFi passphrase/password/key') + parser.add_argument('--target_per_ten', help='number of request per 10 minutes', default=100) + parser.add_argument('--file_size', type=str, help='specify the size of file you want to download', default='5MB') + parser.add_argument('--bands', nargs="+", help='specify which band testing you want to run eg 5G OR 2.4G OR 5G 2.4G', default=["5G", "2.4G", "Both"]) + parser.add_argument('--duration', type=int, help='time to run traffic') + parser.add_argument('--threshold_5g',help="Enter the threshold value for 5G Pass/Fail criteria", default="60") + parser.add_argument('--threshold_2g',help="Enter the threshold value for 2.4G Pass/Fail criteria",default="90") + parser.add_argument('--threshold_both',help="Enter the threshold value for Both Pass/Fail criteria" , default="50") + parser.add_argument('--ap_name', help="specify the ap model ", default="TestAP") + parser.add_argument('--ssh_port', type=int, help="specify the shh port eg 22",default=22) + + args = parser.parse_args() + test_time = datetime.datetime.now() + test_time = test_time.strftime("%b %d %H:%M:%S") + print("Test started at ", test_time) + list5G = [] + list5G_bytes = [] + list5G_speed = [] + list2G = [] + list2G_bytes = [] + list2G_speed = [] + Both = [] + Both_bytes =[] + Both_speed =[] + dict_keys = [] + dict_keys.extend(args.bands) + # print(dict_keys) + final_dict = dict.fromkeys(dict_keys) + # print(final_dict) + dict1_keys = ['dl_time', 'min', 'max', 'avg','bytes_rd', 'speed'] + for i in final_dict: + final_dict[i] = dict.fromkeys(dict1_keys) + print(final_dict) + min5 = [] + min2 = [] + min_both = [] + max5 = [] + max2 = [] + max_both = [] + avg2 = [] + avg5 = [] + avg_both = [] + + for bands in args.bands: + http = HttpDownload(lfclient_host=args.mgr, lfclient_port=args.mgr_port, + upstream=args.upstream_port, num_sta=args.num_stations, + security=args.security, + ssid=args.ssid, password=args.passwd, + target_per_ten=args.target_per_ten, + file_size=args.file_size, bands=bands, + twog_radio=args.twog_radio, + fiveg_radio=args.fiveg_radio + ) + http.file_create(ssh_port=args.ssh_port) + http.set_values() + http.precleanup() + http.build() + http.start() + duration = args.duration + duration = 60 * duration + print("time in seconds ", duration) + time.sleep(duration) + http.stop() + value = http.my_monitor() + value2 = http.monitor_bytes() + value3 = http.monitor_rx() + http.postcleanup() + + if bands == "5G": + print("yes") + list5G.extend(value) + list5G_bytes.extend(value2) + list5G_speed.extend(value3) + print(list5G) + print(list5G_bytes) + print(list5G_speed) + final_dict['5G']['dl_time'] = list5G + + min5.append(min(list5G)) + final_dict['5G']['min'] = min5 + max5.append(max(list5G)) + final_dict['5G']['max'] = max5 + avg5.append((sum(list5G) / args.num_stations)) + final_dict['5G']['avg'] = avg5 + final_dict['5G']['bytes_rd'] = list5G_bytes + final_dict['5G']['speed'] = list5G_speed + elif bands == "2.4G": + print("no") + list2G.extend(value) + list2G_bytes.extend(value2) + list2G_speed.extend(value3) + print(list2G) + print(list2G_bytes) + print(list2G_speed) + final_dict['2.4G']['dl_time'] = list2G + min2.append(min(list2G)) + final_dict['2.4G']['min'] = min2 + max2.append(max(list2G)) + final_dict['2.4G']['max'] = max2 + avg2.append((sum(list2G) / args.num_stations)) + final_dict['2.4G']['avg'] = avg2 + final_dict['2.4G']['bytes_rd'] = list2G_bytes + final_dict['2.4G']['speed'] = list2G_speed + elif bands == "Both": + Both.extend(value) + Both_bytes.extend(value2) + Both_speed.extend(value3) + final_dict['Both']['dl_time'] = Both + min_both.append(min(Both)) + final_dict['Both']['min'] = min_both + max_both.append(max(Both)) + final_dict['Both']['max'] = max_both + avg_both.append((sum(Both) /args.num_stations)) + final_dict['Both']['avg'] = avg_both + final_dict['Both']['bytes_rd'] = Both_bytes + final_dict['Both']['speed'] = Both_speed + + result_data = final_dict + print("result", result_data) + print("Test Finished") + test_end = datetime.datetime.now() + test_end = test_end.strftime("%b %d %H:%M:%S") + print("Test ended at ", test_end) + s1 = test_time + s2 = test_end # for example + FMT = '%b %d %H:%M:%S' + test_duration = datetime.datetime.strptime(s2, FMT) - datetime.datetime.strptime(s1, FMT) + + print("total test duration ", test_duration) + date = str(datetime.datetime.now()).split(",")[0].replace(" ", "-").split(".")[0] + test_setup_info = { + "DUT Name": args.ap_name, + "SSID": args.ssid, + "Test Duration": test_duration, + } + test_input_infor={ + "LANforge ip":args.mgr, + "File Size" : args.file_size, + "Bands" : args.bands, + "Upstream": args.upstream_port, + "Stations": args.num_stations, + "SSID" :args.ssid, + "Security": args.security, + "Duration" : args.duration, + "Contact": "support@candelatech.com" + } + http1 = HttpDownload(lfclient_host=args.mgr, lfclient_port=args.mgr_port, + upstream=args.upstream_port, num_sta=args.num_stations, + security=args.security, + ssid=args.ssid, password=args.passwd, + target_per_ten=args.target_per_ten, + file_size=args.file_size, bands=args.bands, + twog_radio=args.twog_radio, + fiveg_radio=args.fiveg_radio) + dataset = http1.download_time_in_sec(result_data=result_data) + lis = [] + for i in range(1, args.num_stations + 1): + lis.append(i) + + dataset2= http1.speed_in_Mbps(result_data=result_data) + data = http1.summary_calculation(result_data=result_data, bands=args.bands, threshold_5g=args.threshold_5g , threshold_2g= args.threshold_2g, threshold_both=args.threshold_both) + + summary_table_value = { + "": args.bands, + "PASS/FAIL": data + } + http1.generate_report(date, num_stations=args.num_stations, + duration=args.duration, test_setup_info=test_setup_info, dataset=dataset, lis=lis, + bands=args.bands, threshold_2g=args.threshold_2g, threshold_5g=args.threshold_5g, + threshold_both=args.threshold_both, dataset2=dataset2, + summary_table_value=summary_table_value, result_data=result_data, test_input_infor=test_input_infor) + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/lf_wifi_capacity_test.py b/lanforge/lanforge-scripts/py-scripts/lf_wifi_capacity_test.py new file mode 100755 index 000000000..b457e4a8a --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/lf_wifi_capacity_test.py @@ -0,0 +1,582 @@ +#!/usr/bin/env python3 +""" +Note: To Run this script gui should be opened with + + path: cd LANforgeGUI_5.4.3 (5.4.3 can be changed with GUI version) + pwd (Output : /home/lanforge/LANforgeGUI_5.4.3) + ./lfclient.bash -cli-socket 3990 + +Note: This is a test file which will run a wifi capacity test. + ex. on how to run this script (if stations are available in lanforge): + The influx part can be skipped if you are not using influx/graphana. + + ./lf_wifi_capacity_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name this_inst --config_name test_con --upstream 1.1.eth2 --batch_size 1,5,25,50,100 --loop_iter 1 \ + --protocol UDP-IPv4 --duration 6000 --pull_report \ + --test_rig Testbed-01 \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01 + + ex. on how to run this script (to create new stations): + ./lf_wifi_capacity_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name wct_instance --config_name wifi_config --upstream 1.1.eth1 --batch_size 1,5,25 --loop_iter 1 \ + --protocol UDP-IPv4 --duration 6000 --pull_report --stations 1.1.sta0000,1.1.sta0001 \ + --create_stations --radio wiphy0 --ssid test-ssid --security open --paswd [BLANK] \ + --test_rig Testbed-01 --set DUT_NAME linksys-8450 + + +Note: + --pull_report == If specified, this will pull reports from lanforge to your code directory, + from where you are running this code + + --stations == Enter stations to use for wifi capacity + + --set DUT_NAME XXXX == Determines which DUT the wifi capacity test should use to get details on + +Example of raw text config for Capacity, to show other possible options: + +sel_port-0: 1.1.eth1 +sel_port-1: 1.1.sta00000 +sel_port-2: 1.1.sta00001 +sel_port-3: 1.1.sta00002 +sel_port-4: 1.1.sta00003 +sel_port-5: 1.1.sta00004 +sel_port-6: 1.1.sta00005 +sel_port-7: 1.1.sta00006 +sel_port-8: 1.1.sta00007 +sel_port-9: 1.1.sta00008 +sel_port-10: 1.1.sta00009 +sel_port-11: 1.1.sta00010 +sel_port-12: 1.1.sta00011 +sel_port-13: 1.1.sta00012 +sel_port-14: 1.1.sta00013 +sel_port-15: 1.1.sta00014 +sel_port-16: 1.1.sta00015 +sel_port-17: 1.1.sta00016 +sel_port-18: 1.1.sta00017 +sel_port-19: 1.1.sta00018 +sel_port-20: 1.1.sta00019 +sel_port-21: 1.1.sta00020 +sel_port-22: 1.1.sta00021 +sel_port-23: 1.1.sta00022 +sel_port-24: 1.1.sta00023 +sel_port-25: 1.1.sta00024 +sel_port-26: 1.1.sta00025 +sel_port-27: 1.1.sta00026 +sel_port-28: 1.1.sta00027 +sel_port-29: 1.1.sta00028 +sel_port-30: 1.1.sta00029 +sel_port-31: 1.1.sta00030 +sel_port-32: 1.1.sta00031 +sel_port-33: 1.1.sta00032 +sel_port-34: 1.1.sta00033 +sel_port-35: 1.1.sta00034 +sel_port-36: 1.1.sta00035 +sel_port-37: 1.1.sta00036 +sel_port-38: 1.1.sta00037 +sel_port-39: 1.1.sta00038 +sel_port-40: 1.1.sta00039 +sel_port-41: 1.1.sta00040 +sel_port-42: 1.1.sta00041 +sel_port-43: 1.1.sta00042 +sel_port-44: 1.1.sta00043 +sel_port-45: 1.1.sta00044 +sel_port-46: 1.1.sta00045 +sel_port-47: 1.1.sta00046 +sel_port-48: 1.1.sta00047 +sel_port-49: 1.1.sta00048 +sel_port-50: 1.1.sta00049 +sel_port-51: 1.1.sta00500 +sel_port-52: 1.1.sta00501 +sel_port-53: 1.1.sta00502 +sel_port-54: 1.1.sta00503 +sel_port-55: 1.1.sta00504 +sel_port-56: 1.1.sta00505 +sel_port-57: 1.1.sta00506 +sel_port-58: 1.1.sta00507 +sel_port-59: 1.1.sta00508 +sel_port-60: 1.1.sta00509 +sel_port-61: 1.1.sta00510 +sel_port-62: 1.1.sta00511 +sel_port-63: 1.1.sta00512 +sel_port-64: 1.1.sta00513 +sel_port-65: 1.1.sta00514 +sel_port-66: 1.1.sta00515 +sel_port-67: 1.1.sta00516 +sel_port-68: 1.1.sta00517 +sel_port-69: 1.1.sta00518 +sel_port-70: 1.1.sta00519 +sel_port-71: 1.1.sta00520 +sel_port-72: 1.1.sta00521 +sel_port-73: 1.1.sta00522 +sel_port-74: 1.1.sta00523 +sel_port-75: 1.1.sta00524 +sel_port-76: 1.1.sta00525 +sel_port-77: 1.1.sta00526 +sel_port-78: 1.1.sta00527 +sel_port-79: 1.1.sta00528 +sel_port-80: 1.1.sta00529 +sel_port-81: 1.1.sta00530 +sel_port-82: 1.1.sta00531 +sel_port-83: 1.1.sta00532 +sel_port-84: 1.1.sta00533 +sel_port-85: 1.1.sta00534 +sel_port-86: 1.1.sta00535 +sel_port-87: 1.1.sta00536 +sel_port-88: 1.1.sta00537 +sel_port-89: 1.1.sta00538 +sel_port-90: 1.1.sta00539 +sel_port-91: 1.1.sta00540 +sel_port-92: 1.1.sta00541 +sel_port-93: 1.1.sta00542 +sel_port-94: 1.1.sta00543 +sel_port-95: 1.1.sta00544 +sel_port-96: 1.1.sta00545 +sel_port-97: 1.1.sta00546 +sel_port-98: 1.1.sta00547 +sel_port-99: 1.1.sta00548 +sel_port-100: 1.1.sta00549 +sel_port-101: 1.1.sta01000 +sel_port-102: 1.1.sta01001 +sel_port-103: 1.1.sta01002 +sel_port-104: 1.1.sta01003 +sel_port-105: 1.1.sta01004 +sel_port-106: 1.1.sta01005 +sel_port-107: 1.1.sta01006 +sel_port-108: 1.1.sta01007 +sel_port-109: 1.1.sta01008 +sel_port-110: 1.1.sta01009 +sel_port-111: 1.1.sta01010 +sel_port-112: 1.1.sta01011 +sel_port-113: 1.1.sta01012 +sel_port-114: 1.1.sta01013 +sel_port-115: 1.1.sta01014 +sel_port-116: 1.1.sta01015 +sel_port-117: 1.1.sta01016 +sel_port-118: 1.1.sta01017 +sel_port-119: 1.1.sta01018 +sel_port-120: 1.1.sta01019 +sel_port-121: 1.1.sta01020 +sel_port-122: 1.1.sta01021 +sel_port-123: 1.1.sta01022 +sel_port-124: 1.1.sta01023 +sel_port-125: 1.1.sta01024 +sel_port-126: 1.1.sta01025 +sel_port-127: 1.1.sta01026 +sel_port-128: 1.1.sta01027 +sel_port-129: 1.1.sta01028 +sel_port-130: 1.1.sta01029 +sel_port-131: 1.1.sta01030 +sel_port-132: 1.1.sta01031 +sel_port-133: 1.1.sta01032 +sel_port-134: 1.1.sta01033 +sel_port-135: 1.1.sta01034 +sel_port-136: 1.1.sta01035 +sel_port-137: 1.1.sta01036 +sel_port-138: 1.1.sta01037 +sel_port-139: 1.1.sta01038 +sel_port-140: 1.1.sta01039 +sel_port-141: 1.1.sta01040 +sel_port-142: 1.1.sta01041 +sel_port-143: 1.1.sta01042 +sel_port-144: 1.1.sta01043 +sel_port-145: 1.1.sta01044 +sel_port-146: 1.1.sta01045 +sel_port-147: 1.1.sta01046 +sel_port-148: 1.1.sta01047 +sel_port-149: 1.1.sta01048 +sel_port-150: 1.1.sta01049 +sel_port-151: 1.1.sta01500 +sel_port-152: 1.1.sta01501 +sel_port-153: 1.1.sta01502 +sel_port-154: 1.1.sta01503 +sel_port-155: 1.1.sta01504 +sel_port-156: 1.1.sta01505 +sel_port-157: 1.1.sta01506 +sel_port-158: 1.1.sta01507 +sel_port-159: 1.1.sta01508 +sel_port-160: 1.1.sta01509 +sel_port-161: 1.1.sta01510 +sel_port-162: 1.1.sta01511 +sel_port-163: 1.1.sta01512 +sel_port-164: 1.1.sta01513 +sel_port-165: 1.1.sta01514 +sel_port-166: 1.1.sta01515 +sel_port-167: 1.1.sta01516 +sel_port-168: 1.1.sta01517 +sel_port-169: 1.1.sta01518 +sel_port-170: 1.1.sta01519 +sel_port-171: 1.1.sta01520 +sel_port-172: 1.1.sta01521 +sel_port-173: 1.1.sta01522 +sel_port-174: 1.1.sta01523 +sel_port-175: 1.1.sta01524 +sel_port-176: 1.1.sta01525 +sel_port-177: 1.1.sta01526 +sel_port-178: 1.1.sta01527 +sel_port-179: 1.1.sta01528 +sel_port-180: 1.1.sta01529 +sel_port-181: 1.1.sta01530 +sel_port-182: 1.1.sta01531 +sel_port-183: 1.1.sta01532 +sel_port-184: 1.1.sta01533 +sel_port-185: 1.1.sta01534 +sel_port-186: 1.1.sta01535 +sel_port-187: 1.1.sta01536 +sel_port-188: 1.1.sta01537 +sel_port-189: 1.1.sta01538 +sel_port-190: 1.1.sta01539 +sel_port-191: 1.1.sta01540 +sel_port-192: 1.1.sta01541 +sel_port-193: 1.1.sta01542 +sel_port-194: 1.1.sta01543 +sel_port-195: 1.1.sta01544 +sel_port-196: 1.1.sta01545 +sel_port-197: 1.1.wlan4 +sel_port-198: 1.1.wlan5 +sel_port-199: 1.1.wlan6 +sel_port-200: 1.1.wlan7 +show_events: 1 +show_log: 0 +port_sorting: 0 +kpi_id: WiFi Capacity +bg: 0xE0ECF8 +test_rig: +show_scan: 1 +auto_helper: 1 +skip_2: 0 +skip_5: 0 +skip_5b: 1 +skip_dual: 0 +skip_tri: 1 +batch_size: 1 +loop_iter: 1 +duration: 6000 +test_groups: 0 +test_groups_subset: 0 +protocol: UDP-IPv4 +dl_rate_sel: Total Download Rate: +dl_rate: 1000000000 +ul_rate_sel: Total Upload Rate: +ul_rate: 10000000 +prcnt_tcp: 100000 +l4_endp: +pdu_sz: -1 +mss_sel: 1 +sock_buffer: 0 +ip_tos: 0 +multi_conn: -1 +min_speed: -1 +ps_interval: 60-second Running Average +fairness: 0 +naptime: 0 +before_clear: 5000 +rpt_timer: 1000 +try_lower: 0 +rnd_rate: 1 +leave_ports_up: 0 +down_quiesce: 0 +udp_nat: 1 +record_other_ssids: 0 +clear_reset_counters: 0 +do_pf: 0 +pf_min_period_dl: 0 +pf_min_period_ul: 0 +pf_max_reconnects: 0 +use_mix_pdu: 0 +pdu_prcnt_pps: 1 +pdu_prcnt_bps: 0 +pdu_mix_ln-0: +show_scan: 1 +show_golden_3p: 0 +save_csv: 0 +show_realtime: 1 +show_pie: 1 +show_per_loop_totals: 1 +show_cx_time: 1 +show_dhcp: 1 +show_anqp: 1 +show_4way: 1 +show_latency: 1 + +""" +import sys +import os +import importlib +import argparse +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +cv_test_manager = importlib.import_module("py-json.cv_test_manager") +cv_test = cv_test_manager.cv_test +cv_add_base_parser = cv_test_manager.cv_add_base_parser +cv_base_adjust_parser = cv_test_manager.cv_base_adjust_parser + + +class WiFiCapacityTest(cv_test): + def __init__(self, + lfclient_host="localhost", + lf_port=8080, + ssh_port=22, + lf_user="lanforge", + lf_password="lanforge", + instance_name="wct_instance", + config_name="wifi_config", + upstream="eth1", + batch_size="1", + loop_iter="1", + protocol="UDP-IPv4", + duration="5000", + pull_report=False, + load_old_cfg=False, + upload_rate="10Mbps", + download_rate="1Gbps", + sort="interleave", + stations="", + create_stations=False, + radio="wiphy0", + security="open", + paswd="[BLANK]", + ssid="", + enables=[], + disables=[], + raw_lines=[], + raw_lines_file="", + sets=[], + influx_host="localhost", + influx_port=8086, + report_dir="", + graph_groups=None, + test_rig="", + test_tag="", + local_lf_report_dir="" + ): + super().__init__(lfclient_host=lfclient_host, lfclient_port=lf_port) + + self.lfclient_host = lfclient_host + self.lf_port = lf_port + self.lf_user = lf_user + self.lf_password = lf_password + self.station_profile = self.new_station_profile() + self.pull_report = pull_report + self.load_old_cfg = load_old_cfg + self.instance_name = instance_name + self.config_name = config_name + self.test_name = "WiFi Capacity" + self.batch_size = batch_size + self.loop_iter = loop_iter + self.protocol = protocol + self.duration = duration + self.upload_rate = upload_rate + self.download_rate = download_rate + self.upstream = upstream + self.sort = sort + self.stations = stations + self.create_stations = create_stations + self.security = security + self.ssid = ssid + self.paswd = paswd + self.ssh_port = ssh_port + self.radio = radio + self.enables = enables + self.disables = disables + self.raw_lines = raw_lines + self.raw_lines_file = raw_lines_file + self.sets = sets + self.influx_host = influx_host, + self.influx_port = influx_port + self.report_dir = report_dir + self.graph_groups = graph_groups + self.test_rig = test_rig + self.test_tag = test_tag + self.local_lf_report_dir = local_lf_report_dir + + def setup(self): + if self.create_stations and self.stations != "": + sta = self.stations.split(",") + self.station_profile.cleanup(sta) + self.station_profile.use_security(self.security, self.ssid, self.paswd) + self.station_profile.create(radio=self.radio, sta_names_=sta, debug=self.debug) + self.station_profile.admin_up() + self.wait_for_ip(station_list=sta) + print("stations created") + + def run(self): + self.sync_cv() + time.sleep(2) + self.sync_cv() + + self.rm_text_blob(self.config_name, "Wifi-Capacity-") # To delete old config with same name + self.show_text_blob(None, None, False) + + # Test related settings + cfg_options = [] + + eid = LFUtils.name_to_eid(self.upstream) + port = "%i.%i.%s" % (eid[0], eid[1], eid[2]) + + port_list = [port] + if self.stations == "": + stas = self.station_map() # See realm + for eid in stas.keys(): + port_list.append(eid) + else: + stas = self.stations.split(",") + for s in stas: + port_list.append(s) + + idx = 0 + for eid in port_list: + add_port = "sel_port-" + str(idx) + ": " + eid + self.create_test_config(self.config_name, "Wifi-Capacity-", add_port) + idx += 1 + + self.apply_cfg_options(cfg_options, self.enables, self.disables, self.raw_lines, self.raw_lines_file) + + if self.batch_size != "": + cfg_options.append("batch_size: " + self.batch_size) + if self.loop_iter != "": + cfg_options.append("loop_iter: " + self.loop_iter) + if self.protocol != "": + cfg_options.append("protocol: " + str(self.protocol)) + if self.duration != "": + cfg_options.append("duration: " + self.duration) + if self.upload_rate != "": + cfg_options.append("ul_rate: " + self.upload_rate) + if self.download_rate != "": + cfg_options.append("dl_rate: " + self.download_rate) + if self.test_rig != "": + cfg_options.append("test_rig: " + self.test_rig) + if self.test_tag != "": + cfg_options.append("test_tag: " + self.test_tag) + + cfg_options.append("save_csv: 1") + + blob_test = "Wifi-Capacity-" + + # We deleted the scenario earlier, now re-build new one line at a time. + self.build_cfg(self.config_name, blob_test, cfg_options) + + cv_cmds = [] + + if self.sort == 'linear': + cmd = "cv click '%s' 'Linear Sort'" % self.instance_name + cv_cmds.append(cmd) + if self.sort == 'interleave': + cmd = "cv click '%s' 'Interleave Sort'" % self.instance_name + cv_cmds.append(cmd) + + self.create_and_run_test(self.load_old_cfg, self.test_name, self.instance_name, + self.config_name, self.sets, + self.pull_report, self.lfclient_host, self.lf_user, self.lf_password, + cv_cmds, ssh_port=self.ssh_port, graph_groups_file=self.graph_groups, local_lf_report_dir=self.local_lf_report_dir) + + self.rm_text_blob(self.config_name, blob_test) # To delete old config with same name + + self.rm_text_blob(self.config_name, "Wifi-Capacity-") # To delete old config with same name + + +def main(): + parser = argparse.ArgumentParser( + prog="lf_wifi_capacity_test.py", + formatter_class=argparse.RawTextHelpFormatter, + description=""" + ./lf_wifi_capacity_test.py --mgr localhost --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name wct_instance --config_name wifi_config --upstream 1.1.eth1 --batch_size 1 --loop_iter 1 \ + --protocol UDP-IPv4 --duration 6000 --pull_report --stations 1.1.sta0000,1.1.sta0001 \ + --create_stations --radio wiphy0 --ssid test-ssid --security open --paswd [BLANK] \ + --test_rig Testbed-01 -test_tag TAG\ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01 + """) + + cv_add_base_parser(parser) # see cv_test_manager.py + + parser.add_argument("-u", "--upstream", type=str, default="", + help="Upstream port for wifi capacity test ex. 1.1.eth1") + parser.add_argument("-b", "--batch_size", type=str, default="", + help="station increment ex. 1,2,3") + parser.add_argument("-l", "--loop_iter", type=str, default="", + help="Loop iteration ex. 1") + parser.add_argument("-p", "--protocol", type=str, default="", + help="Protocol ex.TCP-IPv4") + parser.add_argument("-d", "--duration", type=str, default="", + help="duration in ms. ex. 5000") + parser.add_argument("--download_rate", type=str, default="1Gbps", + help="Select requested download rate. Kbps, Mbps, Gbps units supported. Default is 1Gbps") + parser.add_argument("--upload_rate", type=str, default="10Mbps", + help="Select requested upload rate. Kbps, Mbps, Gbps units supported. Default is 10Mbps") + parser.add_argument("--sort", type=str, default="interleave", + help="Select station sorting behaviour: none | interleave | linear Default is interleave.") + parser.add_argument("-s", "--stations", type=str, default="", + help="If specified, these stations will be used. If not specified, all available stations will be selected. Example: 1.1.sta001,1.1.wlan0,...") + parser.add_argument("-cs", "--create_stations", default=False, action='store_true', + help="create stations in lanforge (by default: False)") + parser.add_argument("-radio", "--radio", default="wiphy0", + help="create stations in lanforge at this radio (by default: wiphy0)") + parser.add_argument("-ssid", "--ssid", default="", + help="ssid name") + parser.add_argument("-security", "--security", default="open", + help="ssid Security type") + parser.add_argument("-paswd", "--paswd", default="[BLANK]", + help="ssid Password") + parser.add_argument("--report_dir", default="") + parser.add_argument("--scenario", default="") + parser.add_argument("--graph_groups", help="File to save graph groups to", default=None) + parser.add_argument("--local_lf_report_dir", help="--local_lf_report_dir default '' put where dataplane script run from",default="") + + args = parser.parse_args() + + cv_base_adjust_parser(args) + + WFC_Test = WiFiCapacityTest(lfclient_host=args.mgr, + lf_port=args.port, + lf_user=args.lf_user, + lf_password=args.lf_password, + instance_name=args.instance_name, + config_name=args.config_name, + upstream=args.upstream, + batch_size=args.batch_size, + loop_iter=args.loop_iter, + protocol=args.protocol, + duration=args.duration, + pull_report=args.pull_report, + load_old_cfg=args.load_old_cfg, + download_rate=args.download_rate, + upload_rate=args.upload_rate, + sort=args.sort, + stations=args.stations, + create_stations=args.create_stations, + radio=args.radio, + ssid=args.ssid, + security=args.security, + paswd=args.paswd, + enables=args.enable, + disables=args.disable, + raw_lines=args.raw_line, + raw_lines_file=args.raw_lines_file, + sets=args.set, + graph_groups=args.graph_groups, + test_rig=args.test_rig, + test_tag=args.test_tag, + local_lf_report_dir=args.local_lf_report_dir + ) + WFC_Test.setup() + WFC_Test.run() + + WFC_Test.check_influx_kpi(args) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/measure_station_time_up.py b/lanforge/lanforge-scripts/py-scripts/measure_station_time_up.py new file mode 100755 index 000000000..7a9f0d9f2 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/measure_station_time_up.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 + +""" + Script for creating a variable number of stations. +""" +import sys +import os +import importlib +import argparse +import datetime +import pandas as pd +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class MeasureTimeUp(Realm): + def __init__(self, + _ssid=None, + _security=None, + _password=None, + _host=None, + _port=None, + _num_sta=None, + _number_template="00000", + _radio=["wiphy0", "wiphy1"], + _proxy_str=None, + _debug_on=False, + _up=True, + _exit_on_error=False, + _exit_on_fail=False, + _load=None, + _action="overwrite", + _clean_chambers="store_true", + _start=None, + _quiesce=None, + _stop=None, + _clean_dut="no"): + super().__init__(_host, + _port) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.num_sta = _num_sta + self.radio = _radio + # self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.up = _up + self.station_profile = self.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + self.load = _load + self.action = _action + self.clean_chambers = _clean_chambers + self.start = _start + self.quiesce = _quiesce + self.stop = _stop + self.clean_dut = _clean_dut + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + + print("Creating stations") + start_num = 0 + sta_names = [] + for item in self.radio: + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + sta_list = LFUtils.port_name_series(prefix="sta", + start_id=start_num, + end_id=self.num_sta + start_num, + padding_number=10000, + radio=item) + start_num = self.num_sta + start_num + 1 + sta_names.extend(sta_list) + self.station_profile.create(radio=item, sta_names_=sta_list, debug=self.debug) + + def station_up(self): + if self.up: + self.station_profile.admin_up() + self.wait_for_ip(station_list=self.station_profile.station_names) + self._pass("PASS: Station build finished") + + def scenario(self): + if self.load is not None: + data = { + "name": self.load, + "action": self.action, + "clean_dut": "no", + "clean_chambers": "no" + } + if self.clean_dut: + data['clean_dut'] = "yes" + if self.clean_chambers: + data['clean_chambers'] = "yes" + print("Loading database %s" % self.load) + self.json_post("/cli-json/load", data) + + elif self.start is not None: + print("Starting test group %s..." % self.start) + self.json_post("/cli-json/start_group", {"name": self.start}) + elif self.stop is not None: + print("Stopping test group %s..." % self.stop) + self.json_post("/cli-json/stop_group", {"name": self.stop}) + elif self.quiesce is not None: + print("Quiescing test group %s..." % self.quiesce) + self.json_post("/cli-json/quiesce_group", {"name": self.quiesce}) + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='measure_station_time_up.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Measure how long it takes to up stations + ''', + + description='''\ + measure_station_time_up.py +-------------------- +Command example: +./measure_station_time_up.py + --radio wiphy0 + --num_stations 3 + --security open + --ssid netgear + --passwd BLANK + --debug + --outfile + ''') + required = parser.add_argument_group('required arguments') + required.add_argument('--report_file', help='where you want to store results', required=True) + + args = parser.parse_args() + + dictionary = dict() + for num_sta in list(filter(lambda x: (x % 2 == 0), [*range(0, 200)])): + print(num_sta) + try: + create_station = MeasureTimeUp(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _num_sta=num_sta, + _radio=["wiphy0", "wiphy1"], + _proxy_str=args.proxy, + _debug_on=args.debug, + _load='FACTORY_DFLT') + create_station.scenario() + time.sleep(5.0 + num_sta / 10) + start = datetime.datetime.now() + create_station.build() + built = datetime.datetime.now() + create_station.station_up() + stationsup = datetime.datetime.now() + dictionary[num_sta] = [start, built, stationsup] + create_station.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=station_list) + time.sleep(5.0 + num_sta / 20) + except: + pass + df = pd.DataFrame.from_dict(dictionary).transpose() + df.columns = ['Start', 'Built', 'Stations Up'] + df['built duration'] = df['Built'] - df['Start'] + df['Up Stations'] = df['Stations Up'] - df['Built'] + df['duration'] = df['Stations Up'] - df['Start'] + for variable in ['built duration', 'duration']: + df[variable] = [x.total_seconds() for x in df[variable]] + df.to_pickle(args.report_file) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/modify_station.py b/lanforge/lanforge-scripts/py-scripts/modify_station.py new file mode 100755 index 000000000..5ed0a4c33 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/modify_station.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +""" + Script for modifying stations. +""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class ModifyStation(Realm): + def __init__(self, + _ssid="NA", + _security="NA", + _password="NA", + _mac="NA", + _host=None, + _port=None, + _station_list=None, + _enable_flags=None, + _disable_flags=None, + _number_template="00000", + _radio=None, + _proxy_str=None, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False, + _dhcp=True): + super().__init__(_host, + _port) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.mac = _mac + self.station_list = _station_list + self.enable_flags = _enable_flags + self.disable_flags = _disable_flags + self.radio = _radio + self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.dhcp = _dhcp + self.station_profile = self.new_station_profile() + self.station_profile.station_names = self.station_list + self.station_profile.ssid = self.ssid + self.station_profile.security = self.security + self.station_profile.ssid_pass = self.password + self.station_profile.mac = self.mac + self.station_profile.dhcp = self.dhcp + self.station_profile.debug = self.debug + self.station_profile.desired_add_sta_flags = self.enable_flags + self.station_profile.desired_add_sta_flags_mask = self.enable_flags + self.disable_flags + + def set_station(self): + return self.station_profile.modify(radio=self.radio) + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='modify_station.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Modify stations on a system. Use the enable_flag to create a flag on a station. Turn off a flag with \ + the disable_flag option. A list of available flags are available in the add_station.py file in \ + py-json/LANforge. + ''', + + description='''\ + modify_station.py + -------------------- + Command example: + ./modify_station.py + --radio wiphy0 + --station 1.1.sta0000 + --security open + --ssid netgear + --passwd BLANK + --enable_flag osen_enable + --disable_flag ht160_enable + --debug + -------------------- + Station flags are currently defined as: + wpa_enable | 0x10 # Enable WPA + custom_conf | 0x20 # Use Custom wpa_supplicant config file. + wep_enable | 0x200 # Use wpa_supplicant configured for WEP encryption. + wpa2_enable | 0x400 # Use wpa_supplicant configured for WPA2 encryption. + ht40_disable | 0x800 # Disable HT-40 even if hardware and AP support it. + scan_ssid | 0x1000 # Enable SCAN-SSID flag in wpa_supplicant. + passive_scan | 0x2000 # Use passive scanning (don't send probe requests). + disable_sgi | 0x4000 # Disable SGI (Short Guard Interval). + lf_sta_migrate | 0x8000 # OK-To-Migrate (Allow station migration between LANforge radios) + verbose | 0x10000 # Verbose-Debug: Increase debug info in wpa-supplicant and hostapd logs. + 80211u_enable | 0x20000 # Enable 802.11u (Interworking) feature. + 80211u_auto | 0x40000 # Enable 802.11u (Interworking) Auto-internetworking feature. Always enabled currently. + 80211u_gw | 0x80000 # AP Provides access to internet (802.11u Interworking) + 80211u_additional | 0x100000 # AP requires additional step for access (802.11u Interworking) + 80211u_e911 | 0x200000 # AP claims emergency services reachable (802.11u Interworking) + 80211u_e911_unauth | 0x400000 # AP provides Unauthenticated emergency services (802.11u Interworking) + hs20_enable | 0x800000 # Enable Hotspot 2.0 (HS20) feature. Requires WPA-2. + disable_gdaf | 0x1000000 # AP: Disable DGAF (used by HotSpot 2.0). + 8021x_radius | 0x2000000 # Use 802.1x (RADIUS for AP). + 80211r_pmska_cache | 0x4000000 # Enable oportunistic PMSKA caching for WPA2 (Related to 802.11r). + disable_ht80 | 0x8000000 # Disable HT80 (for AC chipset NICs only) + ibss_mode | 0x20000000 # Station should be in IBSS mode. + osen_enable | 0x40000000 # Enable OSEN protocol (OSU Server-only Authentication) + disable_roam | 0x80000000 # Disable automatic station roaming based on scan results. + ht160_enable | 0x100000000 # Enable HT160 mode. + disable_fast_reauth | 0x200000000 # Disable fast_reauth option for virtual stations. + mesh_mode | 0x400000000 # Station should be in MESH mode. + power_save_enable | 0x800000000 # Station should enable power-save. May not work in all drivers/configurations. + create_admin_down | 0x1000000000 # Station should be created admin-down. + wds-mode | 0x2000000000 # WDS station (sort of like a lame mesh), not supported on ath10k + no-supp-op-class-ie | 0x4000000000 # Do not include supported-oper-class-IE in assoc requests. May work around AP bugs. + txo-enable | 0x8000000000 # Enable/disable tx-offloads, typically managed by set_wifi_txo command + use-wpa3 | 0x10000000000 # Enable WPA-3 (SAE Personal) mode. + use-bss-transition | 0x80000000000 # Enable BSS transition. + disable-twt | 0x100000000000 # Disable TWT mode + + ''') + + optional = parser.add_argument_group('optional arguments') + optional.add_argument('--enable_flag', help='station flags to add', default=list(), action='append') + optional.add_argument('--disable_flag', help='station flags to disable', default=list(), action='append') + optional.add_argument('--station', help='station to modify', required=True, action='append') + optional.add_argument('--mac', default="NA") + + args = parser.parse_args() + + modify_station = ModifyStation(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _mac=args.mac, + _station_list=args.station, + _radio=args.radio, + _proxy_str=args.proxy, + _enable_flags=args.enable_flag, + _disable_flags=args.disable_flag, + _debug_on=args.debug) + modify_station.set_station() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/modify_vap.py b/lanforge/lanforge-scripts/py-scripts/modify_vap.py new file mode 100755 index 000000000..5ef8438a5 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/modify_vap.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +""" + Script for modifying VAPs. +""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class ModifyVAP(Realm): + def __init__(self, + _ssid="NA", + _security="NA", + _password="NA", + _mac="NA", + _host=None, + _port=None, + _vap_list=None, + _enable_flags=None, + _disable_flags=None, + _number_template="00000", + _radio=None, + _proxy_str=None, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False, + _dhcp=True): + super().__init__(_host, + _port) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.mac = _mac + self.vap_list = _vap_list + self.enable_flags = _enable_flags + self.disable_flags = _disable_flags + self.radio = _radio + self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.dhcp = _dhcp + self.vap_profile = self.new_vap_profile() + self.vap_profile.vap_name = self.vap_list + self.vap_profile.ssid = self.ssid + self.vap_profile.security = self.security + self.vap_profile.ssid_pass = self.password + self.vap_profile.mac = self.mac + self.vap_profile.dhcp = self.dhcp + self.vap_profile.debug = self.debug + self.vap_profile.desired_add_vap_flags = self.enable_flags + self.vap_profile.desired_add_vap_flags_mask = self.enable_flags + self.disable_flags + + def set_vap(self): + return self.vap_profile.modify(radio=self.radio) + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='modify_vap.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Modify VAPs on a system. Use the enable_flag to create a flag on a VAP. Turn off a flag with \ + the disable_flag option. A list of available flags are available in the add_vap.py file in \ + py-json/LANforge. + ''', + + description='''\ + modify_vap.py + -------------------- + Command example: + ./modify_vap.py + --radio wiphy0 + --vap 1.1.vap0000 + --security open + --ssid netgear + --passwd BLANK + --enable_flag osen_enable + --disable_flag ht160_enable + --debug + -------------------- + AP flags are currently defined as: + +enable_wpa | 0x10 # Enable WPA +hostapd_config | 0x20 # Use Custom hostapd config file. +enable_80211d | 0x40 # Enable 802.11D to broadcast country-code & channels in VAPs +short_preamble | 0x80 # Allow short-preamble +pri_sec_ch_enable | 0x100 # Enable Primary/Secondary channel switch. +wep_enable | 0x200 # Enable WEP Encryption +wpa2_enable | 0x400 # Enable WPA2 Encryption +disable_ht40 | 0x800 # Disable HT-40 (will use HT-20 if available). +verbose | 0x10000 # Verbose-Debug: Increase debug info in wpa-supplicant and hostapd logs. +80211u_enable | 0x20000 # Enable 802.11u (Interworking) feature. +80211u_auto | 0x40000 # Enable 802.11u (Interworking) Auto-internetworking feature. Always enabled currently. +80211u_gw | 0x80000 # AP Provides access to internet (802.11u Interworking) +80211u_additional | 0x100000 # AP requires additional step for access (802.11u Interworking) +80211u_e911 | 0x200000 # AP claims emergency services reachable (802.11u Interworking) +80211u_e911_unauth | 0x400000 # AP provides Unauthenticated emergency services (802.11u Interworking) +hs20_enable | 0x800000 # Enable Hotspot 2.0 (HS20) feature. Requires WPA-2. +disable_dgaf | 0x1000000 # AP Disable DGAF (used by HotSpot 2.0). +8021x_radius | 0x2000000 # Use 802.1x (RADIUS for AP). +80211r_pmska_cache | 0x4000000 # Enable oportunistic PMSKA caching for WPA2 (Related to 802.11r). +disable_ht80 | 0x8000000 # Disable HT80 (for AC chipset NICs only) +80211h_enable | 0x10000000 # Enable 802.11h (needed for running on DFS channels) Requires 802.11d. +osen_enable | 0x40000000 # Enable OSEN protocol (OSU Server-only Authentication) +ht160_enable | 0x100000000 # Enable HT160 mode. +create_admin_down | 0x1000000000 # Station should be created admin-down. +use-wpa3 | 0x10000000000 # Enable WPA-3 (SAE Personal) mode. +use-bss-load | 0x20000000000 # Enable BSS Load IE in Beacons and Probe Responses (.11e). +use-rrm-report | 0x40000000000 # Enable Radio measurements IE in beacon and probe responses. +use-bss-transition | 0x80000000000 # Enable BSS transition. + + ''') + + optional = parser.add_argument_group('optional arguments') + optional.add_argument('--enable_flag', help='VAP flags to add', default=list(), action='append') + optional.add_argument('--disable_flag', help='VAP flags to disable', default=list(), action='append') + optional.add_argument('--vap', help='VAP to modify', required=True) + optional.add_argument('--mac', default="NA") + + args = parser.parse_args() + + modify_vap = ModifyVAP(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _mac=args.mac, + _vap_list=args.vap, + _enable_flags=args.enable_flag, + _disable_flags=args.disable_flag, + _radio=args.radio, + _proxy_str=args.proxy, + _debug_on=args.debug) + modify_vap.set_vap() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/port_probe.py b/lanforge/lanforge-scripts/py-scripts/port_probe.py new file mode 100755 index 000000000..64e9ca3f7 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/port_probe.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 + +""" +NAME: port_probe.py + +PURPOSE: +Probes a port for information. If a station is used, an ssid, radio, and security type must be specified + +Use './port_probe.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys +import os + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + +if 'py-dashboard' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-dashboard')) + +import argparse +from LANforge import LFUtils +from realm import Realm +import datetime +import time +import pprint + + +class PortProbe(Realm): + def __init__(self, + ssid=None, + security=None, + password=None, + port_name=None, + upstream=None, + radio=None, + host="localhost", + port=8080, + mode=0, + number_template="00000", + use_ht160=False, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(lfclient_host=host, + lfclient_port=port), + self.upstream = upstream + self.host = host + self.port = port + self.ssid = ssid + self.port_name = port_name + self.security = security + self.password = password + self.radio = radio + self.mode = mode + self.debug = _debug_on + if 'sta' in self.port_name: + self.number_template = number_template + self.station_profile = self.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.debug = self.debug + + self.station_profile.use_ht160 = use_ht160 + if self.station_profile.use_ht160: + self.station_profile.mode = 9 + self.station_profile.mode = mode + + def start(self): + if 'sta' in self.port_name: + self.station_profile.admin_up() + print("Probing %s" % self.port_name) + port_info = self.name_to_eid(self.port_name) + data = { + "shelf": 1, + "resource": 1, + "port": self.port_name, + "key": "probe_port.quiet.%d.%d.%s" % (port_info[0], port_info[1], port_info[2]) + } + self.json_post("/cli-json/probe_port", data) + time.sleep(10) + probe_results = self.json_get("probe/1/1/%s" % self.port_name) + + print(probe_results) + + def cleanup(self): + if 'sta' in self.port_name: + self.station_profile.cleanup() + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + + def build(self): + if 'sta' in self.port_name: + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=[self.port_name], num_stations=1, debug=self.debug) + LFUtils.wait_until_ports_appear(self.port_name) + self._pass("PASS: Station build finished") + + +def main(): + parser = Realm.create_basic_argparse( + prog='port_probe_test.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Used to probe ports for information + ''', + description='''\ + Probes a port for information. If a station is used, an ssid, radio, and security type must be specified + + Example: + ./port_probe.py --ssid test_name --security open --radio wiphy0 + ''') + + parser.add_argument('--mode', help='Used to force mode of stations') + parser.add_argument('--port_name', help='Name of station to be used', default="sta0000") + + args = parser.parse_args() + + port_probe = PortProbe(host=args.mgr, + port=args.mgr_port, + number_template="0000", + port_name=args.port_name, + upstream=args.upstream_port, + ssid=args.ssid, + password=args.passwd, + radio=args.radio, + security=args.security, + use_ht160=False, + mode=args.mode, + _debug_on=args.debug) + + port_probe.build() + # exit() + if not port_probe.passes(): + print(port_probe.get_fail_message()) + port_probe.exit_fail() + + port_probe.start() + port_probe.cleanup() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/recordinflux.py b/lanforge/lanforge-scripts/py-scripts/recordinflux.py new file mode 100755 index 000000000..7493e1114 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/recordinflux.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""recordinflux will record data from existing lanforge endpoints to record to an already existing influx database. + +This data can then be streamed in Grafana or any other graphing program the user chooses while this script runs. + +https://influxdb-python.readthedocs.io/en/latest/include-readme.html + + +Use './recordinflux.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase + + +def main(): + parser = LFCliBase.create_bare_argparse( + prog='recordinflux.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog=''' + Record data to an Influx database in order to be able stream to Grafana or other graphing software''', + description=''' + recordinflux.py: + ---------------------------- + Generic command example: + ./recordinflux.py --influx_user lanforge \\ + --influx_passwd password \\ + --influx_db lanforge \\ + --stations \\ + --longevity 5h''' + ) + target_kpi = ['bps rx', 'rx bytes', 'pps rx', 'rx pkts', 'rx drop'] + parser.add_argument('--influx_user', help='Username for your Influx database') + parser.add_argument('--influx_passwd', help='Password for your Influx database') + parser.add_argument('--influx_token', help='Token for your Influx database', default=None) + parser.add_argument('--influx_db', help='Name of your Influx database') + parser.add_argument('--influx_bucket', help='Name of your Influx bucket') + parser.add_argument('--influx_org', help='Name of your Influx Organization') + parser.add_argument('--influx_port', help='Name of your Influx Port', default=8086) + parser.add_argument('--longevity', help='How long you want to gather data', default='4h') + parser.add_argument('--device', help='Device to monitor', action='append', required=True) + parser.add_argument('--monitor_interval', help='How frequently you want to append to your database', default='5s') + parser.add_argument('--target_kpi', help='Monitor only selected columns', action='append', default=target_kpi) + args = parser.parse_args() + monitor_interval = LFCliBase.parse_time(args.monitor_interval).total_seconds() + longevity = LFCliBase.parse_time(args.longevity).total_seconds() + tags = dict() + tags['script'] = 'recordinflux' + if args.influx_user is None: + from InfluxRequest import RecordInflux + grapher = RecordInflux(_influx_host=args.mgr, + _influx_port=args.influx_port, + _influx_bucket=args.influx_db, + _influx_token=args.influx_token, + _influx_org=args.influx_org) + grapher.monitor_port_data(longevity=longevity, + devices=args.device, + monitor_interval=monitor_interval, + tags=tags) + + else: + from influx import RecordInflux + grapher = RecordInflux(_influx_host=args.mgr, + _influx_port=args.mgr_port, + _influx_db=args.influx_db, + _influx_user=args.influx_user, + _influx_passwd=args.influx_passwd) + grapher.monitor_port_data(longevity=longevity, + devices=args.device, + monitor_interval=monitor_interval) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/regression_test.rc.example b/lanforge/lanforge-scripts/py-scripts/regression_test.rc.example new file mode 100644 index 000000000..43a021606 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/regression_test.rc.example @@ -0,0 +1,17 @@ +# Example settings file for regression_test.sh # +# # +# Edit your own copy of regression_test.rc and regression # +# test.sh should use it. Please do not check in your # +# regression_test.rc file, That will break other people's settings. # +# # + +# Run tests against these AP credentials: +SSID_USED=ChickenNet +PASSWD_USED=ameliachicken +SECURITY=wpa3 + +# Point tests at this GUI: +MGR=localhost +MGR_PORT=8080 + +# diff --git a/lanforge/lanforge-scripts/py-scripts/regression_test.sh b/lanforge/lanforge-scripts/py-scripts/regression_test.sh new file mode 100755 index 000000000..6e25430c3 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/regression_test.sh @@ -0,0 +1,568 @@ +#!/bin/bash +########################## +# Help +########################## +Help() +{ + echo "This bash script aims to automate the test process of all Candela Technologies's test_* scripts in the lanforge-scripts directory. The script can be run 2 ways and may include (via user input) the \"start_num\" and \"stop_num\" variables to select which tests should be run." + echo "OPTION ONE: ./regression_test.sh : this command runs all the scripts in the array \"testCommands\"" + echo "OPTION TWO: ./regression_test.sh 4 5 : this command runs py-script commands (in testCommands array) that include the py-script options beginning with 4 and 5 (inclusive) in case function ret_case_num." + echo "Optional Variables:" + echo "SSID is the name of the network you are testing against" + echo "PASSWD is the password of said network" + echo "SECURITY is the security protocol of the network" + echo "MGR is the IP address of the device which has LANforge installed, if different from the system you are using." + echo "A is used to call to test a specific command based on" + echo "F is used to pass in an RC file which can store the credentials for running regression multiple times on your system" + echo "Example command: ./regression_test.sh -s SSID -p PASSWD -w SECURITY -m MGR" +} + +while getopts ":h:s:p:w:m:A:r:F:B:U:" option; do + case "${option}" in + h) # display Help + Help + exit 1 + ;; + s) + SSID_USED=${OPTARG} + ;; + p) + PASSWD_USED=${OPTARG} + ;; + w) + SECURITY=${OPTARG} + ;; + m) + MGR=${OPTARG} + ;; + A) + A=${OPTARG} + ;; + r) + RADIO_USED=${OPTARG} + ;; + F) + RC_FILE=${OPTARG} + ;; + B) + BSSID=${OPTARG} + ;; + U) + UPSTREAM=$OPTARG + ;; + *) + + ;; + esac +done + +if [[ ${#SSID_USED} -eq 0 ]]; then #Network credentials + SSID_USED="jedway-wpa2-x2048-5-3" + PASSWD_USED="jedway-wpa2-x2048-5-3" + SECURITY="wpa2" +fi + +if [[ ${#RADIO_USED} -eq 0 ]]; then # Allow the user to change the radio they test against + RADIO_USED="wiphy1" +fi + +if [[ ${#UPSTREAM} -eq 0 ]]; then + UPSTREAM="eth1" +fi + +if [[ ${#BSSID} -eq 0 ]]; then + BSSID="04:f0:21:2c:41:84" +fi + +FILE="/tmp/gui-update.lock" +if test -f "$FILE"; then + echo "Finish updating your GUI" + exit 0 +fi + +HOMEPATH=$(realpath ~) + +if [[ ${#RC_FILE} -gt 0 ]]; then + source "$RC_FILE" +fi + +if [[ ${#SSID_USED} -gt 0 ]]; then + if [ -f ./regression_test.rc ]; then + source ./regression_test.rc # this version is a better unix name + elif [ -f ./regression_test.txt ]; then + source ./regression_test.txt # this less unixy name was discussed earlier + fi +fi +NUM_STA=${NUM_STA:-4} +TEST_HTTP_IP=${TEST_HTTP_IP:-10.40.0.1} +MGRLEN=${#MGR} +COL_NAMES="name,tx_bytes,rx_bytes,dropped" + +#CURR_TEST_NUM=0 +CURR_TEST_NAME="BLANK" + +REPORT_DIR="${HOMEPATH}/html-reports" +if [ ! -d "$REPORT_DIR" ]; then + echo "Report directory [$REPORT_DIR] not found, bye." + exit 1 +fi +REPORT_DATA="${HOMEPATH}/report-data" +if [ ! -d "${REPORT_DATA}" ]; then + echo "Data directory [$REPORT_DATA] not found, bye." + exit 1 +fi +TEST_DIR="${REPORT_DATA}/${NOW}" + +function run_l3_longevity() { + ./test_l3_longevity.py --test_duration 15s --upstream_port eth1 --radio "radio==wiphy0 stations==4 ssid==$SSID_USED ssid_pw==$PASSWD_USED security==$SECURITY" --radio "radio==wiphy1 stations==4 ssid==$SSID_USED ssid_pw==$PASSWD_USED security==$SECURITY" --mgr "$MGR" +} +function testgroup_list_groups() { + ./scenario.py --load test_l3_scenario_throughput + ./testgroup.py --group_name group1 --add_group --add_cx cx0000,cx0001,cx0002 --remove_cx cx0003 --list_groups --debug --mgr "$MGR" +} +function testgroup_list_connections() { + ./scenario.py --load test_l3_scenario_throughput + ./testgroup.py --group_name group1 --add_group --add_cx cx0000,cx0001,cx0002 --remove_cx cx0003 --show_group --debug --mgr "$MGR" +} +function testgroup_delete_group() { + ./scenario.py --load test_l3_scenario_throughput + ./testgroup.py --group_name group1 --add_group --add_cx cx0000,cx0001,cx0002 --remove_cx cx0003 + ./testgroup.py --group_name group1--del_group --debug --mgr "$MGR" +} +if [[ $MGRLEN -gt 0 ]]; then + testCommands=( + #"./create_bond.py --network_dev_list eth0,eth1 --debug --mgr $MGR" + #"./create_bridge.py --radio $RADIO_USED --upstream_port eth1 --target_device sta0000 --debug --mgr $MGR" + "./create_chamberview.py -m $MGR -cs \"regression_test\" --line \"Resource=1.1 Profile=STA-AC Amount=1 Uses-1 $RADIO_USED Freq=-1 DUT=TEST DUT_RADIO=$RADIO_USED Traffic=http\" --line \"Resource=1.1 Profile=upstream Amount=1 Uses-1=eth1 Uses-2=AUTO Freq=-1 DUT=Test DUT_RADIO=$RADIO_USED Traffic=http\"" + "./create_chamberview_dut.py --lfmgr $MGR --dut_name regression_dut --ssid \"ssid_idx=0 ssid=$SSID_USED security=$SECURITY password=$PASSWD_USED bssid=04:f0:21:2c:41:84\"" + #"./create_l3.py --radio $RADIO_USED --ssid $SSID_USED --password $PASSWD_USED --security $SECURITY --debug --mgr $MGR" + #"./create_l4.py --radio $RADIO_USED --ssid $SSID_USED --password $PASSWD_USED --security $SECURITY --debug --mgr $MGR" + #"./create_macvlan.py --radio 1.$RADIO_USED --macvlan_parent eth1 --debug --mgr $MGR" + #"./create_qvlan.py --first_qvlan_ip 192.168.1.50 --mgr $MGR" + #"./create_station.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug --mgr $MGR" + "./create_vap.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug --mgr $MGR" + "./create_vr.py --vr_name 2.vr0 --ports 2.br0,2.vap2 --services 1.br0=dhcp,nat --services 1.vr0=radvd" + #./create_wanlink + #./csv_convert + #./csv_processor + #./csv_to_grafana + #./csv_to_influx + "./cv_manager.py --mgr $MGR --scenario FACTORY_DFLT" + #"./cv_to_grafana --mgr $MGR " + #"./docstrings.py --mgr $MGR" + #"./event_breaker --mgr $MGR" + #"./event_flood --mgr $MGR" + "./example_security_connection.py --num_stations $NUM_STA --ssid $SSID_USED --passwd $PASSWD_USED --radio $RADIO_USED --security wpa2 --debug --mgr $MGR" + #./ftp_html.py + #./ghost_profile + #./grafana_profile + #./html_template + #./influx + #./layer3_test.py --mgr $MGR --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY + #./layer4_test --mgr $MGR --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY + "./lf_ap_auto_test.py --mgr $MGR --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name ap-auto-instance --config_name test_con --upstream 1.1.eth2 \ + --dut5_0 \"linksys-8450 Default-SSID-5gl c4:41:1e:f5:3f:25 (2)\" \ + --dut2_0 \"linksys-8450 Default-SSID-2g c4:41:1e:f5:3f:24 (1)\" \ + --max_stations_2 100 --max_stations_5 100 --max_stations_dual 200 \ + --radio2 1.1.wiphy0 --radio2 1.1.wiphy1 \ + --set \"Basic Client Connectivity\" 1 --set \"Multi Band Performance\" 1 \ + --set \"Skip 2.4Ghz Tests\" 1 --set \"Skip 5Ghz Tests\" 1 \ + --set \"Throughput vs Pkt Size\" 0 --set 'Capacity' 0 --set 'Stability' 0 --set 'Band-Steering' 0 \ + --set \"Multi-Station Throughput vs Pkt Size\" 0 --set \"Long-Term\" 0 \ + --pull_report \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01" + #./lf_atten_mod_test + #./lf_csv + #./lf_dataplane_config + "./lf_dataplane_test.py --mgr $MGR --lf_user lanforge --lf_password lanforge \ + --instance_name dataplane-instance --config_name test_con --upstream 1.1.$UPSTREAM \ + --dut linksys-8450 --duration 15s --station 1.1.sta01500 \ + --download_speed 85% --upload_speed 0 \ + --raw_line \"pkts: Custom;60;142;256;512;1024;MTU\" \ + --raw_line \"cust_pkt_sz: 88 1200\" \ + --raw_line \"directions: DUT Transmit;DUT Receive\" \ + --raw_line \"traffic_types: UDP;TCP\" \ + --test_rig Testbed-01 --pull_report \ + --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben \ + --influx_tag testbed Ferndale-01" + #./lf_dfs_test + #./lf_dut_sta_vap_test + #"./lf_ftp.py --mgr $MGR --mgr_port 8080 --upstream_port $UPSTREAM --ssid $SSID --security $SECURITY --passwd $PASSWD_USED \ + # --ap_name WAC505 --ap_ip 192.168.213.90 --bands Both --directions Download --twog_radio wiphy1 --fiveg_radio wiphy0 --file_size 2MB --num_stations 40 --Both_duration 1 --traffic_duration 2 --ssh_port 22_" + "./lf_ftp_test.py --mgr $MGR --ssid $SSID --passwd $PASSWD_USED --security $SECURITY --bands 5G --direction Download \ + --file_size 2MB --num_stations 2" + "./lf_graph.py --mgr $MGR" + #"./lf_mesh_test.py --mgr $MGR --upstream $UPSTREAM --raw_line 'selected_dut2 RootAP wactest $BSSID'" + #./lf_multipsk + #./lf_report + #./lf_report_test + #./lf_rvr_test + #./lf_rx_sensitivity_test.py + #./lf_sniff_radio + #./lf_snp_test + "./lf_tr398_test.py --mgr $MGR" + #./lf_webpage + "./lf_wifi_capacity_test.py --mgr $MGR --port 8080 --lf_user lanforge --lf_password lanforge \ + --instance_name this_inst --config_name test_con --upstream 1.1.eth2 --batch_size 1,5,25,50,100 --loop_iter 1 \ + --protocol UDP-IPv4 --duration 6000 --pull_report \ + --test_rig Testbed-01" + #--influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + #--influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + #--influx_bucket ben \ + #measure_station_time_up.py + #modify_station.py + #modify_vap.py + #recordinflux.py + #run_cv_scenario.py + #rvr_scenario.py + #scenario.py + #sta_connect2.py + #sta_connect_bssid_mac.py + #sta_connect_example.py + #sta_connect_multi_example.py + #sta_connect.py + #sta_scan_test.py + #station_layer3.py + #stations_connected.py + #test_1k_clients_jedtest.py + #test_client_admission.py + "./test_fileio.py --macvlan_parent eth2 --num_ports 3 --use_macvlans --first_mvlan_ip 192.168.92.13 --netmask 255.255.255.0 --gateway 192.168.92.1 --test_duration 30s --mgr $MGR" # Better tested on Kelly, where VRF is turned off + "./test_generic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --num_stations $NUM_STA --type lfping --dest $TEST_HTTP_IP --debug --mgr $MGR" + "./test_generic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --num_stations $NUM_STA --type speedtest --speedtest_min_up 20 --speedtest_min_dl 20 --speedtest_max_ping 150 --security $SECURITY --debug --mgr $MGR" + "./test_generic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --num_stations $NUM_STA --type iperf3 --debug --mgr $MGR" + "./test_generic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --num_stations $NUM_STA --type lfcurl --dest $TEST_HTTP_IP --file_output ${HOMEPATH}/Documents/lfcurl_output.txt --debug --mgr $MGR" + "./testgroup.py --group_name group1 --add_group --list_groups --debug --mgr $MGR" + #testgroup_list_groups + #testgroup_list_connections + #testgroup_delete_group + #"./testgroup2.py --num_stations 4 --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --radio $RADIO_USED --group_name group0 --add_group --mgr $MGR" + "./test_ip_connection.py --radio $RADIO_USED --num_stations $NUM_STA --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug --mgr $MGR" + #"./test_ip_variable_time.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --test_duration 15s --output_format excel --layer3_cols $COL_NAMES --debug --mgr $MGR --traffic_type lf_udp" + #"./test_ip_variable_time.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --test_duration 15s --output_format csv --layer3_cols $COL_NAMES --debug --mgr $MGR --traffic_type lf_udp" + "./test_ip_connection.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug --mgr $MGR --ipv6" + #"./test_ip_variable_time.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --test_duration 15s --debug --mgr $MGR --ipv6 --traffic_type lf_udp" + #./test_ipv4_ps + #./test_ipv4_ttls + "./test_l3_longevity.py --mgr $MGR --endp_type 'lf_udp lf_tcp' --upstream_port 1.1.$UPSTREAM \ + --radio \"radio==1.1.wiphy0 stations==10 ssid==ASUS_70 ssid_pw==[BLANK] security==open\" \ + --radio \"radio==1.1.wiphy1 stations==1 ssid==ASUS_70 ssid_pw==[BLANK] security==open\" \ + --test_duration 5s --influx_host c7-graphana --influx_port 8086 --influx_org Candela \ + --influx_token=-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ== \ + --influx_bucket ben --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000 \ + --influx_tag testbed regression_test --influx_tag DUT ROG -o longevity.csv" + "./test_l3_powersave_traffic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug --mgr $MGR" + "./test_l3_scenario_throughput.py -t 15s -sc test_l3_scenario_throughput -m $MGR" + #./test_l3_unicast_traffic_gen + #./test_l3_unicast_traffic_gen + #./test_l3_WAN_LAN + #./test_l4 + "./test_status_msg.py --debug --mgr $MGR" #this is all which is needed to run + #"./test_wanlink.py --name my_wanlink4 --latency_A 20 --latency_B 69 --rate 1000 --jitter_A 53 --jitter_B 73 --jitter_freq 6 --drop_A 12 --drop_B 11 --debug --mgr $MGR" + #./test_wpa_passphrases + #./tip_station_powersave + #./vap_stations_example + #./video_rates + "./wlan_capacity_calculator.py -sta 11abg -t Voice -p 48 -m 106 -e WEP -q Yes -b 1 2 5.5 11 -pre Long -s N/A -co G.711 -r Yes -c Yes -m $MGR" + "./wlan_capacity_calculator.py -sta 11n -t Voice -d 17 -ch 40 -gu 800 -high 9 -e WEP -q Yes -ip 5 -mc 42 -b 6 9 12 24 -m 1538 -co G.729 -pl Greenfield -cw 15 -r Yes -c Yes -m $MGR" + "./wlan_capacity_calculator.py -sta 11ac -t Voice -d 9 -spa 3 -ch 20 -gu 800 -high 1 -e TKIP -q Yes -ip 3 -mc 0 -b 6 12 24 54 -m 1518 -co Greenfield -cw 15 -rc Yes -m $MGR" + #"./ws_generic_monitor_test.py --mgr $MGR" + ) +elif [[ $MGR == "short" ]]; then + testCommands=( + run_l3_longevity + "./test_ipv4_variable_time.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --test_duration 15s --output_format excel --layer3_cols $COL_NAMES --debug --mgr $MGR" + ) +else + testCommands=( + #"../cpu_stats.py --duration 15" + "./example_security_connection.py --num_stations $NUM_STA --ssid jedway-wpa-1 --passwd jedway-wpa-1 --radio $RADIO_USED --security wpa --debug" + "./example_security_connection.py --num_stations $NUM_STA --ssid $SSID_USED --passwd $PASSWD_USED --radio $RADIO_USED --security wpa2 --debug" + "./example_security_connection.py --num_stations $NUM_STA --ssid jedway-wep-48 --passwd 0123456789 --radio $RADIO_USED --security wep --debug" + "./example_security_connection.py --num_stations $NUM_STA --ssid jedway-wpa3-1 --passwd jedway-wpa3-1 --radio $RADIO_USED --security wpa3 --debug" + "./sta_connect2.py --dut_ssid $SSID_USED --dut_passwd $PASSWD_USED --dut_security $SECURITY" + "./sta_connect_example.py" + # want if [[ $DO_FILEIO = 1 ]] + "./test_fileio.py --macvlan_parent eth2 --num_ports 3 --use_macvlans --first_mvlan_ip 192.168.92.13 --netmask 255.255.255.0 --test_duration 30s --gateway 192.168.92.1" # Better tested on Kelly, where VRF is turned off + "./test_generic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --num_stations $NUM_STA --type lfping --dest $TEST_HTTP_IP --debug" + "./test_generic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --num_stations $NUM_STA --type speedtest --speedtest_min_up 20 --speedtest_min_dl 20 --speedtest_max_ping 150 --security $SECURITY --debug" + "./test_generic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --num_stations $NUM_STA --type iperf3 --debug" + "./test_generic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --num_stations $NUM_STA --type lfcurl --dest $TEST_HTTP_IP --file_output ${HOMEPATH}/Documents/lfcurl_output.txt --debug" + "./testgroup.py --group_name group1 --add_group --list_groups --debug" + testgroup_list_groups + testgroup_list_connections + testgroup_delete_group + "./testgroup2.py --num_stations 4 --ssid lanforge --passwd password --security wpa2 --radio wiphy0 --group_name group0 --add_group" + "./test_ipv4_connection.py --radio $RADIO_USED --num_stations $NUM_STA --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug" + "./test_ipv4_l4_urls_per_ten.py --radio $RADIO_USED --num_stations $NUM_STA --security $SECURITY --ssid $SSID_USED --passwd $PASSWD_USED --num_tests 1 --requests_per_ten 600 --target_per_ten 600 --debug" + "./test_ipv4_l4_wifi.py --radio $RADIO_USED --num_stations $NUM_STA --security $SECURITY --ssid $SSID_USED --passwd $PASSWD_USED --test_duration 15s --debug" + "./test_ipv4_l4.py --radio $RADIO_USED --num_stations 4 --security $SECURITY --ssid $SSID_USED --passwd $PASSWD_USED --test_duration 15s --debug" + "./test_ipv4_variable_time.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --test_duration 15s --output_format excel --layer3_cols $COL_NAMES --traffic_type lf_udp --debug" + "./test_ipv4_variable_time.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --test_duration 15s --output_format csv --layer3_cols $COL_NAMES --traffic_type lf_udp --debug" + "./test_ipv4_l4_ftp_upload.py --upstream_port eth1 --radio $RADIO_USED --num_stations $NUM_STA --security $SECURITY --ssid $SSID_USED --passwd $PASSWD_USED --test_duration 15s --debug" + "./test_ipv6_connection.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug" + "./test_ipv6_variable_time.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --test_duration 15s --cx_type tcp6 --debug" + run_l3_longevity + "./test_l3_powersave_traffic.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug" + #"./test_l3_scenario_throughput.py -t 15s -sc test_l3_scenario_throughput" #always hangs the regression + "./test_status_msg.py --action run_test " #this is all which is needed to run + "./test_wanlink.py --debug" + #"./ws_generic_monitor_test.py" + #"../py-json/ws-sta-monitor.py --debug" + "./create_bridge.py --radio $RADIO_USED --upstream_port eth1 --target_device sta0000 --debug" + "./create_l3.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug" + "./create_l4.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug" + "./create_macvlan.py --radio $RADIO_USED --macvlan_parent eth1 --debug" + "./create_station.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug" + "./create_vap.py --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug" + "./create_vr.py --vr_name 2.vr0 --ports 2.br0,2.vap2 --services" + "./create_qvlan.py --radio $RADIO_USED --qvlan_parent eth1" + "./wlan_capacity_calculator.py -sta 11abg -t Voice -p 48 -m 106 -e WEP -q Yes -b 1 2 5.5 11 -pre Long -s N/A -co G.711 -r Yes -c Yes" + "./wlan_capacity_calculator.py -sta 11n -t Voice -d 17 -ch 40 -gu 800 -high 9 -e WEP -q Yes -ip 5 -mc 42 -b 6 9 12 24 -m 1538 -co G.729 -pl Greenfield -cw 15 -r Yes -c Yes" + "./wlan_capacity_calculator.py -sta 11ac -t Voice -d 9 -spa 3 -ch 20 -gu 800 -high 1 -e TKIP -q Yes -ip 3 -mc 0 -b 6 12 24 54 -m 1518 -co Greenfield -cw 15 -rc Yes" + ) +fi +#declare -A name_to_num +#if you want to run just one test as part of regression_test, you can call one test by calling its name_to_num identifier. +name_to_num=( + ["create_bond"]=1 + ["create_bridge"]=2 + ["create_l3"]=3 + ["create_l4"]=4 + ["create_macvlan"]=5 + ["create_qvlan"]=6 + ["create_station"]=7 + ["create_va"]=8 + ["create_vr"]=9 + ["create_wanlink"]=10 + ["csv_convert"]=11 + ["csv_processor"]=12 + ["csv_to_grafana"]=13 + ["csv_to_influx"]=14 + ["cv_manager"]=15 + ["cv_to_grafana"]=16 + ["docstrings"]=17 + ["event_breaker"]=18 + ["event_flood"]=19 + ["example_security_connection"]=20 + ["ftp_html"]=21 + ["ghost_profile"]=22 + ["grafana_profile"]=23 + ["html_template"]=24 + ["influx"]=25 + ["layer3_test"]=26 + ["layer4_test"]=27 + ["lf_ap_auto_test"]=28 + ["lf_atten_mod_test"]=29 + ["lf_csv"]=30 + ["lf_dataplane_config"]=31 + ["lf_dataplane_test"]=32 + ["lf_dfs_test"]=33 + ["lf_dut_sta_vap_test"]=34 + ["lf_ft"]=35 + ["lf_ftp_test"]=36 + ["lf_graph"]=37 + ["lf_influx_db"]=38 + ["lf_mesh_test"]=39 + ["lf_multipsk"]=40 + ["lf_report"]=41 + ["lf_report_test"]=42 + ["lf_rvr_test"]=43 + ["lf_rx_sensitivity_test"]=44 + ["lf_sniff_radio"]=45 + ["lf_snp_test"]=46 + ["lf_tr398_test"]=47 + ["lf_webpage"]=48 + ["lf_wifi_capacity_test"]=49 + ["measure_station_time_u"]=50 + ["modify_station"]=51 + ["modify_va"]=52 + ["run_cv_scenario"]=53 + ["rvr_scenario"]=54 + ["scenario"]=55 + ["sta_connect"]=56 + ["sta_connect2"]=57 + ["sta_connect_bssid_mac"]=58 + ["sta_connect_example"]=59 + ["sta_connect_multi_example"]=60 + ["sta_connect_bssid_mac"]=61 + ["sta_connect_example"]=62 + ["sta_connect_multi_example"]=63 + ["sta_scan_test"]=64 + ["station_layer3"]=65 + ["stations_connected"]=66 + ["sta_scan_test"]=67 + ["station_layer3"]=68 + ["stations_connected"]=69 + ["test_1k_clients_jedtest"]=70 + ["test_client_admission"]=71 + ["test_fileio"]=72 + ["test_generic"]=73 + ["test_generic"]=74 + ["test_generic"]=75 + ["test_generic"]=76 + ["testgrou"]=77 + ["testgroup_list_groups"]=78 + ["testgroup_list_connections"]=79 + ["testgroup_delete_grou"]=80 + ["testgroup2"]=81 + ["test_ip_connection"]=82 + ["test_ip_variable_time"]=83 + ["test_ip_variable_time"]=84 + ["test_ip_connection"]=85 + ["test_ip_variable_time"]=86 + ["test_ipv4_ps"]=87 + ["test_ipv4_ttls"]=88 + ["test_l3_longevit"]=89 + ["test_l3_powersave_traffic"]=90 + ["test_l3_scenario_throughput"]=91 + ["test_l3_unicast_traffic_gen"]=92 + ["test_l3_unicast_traffic_gen"]=93 + ["test_l3_WAN_LAN"]=94 + ["test_l4"]=95 + ["test_status_msg"]=96 + ["test_wanlink"]=97 + ["test_wpa_passphrases"]=98 + ["tip_station_powersave"]=99 + ["vap_stations_example"]=100 + ["video_rates"]=101 + ["wlan_capacity_calculator"]=102 + ["wlan_capacity_calculator"]=103 + ["wlan_capacity_calculator"]=104 + ["ws_generic_monitor_test"]=105 +) + +function blank_db() { + echo "Loading blank scenario..." >>"${HOMEPATH}/regression_file.txt" + ./scenario.py --load BLANK >>"${HOMEPATH}/regression_file.txt" + #check_blank.py +} + +function echo_print() { + echo "Beginning $CURR_TEST_NAME test..." >>"${HOMEPATH}/regression_file.txt" +} + +function test() { + if [[ $MGRLEN -gt 0 ]]; then + ./scenario.py --load FACTORY_DFLT --mgr "${MGR}" + else + ./scenario.py --load FACTORY_DFLT + fi + + echo "" + echo "Test $CURR_TEST_NAME" + + echo_print + echo "$i" + $i > "${TEST_DIR}/${NAME}.txt" 2> "${TEST_DIR}/${NAME}_stderr.txt" + chmod 664 "${TEST_DIR}/${NAME}.txt" + FILESIZE=$(stat -c%s "${TEST_DIR}/${NAME}_stderr.txt") || 0 + if (( FILESIZE > 0)); then + results+=("${CURR_TEST_NAME}${i} + Failure + STDOUT + STDERR") + else + results+=("${CURR_TEST_NAME}${i} + Success + STDOUT + ") + fi +} + +function run_test() { + if [[ ${#A} -gt 0 ]]; then + for i in "${testCommands[@]}"; do + NAME=$(cat < /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) + CURR_TEST_NAME=${i%%.py*} + CURR_TEST_NAME=${CURR_TEST_NAME#./*} + #CURR_TEST_NUM="${name_to_num[$CURR_TEST_NAME]}" + if [[ $A == "$CURR_TEST_NAME" ]]; then + test + fi + done + else + for i in "${testCommands[@]}"; do + NAME=$(cat < /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) + CURR_TEST_NAME=${i%%.py*} + CURR_TEST_NAME=${CURR_TEST_NAME#./*} + #CURR_TEST_NUM="${name_to_num[$CURR_TEST_NAME]}" + test + done + fi +} + +function html_generator() { + LAST_COMMIT=$(git log --pretty=oneline | tail -n 1) + header=" + + Regression Test Results $NOW + + + + +

    Regression Results

    +

    $NOW

    +

    $LAST_COMMIT

    + + + + + + + + " + tail=" + " + + fname="${HOMEPATH}/html-reports/regression_file-${NOW}.html" + echo "$header" >> "$fname" + echo "${results[@]}" >> "$fname" + echo "
    Command NameCommandStatusSTDOUTSTDERR
    " >> "$fname" + echo "$tail" >> "$fname" + if [ -f "${HOMEPATH}/html-reports/latest.html" ]; then + rm -f "${HOMEPATH}/html-reports/latest.html" + fi + ln -s "${fname}" "${HOMEPATH}/html-reports/latest.html" + HOSTNAME=$(ip -4 addr show enp3s0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + content="View the latest regression report at ${HOSTNAME}/html-reports/latest.html" + echo "${content}" + #mail -s "Regression Results" scripters@candelatech.com <<<$content +} + +results=() +NOW=$(date +"%Y-%m-%d-%H-%M") +NOW="${NOW/:/-}" +TEST_DIR="${REPORT_DATA}/${NOW}" +URL2="${HOMEPATH}/report-data/${NOW}" +mkdir "${TEST_DIR}" +echo "Recording data to $TEST_DIR" + +run_test +html_generator diff --git a/lanforge/lanforge-scripts/py-scripts/run_cv_scenario.py b/lanforge/lanforge-scripts/py-scripts/run_cv_scenario.py new file mode 100755 index 000000000..5e931b6a8 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/run_cv_scenario.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# This script will set the LANforge to a BLANK database then it will load the specified database +# and start a graphical report +import sys +import os +import importlib +import argparse +from time import sleep +import pprint + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + +""" + cvScenario.scenario_db = args.scenario_db + if args.cv_test is not None: + cvScenario.cv_test = args.cv_test + if args.test_scenario is not None: + cvScenario.test_scenario = args.test_scenario +""" + +class RunCvScenario(LFCliBase): + def __init__(self, lfhost="localhost", lfport=8080, debug_=False, lanforge_db_=None, cv_scenario_=None, cv_test_=None, test_scenario_=None): + super().__init__( _lfjson_host=lfhost, _lfjson_port=lfport, _debug=debug_, _exit_on_error=True, _exit_on_fail=True) + self.lanforge_db = lanforge_db_ + self.cv_scenario = cv_scenario_ + self.cv_test = cv_test_ + self.test_profile = test_scenario_ + self.localrealm = Realm(lfclient_host=lfhost, lfclient_port=lfport, debug_=debug_) + self.report_name = None + + def get_report_file_name(self): + return self.report_name + + def build(self): + data = { + "name": "BLANK", + "action":"overwrite", + "clean_dut":"yes", + "clean_chambers": "yes" + } + self.json_post("/cli-json/load", data) + sleep(1) + port_counter = 0; + attempts = 6 + while (attempts > 0) and (port_counter > 0): + sleep(1) + attempts -= 1 + print("looking for ports like vap+") + port_list = self.localrealm.find_ports_like("vap+") + alias_map = LFUtils.portListToAliasMap(port_list) + port_counter = len(alias_map) + + port_list = self.localrealm.find_ports_like("sta+") + alias_map = LFUtils.portListToAliasMap(port_list) + port_counter += len(alias_map) + if port_counter == 0: + break + + if (port_counter != 0) and (attempts == 0): + print("There appears to be a vAP in this database, quitting.") + pprint(alias_map); + exit(1) + + data = { + "name": self.lanforge_db, + "action":"overwrite", + "clean_dut":"yes", + "clean_chambers": "yes" + } + self.json_post("/cli-json/load", data) + sleep(1) + self._pass("Loaded scenario %s" % self.lanforge_db, True) + return True + + def start(self, debug_=False): + # /gui_cli takes commands keyed on 'cmd', so we create an array of commands + commands = [ + "cv apply '%s'" % self.cv_scenario, + "cv build", + "sleep 4", + "cv is_built", + "cv sync", + "sleep 2", + "cv create '%s' 'test_ref' 'true'" % self.cv_test, + "sleep 2", + "cv load test_ref '%s'" % self.test_profile, + "sleep 1", + "cv click test_ref 'Auto Save Report'", + "sleep 5", + "cv click test_ref Start", + "sleep 60", + "cv get test_ref 'Report Location:'", + "sleep 5", + #"cv click test_ref 'Save HTML'", + "cv click test_ref 'Close'", + "sleep 1", + "cv click test_ref Cancel", + "sleep 1", + "exit" + ] + response_json = [] + for command in commands: + data = { + "cmd": command + } + try: + debug_par = "" + if debug_: + debug_par="?_debug=1" + if command.endswith("is_built"): + print("Waiting for scenario to build...", end='') + self.localrealm.wait_while_building(debug_=False) + print("...proceeding") + elif command.startswith("sleep "): + nap = int(command.split(" ")[1]) + print("sleeping %d..." % nap) + sleep(nap) + print("...proceeding") + else: + response_json = [] + print("running %s..." % command, end='') + response = self.json_post("/gui-json/cmd%s" % debug_par, data, debug_=False, response_json_list_=response_json) + if debug_: + LFUtils.debug_printer.pprint(response_json) + print("...proceeding") + + + except Exception as x: + print(x) + + self._pass("report finished", print_=True) + + + def stop(self): + pass + + def cleanup(self): + pass + + +def main(): + lfjson_host = "localhost" + lfjson_port = 8080 + parser = argparse.ArgumentParser( + prog="run_cv_scenario.py", + formatter_class=argparse.RawTextHelpFormatter, + description="""LANforge Reporting Script: Load a scenario and run a RvR report + Example: + ./load_ap_scenario.py --lfmgr 127.0.0.1 --scenario_db 'handsets' --cv_test --test_scenario 'test-20' + """) + parser.add_argument("-m", "--lfmgr", type=str, help="address of the LANforge GUI machine (localhost is default)") + parser.add_argument("-o", "--port", type=int, help="IP Port the LANforge GUI is listening on (8080 is default)") + parser.add_argument("-d", "--lanforge_db", type=str, help="Name of test scenario database (see Status Tab)") + parser.add_argument("-c", "--cv_scenario", type=str, help="Name of Chamber View test scenario (see CV Manage Scenarios)") + parser.add_argument("-n", "--cv_test", type=str, help="Chamber View test") + parser.add_argument("-s", "--test_profile", type=str, help="Name of the saved CV test profile") + parser.add_argument("--debug", help='Enable debugging', default=False, action="store_true") + + args = parser.parse_args() + if args.lfmgr is not None: + lfjson_host = args.lfmgr + if args.port is not None: + lfjson_port = args.port + debug = False + if args.debug is not None: + debug = args.debug + run_cv_scenario = RunCvScenario(lfjson_host, lfjson_port, debug_=debug) + + if args.lanforge_db is not None: + run_cv_scenario.lanforge_db = args.lanforge_db + if args.cv_scenario is not None: + run_cv_scenario.cv_scenario = args.cv_scenario + if args.cv_test is not None: + run_cv_scenario.cv_test = args.cv_test + if args.test_profile is not None: + run_cv_scenario.test_profile = args.test_profile + + if (run_cv_scenario.lanforge_db is None) or (run_cv_scenario.lanforge_db == ""): + raise ValueError("Please specificy scenario database name with --scenario_db") + + if not (run_cv_scenario.build() and run_cv_scenario.passes()): + print("scenario failed to build.") + print(run_cv_scenario.get_fail_message()) + exit(1) + + if not (run_cv_scenario.start() and run_cv_scenario.passes()): + print("scenario failed to start.") + print(run_cv_scenario.get_fail_message()) + exit(1) + + if not (run_cv_scenario.stop() and run_cv_scenario.passes()): + print("scenario failed to stop:") + print(run_cv_scenario.get_fail_message()) + exit(1) + + if not (run_cv_scenario.cleanup() and run_cv_scenario.passes()): + print("scenario failed to clean up:") + print(run_cv_scenario.get_fail_message()) + exit(1) + + report_file = run_cv_scenario.get_report_file_name() + print("Report file saved to "+report_file) +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/rvr_scenario.py b/lanforge/lanforge-scripts/py-scripts/rvr_scenario.py new file mode 100755 index 000000000..45d09daa4 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/rvr_scenario.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# This script will set the LANforge to a BLANK database then it will load the specified database +# and start a graphical report +import sys +import os +import importlib +import argparse +from pprint import pprint +from time import sleep + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + +""" + cvScenario.lanforge_db = args.lanforge_db + if args.cv_test is not None: + cvScenario.cv_test = args.cv_test + if args.test_scenario is not None: + cvScenario.test_scenario = args.test_scenario +""" + +class RunCvScenario(LFCliBase): + def __init__(self, lfhost="localhost", lfport=8080, debug_=False, lanforge_db_=None, cv_scenario_=None, cv_test_=None, test_scenario_=None): + super().__init__( _lfjson_host=lfhost, _lfjson_port=lfport, _debug=debug_, _exit_on_error=True, _exit_on_fail=True) + self.lanforge_db = lanforge_db_ + self.cv_scenario = cv_scenario_ + self.cv_test = cv_test_ + self.test_profile = test_scenario_ + self.localrealm = Realm(lfclient_host=lfhost, lfclient_port=lfport, debug_=debug_) + self.report_name = None + + def get_report_file_name(self): + return self.report_name + + def build(self): + data = { + "name": "BLANK", + "action":"overwrite", + "clean_dut":"yes", + "clean_chambers": "yes" + } + self.json_post("/cli-json/load", data) + sleep(1) + port_counter = 0; + attempts = 6 + while (attempts > 0) and (port_counter > 0): + sleep(1) + attempts -= 1 + print("looking for ports like vap+") + port_list = self.localrealm.find_ports_like("vap+") + alias_map = LFUtils.portListToAliasMap(port_list) + port_counter = len(alias_map) + + port_list = self.localrealm.find_ports_like("sta+") + alias_map = LFUtils.portListToAliasMap(port_list) + port_counter += len(alias_map) + if port_counter == 0: + break + + if (port_counter != 0) and (attempts == 0): + print("There appears to be a vAP in this database, quitting.") + pprint(alias_map) + exit(1) + + data = { + "name": self.lanforge_db, + "action":"overwrite", + "clean_dut":"yes", + "clean_chambers": "yes" + } + self.json_post("/cli-json/load", data) + sleep(5) + self._pass("Loaded scenario %s" % self.lanforge_db, True) + return True + + def start(self, debug_=False): + # /gui_cli takes commands keyed on 'cmd', so we create an array of commands + commands = [ + #"cv apply '%s'" % self.cv_scenario, + "sleep 5", + "cv build", + "sleep 5", + "cv is_built", + "sleep 2", + "cv sync", + "sleep 1", + "cv create '%s' test_ref 'true'" % self.cv_test, + "sleep 5", + "cv load test_ref '%s'" % self.test_profile, + "sleep 2", + "cv click test_ref 'Auto Save Report'", + "sleep 4", + "cv click test_ref Start", + "sleep 2", + "cv click test_ref 'Another Iteration'", + "sleep 240", #sleep for (test duration for 1 time test takes x num iterations requested) - this example is 2 iterations + "cv click test_ref 'Another Iteration'", #unselect Another Iteration before test ends + "sleep 50" #finish test + "cv get test_ref 'Report Location:'", + "sleep 5", + "cv click test_ref 'Save HTML'", + "cv click test_ref 'Close'", + "sleep 1", + "cv click test_ref Cancel", + "sleep 1", + "exit" + ] + response_json = [] + for command in commands: + data = { + "cmd": command + } + try: + debug_par = "" + if debug_: + debug_par="?_debug=1" + if command.endswith("is_built"): + print("Waiting for scenario to build...", end='') + self.localrealm.wait_while_building(debug_=False) + print("...proceeding") + elif command.startswith("sleep "): + nap = int(command.split(" ")[1]) + print("sleeping %d..." % nap) + sleep(nap) + print("...proceeding") + else: + response_json = [] + print("running %s..." % command, end='') + response = self.json_post("/gui-json/cmd%s" % debug_par, data, debug_=False, response_json_list_=response_json) + if debug_: + LFUtils.debug_printer.pprint(response_json) + print("...proceeding") + + + except Exception as x: + print(x) + + self._pass("report finished", print_=True) + + + def stop(self): + pass + + def cleanup(self): + pass + + +def main(): + lfjson_host = "localhost" + lfjson_port = 8080 + parser = argparse.ArgumentParser( + prog="rvr_scenario.py", + formatter_class=argparse.RawTextHelpFormatter, + description="""LANforge Reporting Script: Load a scenario and run a RvR report +Example: +./load_ap_scenario.py --lfmgr 127.0.0.1 --lanforge_db 'handsets' --cv_test --test_scenario 'test-20' +""") + parser.add_argument("-m", "--lfmgr", type=str, + help="address of the LANforge GUI machine (localhost is default)") + parser.add_argument("-o", "--port", type=int, + help="IP Port the LANforge GUI is listening on (8080 is default)") + parser.add_argument("--lanforge_db", "--db", "--lanforge_scenario", type=str, + help="Name of test scenario database (see Status Tab)") + parser.add_argument("-c", "--cv_scenario", type=str, required=True, + help="Name of Chamber View test scenario (see CV Manage Scenarios)") + parser.add_argument("-n", "--cv_test", type=str, required = True, + help="Chamber View test") + parser.add_argument("-s", "--test_profile", "--test_settings", type=str, required=True, + help="Name of the saved CV test settings") + + args = parser.parse_args() + if args.lfmgr is not None: + lfjson_host = args.lfmgr + if args.port is not None: + lfjson_port = args.port + + run_cv_scenario = RunCvScenario(lfjson_host, lfjson_port) + + if args.lanforge_db is not None: + run_cv_scenario.lanforge_db = args.lanforge_db + if args.cv_scenario is not None: + run_cv_scenario.cv_scenario = args.cv_scenario + if args.cv_test is not None: + run_cv_scenario.cv_test = args.cv_test + if args.test_profile is not None: + run_cv_scenario.test_profile = args.test_profile + + if (run_cv_scenario.lanforge_db is None) or (run_cv_scenario.lanforge_db == ""): + run_cv_scenario.lanforge_db = "DFLT" + # raise ValueError("Please specificy scenario database name with --lanforge_db") + + if not (run_cv_scenario.build() and run_cv_scenario.passes()): + print("scenario failed to build.") + print(run_cv_scenario.get_fail_message()) + exit(1) + + if not (run_cv_scenario.start() and run_cv_scenario.passes()): + print("scenario failed to start.") + print(run_cv_scenario.get_fail_message()) + exit(1) + + if not (run_cv_scenario.stop() and run_cv_scenario.passes()): + print("scenario failed to stop:") + print(run_cv_scenario.get_fail_message()) + exit(1) + + if not (run_cv_scenario.cleanup() and run_cv_scenario.passes()): + print("scenario failed to clean up:") + print(run_cv_scenario.get_fail_message()) + exit(1) + + report_file = run_cv_scenario.get_report_file_name() + print("Report file saved to "+report_file) +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/__init__.py b/lanforge/lanforge-scripts/py-scripts/sandbox/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/csv_sqlite.py b/lanforge/lanforge-scripts/py-scripts/sandbox/csv_sqlite.py new file mode 100644 index 000000000..cb1f22b94 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/csv_sqlite.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +''' +File: will search sub diretories for kpi.csv and place the data into an sqlite database +Usage: csv_sqlite.py --path --database +''' + +import sys +if sys.version_info[0] != 3: + print("This script requires Python3") + exit +import pandas as pd +import sqlite3 +import argparse +from pathlib import Path + +class csv_to_sqlite(): + def __init__(self, + _path = '.', + _file = 'kpi.csv', + _database = 'qa_db', + _table = 'qa_table'): + self.path = _path + self.file = _file + self.database = _database + self.table = _table + + # information on sqlite database + # https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_sql.html + def store(self): + path = Path(self.path) + kpi_list = list(path.glob('**/{}'.format(self.file))) + + df = pd.DataFrame() + for kpi in kpi_list: + append_df = pd.read_csv(kpi, sep='\t') + df = df.append(append_df, ignore_index=True) + + print(self.database) + conn = sqlite3.connect(self.database) + #data may be appended setting if_exists='append' + df.to_sql(self.table,conn,if_exists='replace') + conn.close() + +def main(): + + parser = argparse.ArgumentParser( + prog='csv_sqlite.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + read kpi.csv into sqlit database + + ''', + description='''\ +File: will search path recursivly for kpi.csv and place into sqlite database +Usage: csv_sqlite.py --path --database + + ''') + parser.add_argument('--path', help='--path ./top directory path to kpi',required=True) + parser.add_argument('--file', help='--file kpi.csv',default='kpi.csv') + parser.add_argument('--database', help='--database qa_db',default='qa_db') + parser.add_argument('--table', help='--table qa_table',default='qa_table') + + args = parser.parse_args() + + __path = args.path + __file = args.file + __database = args.database + __table = args.table + + csv_sqlite = csv_to_sqlite( + _path = __path, + _file = __file, + _database = __database, + _table= __table) + + csv_sqlite.store() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_001_igg.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_001_igg.json new file mode 100644 index 000000000..8dcc304d6 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_001_igg.json @@ -0,0 +1,39 @@ +{ + "ct_igg":{ + "Notes":[ + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code.", + "this file contains the Influx, Grafana and Ghost configuration", + "Influx, Ghost, and Grafana are up and running on v-centos8s 192.168.100.153" + ] + }, + "test_database":{ + "database_config_influx": "True", + "database_host_influx": "192.168.100.153", + "database_port_influx": "8086", + "database_token_influx": "PwYwrDjUSpLyUa8-0QeJGuf9p6KgPgmTVs0Zz0BZiyele74pNasBMJR-dKiF3LE8Qft5tADHtPSIS0WcVXHc_g==", + "database_org_influx": "Candela", + "database_bucket_influx": "candela", + "database_tag_influx": "testbed CT-US-001", + "test_rig_influx": "CT-US-001" + }, + "test_dashboard":{ + "dashboard_config_grafana": "True", + "dashboard_host_grafana": "192.168.100.153", + "dashboard_token_grafana": "eyJrIjoid1hpM0pwZFRSc3c0bGU2TEpITEVscHh4T0pPMVdZRzEiLCJuIjoiY2h1Y2siLCJpZCI6MX0=" + }, + "test_blog":{ + "blog_config_ghost": "True", + "blog_host_ghost": "192.168.100.153", + "blog_token_ghost": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors_ghost": "Matthew", + "blog_customer_ghost": "candela", + "blog_user_push_ghost": "lanforge", + "blog_password_push_ghost": "lanforge", + "blog_flag_ghost": "--kpi_to_ghost" + } +} + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_id_102.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_id_102.json new file mode 100644 index 000000000..09ac28ff6 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_id_102.json @@ -0,0 +1,139 @@ +{ + "ct_us_001":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-102", + "lf_mgr_ip": "192.168.0.102", + "lf_mgr_port": "8080", + "dut_name": "GT-AXE11000", + "dut_bssid_5G": "fc:34:97:2b:38:94", + "test_timeout": 1200, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "chuck.rekiere@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge QA Testing CT-US-001", + "email_txt": "Lanforge QA Testing CT-US-001 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "axe11000_5G", + "ssid_pw_used": "lf_axe11000_5G", + "security_used": "wpa2", + "num_sta": 1, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth2" + }, + "test_database":{ + "database_config": "True", + "database_host": "192.168.100.201", + "database_port": "8086", + "database_token": "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==", + "database_org": "Candela", + "database_bucket": "lanforge_qa_testing", + "dut_set_name": "DUT_NAME ASUSRT-AX88U", + "database_tag": "testbed CT-US-102", + "test_rig": "CT-US-102" + }, + "test_dashboard":{ + "dashboard_config": "True", + "dashboard_host": "192.168.100.201", + "dashboard_token": "eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ==" + }, + "test_blog":{ + "blog_config": "True", + "blog_host": "192.168.100.153", + "blog_token": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors": "Matthew", + "blog_customer": "candela", + "blog_user_push": "lanforge", + "blog_password_push": "lanforge", + "blog_flag": "--kpi_to_ghost" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_l3":{ + "test_l3_longevity":{"enabled":"TRUE","load_db":"skip","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth2 --radio 'radio==wiphy1,stations==4,ssid==axe11000_5G,ssid_pw==lf_axe11000_5G,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "suite_wc_dp":{ + "CT-US-001_create_chamberview_dut_1":{"enabled":"TRUE","load_db":"skip","command":"create_chamberview_dut.py","args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=SSID_USED security=WPA2 password=SSID_PW_USED bssid=DUT_BSSID'", + " --ssid 'ssid_idx=1 ssid=SSID_USED security=WPA2 password=SSID_PW_USED bssid=DUT_BSSID'", + " --sw_version '5.4.3' --hw_version 5.12.14+ --serial_num ct523c-3b7b --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_64_ATH10K(9984)":{"enabled":"TRUE","load_db":"skip","command":"create_chamberview.py","args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" " + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)_64":{"enabled":"TRUE","load_db":"skip","command":"lf_wifi_capacity_test.py","args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH " + ] + }, + "CT-US-001_create_chamberview_dut_2":{"enabled":"TRUE","load_db":"skip","command":"create_chamberview_dut.py","args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=SSID_USED security=WPA2 password=SSID_PW_USED bssid=DUT_BSSID'", + " --ssid 'ssid_idx=1 ssid=SSID_USED security=WPA2 password=SSID_PW_USED bssid=DUT_BSSID'", + " --sw_version '3.5.4' --hw_version 5.12.14+ --serial_num ct523c-3b7b --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ath9K_200":{"enabled":"TRUE","load_db":"skip","command":"create_chamberview.py","args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 200 'DUT: DUT_NAME Radio-1' NA wiphy3,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH9K_200":{"enabled":"TRUE","load_db":"skip","command":"lf_wifi_capacity_test.py","args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH " + ] + }, + "CT-US-001_dataplane_ATH10K(9984)":{"enabled":"FALSE","load_db":"skip","command":"lf_dataplane_test.py","args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan1", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH " + ] + } + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_test_lf_check.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_test_lf_check.json new file mode 100644 index 000000000..ab6ccfe73 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_test_lf_check.json @@ -0,0 +1,30 @@ +{ + "ct_tests_lf_check":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_suites":{ + "suite_lf_check":{ + "CT-US-001_lf_check":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"./tools/lf_check.py", + "args":"", + "args_list":[ + " --json_rig ./tools/ct_us_001_rig.json --json_test ./tools/ct_tests.json --suite 'suite_wc_dp_short' --path '/home/lanforge/html-reports/ct-us-001'" + ] + } + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001.json new file mode 100644 index 000000000..7c7bb92f5 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001.json @@ -0,0 +1,687 @@ +{ + "ct_us_001":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-001", + "test_rig": "CT-US-001", + "lf_mgr_ip": "192.168.100.116", + "lf_mgr_port": "8080", + "dut_set_name": "DUT_NAME ASUSRT-AX88U", + "dut_name": "ASUSRT-AX88U", + "dut_bssid_2g": "3c:7c:3f:55:4d:60", + "dut_bssid_5g": "3c:7c:3f:55:4d:64", + "dut_sw": "3.0.0.4.386_44266", + "test_timeout": 300, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge QA Testing CT-US-001", + "email_txt": "Lanforge QA Testing CT-US-001 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 1, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth2" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"} + }, + "test_suites":{ + "suite_l3":{ + "test_l3_longevity":{"enabled":"TRUE","load_db":"skip","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth2 --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "auto_suite":{ + "CT-US-001_create_chamberview_dut_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-001_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-001_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 4 --max_stations_5 32 --max_stations_dual 4 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_lf_ap_auto_test1": { + "enabled": "FALSE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_2G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_lf_ap_auto_test_2": { + "enabled": "FALSE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"ghost_profile.py", + "args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp_mt":{ + "CT-US-001_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp_short":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG " + ] + }, + "CT-US-001_QA":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"./tools/lf_qa.py", + "args":"", + "args_list":[ + " --path REPORT_PATH --store --png --database ./tools/qa_test_db" + ] + } + }, + "suite_wc_dp_short_igg":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_dataplane_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan1", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_dut_for_AX210":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + "--lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy3,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\" " + ] + }, + "CT-US-001_wifi_capacity_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.wlan3 --test_tag 'AX210'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_dataplane_wiphy3_AX210_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan3", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'AX210'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_dut_for_mt7915e":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-001_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-001_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_ap_auto_2.4":{ + "CT-US-001_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-2 security=WPA2 password=hello123 bssid=DUT_BSSID_2G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-2 security=WPA2 password=hello123 bssid=DUT_BSSID_2G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-001_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-001_lf_ap_auto_test_2.4G_basic_client": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 5 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_ap_auto_5g":{ + "CT-US-001_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-001_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-001_lf_ap_auto_test_2.4G_basic_client": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 5 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_lf_ap_auto_test_5G": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001_igg.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001_igg.json new file mode 100644 index 000000000..bcac75e6f --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001_igg.json @@ -0,0 +1,126 @@ +{ + "ct_us_001":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_suites":{ + "suite_wc_dp_short":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG " + ] + }, + "CT-US-001_QA":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"./tools/lf_qa.py", + "args":"", + "args_list":[ + " --path REPORT_PATH --store --png --database ./tools/qa_test_db" + ] + } + }, + "suite_wc_dp_short_igg":{ + "CT-US-001_create_chamberview_dut_for_ATH10K":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-001_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-001_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-001_QA":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"./tools/lf_qa.py", + "args":"", + "args_list":[ + " --path REPORT_PATH --store --png --database ./tools/qa_001_test_db" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + } + } +} + + + + diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001_rig.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001_rig.json new file mode 100644 index 000000000..1006076c2 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_001_rig.json @@ -0,0 +1,56 @@ +{ + "ct_us_001":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_001", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-001", + "test_rig": "CT-US-001", + "lf_mgr_ip": "192.168.100.116", + "lf_mgr_port": "8080", + "dut_set_name": "DUT_NAME ASUSRT-AX88U", + "dut_name": "ASUSRT-AX88U", + "dut_bssid_2g": "3c:7c:3f:55:4d:60", + "dut_bssid_5g": "3c:7c:3f:55:4d:64", + "dut_sw": "3.0.0.4.386_44266", + "test_timeout": 300, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge QA Testing CT-US-001", + "email_txt": "Lanforge QA Testing CT-US-001 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 1, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth2" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"} + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_002.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_002.json new file mode 100644 index 000000000..8d82cb352 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_002.json @@ -0,0 +1,346 @@ +{ + "ct_us_002":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_002", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-002", + "test_rig": "CT-US-002", + "lf_mgr_ip": "192.168.100.200", + "lf_mgr_port": "8080", + "dut_set_name": "DUT_NAME NETGEAR59-5G", + "dut_name": "NETGEAR-AX12", + "dut_bssid_5g": "94:a6:7e:54:d4:33", + "test_timeout": 1200, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge QA Testing CT-US-002", + "email_txt": "Lanforge QA Testing CT-US-002" + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "NETGEAR59-5G", + "ssid_pw_used": "crispynest798", + "security_used": "wpa2", + "num_sta": 4, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "1.1.eth2" + }, + "test_database":{ + "database_config": "True", + "database_host": "192.168.100.201", + "database_port": "8086", + "database_token": "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==", + "database_org": "Candela", + "database_bucket": "lanforge_qa_testing", + "database_tag": "testbed CT-US-002" + }, + "test_dashboard":{ + "dashboard_config": "True", + "dashboard_host": "192.168.100.201", + "dashboard_token": "eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ==" + }, + "test_blog":{ + "blog_config": "True", + "blog_host": "192.168.100.153", + "blog_token": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors": "Matthew", + "blog_customer": "candela", + "blog_user_push": "lanforge", + "blog_password_push": "lanforge", + "blog_flag": "--kpi_to_ghost" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"4","SSID":"NETGEAR59-5G","PASSWD":"crispynest798","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"NETGEAR59-5G","PASSWD":"crispynest798","SECURITY":"wpa2"} +<<<<<<< Updated upstream +======= + }, + "test_suites":{ + "suite_two":{ + "test_l3_longevity":{"enabled":"TRUE","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port UPSTREAM_PORT --radio 'radio==wiphy1,stations==4,ssid==ct523c-vap,ssid_pw==ct523c-vap,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "auto_suite":{ + "CT-US-002_create_chamberview_dut_1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"4800", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"ghost_profile.py", + "args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp":{ + "CT-US-002_create_chamberview_dut_1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_ATH10k_sta64":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" ", + " --raw_line \"profile_link 1.1 routed-AP 1 NA NA wiphy0 -1 NA \" " + ] + }, + "CT-US-002_wifi_capacity_ATH10k(9984)":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream UPSTREAM_PORT --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-002_create_chamberview_dut_ATH10K_wan1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_ATH10k_wan1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" ", + " --raw_line \"profile_link 1.1 routed-AP 1 NA NA wiphy0.eth1 -1 NA \" " + ] + }, + "CT-US-002_dataplane_ATH10k(9984) CT-US-002":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream UPSTREAM_PORT --dut DUT_NAME --duration 30s --station 1.1.wlan1", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' ", + " --raw_line 'spatial_streams: 4' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-002_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_AX200_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_wifi_capacity_AX200 CT-US-002":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream UPSTREAM_PORT --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.wlan4 --test_tag 'ATH10K(9984)' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-002_create_chamberview_dut_AX200_wan1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_AX200_wan1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_dataplane_AX200":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream UPSTREAM_PORT --dut DUT_NAME --duration 30s --station 1.1.wlan4", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 4' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'AX200'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-002_create_chamberview_dut_auto":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=NETGEAR59-5G security=WPA2 password=crispynest798 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-002_create_chamberview_auto":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-002_lf_ap_auto_test": { + "enabled": "FALSE", + "command": "lf_ap_auto_test.py", + "timeout": "1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'AX200'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + } +>>>>>>> Stashed changes + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_003.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_003.json new file mode 100644 index 000000000..b8546745f --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_003.json @@ -0,0 +1,167 @@ +{ + "test_parameters":{ + "test_timeout": 200, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,chuck.rekiere@candelatech.com,", + "host_ip_production": "192.168.95.6", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.95.6", + "lf_mgr": "192.168.100.116", + "lf_mgr_ip": "192.168.100.233", + "email_title_txt": "Lanforge QA Testing CT-US-001", + "email_txt": "Lanforge QA Testing CT-US-001 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 4, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth1" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"4","SSID":"ct523c-vap","PASSWD":"ct523c-vap","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"4","SSID":"ct523c-vap","PASSWD":"ct523c-vap","SECURITY":"wpa2"} + }, + "test_suites": { + "CT-US-003_create_chamberview_dut_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=ct-us-ssid security=WPA2 password=ct-us-ssid bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=ct-us-ssid security=WPA2 password=ct-us-ssid bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-003_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" ", + " --raw_line \"profile_link 1.3 Test_AP 1 NA NA wiphy0, eth1 -1 NA\" " + ] + }, + "vap_suite": { + "CT-US-003_VAP": { + "enabled": "TRUE", + "command": "create_vap.py", + "args": "", + "args_list": [ + " --mgr LF_MGR_IP", + " --resource 3", + " --ssid ct-us-ssid", + " --password ct-us-ssid", + " --security wpa2", + " --radio wiphy1", + " --upstream_port eth1", + " --mode 802.11anAC" + ] + }, + "CT-US-003_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 4 --max_stations_5 32 --max_stations_dual 4 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-003_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=ct-us-ssid security=WPA2 password=ct-us-ssid bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=ct-us-ssid security=WPA2 password=ct-us-ssid bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-003_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"", + " --raw_line \"profile_link 1.3 Test_AP 1 NA NA wiphy0, eth1 -1 NA\" " + ] + }, + "CT-US-003_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-003_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ct-us-001-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-003_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + } + } + } +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_004.json b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_004.json new file mode 100644 index 000000000..5d3e9586b --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/ct_us_004.json @@ -0,0 +1,481 @@ +{ + "ct_us_004":{ + "Notes":[ + "The json is used to orchastrate the tests to be run on testbed ct_us_004", + "This json file is used as an input to the ./lf_check.py file", + "The variables that are all capitalized below are replaced with configuration", + "from the json file. so LF_MGR_IP in the test below is replaced by the json lf_mgr_ip", + "The replacement is loosely coupled so the upper and lower case convention is used", + "to identify replaced strings in the lf_check.py code." + ] + }, + "test_parameters":{ + "test_bed": "CT-US-004", + "test_rig": "CT-US-004", + "lf_mgr_ip": "192.168.100.194", + "lf_mgr_port": "8080", + "dut_set_name": "DUT_NAME Asus-RT-AX88U", + "dut_name": "Asus-RT-AX88U", + "dut_bssid_2g": "d4:5d:64:a0:7f:78", + "dut_bssid_5g": "d4:5d:64:a0:7f:7c", + "dut_sw": "3.0.0.4.386_44266", + "test_timeout": 300, + "load_blank_db": false, + "load_factory_default_db": true, + "load_custom_db": false, + "custom_db": "DFLT_ETH1_GEN", + "email_list_production": "konikofi@candelatech.com,greearb@candelatech.com,logan.lipke@candelatech.com,dipti.dhond@candelatech.com,chuck.rekiere@candelatech.com,matthew@candelatech.com,iain.davidson@candelatech.com,jreynolds@candelatech.com", + "host_ip_production": "192.168.100.201", + "email_list_test": "chuck.rekiere@candelatech.com", + "host_ip_test": "192.168.100.201", + "email_title_txt": "Lanforge QA Testing CT-US-004", + "email_txt": "Lanforge QA Testing CT-US-004 " + }, + "test_network":{ + "http_test_ip": "10.40.0.10", + "ftp_test_ip": "10.40.0.10", + "test_ip": "192.168.0.104" + }, + "test_generic":{ + "radio_used": "wiphy1", + "ssid_used": "asus11ax-5", + "ssid_pw_used": "hello123", + "security_used": "wpa2", + "num_sta": 1, + "col_names": "name,tx_byptes,rx_bytes,dropped", + "upstream_port": "eth2" + }, + "test_database":{ + "database_config": "True", + "database_host": "192.168.100.201", + "database_port": "8086", + "database_token": "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==", + "database_org": "Candela", + "database_bucket": "lanforge_qa_testing", + "database_tag": "testbed CT-US-004" + }, + "test_dashboard":{ + "dashboard_config": "True", + "dashboard_host": "192.168.100.201", + "dashboard_token": "eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ==" + }, + "test_blog":{ + "blog_config": "True", + "blog_host": "192.168.100.153", + "blog_token": "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742", + "blog_authors": "Matthew", + "blog_customer": "candela", + "blog_user_push": "lanforge", + "blog_password_push": "lanforge", + "blog_flag": "--kpi_to_ghost" + }, + "radio_dict":{ + "RADIO_0_CFG":{"KEY":"RADIO_0_CFG","RADIO":"wiphy0","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"}, + "RADIO_1_CFG":{"KEY":"RADIO_1_CFG","RADIO":"wiphy1","STATIONS":"1","SSID":"asus11ax-5","PASSWD":"hello123","SECURITY":"wpa2"} +<<<<<<< Updated upstream + } +======= + }, + "test_suites":{ + "suite_l3":{ + "test_l3_longevity":{"enabled":"TRUE","load_db":"skip","command":"test_l3_longevity.py","args":"--test_duration 15s --polling_interval 5s --upstream_port eth2 --radio 'radio==wiphy1,stations==4,ssid==asus11ax-5,ssid_pw==hello123,security==wpa2' --endp_type lf_udp --rates_are_totals --side_a_min_bps=20000 --side_b_min_bps=300000000"} + }, + "auto_suite":{ + "CT-US-004_create_chamberview_dut_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ]}, + "CT-US-004_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 64 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" ", + " --raw_line \"profile_link 1.1 routed-AP 1 NA NA wiphy0.eth1 -1 NA \" " + ] + }, + "CT-US-004_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_lf_ap_auto_test_2": { + "enabled": "FALSE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"ghost_profile.py", + "args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp_mt":{ + "CT-US-004_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G mode=5'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G mode=5'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"", + " --raw_line \"profile_link 1.1 routed-AP 1 NA NA wiphy0.eth1 -1 NA \" " + ] + }, + "CT-US-004_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"", + " --raw_line \"profile_link 1.1 routed-AP 1 NA NA wiphy0.eth1 -1 NA \" " + ] + }, + "CT-US-004_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + }, + "suite_wc_dp":{ + "CT-US-004_create_chamberview_dut_0":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_ATH10K(9984)_sta50":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 50 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"", + " --raw_line \"profile_link 1.1 routed-AP 1 NA NA wiphy0.eth1 -1 NA \" " + ] + }, + "CT-US-004_wifi_capacity_ATH10K(9984)":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"", + " --raw_line \"profile_link 1.1 routed-AP 1 NA NA wiphy0.eth1 -1 NA \" " + ] + }, + "CT-US-004_dataplane_ATH10K(9984)_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan1", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'ATH10K(9984)'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_dut_1":{ + "enabled":"FALSE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + "--lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_wiphy3_AX210_sta1":{ + "enabled":"FALSE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy3,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\" " + ] + }, + "CT-US-004_wifi_capacity_wiphy3_AX210_sta1":{ + "enabled":"FALSE", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --stations 1.1.wlan3", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_dataplane_wiphy3_AX210_sta1":{ + "enabled":"FALSE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan3", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_dut_for_mt7915e":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_mt7915e_sta19":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 19 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-004_wifi_capacity_mt7915e":{ + "enabled":"TRUE", + "timeout":"600", + "load_db":"skip", + "command":"lf_wifi_capacity_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-wct", + " --upstream 1.1.eth2 --batch_size 1,5,25 --loop_iter 1 --protocol UDP-IPv4 --duration 6000", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e'", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario CT-US-004-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy7,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA\"" + ] + }, + "CT-US-004_dataplane_ATH10K_mt7915e_sta1":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"lf_dataplane_test.py", + "args":"", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge --instance_name cicd-dpt", + " --config_name test_con --upstream 1.1.eth2 --dut asus_5g --duration 30s --station 1.1.wlan7", + " --download_speed 85% --upload_speed 0 --raw_line 'pkts: 60;88;120;256;512;1024;MTU' ", + " --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20'", + " --raw_line 'spatial_streams: 1' --pull_report --local_lf_report_dir REPORT_PATH --test_tag 'mt7915e' ", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "CT-US-004_create_chamberview_dut_2":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview_dut.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --dut_name DUT_NAME", + " --ssid 'ssid_idx=0 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --ssid 'ssid_idx=1 ssid=asus11ax-5 security=WPA2 password=hello123 bssid=DUT_BSSID_5G'", + " --sw_version DUT_SW --hw_version DUT_HW --serial_num DUT_SERIAL --model_num DUT_NAME" + ] + }, + "CT-US-004_create_chamberview_ap":{ + "enabled":"TRUE", + "load_db":"skip", + "command":"create_chamberview.py", + "args":"", + "args_list":[ + " --lfmgr LF_MGR_IP --port LF_MGR_PORT --delete_scenario", + " --create_scenario ucentral-scenario ", + " --raw_line \"profile_link 1.1 STA-AC 32 'DUT: DUT_NAME Radio-1' NA wiphy1,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 STA-AC 1 'DUT: DUT_NAME Radio-1' NA wiphy4,AUTO -1 NA\" ", + " --raw_line \"profile_link 1.1 upstream-dhcp 1 NA NA UPSTREAM_PORT,AUTO -1 NA \" " + ] + }, + "CT-US-004_lf_ap_auto_test": { + "enabled": "TRUE", + "command": "lf_ap_auto_test.py", + "timeout":"1200", + "args": "", + "args_list":[ + " --mgr LF_MGR_IP --port LF_MGR_PORT --lf_user lanforge --lf_password lanforge", + " --instance_name ap-auto-instance --config_name test_con --upstream UPSTREAM_PORT", + " --dut5_0 'DUT_NAME lanforge DUT_BSSID_5G (1)' --dut2_0 'DUT_NAME lanforge DUT_BSSID_5G (1)'", + " --max_stations_2 32 --max_stations_5 32 --max_stations_dual 100 --radio2 1.1.wiphy1", + " --radio5 1.1.wiphy2 --set 'Basic Client Connectivity' 1", + " --set 'Multi Band Performance' 0 --set 'Stability' 0 --set 'Multi-Station Throughput vs Pkt Size' 0,", + " --set 'Throughput vs Pkt Size' 0 --set 'Capacity' 0 --set 'Band-Steering' 0 --set 'Skip 2.4 Ghz Tests' 1", + " --pull_report --local_lf_report_dir REPORT_PATH --test_tag ATH10K(9984)", + " --test_rig TEST_RIG --influx_host DATABASE_HOST --influx_port DATABASE_PORT --influx_org DATABASE_ORG", + " --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET --influx_tag DATABASE_TAG --set DUT_SET_NAME" + ] + }, + "GHOST":{"enabled":"TRUE","load_db":"skip","command":"ghost_profile.py","args":"", + "args_list":[ + " --ghost_token BLOG_TOKEN --ghost_host BLOG_HOST --authors BLOG_AUTHORS --customer BLOG_CUSTOMER", + " --user_push BLOG_USER_PUSH --password BLOG_PASSWORD_PUSH BLOG_FLAG --grafana_token DASHBOARD_TOKEN", + " --grafana_host DASHBOARD_HOST --grafana_bucket DATABASE_BUCKET --parent_folder REPORT_PATH", + " --influx_host DATABASE_HOST --influx_org DATABASE_ORG --influx_token=DATABASE_TOKEN --influx_bucket DATABASE_BUCKET", + " --influx_tag DATABASE_TAG " + ] + } + } + } +>>>>>>> Stashed changes +} + + + + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/jbr_jag_test.py b/lanforge/lanforge-scripts/py-scripts/sandbox/jbr_jag_test.py new file mode 100755 index 000000000..44f088e59 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/jbr_jag_test.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +''' +NAME: jbr_jag_test.py + +PURPOSE: exercises the LANforge/lf_json_autogen.py library + +EXAMPLE: +$ ./jbr_jag_test.py --host ct521a-jana --test set_port + +To enable using lf_json_autogen in other parts of the codebase, set LF_USE_AUTOGEN=1: +$ LF_USE_AUTOGEN=1 ./jbr_jag_test.py --test set_port --host ct521a-lion + +NOTES: + + +TO DO NOTES: + +''' +import sys + +if sys.version_info[0] != 3: + print("This script requires Python3") + exit() + +sys.path.insert(1, "../../py-json") +import argparse +import pprint +import traceback +from LANforge import lf_json_autogen +from LANforge.lf_json_autogen import LFJsonGet as LFG +from LANforge.lf_json_autogen import LFJsonPost as LFP +# import LANforge.lfcli_base +# from LANforge.lfcli_base import LFCliBase +from realm import Realm +from station_profile import StationProfile + + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # +class TestStation(Realm): + def __init__(self, + host="localhost", + port=8080, + _debug_on=True, + _exit_on_error=True, + _exit_on_fail=False): + super().__init__(lfclient_host=host, + lfclient_port=port, + debug_=_debug_on, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail) + + def run(self, get_request: LFG = None, post_request: LFP = None): + station_profile = StationProfile( + "http://%s:%s" % (self.lfclient_host, self.lfclient_port), + self, + ssid="jedway-r7800-5G", + ssid_pass="jedway-r7800-5G", + security="wpa2", + number_template_="000", + mode=0, + up=True, + resource=1, + shelf=1, + dhcp=True, + debug_=self.debug, + use_ht160=False) + + print("Checking for previous station:") + try: + response = get_request.get_port(eid_list="1.1.sta000", requested_col_names=['_links', 'alias'], + debug_=self.debug) + if response: + pprint.pprint(response) + print("Deleting previous station:") + post_request.post_rm_vlan(shelf=1, resource=1, port="sta000", debug_=self.debug) + else: + print("Previous station not seen") + + print("Creating station:") + station_profile.create(radio="1.1.wiphy0", + num_stations=1, + sta_names_=['sta000'], + debug=True) + print("Created station:") + response = get_request.get_port("1.1.sta000") + if response: + pprint.pprint(response) + else: + print("Station did not get created.") + + except Exception as x: + traceback.print_tb(x) + print(x.__repr__()) + exit(1) + + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # +def main(): + parser = argparse.ArgumentParser( + prog=__file__, + formatter_class=argparse.RawTextHelpFormatter, + description='tests lf_json_autogen') + parser.add_argument("--host", help='specify the GUI to connect to, assumes port 8080') + parser.add_argument("--test", help='specify a test to run') + + args = parser.parse_args() + if not args.test: + print("No test requested") + exit(1) + post_request = lf_json_autogen.LFJsonPost(lfclient_host=args.host, + lfclient_port=8080, + debug_=True, + _exit_on_error=True) + get_request = LFG(lfclient_host=args.host, + lfclient_port=8080, + debug_=True, + _exit_on_error=True) + + if args.test.endswith("get_port"): + test_get_port(args, get_request=get_request, post_request=post_request) + if args.test.endswith("set_port"): + test_set_port(args, get_request=get_request, post_request=post_request) + + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # +def test_get_port(args=None, + get_request: LFG = None, + post_request: LFP = None): + print("test_get_port") + if not args: + raise ValueError("test_get_port needs args") + + result = get_request.get_port(eid_list=["1.1.eth0", "1.1.eth1", "1.1.eth2"], + requested_col_names=(), + debug_=True) + pprint.pprint(result) + + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # +def test_set_port(args=None, + get_request: LFG = None, + post_request: LFP = None): + print("test_set_port") + if not args: + raise ValueError("test_set_port needs args") + + my_current_flags = 0 + my_interest_flags = 0 + try: + my_current_flags = LFP.set_flags(LFP.SetPortCurrentFlags, + 0, + ['if_down', 'use_dhcp']) + + my_current_flags = LFP.set_flags(LFP.SetPortCurrentFlags, + my_current_flags, + [ + LFP.SetPortCurrentFlags.if_down, + LFP.SetPortCurrentFlags.use_dhcp + ]) + + my_interest_flags = LFP.set_flags(LFP.SetPortInterest, + 0, + [ + 'current_flags', + 'ifdown', + 'dhcp' + ]) + post_request.post_set_port(current_flags=my_current_flags, # See above, or NA. + current_flags_msk=my_current_flags, + mac='NA', + # This sets 'interest' for flags 'Enable RADIUS service' and higher. See above, or NA. + interest=my_interest_flags, + port='eth2', # Port number for the port to be modified. + report_timer=2000, + resource=1, # Resource number for the port to be modified. + shelf=1, # Shelf number for the port to be modified. + debug_=True) + + my_current_flags = LFP.clear_flags(LFP.SetPortCurrentFlags, + my_current_flags, + flag_names=LFP.SetPortCurrentFlags.use_dhcp) + my_current_flags = LFP.clear_flags(LFP.SetPortCurrentFlags, + my_current_flags, + flag_names=[LFP.SetPortCurrentFlags.if_down]) + + my_interest_flags = LFP.set_flags(LFP.SetPortInterest, + 0, + [ + 'current_flags', + 'ifdown', + 'dhcp', + LFP.SetPortInterest.ip_address, + LFP.SetPortInterest.ip_gateway, + LFP.SetPortInterest.ip_Mask, + ]) + + post_request.post_set_port(alias=None, # A user-defined name for this interface. Can be BLANK or NA. + current_flags=my_current_flags, # See above, or NA. + current_flags_msk=my_current_flags, + mac='NA', + ip_addr='10.32.23.1', + netmask='255.255.255.0', + gateway='0.0.0.0', + # This sets 'interest' for flags 'Enable RADIUS service' and higher. See above, or NA. + interest=my_interest_flags, + port='eth2', # Port number for the port to be modified. + report_timer=2000, + resource=1, # Resource number for the port to be modified. + shelf=1, # Shelf number for the port to be modified. + debug_=True) + + result = get_request.get_port(eid_list="1.1.eth2", + requested_col_names=["_links", + "alias", + "port", + "mac", + "down", + "ip", + "PORT_SUPPORTED_FLAGS_L", + "PORT_SUPPORTED_FLAGS_H", + "PORT_CUR_FLAGS_L", + "PORT_CUR_FLAGS_H"], + debug_=True) + pprint.pprint(result) + except Exception as x: + traceback.print_tb(x) + print(x.__repr__()) + exit(1) + + # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # + # Create a station using the station_profile object + # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # + try: + station_test = TestStation(host=args.host, + port=8080, + _debug_on=False) + station_test.run(get_request=get_request, post_request=post_request) + + except Exception as x: + traceback.print_tb(x) + print(x.__repr__()) + exit(1) + + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # + + +if __name__ == "__main__": + main() +# diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_3.py b/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_3.py new file mode 100755 index 000000000..da7f648a7 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_3.py @@ -0,0 +1,54 @@ +# Run this app with `python app.py` and +# visit http://127.0.0.1:8050/ in your web browser. + +import dash +import dash_core_components as dcc +import dash_html_components as html +import plotly.express as px +import pandas as pd + + +external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] + +app = dash.Dash(__name__, external_stylesheets=external_stylesheets) + +df = pd.read_csv('http://192.168.95.6/html-reports/2021-07-20-16-25-05_lf_check/dataplane-2021-07-20-04-28-42/kpi.csv', sep='\t') + +append_df = pd.read_csv('http://192.168.95.6/html-reports/2021-07-24-03-00-01_lf_check/dataplane-2021-07-24-03-06-02/kpi.csv', sep='\t') + +df = df.append(append_df, ignore_index=True) + +#print(df) + +fig = (px.scatter(df, x="Date", y="numeric-score", + color="short-description", hover_name="short-description", + size_max=60)).update_traces(mode='lines+markers') + +''' +fig = px.scatter(df, x="Date", y="numeric-score", + color="short-description", hover_name="short-description", + size_max=60) +''' +''' +fig = px.scatter(df, x="short-description", y="numeric-score", + color="short-description", hover_name="short-description", + size_max=60) +''' +fig .update_layout( + title="Throughput vs Packet size", + xaxis_title="Packet Size", + yaxis_title="Mbps", + xaxis = {'type' : 'date'} +) + + +app.layout = html.Div([ + dcc.Graph( + id='packet-size vs rate', + figure=fig + ) +]) + +if __name__ == '__main__': + app.run_server(debug=True) + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_csv_sq.py b/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_csv_sq.py new file mode 100755 index 000000000..b91514d79 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_csv_sq.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python3 +''' +File: read kpi.csv place in sql database, create png of historical kpi and present graph on dashboard +Usage: kpi_csv_sq.py --store --png --show --path --database +Example: kpi_csv_sq.py --show (show dashboard generated from database) +Example: kpi_csv_sq.py --store --png --show --path (read kpi.csv store to database, write png, show dashboard ) + +''' +# visit http://127.0.0.1:8050/ in your web browser. + +import os +import dash +import dash_core_components as dcc +import dash_html_components as html +import plotly.express as px +import pandas as pd +import sqlite3 +import argparse +from pathlib import Path +import time + +# Any style components can be used +external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] + +class csv_sqlite_dash(): + def __init__(self, + _path = '.', + _file = 'kpi.csv', + _database = 'qa_db', + _table = 'qa_table', + _png = False): + self.path = _path + self.file = _file + self.database = _database + self.table = _table + self.png = _png + self.png_generated = False + self.kpi_list = [] + self.html_list = [] + self.conn = None + self.df = pd.DataFrame() + self.plot_figure = [] + self.children_div = [] + self.server_html_reports = 'http://192.168.95.6/html-reports/' #TODO pass in server + self.server = 'http://192.168.95.6/' #TODO pass in server + self.server_started = False + self.app = dash.Dash(__name__, external_stylesheets=external_stylesheets) + # https://community.plotly.com/t/putting-a-dash-instance-inside-a-class/6097/3 + #https://dash.plotly.com/dash-html-components/button + #self.app.callback(dash.dependencies.Output('container-button-basic', 'children'), + # [dash.dependencies.Input(component_id ='submit-val', component_property ='n_clicks')])(self.show) + + # information on sqlite database + # https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_sql.html + def store(self): + print("reading kpi and storing in db {}".format(self.database)) + path = Path(self.path) + self.kpi_list = list(path.glob('**/kpi.csv')) # Hard code for now + + if not self.kpi_list: + print("WARNING: used --store , no new kpi.csv found, check input path or remove --store from command line") + + for kpi in self.kpi_list: #TODO note empty kpi.csv failed test + df_kpi_tmp = pd.read_csv(kpi, sep='\t') + df_kpi_tmp['kpi_path'] = str(kpi).replace('kpi.csv','') # only store the path to the kpi.csv file + df_kpi_tmp = df_kpi_tmp.append(df_kpi_tmp, ignore_index=True) + self.df = self.df.append(df_kpi_tmp, ignore_index=True) + + self.conn = sqlite3.connect(self.database) + try: + self.df.to_sql(self.table,self.conn,if_exists='append') + except: + print("attempt to append to database with different column layout, casused exception, input new name --database ") + exit(1) + self.conn.close() + + def generate_graph_png(self): + print("generating graphs to display") + print("generate_graph_png: {}".format(time.time())) + + #https://datacarpentry.org/python-ecology-lesson/09-working-with-sql/index.html- + self.conn = sqlite3.connect(self.database) + df3 = pd.read_sql_query("SELECT * from {}".format(self.table) ,self.conn) #current connection is sqlite3 /TODO move to SQLAlchemy + # sort by date column + try: + df3 = df3.sort_values(by='Date') + except: + print("Database empty: KeyError(key) when sorting by Date, check Database name, path to kpi, typo in path, exiting") + exit(1) + self.conn.close() + + # graph group and test-tag are used for detemining the graphs, can use any columns + # the following list manipulation removes the duplicates + graph_group_list = list(df3['Graph-Group']) + graph_group_list = list(set(graph_group_list)) + + test_tag_list = list(df3['test-tag']) + test_tag_list = list(set(test_tag_list)) + + test_rig_list = list(df3['test-rig']) + test_rig_list = list(set(test_rig_list)) + + self.children_div.append(html.A('html_reports', href=self.server_html_reports, target='_blank')) + for test_rig in test_rig_list: + for test_tag in test_tag_list: + for group in graph_group_list: + df_tmp = df3.loc[(df3['test-rig'] == test_rig) & (df3['Graph-Group'] == str(group)) & (df3['test-tag'] == str(test_tag))] + if df_tmp.empty == False: + kpi_fig = (px.scatter(df_tmp, x="Date", y="numeric-score", + color="short-description", hover_name="short-description", + size_max=60)).update_traces(mode='lines+markers') + + df_tmp = df_tmp.sort_values(by='Date') + test_id_list = list(df_tmp['test-id']) + kpi_path_list = list(df_tmp['kpi_path']) + + units_list = list(df_tmp['Units']) + + kpi_fig.update_layout( + title="{} : {} : {} : {}".format(test_id_list[-1], group, test_tag, test_rig), + xaxis_title="Time", + yaxis_title="{}".format(units_list[-1]), + xaxis = {'type' : 'date'} + ) + # save the figure - figures will be over written png + # for testing + png_server_img = '' + if self.png: + if self.png_generated: + pass + else: + print("generating png files") + print("kpi_path:{}".format(df_tmp['kpi_path'])) + png_path = os.path.join(kpi_path_list[-1],"kpi.png") # use simple names {}_{}_{}_{}_kpi.png".format(test_id_list[-1], group, test_tag, test_rig)) + html_path = os.path.join(kpi_path_list[-1],"kpi.html") # use simple names {}_{}_{}_{}_kpi.html".format(test_id_list[-1], group, test_tag, test_rig)) + print("png_path {}".format(png_path)) + png_server_img = self.server + png_path.replace('/home/lanforge','') + print("png_server_img {}".format(png_server_img)) + kpi_fig.write_image(png_path,scale=1,width=1200,height=350) + #https://plotly.com/python/interactive-html-export/ + kpi_fig.write_html(html_path) + # png of graph data to display + self.children_div.append(html.Img(src=png_server_img)) + + # use image from above to creat html display - this uses dynamic graphing + #self.children_div.append(dcc.Graph(figure=kpi_fig)) + + #TODO the link must be to a server to display html + # WARNING: DO NOT USE os.path.join will use the path for where the script is RUN which can be container. + # need to construct path to server manually. + #TODO need to work out the reporting paths - pass in path adjust + self.children_div.append(html.Br()) + + # link to interactive results + kpi_html_path = self.server + kpi_path_list[-1] + "kpi.html" + kpi_html_path = kpi_html_path.replace('/home/lanforge/','') + self.children_div.append(html.Br()) + self.children_div.append(html.A('{}_{}_{}_{}_kpi.html'.format(test_id_list[-1], group, test_tag, test_rig), + href=kpi_html_path, target='_blank')) + + # link to full test results + index_html_path = self.server + kpi_path_list[-1] + "index.html" + index_html_path = index_html_path.replace('/home/lanforge/','') + self.children_div.append(html.Br()) + self.children_div.append(html.A('{}_{}_{}_{}_index.html'.format(test_id_list[-1], group, test_tag, test_rig), + href=index_html_path, target='_blank')) + self.children_div.append(html.Br()) + self.children_div.append(html.Br()) + self.children_div.append(html.Br()) + + # TODO see if this stops the regenration of the graphs each time + self.png_generated = True + + + # access from server + # https://stackoverflow.com/questions/61678129/how-to-access-a-plotly-dash-app-server-via-lan + #def show(self,n_clicks): + def show(self): + #print("refreshes: {}".format(n_clicks)) + self.generate_graph_png() + if not self.children_div: + print("NOTE: test-tag may not be present in the kpi thus no results generated") + print("show: {}".format(time.time())) + self.app.layout = html.Div([ + html.Div(id='my-output'), + html.H1(children= "LANforge Testing",className="lanforge", + style={'color':'green','text-align':'center'}), + #html.Button('Submit Recalculate',id='submit-val', n_clicks=0), + #html.Div(id='container-button-basic', children='to recalculate hit submit'), + html.H2(children= "Results",className="ts1", + style={'color':'#00361c','text-align':'left'}), + # images_div is already a list, children = a list of html components + # remove scrolling : html.Div(children= self.children_div, style={"maxHeight": "600px", "overflow": "scroll"} ), + html.Div(children= self.children_div ), + html.A('www.candelatech.com',href='http://www.candelatech.com', target='_blank', + style={'color':'#00361c','text-align':'left'}), + ]) + + # save as standalone files + #https://plotly.com/python/static-image-export/ + + if self.server_started: + print("refresh complete") + pass + else: + self.server_started = True + print("self.server_started {}".format(self.server_started)) + #NOTE: the server_started flag needs to be set prior to run_server (or you get to debug an infinite loop) + self.app.run_server(host= '0.0.0.0', debug=True) + # host = '0.0.0.0' allows for remote access, local debug host = '127.0.0.1' + # app.run_server(host= '0.0.0.0', debug=True) + +def main(): + + parser = argparse.ArgumentParser( + prog='kpi_csv_sq.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + read kpi.csv into sqlite database , save png of history and preset on dashboard + + ''', + description='''\ +File: read kpi.csv place in sql database, create png of historical kpi and present graph on dashboard +Usage: kpi_csv_sq.py --store --png --show --path --database +Example: kpi_csv_sq.py --show (show dashboard generated from database) +Example: kpi_csv_sq.py --store --png --show --path (read kpi.csv store to database, write png, show dashboard ) + + ''') + parser.add_argument('--path', help='--path top directory path to kpi if regererating database or png files',default='') + parser.add_argument('--file', help='--file kpi.csv default: kpi.csv',default='kpi.csv') #TODO is this needed + parser.add_argument('--database', help='--database qa_test_db default: qa_test_db',default='qa_test_db') + parser.add_argument('--table', help='--table qa_table default: qa_table',default='qa_table') + parser.add_argument('--store', help='--store , store kpi to db, action store_true',action='store_true') + parser.add_argument('--png', help='--png, generate png for kpi in path, generate display, action store_true',action='store_true') + parser.add_argument('--show', help='--show generate display and show dashboard, action store_true',action='store_true') + + args = parser.parse_args() + + __path = args.path + __file = args.file + __database = args.database + __table = args.table + __png = args.png + + # needed for refresh button + # n_clicks = 0 + + print("config: path:{} file:{} database:{} table:{} store:{} png:{} show:{} " + .format(__path,__file,__database,__table,args.store, args.png,args.show)) + + if(__path == '' and args.store == True): + print("--path must be entered if --store , exiting") + exit(1) + + if(args.png == True and args.store == False): + print("if --png set to create png files then --store must also be set, exiting") + exit(1) + + if(args.png == True and args.show == True): + print("WARNING: generating png files will effect initial display performance") + + csv_dash = csv_sqlite_dash( + _path = __path, + _file = __file, + _database = __database, + _table = __table, + _png = __png) + if args.store: + csv_dash.store() + if args.png: + csv_dash.generate_graph_png() + if args.show: + #csv_dash.show(n_clicks) + csv_dash.show() + + if args.store == False and args.png == False and args.show == False: + print("Need to enter an action of --store --png --show ") + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_sqlite.py b/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_sqlite.py new file mode 100755 index 000000000..b8490662e --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/kpi_sqlite.py @@ -0,0 +1,77 @@ +# Run this app with `python app.py` and +# visit http://127.0.0.1:8050/ in your web browser. + +import dash +import dash_core_components as dcc +import dash_html_components as html +import plotly.express as px +import pandas as pd +import sqlite3 + + +external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] + +app = dash.Dash(__name__, external_stylesheets=external_stylesheets) + +# load data +df = pd.read_csv('http://192.168.95.6/html-reports/2021-07-20-16-25-05_lf_check/dataplane-2021-07-20-04-28-42/kpi.csv', sep='\t') +append_df = pd.read_csv('http://192.168.95.6/html-reports/2021-07-24-03-00-01_lf_check/dataplane-2021-07-24-03-06-02/kpi.csv', sep='\t') +df = df.append(append_df, ignore_index=True) + +# information on sqlite database +# https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_sql.html + +# write to data base local for now +conn = sqlite3.connect("qa_db") + +# can append +df.to_sql("dp_table",conn,if_exists='append') + +conn.close() + +conn = sqlite3.connect("qa_db") + + +#https://datacarpentry.org/python-ecology-lesson/09-working-with-sql/index.html +df2 = pd.read_sql_query("SELECT * from dp_table" ,conn) +print(df.head()) +conn.close() +#print(df) + +fig = (px.scatter(df2, x="Date", y="numeric-score", + color="short-description", hover_name="short-description", + size_max=60)).update_traces(mode='lines+markers') + +''' +fig = px.scatter(df, x="Date", y="numeric-score", + color="short-description", hover_name="short-description", + size_max=60) +''' +''' +fig = px.scatter(df, x="short-description", y="numeric-score", + color="short-description", hover_name="short-description", + size_max=60) +''' +fig.update_layout( + title="Throughput vs Packet size", + xaxis_title="Packet Size", + yaxis_title="Mbps", + xaxis = {'type' : 'date'} +) + + +app.layout = html.Div([ + dcc.Graph( + id='packet-size vs rate', + figure=fig + ), + dcc.Graph( + id='packet-size vs rate2', + figure=fig + ) + +]) + +if __name__ == '__main__': + app.run_server(debug=True) + \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/lf_check_ap.py b/lanforge/lanforge-scripts/py-scripts/sandbox/lf_check_ap.py new file mode 100755 index 000000000..22049a885 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/lf_check_ap.py @@ -0,0 +1,134 @@ +#!/usr/bin/python3 + +''' +NAME: +lf_check.py + +PURPOSE: +Script to verify connectivity to an AP + +EXAMPLE: + +./lf_check_ap.py --ap_port '/dev/ttyUSB0' --ap_baud '115200' --ap_cmd "wl -i wl1 bs_data" + +./lf_check_ap.py --ap_port '/dev/ttyUSB0' --ap_baud '115200' --ap_cmd "wl -i wl1 bs_data" --ap_file 'ap_file.txt' + + + +NOTES: + +Script is in the sandbox +run /py-scripts/update_dependencies.py + +''' + +import sys +if sys.version_info[0] != 3: + print("This script requires Python3") + exit() + +import argparse +import pexpect +import serial +from pexpect_serial import SerialSpawn + + +# see https://stackoverflow.com/a/13306095/11014343 +class FileAdapter(object): + def __init__(self, logger): + self.logger = logger + def write(self, data): + # NOTE: data can be a partial line, multiple lines + data = data.strip() # ignore leading/trailing whitespace + if data: # non-blank + self.logger.info(data) + def flush(self): + pass # leave it to logging to flush properly + + +class lf_check(): + def __init__(self, + _ap_port, + _ap_baud, + _ap_cmd, + _ap_file): + self.ap_port = _ap_port + self.ap_baud = _ap_baud + self.ap_cmd = _ap_cmd + self.ap_file = _ap_file + + def ap_action(self): + + print("ap_cmd: {}".format(self.ap_cmd)) + try: + ser = serial.Serial(self.ap_port, int(self.ap_baud), timeout=5) + ss = SerialSpawn(ser) + ss.sendline(str(self.ap_cmd)) + ss.expect([pexpect.TIMEOUT], timeout=2) # do not detete line, waits for output + ap_results = ss.before.decode('utf-8','ignore') + print("ap_results {}".format(ap_results)) + except: + ap_results = "exception on accessing {} Command: {}\r\n".format(self.ap_port,self.ap_cmd) + print("{}".format(ap_results)) + + if(self.ap_file != None): + ap_file = open(str(self.ap_file),"a") + ap_file.write(ap_results) + ap_file.close() + print("ap file written {}".format(str(self.ap_file))) + +def main(): + + parser = argparse.ArgumentParser( + prog='lf_check.py', + #formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Useful Information: + 1. Useful Information goes here + ''', + + description='''\ +lf_check.py: +-------------------- +#ssid TCH-XB7 +#ssidpw comcats123 +Summary : +---------- +This file is used for verification + +Commands: (wl1 == 5ghz , wl0 == 24ghz) + +read ap data:: 'wl -i wl1 bs_data' +reset scheduler's counters:: 'wl -i wl1 dump_clear' +UL scheduler statistics:: 'wl -i wl1 dump umsched' +DL scheduler statistics:: 'wl -i wl1 dump msched' + +Generic command layout: +----------------------- + + ''') + parser.add_argument('--ap_port', help='--ap_port \'/dev/ttyUSB0\'',default='/dev/ttyUSB0') + parser.add_argument('--ap_baud', help='--ap_baud \'115200\'',default='115200') + parser.add_argument('--ap_cmd', help='--ap_cmd \'wl -i wl1 bs_data\'',default='wl -i wl1 bs_data') + parser.add_argument('--ap_file', help='--ap_file \'ap_file.txt\'') + + args = parser.parse_args() + + __ap_port = args.ap_port + __ap_baud = args.ap_baud + __ap_cmd = args.ap_cmd + __ap_file = args.ap_file + + check = lf_check( + _ap_port = __ap_port, + _ap_baud = __ap_baud, + _ap_cmd = __ap_cmd , + _ap_file = __ap_file) + + check.ap_action() + +if __name__ == '__main__': + main() + + diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/lf_check_igg.py b/lanforge/lanforge-scripts/py-scripts/sandbox/lf_check_igg.py new file mode 100755 index 000000000..2490daa70 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/lf_check_igg.py @@ -0,0 +1,1541 @@ +#!/usr/bin/python3 + +''' +NAME: lf_check.py + +PURPOSE: lf_check.py run tests based on test rig json input, test dut json input, and test command line inputs + +EXAMPLE: + +./lf_check.py --json_rig --json_dut --json_test --test_suite --path +./lf_check.py --json_rig --json_dut --json_test --test_suite --path --production + +./lf_check.py --json_rig ct_us_001_rig.json --json_dut ct_001_AX88U_dut.json --json_test ct_us_001_tests.json --suite "suite_wc_dp" --path '/home/lanforge/html-reports/ct-us-001' + + +rig is the LANforge +dut is the device under test +NOTE : if all json data (rig,dut,tests) in same json file pass same json in for all 3 inputs + +NOTES: +Create three json files: 1. discribes rig (lanforge), dut (device under test) and other for the description of the tests + +Starting LANforge: + On local or remote system: /home/lanforge/LANforgeGUI/lfclient.bash -cli-socket 3990 -s LF_MGR + On local system the -s LF_MGR will be local_host if not provided + +### Below are development Notes ### + + +NOTES: getting radio information: +1. (Using Curl) curl -H 'Accept: application/json' http://localhost:8080/radiostatus/all | json_pp +2. , where --user "USERNAME:PASSWORD" +# https://itnext.io/curls-just-want-to-have-fun-9267432c4b55 +3. (using Python) response = self.json_get("/radiostatus/all"), Currently lf_check.py is independent of py-json libraries + +4. if the connection to 8080 is rejected check : pgrep -af java , to see the number of GUI instances running + +GENERIC NOTES: +Starting LANforge: + On local or remote system: /home/lanforge/LANforgeGUI/lfclient.bash -cli-socket 3990 -s LF_MGR + On local system the -s LF_MGR will be local_host if not provided + Saving stdout and stderr to find LANforge issues + ./lfclient.bash -cli-socket 3990 > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2) + + + On LANforge ~lanforge/.config/autostart/LANforge-auto.desktop is used to restart lanforge on boot. + http://www.candelatech.com/cookbook.php?vol=misc&book=Automatically+starting+LANforge+GUI+on+login + +1. add server (telnet localhost 4001) build info, GUI build sha, and Kernel version to the output. + A. for build information on LANforgeGUI : /home/lanforge ./btserver --version + B. for the kernel version uname -r (just verion ), uname -a build date + C. for getting the radio firmware: ethtool -i wlan0 + +# may need to build in a testbed reboot at the beginning of a day's testing... +# seeing some dhcp exhaustion and high latency values for testbeds that have been running +# for a while that appear to clear up once the entire testbed is power cycled + +# Capture the LANforge client output sdtout and stdwrr +/home/lanforge/LANforgeGUI/lfclient.bash -cli-socket 3990 -s LF_MGR command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2) + +# To get the --raw_line commands +# Build Chamber View Scenario in the GUI and then # copy/tweak what it shows in the 'Text Output' tab after saving and re-opening # the scenario + +# issue a shutdown command on the lanforge(s) +# ssh root@lanforge reboot (need to verify) or do a shutdown +# send curl command to remote power switch to reboot testbed +# curl -s http://admin:lanforge@192.168.100.237/outlet?1=CCL -o /dev/null 2>&1 +# + + +''' +import datetime +import sys +import traceback + +if sys.version_info[0] != 3: + print("This script requires Python3") + exit() + +import os +import socket +import logging +import time +from time import sleep +import argparse +import json +import subprocess +import csv +import shlex +import paramiko +import pandas as pd +import requests + +# lf_report is from the parent of the current file +dir_path = os.path.dirname(os.path.realpath(__file__)) +parent_dir_path = os.path.abspath(os.path.join(dir_path, os.pardir)) +sys.path.insert(0, parent_dir_path) + +from lf_report import lf_report + +sys.path.append('/') + +# setup logging FORMAT +FORMAT = '%(asctime)s %(name)s %(levelname)s: %(message)s' + + +# lf_check class contains verificaiton configuration and ocastrates the testing. +class lf_check(): + def __init__(self, + _json_rig, + _json_dut, + _json_test, + _test_suite, + _json_igg, + _production, + _csv_results, + _outfile, + _outfile_name, + _report_path, + _log_path): + self.json_rig = _json_rig + self.json_dut = _json_dut + self.json_test = _json_test + self.test_suite = _test_suite + self.json_igg = _json_igg + self.production_run = _production + self.report_path = _report_path + self.log_path = _log_path + self.radio_dict = {} + self.test_dict = {} + path_parent = os.path.dirname(os.getcwd()) + os.chdir(path_parent) + self.scripts_wd = os.getcwd() + self.results = "" + self.outfile = _outfile + self.outfile_name = _outfile_name + self.test_result = "Failure" + self.results_col_titles = ["Test", "Command", "Result", "STDOUT", "STDERR"] + self.html_results = "" + self.background_green = "background-color:green" + self.background_red = "background-color:red" + self.background_purple = "background-color:purple" + self.background_blue = "background-color:blue" + + # LANforge information + self.lanforge_system_node_version = "" + self.lanforge_kernel_version = "" + self.lanforge_gui_version_full = "" + self.lanforge_gui_version = "" + + # meta.txt + self.meta_data_path = "" + + # lanforge configuration + self.lf_mgr_ip = "192.168.0.102" + self.lf_mgr_port = "8080" + self.lf_mgr_user = "lanforge" + self.lf_mgr_pass = "lanforge" + self.upstream_port = "" + + + # results + self.database_sqlite = "" + self.test_start_time = "" + self.test_end_time = "" + self.duration = "" + + self.http_test_ip = "" + self.ftp_test_ip = "" + self.test_ip = "" + + # section DUT + # dut selection + self.dut_set_name = 'DUT_NAME ASUSRT-AX88U' # note the name will be set as --set DUT_NAME ASUSRT-AX88U, this is not dut_name (see above) + + # dut configuration + self.dut_name = "DUT_NAME_NA" # "ASUSRT-AX88U" note this is not dut_set_name + self.dut_hw = "DUT_HW_NA" + self.dut_sw = "DUT_SW_NA" + self.dut_model = "DUT_MODEL_NA" + self.dut_serial = "DUT_SERIAL_NA" + self.dut_bssid_2g = "BSSID_2G_NA" # "3c:7c:3f:55:4d:64" - this is the mac for the 2.4G radio this may be seen with a scan + self.dut_bssid_5g = "BSSID_5G_NA" # "3c:7c:3f:55:4d:64" - this is the mac for the 5G radio this may be seen with a scan + self.dut_bssid_6g = "BSSID_6G_NA" # "3c:7c:3f:55:4d:64" - this is the mac for the 6G radio this may be seen with a scan + + self.ssid_2g = "" + self.ssid_2g_pw = "" + self.security_2g = "" + + self.ssid_5g = "" + self.ssid_5g_pw = "" + self.security_5g = "" + + self.ssid_6g = "" + self.ssid_6g_pw = "" + self.security_6g = "" + + self.csv_results = _csv_results + self.csv_results_file = "" + self.csv_results_writer = "" + self.csv_results_column_headers = "" + self.logger = logging.getLogger(__name__) + self.test_timeout = 120 + self.test_timeout_default = 120 + self.test_iterations_default = 1 + self.use_blank_db = "FALSE" + self.use_factory_default_db = "FALSE" + self.use_custom_db = "FALSE" + self.email_list_production = "" + self.host_ip_production = None + self.email_list_test = "" + self.host_ip_test = None + self.email_title_txt = "" + self.email_txt = "" + + # NOTE: My influx token is unlucky and starts with a '-', but using the syntax below # with '=' right after the argument keyword works as hoped. + # --influx_token= + + # DUT , Test rig must match testbed + self.test_rig = "CT-US-NA" + self.test_rig_json = "" + + # QA report + self.qa_report_html = "NA" + self.database_qa = "" + self.table_qa = "" + + # database configuration # database + self.influx_database_json = "" + self.influx_database_config = "True" # default to False once testing done + self.influx_database_host = "192.168.100.201" # "c7-grafana.candelatech.com" # influx and grafana have the same host "192.168.100.201" + self.influx_database_port = "8086" + self.influx_database_token = "-u_Wd-L8o992701QF0c5UmqEp7w7Z7YOMaWLxOMgmHfATJGnQbbmYyNxHBR9PgD6taM_tcxqJl6U8DjU1xINFQ==" + self.influx_database_org = "Candela" + self.influx_database_bucket = "lanforge_qa_testing" + self.influx_database_tag = 'testbed CT-US-001' # the test_rig needs to match + + # grafana configuration #dashboard + self.dashboard_json_grafana = "" + self.dashboard_config_grafana = "True" # default to False once testing done + self.dashboard_host_grafana = "192.168.100.201" # "c7-grafana.candelatech.com" # 192.168.100.201 + self.dashboard_port_grafana = "3000" + self.dashboard_token_grafana = "eyJrIjoiS1NGRU8xcTVBQW9lUmlTM2dNRFpqNjFqV05MZkM0dzciLCJuIjoibWF0dGhldyIsImlkIjoxfQ==" + + # ghost configuration + self.blog_json_ghost = "" + self.blog_config_ghost = False + self.blog_host_ghost = "192.168.100.153" + self.blog_port_ghost = "2368" + self.blog_token_ghost = "60df4b0175953f400cd30650:d50e1fabf9a9b5d3d30fe97bc3bf04971d05496a89e92a169a0d72357c81f742" + self.blog_authors_ghost = "Matthew" + self.blog_customer_ghost = "candela" + self.blog_user_push_ghost = "lanforge" + self.blog_password_push_ghost = "lanforge" + self.blog_flag = "--kpi_to_ghost" + + self.test_run = "" + + def get_test_rig(self): + return self.test_rig + + def check_if_port_exists(self,json_igg): + queries = dict() + queries['LANforge Manager'] = 'http://%s:%s' % (self.lf_mgr_ip, self.lf_mgr_port) + # Frame work not required to use specific databases or presentation + if json_igg != "": + queries['Blog Host'] = 'http://%s:%s' % (self.blog_host_ghost, self.blog_port_ghost) + queries['Influx Host'] = 'http://%s:%s' % (self.influx_database_host, self.influx_database_port) + queries['Grafana Host'] = 'http://%s:%s' % (self.dashboard_host_grafana, self.dashboard_port_grafana) + results = dict() + for key, value in queries.items(): + try: + ping = requests.get(value).text + results[key] = [str(ping), value] + except: + print('%s not found' % value) + results[key] = [value, None] + return results + + def get_scripts_git_sha(self): + # get git sha + process = subprocess.Popen(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE) + (commit_hash, err) = process.communicate() + exit_code = process.wait() + print("get_scripts_get_sha exit_code: {exit_code}".format(exit_code=exit_code)) + scripts_git_sha = commit_hash.decode('utf-8', 'ignore') + return scripts_git_sha + + ''' + self.lf_mgr_ip = "192.168.0.102" + self.lf_mgr_port = "8080" + self.lf_mgr_user = "lanforge" + self.lf_mgr_pass = "lanforge" + ''' + def get_lanforge_radio_information(self): + # https://docs.python-requests.org/en/latest/ + # https://stackoverflow.com/questions/26000336/execute-curl-command-within-a-python-script - use requests + # curl --user "lanforge:lanforge" -H 'Accept: application/json' http://192.168.100.116:8080/radiostatus/all | json_pp , where --user "USERNAME:PASSWORD" + request_command = 'http://{lfmgr}:{port}/radiostatus/all'.format(lfmgr=self.lf_mgr_ip,port=self.lf_mgr_port) + request = requests.get(request_command, auth=(self.lf_mgr_user,self.lf_mgr_pass)) + print("radio request command: {request_command}".format(request_command=request_command)) + print("radio request status_code {status}".format(status=request.status_code)) + lanforge_radio_json = request.json() + print("radio request.json: {json}".format(json=lanforge_radio_json)) + lanforge_radio_text = request.text + print("radio request.test: {text}".format(text=lanforge_radio_text)) + return lanforge_radio_json, lanforge_radio_text + + + def get_lanforge_system_node_version(self): + ssh = paramiko.SSHClient() # creating shh client object we use this object to connect to router + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # automatically adds the missing host key + ssh.connect(hostname=self.lf_mgr_ip, port=22, username=self.lf_mgr_user, password=self.lf_mgr_pass, + allow_agent=False, look_for_keys=False, banner_timeout=600) + stdin, stdout, stderr = ssh.exec_command('uname -n') + self.lanforge_system_node_version = stdout.readlines() + self.lanforge_system_node_version = [line.replace('\n', '') for line in self.lanforge_system_node_version] + ssh.close() + time.sleep(1) + return self.lanforge_system_node_version + + def get_lanforge_kernel_version(self): + ssh = paramiko.SSHClient() # creating shh client object we use this object to connect to router + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # automatically adds the missing host key + ssh.connect(hostname=self.lf_mgr_ip, port=22, username=self.lf_mgr_user, password=self.lf_mgr_pass, + allow_agent=False, look_for_keys=False, banner_timeout=600) + stdin, stdout, stderr = ssh.exec_command('uname -r') + self.lanforge_kernel_version = stdout.readlines() + self.lanforge_kernel_version = [line.replace('\n', '') for line in self.lanforge_kernel_version] + ssh.close() + time.sleep(1) + return self.lanforge_kernel_version + + def get_lanforge_gui_version(self): + ssh = paramiko.SSHClient() # creating shh client object we use this object to connect to router + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # automatically adds the missing host key + ssh.connect(hostname=self.lf_mgr_ip, port=22, username=self.lf_mgr_user, password=self.lf_mgr_pass, + allow_agent=False, look_for_keys=False, banner_timeout=600) + stdin, stdout, stderr = ssh.exec_command('./btserver --version | grep Version') + self.lanforge_gui_version_full = stdout.readlines() + self.lanforge_gui_version_full = [line.replace('\n', '') for line in self.lanforge_gui_version_full] + print("lanforge_gui_version_full: {lanforge_gui_version_full}".format(lanforge_gui_version_full=self.lanforge_gui_version_full)) + self.lanforge_gui_version = self.lanforge_gui_version_full[0].split('Version:',maxsplit=1)[-1].split(maxsplit=1)[0] + self.lanforge_gui_version = self.lanforge_gui_version.strip() + print("lanforge_gui_version: {lanforge_gui_version}".format(lanforge_gui_version=self.lanforge_gui_version)) + ssh.close() + time.sleep(1) + return self.lanforge_gui_version_full + + def get_radio_status(self): + radio_status = self.json_get("/radiostatus/all") + print("radio status {radio_status}".format(radio_status=radio_status)) + + # NOT complete : will send the email results + def send_results_email(self, report_file=None): + if (report_file is None): + print("No report file, not sending email.") + return + report_url = report_file.replace('/home/lanforge/', '') + if report_url.startswith('/'): + report_url = report_url[1:] + qa_url = self.qa_report_html.replace('home/lanforge','') + if qa_url.startswith('/'): + qa_url = qa_url[1:] + # following recommendation + # NOTE: https://stackoverflow.com/questions/24196932/how-can-i-get-the-ip-address-from-nic-in-python + # Mail + # command to check if mail running : systemctl status postfix + # command = 'echo "$HOSTNAME mail system works!" | mail -s "Test: $HOSTNAME $(date)" chuck.rekiere@candelatech.com' + hostname = socket.getfqdn() + ip = socket.gethostbyname(hostname) + + # a hostname lacking dots by definition lacks a domain name + # this is not useful for hyperlinks outside the known domain, so an IP address should be preferred + if hostname.find('.') < 1: + hostname = ip + + message_txt ="" + if (self.email_txt != ""): + message_txt = """{email_txt} lanforge target {lf_mgr_ip} +Results from {hostname}: +http://{hostname}/{report} +""".format(email_txt=self.email_txt,lf_mgr_ip=self.lf_mgr_ip,hostname=hostname,report=report_url) + else: + message_txt = """Results from {hostname}: +http://{hostname}/{report}""".format(hostname=hostname,report=report_url) + + # Put in report information current two methods supported, + message_txt +=""" +QA Report Dashboard: +http://{ip_qa}/{qa_url} +NOTE: Diagrams are links in dashboard""".format(ip_qa=ip,qa_url=qa_url) + + if(self.json_igg != "" ): + message_txt += """ + +Ghost Blog: +http://{blog}:2368""".format(blog=self.blog_host_ghost) + + if (self.email_title_txt != ""): + mail_subject = "{} [{hostname}] {date}".format(self.email_title_txt, hostname=hostname, + date=datetime.datetime.now()) + else: + mail_subject = "Regression Test [{hostname}] {date}".format(hostname=hostname, date=datetime.datetime.now()) + try: + if self.production_run == True: + msg = message_txt.format(ip=self.host_ip_production) + # for postfix from command line echo "My message" | mail -s subject user@candelatech.com + command = "echo \"{message}\" | mail -s \"{subject}\" {address}".format( + message=msg, + subject=mail_subject, + address=self.email_list_production) + else: + msg = message_txt.format(ip=ip) + command = "echo \"{message}\" | mail -s \"{subject}\" {address}".format( + message=msg, + subject=mail_subject, + address=self.email_list_test) + + print("running:[{}]".format(command)) + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + # have email on separate timeout + process.wait(timeout=int(self.test_timeout)) + except subprocess.TimeoutExpired: + print("send email timed out") + process.terminate() + + def get_csv_results(self): + return self.csv_file.name + + def start_csv_results(self): + print("self.csv_results") + self.csv_results_file = open(self.csv_results, "w") + self.csv_results_writer = csv.writer(self.csv_results_file, delimiter=",") + self.csv_results_column_headers = ['Test', 'Command', 'Result', 'STDOUT', 'STDERR'] + self.csv_results_writer.writerow(self.csv_results_column_headers) + self.csv_results_file.flush() + + def get_html_results(self): + return self.html_results + + def start_html_results(self): + self.html_results += """ + + + + + + + + + + + + + + + """ + + def finish_html_results(self): + self.html_results += """ + +
    TestCommandDurationStartEndResultSTDOUTSTDERR
    +
    +
    +
    + """ + + # Read the json configuration + # Read the test rig configuration, which is the LANforge system configuration + # Read the dut configuration, which is the specific configuration for the AP / VAP or other device under test + # Read the test configuration, replace the wide card parameters + # Read igg configuration is for Influx, Grafana and Ghost + + # Reading the test rig configuration + def read_json_rig(self): + # self.logger.info("read_config_json_contents {}".format(self.json_rig)) + if "test_parameters" in self.json_rig: + self.logger.info("json: read test_parameters") + # self.logger.info("test_parameters {}".format(self.json_rig["test_parameters"])) + self.read_test_parameters() + else: + self.logger.info("EXITING test_parameters not in json {}".format(self.json_rig)) + exit(1) + + if "test_network" in self.json_rig: + self.logger.info("json: read test_network") + # self.logger.info("test_network {}".format(self.json_rig["test_network"])) + self.read_test_network() + else: + self.logger.info("EXITING test_network not in json {}".format(self.json_rig)) + exit(1) + + if "radio_dict" in self.json_rig: + self.logger.info("json: read radio_dict") + # self.logger.info("radio_dict {}".format(self.json_rig["radio_dict"])) + self.radio_dict = self.json_rig["radio_dict"] + self.logger.info("self.radio_dict {}".format(self.radio_dict)) + else: + self.logger.info("EXITING radio_dict not in json {}".format(self.json_rig)) + exit(1) + + # read dut configuration + def read_json_dut(self): + if "test_dut" in self.json_dut: + self.logger.info("json: read test_dut") + self.read_dut_parameters() + else: + self.logger.info("EXITING test_dut not in json {}".format(self.json_dut)) + exit(1) + + # Top Level for reading the tests to run + def read_json_test(self): + if "test_suites" in self.json_test: + self.logger.info("json: read test_suites looking for: {}".format(self.test_suite)) + # self.logger.info("test_suites {}".format(self.json_test["test_suites"])) + if self.test_suite in self.json_test["test_suites"]: + self.test_dict = self.json_test["test_suites"][self.test_suite] + # self.logger.info("self.test_dict {}".format(self.test_dict)) + else: + self.logger.info("EXITING test_suite {} Not Present in json test_suites: {}".format(self.test_suite, + self.json_test[ + "test_suites"])) + exit(1) + else: + self.logger.info("EXITING test_suites not in json {}".format(self.json_test)) + exit(1) + + # Top Level for Influx, Grafana, Ghost configuration + def read_json_igg(self): + if "test_database" in self.json_igg: + self.logger.info("json: read test_database") + # self.logger.info("test_database {}".format(self.json_rig["test_database"])) + self.read_test_database() + else: + self.logger.info("NOTE: test_database not found in json") + + if "test_dashboard" in self.json_igg: + self.logger.info("json: read test_dashboard") + # self.logger.info("test_dashboard {}".format(self.json_rig["test_dashboard"])) + self.read_test_dashboard() + else: + self.logger.info("NOTE: test_dashboard not found in json") + + if "test_blog" in self.json_igg: + self.logger.info("json: read test_blog") + # self.logger.info("test_blog {}".format(self.json_rig["test_blog"])) + self.read_test_blog() + else: + self.logger.info("NOTE: test_blog not found in json") + + #TODO change code so if parameter is not present then implied to be false + def read_test_parameters(self): + if "test_rig" in self.json_rig["test_parameters"]: + self.test_rig = self.json_rig["test_parameters"]["test_rig"] + else: + self.logger.info("test_rig not in test_parameters json") + if "database_sqlite" in self.json_rig["test_parameters"]: + self.database_sqlite = self.json_rig["test_parameters"]["database_sqlite"] + else: + self.logger.info("database_sqlite not in test_parameters json") + if "lf_mgr_ip" in self.json_rig["test_parameters"]: + self.lf_mgr_ip = self.json_rig["test_parameters"]["lf_mgr_ip"] + else: + self.logger.info("lf_mgr_ip not in test_parameters json") + if "lf_mgr_port" in self.json_rig["test_parameters"]: + self.lf_mgr_port = self.json_rig["test_parameters"]["lf_mgr_port"] + else: + self.logger.info("lf_mgr_port not in test_parameters json") + if "lf_mgr_user" in self.json_rig["test_parameters"]: + self.lf_mgr_user = self.json_rig["test_parameters"]["lf_mgr_user"] + else: + self.logger.info("lf_mgr_user not in test_parameters json") + if "lf_mgr_pass" in self.json_rig["test_parameters"]: + self.lf_mgr_pass = self.json_rig["test_parameters"]["lf_mgr_pass"] + else: + self.logger.info("lf_mgr_pass not in test_parameters json") + if "upstream_port" in self.json_rig["test_parameters"]: + self.upstream_port = self.json_rig["test_parameters"]["upstream_port"] + else: + self.logger.info("upstream_port not in test_parameters json") + if "test_timeout" in self.json_rig["test_parameters"]: + self.test_timeout = self.json_rig["test_parameters"]["test_timeout"] + self.test_timeout_default = self.test_timeout + else: + self.logger.info("test_timeout not in test_parameters json") + exit(1) + if "load_blank_db" in self.json_rig["test_parameters"]: + self.load_blank_db = self.json_rig["test_parameters"]["load_blank_db"] + else: + self.logger.info("load_blank_db not in test_parameters json") + exit(1) + if "load_factory_default_db" in self.json_rig["test_parameters"]: + self.load_factory_default_db = self.json_rig["test_parameters"]["load_factory_default_db"] + else: + self.logger.info("load_factory_default_db not in test_parameters json") + exit(1) + if "load_custom_db" in self.json_rig["test_parameters"]: + self.load_custom_db = self.json_rig["test_parameters"]["load_custom_db"] + else: + self.logger.info("load_custom_db not in test_parameters json") + exit(1) + if "custom_db" in self.json_rig["test_parameters"]: + self.custom_db = self.json_rig["test_parameters"]["custom_db"] + else: + self.logger.info("custom_db not in test_parameters json, if not using custom_db just put in a name") + exit(1) + if "email_list_production" in self.json_rig["test_parameters"]: + self.email_list_production = self.json_rig["test_parameters"]["email_list_production"] + else: + self.logger.info("email_list_production not in test_parameters json") + exit(1) + if "host_ip_production" in self.json_rig["test_parameters"]: + self.host_ip_production = self.json_rig["test_parameters"]["host_ip_production"] + else: + self.logger.info("host_ip_production not in test_parameters json") + exit(1) + if "email_list_test" in self.json_rig["test_parameters"]: + self.email_list_test = self.json_rig["test_parameters"]["email_list_test"] + print(self.email_list_test) + else: + self.logger.info("email_list_test not in test_parameters json") + exit(1) + if "host_ip_test" in self.json_rig["test_parameters"]: + self.host_ip_test = self.json_rig["test_parameters"]["host_ip_test"] + else: + self.logger.info("host_ip_test not in test_parameters json") + exit(1) + if "email_title_txt" in self.json_rig["test_parameters"]: + self.email_title_txt = self.json_rig["test_parameters"]["email_title_txt"] + else: + self.logger.info("email_title_txt not in test_parameters json") + if "email_txt" in self.json_rig["test_parameters"]: + self.email_txt = self.json_rig["test_parameters"]["email_txt"] + else: + self.logger.info("email_txt not in test_parameters json") + + # dut_set_name selectes the DUT to test against , it is different then dut_name + # this value gets set in the test + def read_dut_parameters(self): + if "dut_set_name" in self.json_dut["test_dut"]: + self.dut_set_name = self.json_dut["test_dut"]["dut_set_name"] + else: + self.logger.info("dut_set_name not in test_dut json") + # dut name will set a chamberview scenerio for a DUT which can be selected with dut_set_name + if "dut_name" in self.json_dut["test_dut"]: + self.dut_name = self.json_dut["test_dut"]["dut_name"] + else: + self.logger.info("dut_name not in test_dut json") + + if "dut_hw" in self.json_dut["test_dut"]: + self.dut_hw = self.json_dut["test_dut"]["dut_hw"] + else: + self.logger.info("dut_hw not in test_dut json") + + if "dut_sw" in self.json_dut["test_dut"]: + self.dut_sw = self.json_dut["test_dut"]["dut_sw"] + else: + self.logger.info("dut_sw not in test_parameters json") + + if "dut_model" in self.json_dut["test_dut"]: + self.dut_model = self.json_dut["test_dut"]["dut_model"] + else: + self.logger.info("dut_model not in test_dut json") + + if "dut_serial" in self.json_dut["test_dut"]: + self.dut_serial = self.json_dut["test_dut"]["dut_serial"] + else: + self.logger.info("dut_serial not in test_dut json") + + if "dut_bssid_2g" in self.json_dut["test_dut"]: + self.dut_bssid_2g = self.json_dut["test_dut"]["dut_bssid_2g"] + else: + self.logger.info("dut_bssid_2G not in test_dut json") + + if "dut_bssid_5g" in self.json_dut["test_dut"]: + self.dut_bssid_5g = self.json_dut["test_dut"]["dut_bssid_5g"] + else: + self.logger.info("dut_bssid_5g not in test_dut json") + + if "dut_bssid_6g" in self.json_dut["test_dut"]: + self.dut_bssid_6g = self.json_dut["test_dut"]["dut_bssid_6g"] + else: + self.logger.info("dut_bssid_6g not in test_dut json") + + if "ssid_6g_used" in self.json_dut["test_dut"]: + self.ssid_6g = self.json_dut["test_dut"]["ssid_6g_used"] + else: + self.logger.info("ssid_6g_used not in test_dut json") + + if "ssid_6g_pw_used" in self.json_dut["test_dut"]: + self.ssid_6g_pw = self.json_dut["test_dut"]["ssid_6g_pw_used"] + else: + self.logger.info("ssid_6g_pw_used not in test_dut json") + + if "security_6g_used" in self.json_dut["test_dut"]: + self.security_6g = self.json_dut["test_dut"]["security_6g_used"] + else: + self.logger.info("security_6g_used not in test_dut json") + + if "ssid_5g_used" in self.json_dut["test_dut"]: + self.ssid_5g = self.json_dut["test_dut"]["ssid_5g_used"] + else: + self.logger.info("ssid_5g_used not in test_dut json") + + if "ssid_5g_pw_used" in self.json_dut["test_dut"]: + self.ssid_5g_pw = self.json_dut["test_dut"]["ssid_5g_pw_used"] + else: + self.logger.info("ssid_5g_pw_used not in test_dut json") + + if "security_5g_used" in self.json_dut["test_dut"]: + self.security_5g = self.json_dut["test_dut"]["security_5g_used"] + else: + self.logger.info("security_5g_used not in test_dut json") + + if "ssid_2g_used" in self.json_dut["test_dut"]: + self.ssid_2g = self.json_dut["test_dut"]["ssid_2g_used"] + else: + self.logger.info("ssid_2g_used not in test_dut json") + + if "ssid_2g_pw_used" in self.json_dut["test_dut"]: + self.ssid_2g_pw = self.json_dut["test_dut"]["ssid_2g_pw_used"] + else: + self.logger.info("ssid_2g_pw_used not in test_dut json") + + if "security_2g_used" in self.json_dut["test_dut"]: + self.security_2g = self.json_dut["test_dut"]["security_2g_used"] + else: + self.logger.info("security_2g_used not in test_dut json") + + def read_test_network(self): + if "http_test_ip" in self.json_rig["test_network"]: + self.http_test_ip = self.json_rig["test_network"]["http_test_ip"] + else: + self.logger.info("http_test_ip not in test_network json") + exit(1) + if "ftp_test_ip" in self.json_rig["test_network"]: + self.ftp_test_ip = self.json_rig["test_network"]["ftp_test_ip"] + else: + self.logger.info("ftp_test_ip not in test_network json") + exit(1) + if "test_ip" in self.json_rig["test_network"]: + self.ftp_test_ip = self.json_rig["test_network"]["test_ip"] + else: + self.logger.info("test_ip not in test_network json") + exit(1) + + # Optional from --json_igg switch + # kpi.csv and meta.txt can be read after test run performed holds same data + def read_test_database(self): + if "influx_database_config" in self.json_igg["test_database"]: + self.influx_database_config = self.json_igg["test_database"]["influx_database_config"] + else: + self.logger.info("influx_database_config not in test_database json") + if "influx_database_host" in self.json_igg["test_database"]: + self.influx_database_host = self.json_igg["test_database"]["influx_database_host"] + else: + self.logger.info("influx_database_host not in test_database json") + if "influx_database_port" in self.json_igg["test_database"]: + self.influx_database_port = self.json_igg["test_database"]["influx_database_port"] + else: + self.logger.info("influx_database_port not in test_database json") + if "influx_database_token" in self.json_igg["test_database"]: + self.influx_database_token = self.json_igg["test_database"]["influx_database_token"] + else: + self.logger.info("influx_database_token not in test_database json") + if "influx_database_org" in self.json_igg["test_database"]: + self.influx_database_org = self.json_igg["test_database"]["influx_database_org"] + else: + self.logger.info("influx_database_org not in test_database json") + if "influx_database_bucket" in self.json_igg["test_database"]: + self.influx_database_bucket = self.json_igg["test_database"]["influx_database_bucket"] + else: + self.logger.info("influx_database_bucket not in test_database json") + if "influx_database_tag" in self.json_igg["test_database"]: + self.influx_database_tag = self.json_igg["test_database"]["influx_database_tag"] + else: + self.logger.info("influx_database_tag not in test_database json") + + # Optional only if --json_igg switch + def read_test_dashboard(self): + if "dashboard_config_grafana" in self.json_igg["test_dashboard"]: + self.dashboard_config_grafana = self.json_igg["test_dashboard"]["dashboard_config_grafana"] + else: + self.logger.info("dashboard_config_grafana not in test_dashboard json") + + if "dashboard_host_grafana" in self.json_igg["test_dashboard"]: + self.dashboard_host_grafana = self.json_igg["test_dashboard"]["dashboard_host_grafana"] + else: + self.logger.info("dashboard_host_grafana not in test_dashboard json") + + if "dashboard_token_grafana" in self.json_igg["test_dashboard"]: + self.dashboard_token_grafana = self.json_igg["test_dashboard"]["dashboard_token_grafana"] + else: + self.logger.info("dashboard_token_grafana not in test_dashboard json") + + # Optional on if --json_igg switch + def read_test_blog(self): + if "blog_config_ghost" in self.json_igg["test_blog"]: + self.blog_config_ghost = self.json_igg["test_blog"]["blog_config_ghost"] + else: + self.logger.info("blog_config_ghost not in test_blog json") + + if "blog_host_ghost" in self.json_igg["test_blog"]: + self.blog_host_ghost = self.json_igg["test_blog"]["blog_host_ghost"] + else: + self.logger.info("blog_host_ghost not in test_blog json") + + if "blog_token_ghost" in self.json_igg["test_blog"]: + self.blog_token_ghost = self.json_igg["test_blog"]["blog_token_ghost"] + else: + self.logger.info("blog_token_ghost not in test_blog json") + + if "blog_authors_ghost" in self.json_igg["test_blog"]: + self.blog_authors_ghost = self.json_igg["test_blog"]["blog_authors_ghost"] + else: + self.logger.info("blog_authors_ghost not in test_blog json") + + if "blog_customer_ghost" in self.json_igg["test_blog"]: + self.blog_customer_ghost = self.json_igg["test_blog"]["blog_customer_ghost"] + else: + self.logger.info("blog_customer_ghost not in test_blog json") + + if "blog_user_push_ghost" in self.json_igg["test_blog"]: + self.blog_user_push_ghost = self.json_igg["test_blog"]["blog_user_push_ghost"] + else: + self.logger.info("blog_user_push_ghost not in test_blog json") + + if "blog_password_push_ghost" in self.json_igg["test_blog"]: + self.blog_password_push_ghost = self.json_igg["test_blog"]["blog_password_push_ghost"] + else: + self.logger.info("blog_password_push_ghost not in test_blog json") + + if "blog_flag" in self.json_igg["test_blog"]: + self.blog_flag = self.json_igg["test_blog"]["blog_flag"] + else: + self.logger.info("blog_flag not in test_blog json") + + + def load_factory_default_db(self): + # self.logger.info("file_wd {}".format(self.scripts_wd)) + try: + os.chdir(self.scripts_wd) + # self.logger.info("Current Working Directory {}".format(os.getcwd())) + except: + self.logger.info("failed to change to {}".format(self.scripts_wd)) + + # no spaces after FACTORY_DFLT + command = "./{} {}".format("scenario.py", "--load FACTORY_DFLT") + process = subprocess.Popen((command).split(' '), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + # wait for the process to terminate + out, err = process.communicate() + errcode = process.returncode + print("load_factory_default_db errcode: {errcode}".format(errcode=errcode)) + + # not currently used + def load_blank_db(self): + try: + os.chdir(self.scripts_wd) + except: + self.logger.info("failed to change to {}".format(self.scripts_wd)) + + # no spaces after FACTORY_DFLT + command = "./{} {}".format("scenario.py", "--load BLANK") + process = subprocess.Popen((command).split(' '), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + # wait for the process to terminate + out, err = process.communicate() + errcode = process.returncode + print("load_blank_db errcode: {errcode}".format(errcode=errcode)) + + + def load_custom_db(self, custom_db): + try: + os.chdir(self.scripts_wd) + except: + self.logger.info("failed to change to {}".format(self.scripts_wd)) + + # no spaces after FACTORY_DFLT + command = "./{} {}".format("scenario.py", "--load {}".format(custom_db)) + process = subprocess.Popen((command).split(' '), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + # wait for the process to terminate + out, err = process.communicate() + errcode = process.returncode + print("load_custome_db errcode: {errcode}".format(errcode=errcode)) + + def run_script_test(self): + self.start_html_results() + self.start_csv_results() + print(self.test_dict) + + # loop through radios (For future functionality based on radio) + for radio in self.radio_dict: + # This has been changed to reflect the Radio configuriaton of LANforge, for now print + print("rig json config: RADIO: {radio} DRIVER: {driver} CAPABILITIES {cap} MAX_STA {max_sta} MAX_VAP {max_vap} MAX_VIFS {max_vif}".format( + radio=self.radio_dict[radio]['RADIO'],driver=self.radio_dict[radio]['DRIVER'],cap=self.radio_dict[radio]['CAPABILITIES'], + max_sta=self.radio_dict[radio]['MAX_STA'],max_vap=self.radio_dict[radio]['MAX_VAP'],max_vif=self.radio_dict[radio]['MAX_VIFS'])) + + # Configure Tests + for test in self.test_dict: + if self.test_dict[test]['enabled'] == "FALSE": + self.logger.info("test: {} skipped".format(test)) + # load the default database + elif self.test_dict[test]['enabled'] == "TRUE": + #TODO Place test interations here + if 'iterations' in self.test_dict[test]: + self.logger.info("iterations : {}".format(self.test_dict[test]['iterations'])) + self.test_iterations = int(self.test_dict[test]['iterations']) + else: + self.test_iterations = self.test_iterations_default + + iteration = 0 + for iteration in range(self.test_iterations): + iteration += 1 + + # if args key has a value of an empty string then need to manipulate the args_list to args + # list does not have replace only stings do to args_list will be joined and converted to a string and placed + # in args. Then the replace below will work. + if self.test_dict[test]['args'] == "": + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace(self.test_dict[test]['args'], + ''.join(self.test_dict[test][ + 'args_list'])) + + if 'DATABASE_SQLITE' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DATABASE_SQLITE', self.database_sqlite) + if 'HTTP_TEST_IP' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('HTTP_TEST_IP', self.http_test_ip) + if 'FTP_TEST_IP' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('FTP_TEST_IP', self.ftp_test_ip) + if 'TEST_IP' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('TEST_IP', self.test_ip) + + if 'LF_MGR_USER' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('LF_MGR_USER', self.lf_mgr_user) + if 'LF_MGR_PASS' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('LF_MGR_PASS', self.lf_mgr_pass) + + if 'LF_MGR_IP' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('LF_MGR_IP', self.lf_mgr_ip) + if 'LF_MGR_PORT' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('LF_MGR_PORT', self.lf_mgr_port) + + # DUT Configuration + if 'DUT_NAME' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_NAME', self.dut_name) + if 'DUT_HW' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_HW', self.dut_hw) + if 'DUT_SW' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_SW', self.dut_sw) + if 'DUT_MODEL' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_MODEL', self.dut_model) + if 'DUT_SERIAL' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_SERIAL', self.dut_serial) + + if 'DUT_BSSID_2G' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_BSSID_2G', self.dut_bssid_2g) + if 'SSID_2G_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SSID_2G_USED', self.ssid_2g) + if 'SSID_2G_PW_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SSID_2G_PW_USED', self.ssid_2g_pw) + if 'SECURITY_2G_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SECURITY_2G_USED', self.security_2g) + + if 'DUT_BSSID_5G' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_BSSID_5G', self.dut_bssid_5g) + if 'SSID_5G_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SSID_5G_USED', self.ssid_5g) + if 'SSID_5G_PW_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SSID_5G_PW_USED', self.ssid_5g_pw) + if 'SECURITY_5G_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SECURITY_5G_USED', self.security_5g) + + if 'DUT_BSSID_6G' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_BSSID_6G', self.dut_bssid_6g) + if 'SSID_6G_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SSID_6G_USED', self.ssid_6g) + if 'SSID_6G_PW_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SSID_6G_PW_USED', self.ssid_6g_pw) + if 'SECURITY_6G_USED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('SECURITY_6G_USED', self.security_6g) + + + if 'NUM_STA' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('NUM_STA', self.num_sta) + if 'COL_NAMES' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('COL_NAMES', self.col_names) + if 'UPSTREAM_PORT' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('UPSTREAM_PORT', + self.upstream_port) + + # lf_dataplane_test.py and lf_wifi_capacity_test.py use a parameter --local_path for the location + # of the reports when the reports are pulled. + if 'REPORT_PATH' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('REPORT_PATH', self.report_path) + + # The TEST_BED is the database tag + if 'TEST_BED' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('TEST_BED', self.influx_database_tag) + + # Influx database configuration + if 'influx_database_host' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('influx_database_host', + self.influx_database_host) + if 'influx_database_port' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('influx_database_port', + self.influx_database_port) + if 'influx_database_token' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('influx_database_token', + self.influx_database_token) + if 'influx_database_org' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('influx_database_org', + self.influx_database_org) + if 'influx_database_bucket' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('influx_database_bucket', + self.influx_database_bucket) + if 'influx_database_tag' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('influx_database_tag', + self.influx_database_tag) + if 'DUT_SET_NAME' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('DUT_SET_NAME', + self.dut_set_name) + if 'TEST_RIG' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('TEST_RIG', self.test_rig) + # end of database configuration + + # dashboard configuration + if 'dashboard_host_grafana' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('dashboard_host_grafana', + self.dashboard_host_grafana) + if 'dashboard_token_grafana' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('dashboard_token_grafana', + self.dashboard_token_grafana) + # end of dashboard configuraiton + + # blog configuration + if 'blog_host_ghost' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('blog_host_ghost', self.blog_host_ghost) + if 'blog_token_ghost' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('blog_token_ghost', self.blog_token_ghost) + if 'blog_authors_ghost' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('blog_authors_ghost', + self.blog_authors_ghost) + if 'blog_customer_ghost' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('blog_customer_ghost', + self.blog_customer_ghost) + if 'blog_user_push_ghost' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('blog_user_push_ghost', + self.blog_user_push_ghost) + if 'blog_password_push_ghost' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('blog_password_push_ghost', + self.blog_password_push_ghost) + if 'BLOG_FLAG' in self.test_dict[test]['args']: + self.test_dict[test]['args'] = self.test_dict[test]['args'].replace('BLOG_FLAG', self.blog_flag) + # end of blog configruation + + if 'timeout' in self.test_dict[test]: + self.logger.info("timeout : {}".format(self.test_dict[test]['timeout'])) + self.test_timeout = int(self.test_dict[test]['timeout']) + else: + self.test_timeout = self.test_timeout_default + + if 'load_db' in self.test_dict[test]: + self.logger.info("load_db : {}".format(self.test_dict[test]['load_db'])) + if str(self.test_dict[test]['load_db']).lower() != "none" and str( + self.test_dict[test]['load_db']).lower() != "skip": + try: + self.load_custom_db(self.test_dict[test]['load_db']) + except: + self.logger.info("custom database failed to load check existance and location: {}".format( + self.test_dict[test]['load_db'])) + else: + self.logger.info("no load_db present in dictionary, load db normally") + if self.use_factory_default_db == "TRUE": + self.load_factory_default_db() + sleep(3) + self.logger.info("FACTORY_DFLT loaded between tests with scenario.py --load FACTORY_DFLT") + if self.use_blank_db == "TRUE": + self.load_blank_db() + sleep(1) + self.logger.info("BLANK loaded between tests with scenario.py --load BLANK") + if self.use_custom_db == "TRUE": + try: + self.load_custom_db(self.custom_db) + sleep(1) + self.logger.info("{} loaded between tests with scenario.py --load {}".format(self.custom_db, + self.custom_db)) + except: + self.logger.info("custom database failed to load check existance and location: {}".format( + self.custom_db)) + else: + self.logger.info("no db loaded between tests: {}".format(self.use_custom_db)) + + sleep(1) # DO NOT REMOVE the sleep is to allow for the database to stablize + try: + os.chdir(self.scripts_wd) + # self.logger.info("Current Working Directory {}".format(os.getcwd())) + except: + self.logger.info("failed to change to {}".format(self.scripts_wd)) + cmd_args = "{}".format(self.test_dict[test]['args']) + command = "./{} {}".format(self.test_dict[test]['command'], cmd_args) + self.logger.info("command: {}".format(command)) + self.logger.info("cmd_args {}".format(cmd_args)) + + if self.outfile_name is not None: + stdout_log_txt = os.path.join(self.log_path, "{}-{}-stdout.txt".format(self.outfile_name,test)) + self.logger.info("stdout_log_txt: {}".format(stdout_log_txt)) + stdout_log = open(stdout_log_txt, 'a') + stderr_log_txt = os.path.join(self.log_path, "{}-{}-stderr.txt".format(self.outfile_name,test)) + self.logger.info("stderr_log_txt: {}".format(stderr_log_txt)) + stderr_log = open(stderr_log_txt, 'a') + + # need to take into account --raw_line parameters thus need to use shlex.split + # need to preserve command to have correct command syntax in command output + command_to_run = command + command_to_run = shlex.split(command_to_run) + print("running {command_to_run}".format(command_to_run=command_to_run)) + self.test_start_time = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")).replace(':','-') + print("Test start: {time}".format(time=self.test_start_time)) + start_time = datetime.datetime.now() + try: + process = subprocess.Popen(command_to_run, shell=False, stdout=stdout_log, stderr=stderr_log, + universal_newlines=True) + # if there is a better solution please propose, the TIMEOUT Result is different then FAIL + try: + if int(self.test_timeout != 0): + process.wait(timeout=int(self.test_timeout)) + else: + process.wait() + except subprocess.TimeoutExpired: + process.terminate() + self.test_result = "TIMEOUT" + + except: + print("No such file or directory with command: {}".format(command)) + self.logger.info("No such file or directory with command: {}".format(command)) + + end_time = datetime.datetime.now() + self.test_end_time = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")).replace(':','-') + print("Test end time {time}".format(time=self.test_end_time)) + + time_delta = end_time - start_time + self.duration = "{day}d {seconds}s {msec} ms".format( + day=time_delta.days,seconds=time_delta.seconds,msec=time_delta.microseconds) + + # If collect meta data is set + meta_data_path = "" + if self.test_result != "TIMEOUT": + stdout_log_size = os.path.getsize(stdout_log_txt) + if stdout_log_size > 0: + stdout_log_fd = open(stdout_log_txt) + #"Report Location:::/home/lanforge/html-reports/wifi-capacity-2021-08-17-04-02-56" + # + for line in stdout_log_fd: + if "Report Location" in line: + meta_data_path = line.replace('"','') + meta_data_path = meta_data_path.replace('Report Location:::','') + meta_data_path = meta_data_path.split('/')[-1] + meta_data_path = meta_data_path.strip() + meta_data_path = self.report_path + '/' + meta_data_path + '/meta.txt' + break + stdout_log_fd.close() + if meta_data_path != "": + meta_data_fd = open(meta_data_path,'w+') + meta_data_fd.write('$ Generated by Candela Technologies LANforge network testing tool\n') + meta_data_fd.write('$ meta.txt file location\n') + meta_data_fd.write("file {path}\n".format(path=meta_data_path)) + meta_data_fd.write('$ LANforge GUI\n') + meta_data_fd.write('gui_version: {gui_version} \n'.format(gui_version=self.lanforge_gui_version)) + meta_data_fd.write('$ LANforge command\n') + meta_data_fd.write("command {command}\n".format(command=command)) + # split command at test-tag , at rest of string once at the actual test-tag value + test_tag = command.split('test_tag',maxsplit=1)[-1].split(maxsplit=1)[0] + test_tag = test_tag.replace("'","") + meta_data_fd.write('$ LANforge test_tag\n') + meta_data_fd.write("test_tag {test_tag}\n".format(test_tag=test_tag)) + # LANforge information is a list thus [0] + meta_data_fd.write('$ LANforge Information\n') + meta_data_fd.write("lanforge_system_node {lanforge_system_node}\n".format(lanforge_system_node=self.lanforge_system_node_version[0])) + meta_data_fd.write("lanforge_kernel_version {lanforge_kernel_version}\n".format(lanforge_kernel_version=self.lanforge_kernel_version[0])) + meta_data_fd.write("lanforge_gui_version_full {lanforge_gui_version_full}\n".format(lanforge_gui_version_full=self.lanforge_gui_version_full[0])) + meta_data_fd.close() + + if self.test_result != "TIMEOUT": + stderr_log_size = os.path.getsize(stderr_log_txt) + if stderr_log_size > 0: + self.logger.info("File: {} is not empty: {}".format(stderr_log_txt, str(stderr_log_size))) + text = open(stderr_log_txt).read() + if 'Error' in text: + self.text_result = "Failure" + background = self.background_red + else: + self.test_result = "Success" + background = self.background_green + else: + self.logger.info("File: {} is empty: {}".format(stderr_log_txt, str(stderr_log_size))) + self.test_result = "Success" + background = self.background_green + else: + self.logger.info("TIMEOUT FAILURE, Check LANforge Radios") + self.test_result = "Time Out" + background = self.background_purple + + # Ghost will put data in stderr + if 'ghost' in command or 'lf_qa' in command: + if self.test_result != "TIMEOUT": + text = open(stderr_log_txt).read() + if 'Error' in text: + self.test_result = "Failure" + background = self.background_red + else: + self.test_result = "Success" + background = self.background_blue + if 'lf_qa' in command: + line_list = open(stdout_log_txt).readlines() + for line in line_list: + if 'html report:' in line: + self.qa_report_html = line + print("html_report: {report}".format(report=self.qa_report_html)) + break + + self.qa_report_html = self.qa_report_html.replace('html report: ','') + + + # stdout_log_link is used for the email reporting to have the corrected path + stdout_log_link = str(stdout_log_txt).replace('/home/lanforge', '') + stderr_log_link = str(stderr_log_txt).replace('/home/lanforge', '') + if command.find(' ') > 1: + short_cmd = command[0:command.find(' ')] + else: + short_cmd = command + + self.html_results += """ + """ + str(test) + """ + """ + str(short_cmd) + """ + """ + str(self.duration) + """ + """ + str(self.test_start_time) + """ + """ + str(self.test_end_time) + """ + """ + str(self.test_result) + """ + STDOUT""" + if self.test_result == "Failure": + self.html_results += """STDERR""" + elif self.test_result == "Time Out": + self.html_results += """STDERR""" + else: + self.html_results += """""" + + self.html_results += """""" + #TODO - plase copy button at end and selectable , so individual sections may be copied + if command != short_cmd: + '''self.html_results += f""" + Copy + {command} + + """.format(command=command)''' + self.html_results += f""" + {command} + + """.format(command=command) + + #nocopy + ''' + self.html_results += f""" + {command} + + """.format(command=command) + ''' + + row = [test, command, self.test_result, stdout_log_txt, stderr_log_txt] + self.csv_results_writer.writerow(row) + self.csv_results_file.flush() + # self.logger.info("row: {}".format(row)) + self.logger.info("test: {} executed".format(test)) + + else: + self.logger.info( + "enable value {} invalid for test: {}, test skipped".format(self.test_dict[test]['enabled'], test)) + self.finish_html_results() + + +def main(): + # arguments + parser = argparse.ArgumentParser( + prog='lf_check.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + lf_check.py : running scripts + ''', + description='''\ +lf_check.py +----------- + +Summary : +--------- +running scripts + +Example : +./lf_check.py --json_rig rig.json --json_dut dut.json --json_test tests.json --suite suite_test +note if all json data (rig,dut,tests) in same json file pass same json in for all 3 inputs +--------- + ''') + + parser.add_argument('--dir', help="--dir ", default="lf_check") + parser.add_argument('--path', help="--path ", default="/home/lanforge/html-results") + parser.add_argument('--json_rig', help="--json_rig ", default="", required=True) + parser.add_argument('--json_dut', help="--json_dut ", default="", required=True) + parser.add_argument('--json_test', help="--json_test ", default="", required=True) + parser.add_argument('--json_igg', help="--json_igg ", default="") + parser.add_argument('--suite', help="--suite default TEST_DICTIONARY", default="TEST_DICTIONARY") + parser.add_argument('--production', help="--production stores true, sends email results to production email list", + action='store_true') + parser.add_argument('--outfile', help="--outfile used as base name for all files generated", + default="") + parser.add_argument('--logfile', help="--logfile logging for output of lf_check.py script", + default="lf_check.log") + + args = parser.parse_args() + + # load test config file information either .json + json_rig = "" + try: + print("args.json_rig {rig}".format(rig=args.json_rig)) + with open(args.json_rig, 'r') as json_rig_config: + json_rig = json.load(json_rig_config) + except: + print("Error reading {}".format(args.json_rig)) + + json_dut = "" + try: + print("args.json_dut {dut}".format(dut=args.json_dut)) + with open(args.json_dut, 'r') as json_dut_config: + json_dut = json.load(json_dut_config) + except: + print("Error reading {}".format(args.json_dut)) + + json_test = "" + try: + print("args.json_test {}".format(args.json_test)) + with open(args.json_test, 'r') as json_test_config: + json_test = json.load(json_test_config) + except: + print("Error reading {}".format(args.json_test)) + + json_igg = "" + if args.json_igg != "": + try: + print("args.json_igg {}".format(args.json_igg)) + with open(args.json_igg, 'r') as json_igg_config: + json_igg = json.load(json_igg_config) + except: + print("Error reading {}".format(args.json_igg)) + + + # Test-rig information information + lanforge_system_node_version = 'NO_LF_NODE_VER' + scripts_git_sha = 'NO_GIT_SHA' + lanforge_kernel_version = 'NO_KERNEL_VER' + lanforge_gui_version_full = 'NO_LF_GUI_VER' + + # select test suite + test_suite = args.suite + __dir = args.dir + __path = args.path + + if args.production: + production = True + print("Email to production list") + else: + production = False + print("Email to email list") + + # create report class for reporting + report = lf_report(_path = __path, + _results_dir_name=__dir, + _output_html="lf_check.html", + _output_pdf="lf-check.pdf") + + current_time = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) + csv_results = "lf_check{}-{}.csv".format(args.outfile, current_time) + csv_results = report.file_add_path(csv_results) + outfile_name = "lf_check-{}-{}".format(args.outfile, current_time) + outfile = report.file_add_path(outfile_name) + report_path = report.get_report_path() + log_path = report.get_log_path() + + # lf_check() class created + check = lf_check(_json_rig=json_rig, + _json_dut=json_dut, + _json_test=json_test, + _test_suite=test_suite, + _json_igg=json_igg, + _production=production, + _csv_results=csv_results, + _outfile=outfile, + _outfile_name = outfile_name, + _report_path=report_path, + _log_path=log_path) + + # set up logging + logfile = args.logfile[:-4] + print("logfile: {}".format(logfile)) + logfile = "{}-{}.log".format(logfile, current_time) + logfile = report.file_add_path(logfile) + print("logfile {}".format(logfile)) + formatter = logging.Formatter(FORMAT) + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + file_handler = logging.FileHandler(logfile, "w") + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + logger.addHandler(logging.StreamHandler(sys.stdout)) # allows to logging to file and stdout + + # read config and run tests + check.read_json_rig() #check.read_config + check.read_json_dut() + check.read_json_test() + + if args.json_igg != "": + print("Tests need to have influx parameters passed in") + check.read_json_igg() + + ping_result = check.check_if_port_exists(json_igg) + for key, value in ping_result.items(): + if value[1] is None: + print(UserWarning('Check your %s IP address, %s is unreachable' % (key, value[0]))) + else: + print('%s IP address %s accessible' % (key, value[1])) + + + # get sha and lanforge information for results + # Need to do this after reading the configuration + try: + scripts_git_sha = check.get_scripts_git_sha() + print("git_sha {sha}".format(sha=scripts_git_sha)) + except: + print("git_sha read exception ") + + try: + lanforge_system_node_version = check.get_lanforge_system_node_version() + print("lanforge_system_node_version {system_node_ver}".format(system_node_ver=lanforge_system_node_version)) + except: + print("lanforge_system_node_version exception") + + try: + lanforge_kernel_version = check.get_lanforge_kernel_version() + print("lanforge_kernel_version {kernel_ver}".format(kernel_ver=lanforge_kernel_version)) + except: + print("lanforge_kernel_version exception, tests aborted check lanforge ip") + exit(1) + + try: + lanforge_gui_version_full = check.get_lanforge_gui_version() + print("lanforge_gui_version_full {lanforge_gui_version_full}".format(lanforge_gui_version_full=lanforge_gui_version_full)) + except: + print("lanforge_gui_version exception, tests aborted check lanforge ip") + exit(1) + + try: + lanforge_radio_json, lanforge_radio_text = check.get_lanforge_radio_information() + lanforge_radio_formatted_str = json.dumps(lanforge_radio_json, indent = 2) + print("lanforge_radio_json: {lanforge_radio_json}".format(lanforge_radio_json=lanforge_radio_formatted_str)) + + # note put into the meta data + lf_radio_df = pd.DataFrame(columns = ['Radio','WIFI-Radio Driver','Radio Capabilities','Firmware Version','max_sta','max_vap','max_vifs']) + + for key in lanforge_radio_json: + if 'wiphy' in key: + #print("key {}".format(key)) + #print("lanforge_radio_json[{}]: {}".format(key,lanforge_radio_json[key])) + driver = lanforge_radio_json[key]['driver'].split('Driver:',maxsplit=1)[-1].split(maxsplit=1)[0] + try: + firmware_version = lanforge_radio_json[key]['firmware version'] + except: + print("5.4.3 radio fw version not in /radiostatus/all") + firmware_version = "5.4.3 N/A" + + lf_radio_df = lf_radio_df.append( + {'Radio':lanforge_radio_json[key]['entity id'], + 'WIFI-Radio Driver': driver, + 'Radio Capabilities':lanforge_radio_json[key]['capabilities'], + 'Firmware Version':firmware_version, + 'max_sta':lanforge_radio_json[key]['max_sta'], + 'max_vap':lanforge_radio_json[key]['max_vap'], + 'max_vifs':lanforge_radio_json[key]['max_vifs']}, ignore_index = True) + print("lf_radio_df:: {lf_radio_df}".format(lf_radio_df=lf_radio_df)) + + except Exception as error: + print("print_exc(): {error}".format(error=error)) + traceback.print_exc(file=sys.stdout) + lf_radio_df = pd.DataFrame() + print("get_lanforge_radio_json exception, no radio data") + + # LANforge and scripts config for results + lf_test_setup = pd.DataFrame() + lf_test_setup['LANforge'] = lanforge_system_node_version + lf_test_setup['kernel version'] = lanforge_kernel_version + lf_test_setup['GUI version'] = lanforge_gui_version_full + lf_test_setup['scripts git sha'] = scripts_git_sha + + # Successfully gathered LANforge information Run Tests + check.run_script_test() + + # generate output reports + test_rig = check.get_test_rig() + report.set_title("LF Check: {test_rig} lf_check.py".format(test_rig=test_rig)) + report.build_banner_left() + report.start_content_div2() + report.set_obj_html("Objective", "Run QA Tests") + report.build_objective() + report.set_table_title("LANForge") + report.build_table_title() + report.set_table_dataframe(lf_test_setup) + report.build_table() + report.set_table_title("LANForge Radios") + report.build_table_title() + report.set_table_dataframe(lf_radio_df) + report.build_table() + report.set_table_title("LF Check Test Results") + report.build_table_title() + html_results = check.get_html_results() + report.set_custom_html(html_results) + report.build_custom() + report.build_footer() + report.copy_js() + html_report = report.write_html_with_timestamp() + print("html report: {}".format(html_report)) + try: + report.write_pdf_with_timestamp() + except: + print("exception write_pdf_with_timestamp()") + + print("lf_check_html_report: " + html_report) + check.send_results_email(report_file=html_report) + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/lf_hackrf3.py b/lanforge/lanforge-scripts/py-scripts/sandbox/lf_hackrf3.py new file mode 100755 index 000000000..bc9e4ed28 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/lf_hackrf3.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +################################################## +# GNU Radio Python Flow Graph +# Title: Top Block +# Not auto-generated +################################################## + +from gnuradio import blocks +#from gnuradio import eng_notation +from gnuradio import gr +#from gnuradio.eng_option import eng_option +#from gnuradio.filter import firdes +#from optparse import OptionParser +import osmosdr +import time +import getopt, sys +import os +import signal + +default_gain = 14 +default_if_gain = 27 +default_bb_gain = 20 +gain = default_if_gain +if_gain = default_if_gain +bb_gain = default_bb_gain +freq = 5300000000 +vector = [] +pid_file = "lf_hackrf_py.pid" +mgt_pipe = -1 +sample_mod = 2 +repeat_onoff = True + +class top_block(gr.top_block): + + def __init__(self): + global freq + global vector + global sample_mod + global repeat_onoff + global gain + global if_gain + global bb_gain + + gr.top_block.__init__(self, "Top Block") + + ################################################## + # Variables + ################################################## + self.samp_rate = samp_rate = 32000 + + ################################################## + # Blocks + ################################################## + #self.osmosdr_sink_0 = osmosdr.sink( args="numchan=" + str(1) + " " + "" ) # this is mysterious + self.osmosdr_sink_0 = osmosdr.sink( args="numchan=1 " ) + self.osmosdr_sink_0.set_sample_rate(1e6 * sample_mod) + self.osmosdr_sink_0.set_center_freq(freq, 0) + self.osmosdr_sink_0.set_freq_corr(0, 0) + self.osmosdr_sink_0.set_gain(gain, 0) + self.osmosdr_sink_0.set_if_gain(if_gain, 0) + self.osmosdr_sink_0.set_bb_gain(bb_gain, 0) + self.osmosdr_sink_0.set_antenna("", 0) + self.osmosdr_sink_0.set_bandwidth(20e6, 0) + + #print vector + self.blocks_vector_source_x_0 = blocks.vector_source_c(vector, repeat_onoff, 1, []) + + ################################################## + # Connections + ################################################## + self.connect((self.blocks_vector_source_x_0, 0), (self.osmosdr_sink_0, 0)) + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + +def usage(): + print( "[--pulse_width { usecs }]\n") + print( "[--pulse_interval { usecs}]\n") + print( "[--pulse_count { number}]\n") + print( "[--one_burst] # only one burst\n") + print( "[--sweep_time { msec }]\n") + print( "[--freq { khz }]\n") + print( "[--daemon { 0 | 1 }]\n") + print( "[--pid_file { pid-file-name }]\n") + print( "[--gain {%s}]\n" % default_gain) # Main amp, on (14db) or off (0) + print( "[--if_gain {%s} ]\n" % default_if_gain) # Fine tune RX/TX gain, 0-40 + print( "[--bb_gain {%s} ]\n" % default_bb_gain) # RX only 0-62, 2db steps + print( "[--mgt_pipe { pipe-file-name}] # To talk back to LANforge process\n") + print( "[--help]\n\n") + + +def main(top_block_cls=top_block): + pulse_width = 1 + pulse_interval = 1428 + pulse_count = 18 + one_burst = False # False = continuous + sweep_time = 1000 + daemon = 0 + mgt_pipe_name = "" + + global gain + global if_gain + global bb_gain + global vector + global pid_file + global mgt_pipe + global freq + global sample_mod + + try: + opts, args = getopt.getopt(sys.argv[1:], + "w:i:c:bs:f:d:m:p:g:a:v:h", + ["pulse_width=", "pulse_interval=", "pulse_count=", + "one_burst", "sweep_time=", "freq=", "daemon=", + "mgt_pipe=", "pid_file=", "gain=", "if_gain=", "bb_gain=", "help" ]) + except getopt.GetoptError as err: + # print help information and exit: + print(str(err)) # will print something like "option -a not recognized" + usage() + sys.exit(2) + + for o, a in opts: + if o in ("-h", "--help"): + usage() + sys.exit() + elif o in ("-w", "--pulse_width"): + pulse_width = int(a) + elif o in ("-i", "--pulse_interval"): + pulse_interval = int(a) + elif o in ("-c", "--pulse_count"): + pulse_count = int(a) + elif o in ("-b", "--one_burst"): + one_burst = True + elif o in ("-s", "--sweep_time"): + sweep_time = int(a) + elif o in ("-d", "--daemon"): + daemon = 1 + elif o in ("-m", "--mgt_pipe"): + mgt_pipe_name = a; + elif o in ("-p", "--pid_file"): + pid_file = a; + elif o in ("-f", "--freq"): + freq = int(a) * 1000 + elif o in ("-g", "--gain"): + gain = int(a) + elif o in ("-a", "--if_gain"): + if_gain = int(a) + elif o in ("-v", "--bb_gain"): + bb_gain = int(a) + else: + assert False, "unhandled option" + + killer = GracefulKiller() + + writePidFile() + + if mgt_pipe_name != "": + mgt_pipe = open(mgt_pipe_name, "a") + + notifyMgr("starting") + + # Build our vector + #pw = "[1]*%i" % (pulse_width) + #pi = "[0]*%i" % (pulse_interval) + #st = "[0]*%i" % (sweep_time * 1000) + + for i in range(0, pulse_count): + vector += [1] * (pulse_width * sample_mod) + vector += [0] * (pulse_interval * sample_mod) + + vector += [0]*((sweep_time * 1000 * sample_mod)) + + #print vector + + tb = top_block_cls() + + if one_burst: + tb.start() + notifyMgr("started") + # Give it time to run, very unlikely we have more than 1 sec of one-shot data + time.sleep(1) + + else: + tb.start() + notifyMgr("started") + while True: + if daemon: + # TODO: Should ping hackrf device here to make sure everything + # is working??? + if killer.kill_now: + break + time.sleep(1) + continue + + try: + print( "Options: q(uit) s(top) g(o)\n") + cmd = input('>>> ') + if cmd == 'q': + break + elif cmd == 's': + notifyMgr("stopping") + tb.stop() + tb.wait() + notifyMgr("stopped") + elif cmd == 'g': + notifyMgr("starting") + tb.start() + notifyMgr("started") + else: + print( "Options: q(uit) s(top) g(o)\n") + except EOFError: + break + + notifyMgr("stopping") + tb.stop() + notifyMgr("stopped") + tb.wait() + notifyMgr("exiting") + +def notifyMgr(msg): + global mgt_pipe + + if (mgt_pipe != -1): + try: + mgt_pipe.write("admin rfgen '%s'\n" % msg) + mgt_pipe.flush() + return + except: + pass + print( msg + "\n") + + +def writePidFile(): + global pid_file + + pid = str(os.getpid()) + f = open(pid_file, 'w') + f.write(pid) + f.close() + +class GracefulKiller: + kill_now = False + def __init__(self): + signal.signal(signal.SIGINT, self.exit_gracefully) + signal.signal(signal.SIGTERM, self.exit_gracefully) + + def exit_gracefully(self,signum, frame): + print( "Stop signal received.\n") + self.kill_now = True + +if __name__ == '__main__': + main() + diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/lf_json_autogen_test.py b/lanforge/lanforge-scripts/py-scripts/sandbox/lf_json_autogen_test.py new file mode 100644 index 000000000..29e9e6542 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/lf_json_autogen_test.py @@ -0,0 +1,94 @@ +#!/usr/bin/python3 + +''' +NAME: +lf_json_test.py + +PURPOSE: + + +EXAMPLE: +./lf_json_test.py - + +NOTES: + + +TO DO NOTES: + + +''' +import os +import sys +if sys.version_info[0] != 3: + print("This script requires Python3") + exit() + + +from time import sleep +import argparse +import json +#if 'py-json' not in sys.path: +# sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) +# print("path: {}".format(os.path.join(os.path.abspath('..')))) + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + +from LANforge import lf_json_autogen + +class lf_read_json(): + def __init__(self): + + self.timeout = 10 + + + def preprocess_data(self): + pass + + + +def main(): + # arguments + parser = argparse.ArgumentParser( + prog='lf_json_test.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + lf_json_test.py : lf json test + ''', + description='''\ +lf_json_test.py +----------- + +Summary : +--------- + + +./lf_dataplane_json.py --mgr 192.168.0.101 --port 8080 --lf_user lanforge --lf_password lanforge --instance_name dataplane-instance --config_name test_con --upstream 1.1.eth1 --dut asus_5g --duration 15s --station 1.1.13.sta0002 --download_speed 85% --upload_speed 0 --raw_line 'pkts: Custom;60;MTU' --raw_line 'cust_pkt_sz: 88 1200' --raw_line 'directions: DUT Transmit' --raw_line 'traffic_types: UDP' --raw_line 'bandw_options: 20' --raw_line 'spatial_streams: 1 + + ''') + + #parser.add_argument('--json', help="--json json input file", default="config.json") + parser.add_argument('--cmd', help="--cmd json command", default="") + args = parser.parse_args() + json_cmd = args.cmd + print("json cmd {}".format(json_cmd)) + #with open(config_json, 'r') as config_file: + # config_data = json.load(config_file) + #print(config_data) + + lf_get = lf_json_autogen.LFJsonGet(lfclient_host='192.168.0.101', + lfclient_port=8080, + debug_=True, + ) + + duts = lf_get.get_chamber(fields = [lf_get.duts]) + print("duts {}".format(duts)) + + + + + print("END lf_read_json.py") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/test_ipv4_variable_time2.py b/lanforge/lanforge-scripts/py-scripts/sandbox/test_ipv4_variable_time2.py new file mode 100755 index 000000000..e07ee791e --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/test_ipv4_variable_time2.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 + +"""test_ipv4_variable_time.py will create stations and endpoints to generate and verify layer-3 traffic. + +This script will create a variable number of stations each with their own set of cross-connects and endpoints. +It will then create layer 3 traffic over a specified amount of time, testing for increased traffic at regular intervals. +This test will pass if all stations increase traffic over the full test duration. + +Use './test_ipv4_variable_time.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys +import os + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + +import argparse +from LANforge import LFUtils +from realm import Realm +from test_base import TestBase +import time +import datetime + + +class IPV4VariableTime(Realm, TestBase): + def __init__(self, + ssid=None, + security=None, + password=None, + sta_list=[], + name_prefix=None, + upstream=None, + radio=None, + host="localhost", + port=8080, + mode=0, + ap=None, + monitor=False, + side_a_min_rate=56, side_a_max_rate=0, + side_b_min_rate=56, side_b_max_rate=0, + number_template="00000", test_duration="5m", use_ht160=False, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(lfclient_host=host, + lfclient_port=port) + + self.upstream = upstream + self.host = host + self.port = port + self.ssid = ssid + self.sta_list = sta_list + self.security = security + self.password = password + self.radio = radio + self.mode = mode + self.ap = ap + self.number_template = number_template + self.debug = _debug_on + # self.json_post("/cli-json/set_resource", { + # "shelf":1, + # "resource":all, + # "max_staged_bringup": 30, + # "max_trying_ifup": 15, + # "max_station_bringup": 6 + # }) + self.name_prefix = name_prefix + self.test_duration = test_duration + self.station_profile = self.new_station_profile(ver = 2, station_list = sta_list) + self.cx_profile = self.new_l3_cx_profile(ver = 2) + + #station profile settings + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.debug = self.debug + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.use_ht160 = use_ht160 + self.station_profile.mode = mode + if self.ap is not None: + self.station_profile.set_command_param("add_sta", "ap", self.ap) + + #cx profile settings + self.cx_profile.host = self.host + self.cx_profile.port = self.port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = side_a_min_rate + self.cx_profile.side_a_max_bps = side_a_max_rate + self.cx_profile.side_b_min_bps = side_b_min_rate + self.cx_profile.side_b_max_bps = side_b_max_rate + self.profiles.extend([self.station_profile, self.cx_profile]) + +def main(): + optional = [] + optional.append({'name': '--mode', 'help': 'Used to force mode of stations'}) + optional.append({'name': '--ap', 'help': 'Used to force a connection to a particular AP'}) + optional.append({'name': '--output_format', 'help': 'choose either csv or xlsx'}) + optional.append({'name': '--report_file', 'help': 'where you want to store results', 'default': None}) + optional.append({'name': '--a_min', 'help': '--a_min bps rate minimum for side_a', 'default': 256000}) + optional.append({'name': '--b_min', 'help': '--b_min bps rate minimum for side_b', 'default': 256000}) + optional.append( + {'name': '--test_duration', 'help': '--test_duration sets the duration of the test', 'default': "2m"}) + optional.append({'name': '--layer3_cols', 'help': 'Columns wished to be monitored from layer 3 endpoint tab', + 'default': ['name', 'tx bytes', 'rx bytes']}) + optional.append({'name': '--port_mgr_cols', 'help': 'Columns wished to be monitored from port manager tab', + 'default': ['ap', 'ip', 'parent dev']}) + optional.append( + {'name': '--compared_report', 'help': 'report path and file which is wished to be compared with new report', + 'default': None}) + optional.append({'name': '--monitor_interval', + 'help': 'frequency of monitor polls - ex: 250ms, 35s, 2h', + 'default': '2s'}) + optional.append({'name': '--monitor', + 'help': 'whether test data should be recorded and stored in a report'}) + parser = Realm.create_basic_argparse( + prog='test_ipv4_variable_time.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create stations to test connection and traffic on VAPs of varying security types (WEP, WPA, WPA2, WPA3, Open) + ''', + description='''\ +test_ipv4_variable_time.py: +-------------------- +Generic command layout: + +python3 ./test_ipv4_variable_time.py + --upstream_port eth1 + --radio wiphy0 + --num_stations 32 + --security {open|wep|wpa|wpa2|wpa3} + --mode 1 + {"auto" : "0", + "a" : "1", + "b" : "2", + "g" : "3", + "abg" : "4", + "abgn" : "5", + "bgn" : "6", + "bg" : "7", + "abgnAC" : "8", + "anAC" : "9", + "an" : "10", + "bgnAC" : "11", + "abgnAX" : "12", + "bgnAX" : "13"} + --ssid netgear + --password admin123 + --test_duration 2m (default) + --monitor_interval_ms + --monitor + --a_min 3000 + --b_min 1000 + --ap "00:0e:8e:78:e1:76" + --output_format csv + --report_file ~/Documents/results.csv (Example of csv file output - please use another extension for other file formats) + --compared_report ~/Documents/results_prev.csv (Example of csv file retrieval - please use another extension for other file formats) - UNDER CONSTRUCTION + --layer3_cols'name','tx bytes','rx bytes','dropped' (column names from the GUI to print on report - please read below to know what to put here according to preferences) + --port_mgr_cols 'ap','ip' (column names from the GUI to print on report - please read below to know what to put here according to preferences) + --debug +=============================================================================== + ** FURTHER INFORMATION ** + Using the layer3_cols flag: + + Currently the output function does not support inputting the columns in layer3_cols the way they are displayed in the GUI. This quirk is under construction. To output + certain columns in the GUI in your final report, please match the according GUI column display to it's counterpart to have the columns correctly displayed in + your report. + + GUI Column Display Layer3_cols argument to type in (to print in report) + + Name | 'name' + EID | 'eid' + Run | 'run' + Mng | 'mng' + Script | 'script' + Tx Rate | 'tx rate' + Tx Rate (1 min) | 'tx rate (1 min)' + Tx Rate (last) | 'tx rate (last)' + Tx Rate LL | 'tx rate ll' + Rx Rate | 'rx rate' + Rx Rate (1 min) | 'rx rate (1 min)' + Rx Rate (last) | 'rx rate (last)' + Rx Rate LL | 'rx rate ll' + Rx Drop % | 'rx drop %' + Tx PDUs | 'tx pdus' + Tx Pkts LL | 'tx pkts ll' + PDU/s TX | 'pdu/s tx' + Pps TX LL | 'pps tx ll' + Rx PDUs | 'rx pdus' + Rx Pkts LL | 'pps rx ll' + PDU/s RX | 'pdu/s tx' + Pps RX LL | 'pps rx ll' + Delay | 'delay' + Dropped | 'dropped' + Jitter | 'jitter' + Tx Bytes | 'tx bytes' + Rx Bytes | 'rx bytes' + Replays | 'replays' + TCP Rtx | 'tcp rtx' + Dup Pkts | 'dup pkts' + Rx Dup % | 'rx dup %' + OOO Pkts | 'ooo pkts' + Rx OOO % | 'rx ooo %' + RX Wrong Dev | 'rx wrong dev' + CRC Fail | 'crc fail' + RX BER | 'rx ber' + CX Active | 'cx active' + CX Estab/s | 'cx estab/s' + 1st RX | '1st rx' + CX TO | 'cx to' + Pattern | 'pattern' + Min PDU | 'min pdu' + Max PDU | 'max pdu' + Min Rate | 'min rate' + Max Rate | 'max rate' + Send Buf | 'send buf' + Rcv Buf | 'rcv buf' + CWND | 'cwnd' + TCP MSS | 'tcp mss' + Bursty | 'bursty' + A/B | 'a/b' + Elapsed | 'elapsed' + Destination Addr | 'destination addr' + Source Addr | 'source addr' + ''', + more_optional=optional) + + args = parser.parse_args() + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_sta = int(args.num_stations) + + + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=num_sta - 1, padding_number_=10000, + radio=args.radio) + + #transfer below to l3cxprofile2 or base_profile-----------------------# + # try: + # layer3connections = ','.join([[*x.keys()][0] for x in ip_var_test.json_get('endp')['endpoint']]) + # except: + # raise ValueError('Try setting the upstream port flag if your device does not have an eth1 port') + + # if type(args.layer3_cols) is not list: + # layer3_cols = list(args.layer3_cols.split(",")) + # # send col names here to file to reformat + # else: + # layer3_cols = args.layer3_cols + # # send col names here to file to reformat + # if type(args.port_mgr_cols) is not list: + # port_mgr_cols = list(args.port_mgr_cols.split(",")) + # # send col names here to file to reformat + # else: + # port_mgr_cols = args.port_mgr_cols + # # send col names here to file to reformat + # if args.debug: + # print("Layer 3 Endp column names are...") + # print(layer3_cols) + # print("Port Manager column names are...") + # print(port_mgr_cols) + + + ip_var_test = IPV4VariableTime(host=args.mgr, + port=args.mgr_port, + number_template="0000", + sta_list=station_list, + name_prefix="VT", + upstream=args.upstream_port, + ssid=args.ssid, + password=args.passwd, + radio=args.radio, + security=args.security, + test_duration=args.test_duration, + use_ht160=False, + side_a_min_rate=args.a_min, + side_b_min_rate=args.b_min, + mode=args.mode, + ap=args.ap, + _debug_on=args.debug) + + ip_var_test.begin() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/sandbox/update_dut.py b/lanforge/lanforge-scripts/py-scripts/sandbox/update_dut.py new file mode 100755 index 000000000..e2d20c40f --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sandbox/update_dut.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +""" +This script updates a Device Under Test (DUT) entry in the LANforge test scenario. +A common reason to use this would be to update MAC addresses in a DUT when you switch +between different items of the same make/model of a DUT. +""" +import sys + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append('../py-json') + +import argparse +import pprint +from pprint import * +from LANforge.lfcli_base import LFCliBase +from LANforge.LFUtils import * +from LANforge import add_dut +from LANforge import LFUtils +import argparse +import realm +import time +import datetime + + +class UpdateDUT(LFCliBase): + def __init__(self, host, port, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + + super().__init__(host, port, + _debug=_debug_on, + _exit_on_fail=_exit_on_fail, + _local_realm=realm.Realm(lfclient_host=host, lfclient_port=port, debug_=_debug_on)) + self.host = host + self.port = port + self.notes = [] + self.append = [] + self.params = {} + self.flags = 0x0 + self.flags_mask = 0x0 + self.data = {} + self.url = "/cli-json/add_dut" + + def build(self): + self.dut_profile = self.local_realm.new_dut_profile() + self.dut_profile.name = self.name + for param in self.params: + if (self.debug): + print("param: %s: %s"%(param, self.params[param])) + self.dut_profile.set_param(param, self.params[param]) + + if (self.debug): + print("flags: %s"%self.flags) + self.dut_profile.flags = self.flags + self.dut_profile.flags_mask = self.flags_mask + if len(self.notes) > 0: + if (self.debug): + pprint.pprint(self.notes) + self.dut_profile.notes = self.notes + if len(self.append) > 0: + if (self.debug): + pprint.pprint(self.append) + self.dut_profile.append = self.append + + def start(self, print_pass=False, print_fail=False): + self.dut_profile.create() + self._pass("DUT updated") + pass + + def stop(self): + pass + + def cleanup(self, sta_list): + pass + +def main(): + lfjson_host = "localhost" + lfjson_port = 8080 + + param_string = " " + for param in add_dut.dut_params: + param_string += "%s, "%param.name + + flags_string = " " + for flag in add_dut.dut_flags: + flags_string += "%s, "%flag.name + + parser = LFCliBase.create_bare_argparse( prog='update_dut.py', + formatter_class=argparse.RawTextHelpFormatter, + description='''{file} +-------------------- +Generic command layout: +python {file} --dut [DUT name] # update existing DUT record + --entry [key,value] # update/add entry by specifying key and value + --flag [flag,0|1] # toggle a flag on 1 or off 0 + --notes "all lines of text" + --notes "are replaced if any" + --notes "line of text is submitted with --notes" + --append "this appends a line of text" +DUT Parameters: + {params} +DUT Flags: + {flags} + +Command Line Example: +python3 {file} --mgr 192.168.100.24 --update Pathfinder \ + --entry MAC1,"00:00:ae:f0:b1:b9" \ + --notes "build 2901" \ + --flag STA_MODE,0 + --flag AP_MODE,1 + +'''.format(file=__file__, params=param_string, flags=flags_string), + epilog="See", + ) + parser.add_argument("--dut", type=str, help="name of DUT record") + parser.add_argument("-p", "--param", type=str, action="append", help="name,value pair to set parameter") + parser.add_argument("-f", "--flag", type=str, action="append", help="name,1/0/True/False pair to turn parameter on or off") + parser.add_argument("-n", "--notes", type=str, action="append", help="replace lines of notes in the record") + parser.add_argument("-a", "--append", type=str, action="append", help="append lines of text to the record") + args = parser.parse_args() + + if args.dut is None: + raise ValueError("need a name for the dut: --dut something") + + update_dut = UpdateDUT(args.mgr, lfjson_port, _debug_on=args.debug) + update_dut.name = args.dut + + if (args.param is not None): + for param in args.param: + if "," not in param: + raise ValueError("Invalid format for param: %s, please use key,value"%param) + (name,value) = param.split(",") + if (args.debug): + print("name %s = %s"%(name, value)) + if add_dut.dut_params.has(name): + update_dut.params[name] = value + else: + raise ValueError("parameter %s not in dut_params"%name) + + flags_sum = 0 + flags_mask_sum = 0 + + if (args.flag is not None): + for flag in args.flag: + if "," not in flag: + raise ValueError("Invalid format for flag: %s, please use flag,(0/1)"%param) + (name,val_str) = flag.split(",") + if (args.debug): + print("name %s = %s"%(name, val_str)) + if not add_dut.dut_flags.has(name): + raise ValueError("flag %s not in add_dut.dut_flags"%name) + on_off = (0,1)[val_str=="1"] + if (args.debug): + print("name %s = %s"%(name, on_off)) + flags_sum |= (0,add_dut.dut_flags.to_flag(name).value)[on_off] + flags_mask_sum |= add_dut.dut_flags.to_flag(name).value + + if args.debug: + print("params %s; flags %s; mask %s"%(",".join(update_dut.params), + hex(flags_sum), + hex(flags_mask_sum))) + if (args.notes is not None) and (len(args.notes) > 0): + update_dut.notes = args.notes.copy() + if (args.append is not None) and (len(args.append) > 0): + update_dut.append = args.append.copy() + + update_dut.flags = flags_sum + update_dut.flags_mask = flags_mask_sum + + update_dut.build() + update_dut.start() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/scenario.py b/lanforge/lanforge-scripts/py-scripts/scenario.py new file mode 100755 index 000000000..418de7a8e --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/scenario.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +parser = LFCliBase.create_bare_argparse( + prog='scenario.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Load a database file and control test groups\n''', + description='''scenario.py +-------------------- +Generic command example: +scenario.py --load db1 --action overwrite --clean_dut --clean_chambers + +scenario.py --start test_group1 + +scenario.py --quiesce test_group1 + +scenario.py --stop test_group1 +''') + +group = parser.add_mutually_exclusive_group() + +parser.add_argument('--load', help='name of database to load', default=None) + +parser.add_argument('--action', help='action to take with database {overwrite | append}', default="overwrite") + +parser.add_argument('--clean_dut', + help='use to cleanup DUT will be when overwrite is selected, otherwise they will be kept', + action="store_true") + +parser.add_argument('--clean_chambers', + help='use to cleanup Chambers will be when overwrite is selected, otherwise they will be kept', + action="store_true") + +group.add_argument('--start', help='name of test group to start', default=None) +group.add_argument('--quiesce', help='name of test group to quiesce', default=None) +group.add_argument('--stop', help='name of test group to stop', default=None) +args = parser.parse_args() + +local_realm = realm.Realm(lfclient_host=args.mgr, lfclient_port=args.mgr_port, debug_=args.debug) + +if args.load is not None: + data = { + "name": args.load, + "action": args.action, + "clean_dut": "no", + "clean_chambers": "no" + } + if args.clean_dut: + data['clean_dut'] = "yes" + if args.clean_chambers: + data['clean_chambers'] = "yes" + print("Loading database %s" % args.load) + local_realm.json_post("/cli-json/load", data) + +elif args.start is not None: + print("Starting test group %s..." % args.start) + local_realm.json_post("/cli-json/start_group", {"name": args.start}) +elif args.stop is not None: + print("Stopping test group %s..." % args.stop) + local_realm.json_post("/cli-json/stop_group", {"name": args.stop}) +elif args.quiesce is not None: + print("Quiescing test group %s..." % args.quiesce) + local_realm.json_post("/cli-json/quiesce_group", {"name": args.quiesce}) diff --git a/lanforge/lanforge-scripts/py-scripts/scripts_deprecated/csv_to_influx.py b/lanforge/lanforge-scripts/py-scripts/scripts_deprecated/csv_to_influx.py new file mode 100755 index 000000000..7d0b9a159 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/scripts_deprecated/csv_to_influx.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 + +# DEPRECATED, PLEASE USE InfluxRequest.py INSTEAD + +# Copies the data from a CSV file from the KPI file generated from a Wifi Capacity test to an Influx database + +# The CSV requires three columns in order to work: Date, test details, and numeric-score. + +# Date is a unix timestamp, test details is the variable each datapoint is measuring, and numeric-score is the value for that timepoint and variable. + +import sys +import os +from pprint import pprint +from influx2 import RecordInflux + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + +import argparse +import datetime + +def influx_add_parser_args(parser): + parser.add_argument('--influx_host', help='Hostname for the Influx database', default=None) + parser.add_argument('--influx_port', help='IP Port for the Influx database', default=8086) + parser.add_argument('--influx_org', help='Organization for the Influx database', default=None) + parser.add_argument('--influx_token', help='Token for the Influx database', default=None) + parser.add_argument('--influx_bucket', help='Name of the Influx bucket', default=None) + parser.add_argument('--influx_tag', action='append', nargs=2, + help='--influx_tag Can add more than one of these.', default=[]) + + +class CSVtoInflux(): + def __init__(self, + _exit_on_error=False, + _exit_on_fail=False, + _proxy_str=None, + _capture_signal_list=[], + influxdb=None, + _influx_tag=[], + target_csv=None, + sep='\t'): + self.influxdb = influxdb + self.target_csv = target_csv + self.influx_tag = _influx_tag + self.sep = sep + + def read_csv(self, file): + csv = open(file).read().split('\n') + rows = list() + for x in csv: + if len(x) > 0: + rows.append(x.split(self.sep)) + return rows + + # Submit data to the influx db if configured to do so. + def post_to_influx(self): + df = self.read_csv(self.target_csv) + length = list(range(0, len(df[0]))) + columns = dict(zip(df[0], length)) + print('columns: %s' % columns) + influx_variables = ['script', 'short-description', 'test_details', 'Graph-Group', + 'DUT-HW-version', 'DUT-SW-version', 'DUT-Serial-Num', 'testbed', 'Test Tag', 'Units'] + csv_variables = ['test-id', 'short-description', 'test details', 'Graph-Group', + 'dut-hw-version', 'dut-sw-version', 'dut-serial-num', 'test-rig', 'test-tag', 'Units'] + csv_vs_influx = dict(zip(csv_variables, influx_variables)) + for row in df[1:]: + row = [sub.replace('NaN', '0') for sub in row] + tags = dict() + print("row: %s" % row) + short_description = row[columns['short-description']] + if row[columns['numeric-score']] == 'NaN': + numeric_score = '0x0' + else: + numeric_score = float(row[columns['numeric-score']]) + date = row[columns['Date']] + date = datetime.datetime.utcfromtimestamp(int(date) / 1000).isoformat() #convert to datetime so influx can read it, this is required + for variable in csv_variables: + if variable in columns.keys(): + index = columns[variable] + influx_variable = csv_vs_influx[variable] + tags[influx_variable] = row[index] + self.influxdb.post_to_influx(short_description, numeric_score, tags, date) + + def script_name(self): + with open(self.target_csv) as fp: + line = fp.readline() + line = line.split('\t') + test_id_index = line.index('test-id') + line = fp.readline() + line.split('\t') + return line[test_id_index] + + + def create_dashboard(self, + dashboard_name=None): + #Create a dashboard in Grafana to look at the data you just posted to Influx + dashboard_name + + + +def main(): + lfjson_host = "localhost" + lfjson_port = 8080 + endp_types = "lf_udp" + debug = False + + parser = argparse.ArgumentParser( + prog='csv_to_influx.py', + # formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog=''' + ''', + + description='''\ +csv_to_influx.py: +-------------------- + +Summary : +---------- +Copies the data from a CSV file generated by a wifi capacity test to an influx database. + +Column names are designed for the KPI file generated by our Wifi Capacity Test. + +A user can of course change the column names to match these in order to input any csv file. + +The CSV file needs to have the following columns: + --date - which is a UNIX timestamp + --test details - which is the variable being measured by the test + --numeric-score - which is the value for that variable at that point in time. + +Generic command layout: +----------------------- +python .\\csv_to_influx.py + + +Command: +python3 csv_to_influx.py --influx_host localhost --influx_org Candela --influx_token random_token --influx_bucket lanforge + --target_csv kpi.csv + + + ''') + + influx_add_parser_args(parser) + + # This argument is specific to this script, so not part of the generic influxdb parser args + # method above. + parser.add_argument('--target_csv', help='CSV file to record to influx database', default="") + parser.add_argument('--sep', help='character to split CSV by', default='\t') + + args = parser.parse_args() + + influxdb = RecordInflux(_influx_host=args.influx_host, + _influx_port=args.influx_port, + _influx_org=args.influx_org, + _influx_token=args.influx_token, + _influx_bucket=args.influx_bucket) + + csvtoinflux = CSVtoInflux(influxdb=influxdb, + target_csv=args.target_csv, + _influx_tag=args.influx_tag, + sep=args.sep) + csvtoinflux.post_to_influx() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/sta_connect.py b/lanforge/lanforge-scripts/py-scripts/sta_connect.py new file mode 100755 index 000000000..c86314e42 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sta_connect.py @@ -0,0 +1,556 @@ +#!/usr/bin/env python3 +# This will create a station, create TCP and UDP traffic, run it a short amount of time, +# and verify whether traffic was sent and received. It also verifies the station connected +# to the requested BSSID if bssid is specified as an argument. +# The script will clean up the station and connections at the end of the test. +import sys +import os +import importlib +import argparse +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +removeCX = LFUtils.removeCX +removeEndps = LFUtils.removeEndps +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + +OPEN = "open" +WEP = "wep" +WPA = "wpa" +WPA2 = "wpa2" +MODE_AUTO = 0 + + +class StaConnect(LFCliBase): + def __init__(self, host, port, _dut_ssid="MyAP", _dut_passwd="NA", _dut_bssid="", + _user="", _passwd="", _sta_mode="0", _radio="wiphy0", + _resource=1, _upstream_resource=1, _upstream_port="eth2", + _sta_name=None, _debugOn=False, _dut_security=OPEN, _exit_on_error=False, + _cleanup_on_exit=True, _runtime_sec=60, _exit_on_fail=False): + # do not use `super(LFCLiBase,self).__init__(self, host, port, _debugOn) + # that is py2 era syntax and will force self into the host variable, making you + # very confused. + super().__init__(host, port, _debug=_debugOn, _exit_on_fail=_exit_on_fail) + self.debugOn = _debugOn + self.dut_security = "" + self.dut_ssid = _dut_ssid + self.dut_passwd = _dut_passwd + self.dut_bssid = _dut_bssid + self.user = _user + self.passwd = _passwd + self.sta_mode = _sta_mode # See add_sta LANforge CLI users guide entry + self.radio = _radio + self.resource = _resource + self.upstream_resource = _upstream_resource + self.upstream_port = _upstream_port + self.runtime_secs = _runtime_sec + self.cleanup_on_exit = _cleanup_on_exit + self.sta_url_map = None # defer construction + self.upstream_url = None # defer construction + self.station_names = [] + self.cx_names = {} + if _sta_name is not None: + self.station_names = [ _sta_name ] + # self.localrealm :Realm = Realm(lfclient_host=host, lfclient_port=port) # py > 3.6 + self.localrealm = Realm(lfclient_host=host, lfclient_port=port) # py > 3.6 + self.resulting_stations = {} + self.resulting_endpoints = {} + + # def get_realm(self) -> Realm: # py > 3.6 + def get_realm(self): + return self.localrealm + + def get_station_url(self, sta_name_=None): + if sta_name_ is None: + raise ValueError("get_station_url wants a station name") + if self.sta_url_map is None: + self.sta_url_map = {} + for sta_name in self.station_names: + self.sta_url_map[sta_name] = "port/1/%s/%s" % (self.resource, sta_name) + return self.sta_url_map[sta_name_] + + def get_upstream_url(self): + if self.upstream_url is None: + self.upstream_url = "port/1/%s/%s" % (self.upstream_resource, self.upstream_port) + return self.upstream_url + + # Compare pre-test values to post-test values + def compare_vals(self, name, postVal, print_pass=False, print_fail=True): + # print(f"Comparing {name}") + if postVal > 0: + self._pass("%s %s" % (name, postVal), print_pass) + else: + self._fail("%s did not report traffic: %s" % (name, postVal), print_fail) + + def remove_stations(self): + for name in self.station_names: + LFUtils.removePort(self.resource, name, self.lfclient_url) + + def num_associated(self, bssid): + counter = 0 + # print("there are %d results" % len(self.station_results)) + fields = "_links,port,alias,ip,ap,port+type" + self.station_results = self.localrealm.find_ports_like("sta*", fields, debug_=False) + if (self.station_results is None) or (len(self.station_results) < 1): + self.get_failed_result_list() + for eid,record in self.station_results.items(): + #print("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ") + #pprint(eid) + #pprint(record) + if record["ap"] == bssid: + counter += 1 + #print("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ") + return counter + + def clear_test_results(self): + self.resulting_stations = {} + self.resulting_endpoints = {} + super().clear_test_results() + #super(StaConnect, self).clear_test_results().test_results.clear() + + def run(self): + if not self.setup(): + return False + if not self.start(): + return False + time.sleep(self.runtime_secs) + if not self.stop(): + return False + if not self.finish(): + return False + + # remove all endpoints and cxs + if self.cleanup_on_exit: + if not self.cleanup(): + return False + return True + + def setup(self): + self.clear_test_results() + self.check_connect() + eth1IP = self.json_get(self.get_upstream_url()) + if eth1IP is None: + self._fail("Unable to query %s, bye" % self.upstream_port, True) + return False + if eth1IP['interface']['ip'] == "0.0.0.0": + self._fail("Warning: %s lacks ip address" % self.get_upstream_url()) + return False + + for sta_name in self.station_names: + sta_url = self.get_station_url(sta_name) + response = self.json_get(sta_url) + if response is not None: + if response["interface"] is not None: + print("removing old station") + for sta_name in self.station_names: + LFUtils.removePort(self.resource, sta_name, self.lfclient_url) + LFUtils.waitUntilPortsDisappear(self.resource, self.lfclient_url, self.station_names) + + # Create stations and turn dhcp on + + flags = 0x10000 + if self.dut_security == WPA2: + flags += 0x400 + elif self.dut_security == OPEN: + pass + + add_sta_data = { + "shelf": 1, + "resource": self.resource, + "radio": self.radio, + "ssid": self.dut_ssid, + "key": self.dut_passwd, + "mode": self.sta_mode, + "mac": "xx:xx:xx:xx:*:xx", + "flags": flags # verbose, wpa2 + } + print("Adding new stations ", end="") + for sta_name in self.station_names: + add_sta_data["sta_name"] = sta_name + print(" %s," % sta_name, end="") + self.json_post("/cli-json/add_sta", add_sta_data, suppress_related_commands_=True) + time.sleep(0.01) + + set_port_data = { + "shelf": 1, + "resource": self.resource, + "current_flags": 0x80000000, # use DHCP, not down + "interest": 0x4002 # set dhcp, current flags + } + print("\nConfiguring ") + for sta_name in self.station_names: + set_port_data["port"] = sta_name + print(" %s," % sta_name, end="") + self.json_post("/cli-json/set_port", set_port_data, suppress_related_commands_=True) + time.sleep(0.01) + print("\nBringing ports up...") + data = {"shelf": 1, + "resource": self.resource, + "port": "ALL", + "probe_flags": 1} + self.json_post("/cli-json/nc_show_ports", data, suppress_related_commands_=True) + LFUtils.waitUntilPortsAdminUp(self.resource, self.lfclient_url, self.station_names) + + # station_info = self.jsonGet(self.mgr_url, "%s?fields=port,ip,ap" % (self.getStaUrl())) + duration = 0 + maxTime = 300 + ip = "0.0.0.0" + ap = "" + print("Waiting for %s stations to associate to AP: " % len(self.station_names), end="") + connected_stations = {} + while (len(connected_stations.keys()) < len(self.station_names)) and (duration < maxTime): + duration += 3 + time.sleep(3) + print(".", end="") + for sta_name in self.station_names: + sta_url = self.get_station_url(sta_name) + station_info = self.json_get(sta_url + "?fields=port,ip,ap") + + # LFUtils.debug_printer.pprint(station_info) + if (station_info is not None) and ("interface" in station_info): + if "ip" in station_info["interface"]: + ip = station_info["interface"]["ip"] + if "ap" in station_info["interface"]: + ap = station_info["interface"]["ap"] + + if (ap == "Not-Associated") or (ap == ""): + if self.debugOn: + print(" -%s," % sta_name, end="") + else: + if ip == "0.0.0.0": + if self.debugOn: + print(" %s (0.0.0.0)" % sta_name, end="") + else: + connected_stations[sta_name] = sta_url + data = { + "shelf":1, + "resource": self.resource, + "port": "ALL", + "probe_flags": 1 + } + self.json_post("/cli-json/nc_show_ports", data, suppress_related_commands_=True) + + # make a copy of the connected stations for test records + + + for sta_name in self.station_names: + sta_url = self.get_station_url(sta_name) + station_info = self.json_get(sta_url) # + "?fields=port,ip,ap") + self.resulting_stations[sta_url] = station_info + ap = station_info["interface"]["ap"] + ip = station_info["interface"]["ip"] + if (ap != "") and (ap != "Not-Associated"): + print(" %s +AP %s, " % (sta_name, ap), end="") + if self.dut_bssid != "": + if self.dut_bssid.lower() == ap.lower(): + self._pass(sta_name+" connected to BSSID: " + ap) + # self.test_results.append("PASSED: ) + # print("PASSED: Connected to BSSID: "+ap) + else: + self._fail("%s connected to wrong BSSID, requested: %s Actual: %s" % (sta_name, self.dut_bssid, ap)) + else: + self._fail(sta_name+" did not connect to AP") + return False + + if ip == "0.0.0.0": + self._fail("%s did not get an ip. Ending test" % sta_name) + else: + self._pass("%s connected to AP: %s With IP: %s" % (sta_name, ap, ip)) + + if self.passes() == False: + if self.cleanup_on_exit: + print("Cleaning up...") + self.remove_stations() + return False + + # create endpoints and cxs + # Create UDP endpoints + self.cx_names = {} + + for sta_name in self.station_names: + self.cx_names["testUDP-"+sta_name] = { "a": "testUDP-%s-A" % sta_name, + "b": "testUDP-%s-B" % sta_name} + data = { + "alias": "testUDP-%s-A" % sta_name, + "shelf": 1, + "resource": self.resource, + "port": sta_name, + "type": "lf_udp", + "ip_port": "-1", + "min_rate": 1000000 + } + self.json_post("/cli-json/add_endp", data, suppress_related_commands_=True) + + data = { + "name" : "testUDP-%s-A" % sta_name, + "flag" : "UseAutoNAT", + "val" : 1 + } + self.json_post("/cli-json/set_endp_flag", data, suppress_related_commands_=True) + + data = { + "alias": "testUDP-%s-B" % sta_name, + "shelf": 1, + "resource": self.upstream_resource, + "port": self.upstream_port, + "type": "lf_udp", + "ip_port": "-1", + "min_rate": 1000000 + } + self.json_post("/cli-json/add_endp", data, suppress_related_commands_=True) + + data = { + "name" : "testUDP-%s-B" % sta_name, + "flag" : "UseAutoNAT", + "val" : 1 + } + self.json_post("/cli-json/set_endp_flag", data, suppress_related_commands_=True) + + # Create CX + data = { + "alias": "testUDP-%s" % sta_name, + "test_mgr": "default_tm", + "tx_endp": "testUDP-%s-A" % sta_name, + "rx_endp": "testUDP-%s-B" % sta_name, + } + self.json_post("/cli-json/add_cx", data, suppress_related_commands_=True) + + data = { + "test_mgr": "default_tm", + "cx_name": "testUDP-%s" % sta_name, + "milliseconds": 1000 + } + self.json_post("/cli-json/set_cx_report_timer", data, suppress_related_commands_=True) + + # Create TCP endpoints + self.cx_names["testTCP-"+sta_name] = { "a": "testUDP-%s-A" % sta_name, + "b": "testUDP-%s-B" % sta_name} + data = { + "alias": "testTCP-%s-A" % sta_name, + "shelf": 1, + "resource": self.resource, + "port": sta_name, + "type": "lf_tcp", + "ip_port": "0", + "min_rate": 1000000 + } + self.json_post("/cli-json/add_endp", data, suppress_related_commands_=True) + + data = { + "alias": "testTCP-%s-B" % sta_name, + "shelf": 1, + "resource": self.upstream_resource, + "port": self.upstream_port, + "type": "lf_tcp", + "ip_port": "-1", + "min_rate": 1000000 + } + self.json_post("/cli-json/add_endp", data, suppress_related_commands_=True) + + # Create CX + data = { + "alias": "testTCP-%s" % sta_name, + "test_mgr": "default_tm", + "tx_endp": "testTCP-%s-A" % sta_name, + "rx_endp": "testTCP-%s-B" % sta_name, + } + self.json_post("/cli-json/add_cx", data) + + data = { + "test_mgr": "default_tm", + "cx_name": "testTCP-%s" % sta_name, + "milliseconds": 1000 + } + self.json_post("/cli-json/set_cx_report_timer", data, suppress_related_commands_=True) + + return True + + def start(self): + # start cx traffic + print("\nStarting CX Traffic") + for cx_name in self.cx_names.keys(): + data = { + "test_mgr": "ALL", + "cx_name": cx_name, + "cx_state": "RUNNING" + } + self.json_post("/cli-json/set_cx_state", data) + + # Refresh stats + + print("Refresh CX stats") + for cx_name in self.cx_names.keys(): + data = { + "test_mgr": "ALL", + "cross_connect": cx_name + } + self.json_post("/cli-json/show_cxe", data) + return True + + + def stop(self): + # stop cx traffic + print("Stopping CX Traffic") + for cx_name in self.cx_names.keys(): + data = { + "test_mgr": "ALL", + "cx_name": cx_name, + "cx_state": "STOPPED" + } + self.json_post("/cli-json/set_cx_state", data) + return True + + def finish(self): + # Refresh stats + print("\nRefresh CX stats") + for cx_name in self.cx_names.keys(): + data = { + "test_mgr": "ALL", + "cross_connect": cx_name + } + self.json_post("/cli-json/show_cxe", data) + + # print("Sleeping for 5 seconds") + time.sleep(5) + + # get data for endpoints JSON + print("Collecting Data") + for cx_name in self.cx_names.keys(): + + try: + # ?fields=tx+bytes,rx+bytes + endp_url = "/endp/%s" % self.cx_names[cx_name]["a"] + ptest = self.json_get(endp_url) + self.resulting_endpoints[endp_url] = ptest + + ptest_a_tx = ptest['endpoint']['tx bytes'] + ptest_a_rx = ptest['endpoint']['rx bytes'] + + #ptest = self.json_get("/endp/%s?fields=tx+bytes,rx+bytes" % self.cx_names[cx_name]["b"]) + endp_url = "/endp/%s" % self.cx_names[cx_name]["b"] + ptest = self.json_get(endp_url) + self.resulting_endpoints[endp_url] = ptest + + ptest_b_tx = ptest['endpoint']['tx bytes'] + ptest_b_rx = ptest['endpoint']['rx bytes'] + + self.compare_vals("testTCP-A TX", ptest_a_tx) + self.compare_vals("testTCP-A RX", ptest_a_rx) + + self.compare_vals("testTCP-B TX", ptest_b_tx) + self.compare_vals("testTCP-B RX", ptest_b_rx) + + except Exception as e: + self.error(e) + + # Examples of what happens when you add test results that do not begin with PASS + # self.test_results.append("Neutral message will fail") + # self.test_results.append("FAILED message will fail") + # print("\n") + + + def cleanup(self): + for sta_name in self.station_names: + LFUtils.removePort(self.resource, sta_name, self.lfclient_url) + endp_names = [] + removeCX(self.lfclient_url, self.cx_names.keys()) + for cx_name in self.cx_names: + endp_names.append(self.cx_names[cx_name]["a"]) + endp_names.append(self.cx_names[cx_name]["b"]) + removeEndps(self.lfclient_url, endp_names) + +# ~class + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def main(): + lfjson_host = "localhost" + lfjson_port = 8080 + parser = argparse.ArgumentParser( + prog="sta_connect.py", + formatter_class=argparse.RawTextHelpFormatter, + description="""LANforge Unit Test: Connect Station to AP +Example: +./sta_connect.py --dest 192.168.100.209 --dut_ssid OpenWrt-2 --dut_bssid 24:F5:A2:08:21:6C +""") + parser.add_argument("-d", "--dest", type=str, help="address of the LANforge GUI machine (localhost is default)") + parser.add_argument("-o", "--port", type=int, help="IP Port the LANforge GUI is listening on (8080 is default)") + parser.add_argument("-u", "--user", type=str, help="TBD: credential login/username") + parser.add_argument("-p", "--passwd", type=str, help="TBD: credential password") + parser.add_argument("--resource", type=str, help="LANforge Station resource ID to use, default is 1") + parser.add_argument("--upstream_resource", type=str, help="LANforge Ethernet port resource ID to use, default is 1") + parser.add_argument("--upstream_port", type=str, help="LANforge Ethernet port name, default is eth2") + parser.add_argument("--radio", type=str, help="LANforge radio to use, default is wiphy0") + parser.add_argument("--sta_mode", type=str, + help="LANforge station-mode setting (see add_sta LANforge CLI documentation, default is 0 (auto))") + parser.add_argument("--dut_ssid", type=str, help="DUT SSID") + parser.add_argument("--dut_passwd", type=str, help="DUT PSK password. Do not set for OPEN auth") + parser.add_argument("--dut_bssid", type=str, help="DUT BSSID to which we expect to connect.") + + args = parser.parse_args() + if args.dest is not None: + lfjson_host = args.dest + if args.port is not None: + lfjson_port = args.port + + staConnect = StaConnect(lfjson_host, lfjson_port) + staConnect.station_names = [ "sta0000" ] + if args.user is not None: + staConnect.user = args.user + if args.passwd is not None: + staConnect.passwd = args.passwd + if args.sta_mode is not None: + staConnect.sta_mode = args.sta_mode + if args.upstream_resource is not None: + staConnect.upstream_resource = args.upstream_resource + if args.upstream_port is not None: + staConnect.upstream_port = args.upstream_port + if args.radio is not None: + staConnect.radio = args.radio + if args.resource is not None: + staConnect.resource = args.resource + if args.dut_passwd is not None: + staConnect.dut_passwd = args.dut_passwd + if args.dut_bssid is not None: + staConnect.dut_bssid = args.dut_bssid + if args.dut_ssid is not None: + staConnect.dut_ssid = args.dut_ssid + + staConnect.run() + + run_results = staConnect.get_result_list() + + + is_passing = staConnect.passes() + if is_passing == False: + print("FAIL: Some tests failed") + else: + print("PASS: All tests pass") + + print(staConnect.get_all_message()) + + is_passing = staConnect.passes() + if is_passing == False: + print("FAIL: Some tests failed") + else: + print("PASS: All tests pass") + + print(staConnect.get_all_message()) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/sta_connect2.py b/lanforge/lanforge-scripts/py-scripts/sta_connect2.py new file mode 100755 index 000000000..69035d7dd --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sta_connect2.py @@ -0,0 +1,498 @@ +#!/usr/bin/env python3 + +# This will create a station, create TCP and UDP traffic, run it a short amount of time, +# and verify whether traffic was sent and received. It also verifies the station connected +# to the requested BSSID if bssid is specified as an argument. +# The script will clean up the station and connections at the end of the test. +import sys +import os +import importlib +import argparse +import pprint +import time + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +removeCX = LFUtils.removeCX +removeEndps = LFUtils.removeEndps +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +influx = importlib.import_module("py-scripts.influx") +RecordInflux = influx.RecordInflux + +OPEN = "open" +WEP = "wep" +WPA = "wpa" +WPA2 = "wpa2" +WPA3 = "wpa3" +MODE_AUTO = 0 + + +class StaConnect2(LFCliBase): + def __init__(self, host, port, _dut_ssid="jedway-open-1", _dut_passwd="NA", _dut_bssid="", + _user="", _passwd="", _sta_mode="0", _radio="wiphy0", + _influx_host=None, _influx_db=None, _influx_user=None, + _influx_passwd=None, + _resource=1, _upstream_resource=1, _upstream_port="eth1", + _sta_name=None, _sta_prefix='sta', _bringup_time_sec=300, + debug_=False, _dut_security=OPEN, _exit_on_error=False, + _cleanup_on_exit=True, _clean_all_sta=False, _runtime_sec=60, _exit_on_fail=False): + # do not use `super(LFCLiBase,self).__init__(self, host, port, _debugOn) + # that is py2 era syntax and will force self into the host variable, making you + # very confused. + super().__init__(host, port, _debug=debug_, _exit_on_fail=_exit_on_fail) + self.debug = debug_ + self.dut_security = _dut_security + self.dut_ssid = _dut_ssid + self.dut_passwd = _dut_passwd + self.dut_bssid = _dut_bssid + self.user = _user + self.passwd = _passwd + self.sta_mode = _sta_mode # See add_sta LANforge CLI users guide entry + self.radio = _radio + self.resource = _resource + self.upstream_resource = _upstream_resource + self.upstream_port = _upstream_port + self.runtime_secs = _runtime_sec + self.cleanup_on_exit = _cleanup_on_exit + self.clean_all_sta = _clean_all_sta + self.sta_url_map = None # defer construction + self.upstream_url = None # defer construction + self.station_names = [] + if _sta_name is not None: + self.station_names = [ _sta_name ] + self.sta_prefix = _sta_prefix + self.bringup_time_sec = _bringup_time_sec + # self.localrealm :Realm = Realm(lfclient_host=host, lfclient_port=port) # py > 3.6 + self.localrealm = Realm(lfclient_host=host, lfclient_port=port) # py > 3.6 + self.resulting_stations = {} + self.resulting_endpoints = {} + self.station_profile = None + self.l3_udp_profile = None + self.l3_tcp_profile = None + self.influx_host = _influx_host + self.influx_db = _influx_db + self.influx_user = _influx_user + self.influx_passwd = _influx_passwd + + # def get_realm(self) -> Realm: # py > 3.6 + def get_realm(self): + return self.localrealm + + def get_station_url(self, sta_name_=None): + if sta_name_ is None: + raise ValueError("get_station_url wants a station name") + if self.sta_url_map is None: + self.sta_url_map = {} + for sta_name in self.station_names: + self.sta_url_map[sta_name] = "port/1/%s/%s" % (self.resource, sta_name) + return self.sta_url_map[sta_name_] + + def get_upstream_url(self): + if self.upstream_url is None: + self.upstream_url = "port/1/%s/%s" % (self.upstream_resource, self.upstream_port) + return self.upstream_url + + # Compare pre-test values to post-test values + def compare_vals(self, name, postVal, print_pass=False, print_fail=True): + # print(f"Comparing {name}") + if postVal > 0: + self._pass("%s %s" % (name, postVal), print_pass) + else: + self._fail("%s did not report traffic: %s" % (name, postVal), print_fail) + + def remove_stations(self): + for name in self.station_names: + LFUtils.removePort(self.resource, name, self.lfclient_url) + + def num_associated(self, bssid): + counter = 0 + # print("there are %d results" % len(self.station_results)) + fields = "_links,port,alias,ip,ap,port+type" + self.station_results = self.localrealm.find_ports_like("%s*"%self.sta_prefix, fields, debug_=False) + if (self.station_results is None) or (len(self.station_results) < 1): + self.get_failed_result_list() + for eid,record in self.station_results.items(): + #print("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ") + #pprint(eid) + #pprint(record) + if record["ap"] == bssid: + counter += 1 + #print("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ") + return counter + + def clear_test_results(self): + self.resulting_stations = {} + self.resulting_endpoints = {} + super().clear_test_results() + #super(StaConnect, self).clear_test_results().test_results.clear() + + def setup(self, extra_securities=[]): + self.clear_test_results() + self.check_connect() + upstream_json = self.json_get("%s?fields=alias,phantom,down,port,ip" % self.get_upstream_url(), debug_=False) + + if upstream_json is None: + self._fail(message="Unable to query %s, bye" % self.upstream_port, print_=True) + return False + + if upstream_json['interface']['ip'] == "0.0.0.0": + if self.debug: + pprint.pprint(upstream_json) + self._fail("Warning: %s lacks ip address" % self.get_upstream_url(), print_=True) + return False + # remove old stations + if self.clean_all_sta: + print("Removing all stations on resource.") + self.localrealm.remove_all_stations(self.resource) + else: + print("Removing old stations to be created by this test.") + for sta_name in self.station_names: + sta_url = self.get_station_url(sta_name) + response = self.json_get(sta_url) + if (response is not None) and (response["interface"] is not None): + for sta_name in self.station_names: + LFUtils.removePort(self.resource, sta_name, self.lfclient_url) + LFUtils.wait_until_ports_disappear(self.lfclient_url, self.station_names) + + # Create stations and turn dhcp on + self.station_profile = self.localrealm.new_station_profile() + + if self.dut_security == WPA2: + self.station_profile.use_security(security_type="wpa2", ssid=self.dut_ssid, passwd=self.dut_passwd) + elif self.dut_security == OPEN: + self.station_profile.use_security(security_type="open", ssid=self.dut_ssid, passwd="[BLANK]") + elif self.dut_security == WPA: + self.station_profile.use_security(security_type="wpa", ssid=self.dut_ssid, passwd=self.dut_passwd) + elif self.dut_security == WEP: + self.station_profile.use_security(security_type="wep", ssid=self.dut_ssid, passwd=self.dut_passwd) + elif self.dut_security == WPA3: + self.station_profile.use_security(security_type="wpa3", ssid=self.dut_ssid, passwd=self.dut_passwd) + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + for security in extra_securities: + self.station_profile.add_security_extra(security=security) + print("Adding new stations ", end="") + self.station_profile.create(radio=self.radio, sta_names_=self.station_names, up_=False, debug=self.debug, suppress_related_commands_=True) + LFUtils.wait_until_ports_appear(self.lfclient_url, self.station_names, debug=self.debug) + + # Create UDP endpoints + self.l3_udp_profile = self.localrealm.new_l3_cx_profile() + self.l3_udp_profile.side_a_min_bps = 128000 + self.l3_udp_profile.side_b_min_bps = 128000 + self.l3_udp_profile.side_a_min_pdu = 1200 + self.l3_udp_profile.side_b_min_pdu = 1500 + self.l3_udp_profile.report_timer = 1000 + self.l3_udp_profile.name_prefix = "udp" + port_list = list(self.localrealm.find_ports_like("%s+"%self.sta_prefix)) + if (port_list is None) or (len(port_list) < 1): + raise ValueError("Unable to find ports named '%s'+"%self.sta_prefix) + self.l3_udp_profile.create(endp_type="lf_udp", + side_a=port_list, + side_b="%d.%s" % (self.resource, self.upstream_port), + suppress_related_commands=True) + + # Create TCP endpoints + self.l3_tcp_profile = self.localrealm.new_l3_cx_profile() + self.l3_tcp_profile.side_a_min_bps = 128000 + self.l3_tcp_profile.side_b_min_bps = 56000 + self.l3_tcp_profile.name_prefix = "tcp" + self.l3_tcp_profile.report_timer = 1000 + self.l3_tcp_profile.create(endp_type="lf_tcp", + side_a=list(self.localrealm.find_ports_like("%s+"%self.sta_prefix)), + side_b="%d.%s" % (self.resource, self.upstream_port), + suppress_related_commands=True) + + def start(self): + if self.station_profile is None: + self._fail("Incorrect setup") + pprint.pprint(self.station_profile) + if self.station_profile.up is None: + self._fail("Incorrect station profile, missing profile.up") + if self.station_profile.up == False: + print("\nBringing ports up...") + data = {"shelf": 1, + "resource": self.resource, + "port": "ALL", + "probe_flags": 1} + self.json_post("/cli-json/nc_show_ports", data) + self.station_profile.admin_up() + LFUtils.waitUntilPortsAdminUp(self.resource, self.lfclient_url, self.station_names) + + if self.influx_db is not None: + grapher = RecordInflux(_influx_host=self.influx_host, + _influx_db=self.influx_db, + _influx_user=self.influx_user, + _influx_passwd=self.influx_passwd, + _longevity=1, + _devices=self.station_names, + _monitor_interval=1, + _target_kpi=['bps rx']) + + # station_info = self.jsonGet(self.mgr_url, "%s?fields=port,ip,ap" % (self.getStaUrl())) + duration = 0 + maxTime = self.bringup_time_sec + ip = "0.0.0.0" + ap = "" + print("Waiting for %s stations to associate to AP: " % len(self.station_names), end="") + connected_stations = {} + while (len(connected_stations.keys()) < len(self.station_names)) and (duration < maxTime): + duration += 3 + time.sleep(3) + print(".", end="") + for sta_name in self.station_names: + sta_url = self.get_station_url(sta_name) + station_info = self.json_get(sta_url + "?fields=port,ip,ap") + + # LFUtils.debug_printer.pprint(station_info) + if (station_info is not None) and ("interface" in station_info): + if "ip" in station_info["interface"]: + ip = station_info["interface"]["ip"] + if "ap" in station_info["interface"]: + ap = station_info["interface"]["ap"] + + if (ap == "Not-Associated") or (ap == ""): + if self.debug: + print(" -%s," % sta_name, end="") + else: + if ip == "0.0.0.0": + if self.debug: + print(" %s (0.0.0.0)" % sta_name, end="") + else: + connected_stations[sta_name] = sta_url + data = { + "shelf":1, + "resource": self.resource, + "port": "ALL", + "probe_flags": 1 + } + self.json_post("/cli-json/nc_show_ports", data) + if self.influx_db is not None: + grapher.getdata() + LFUtils.wait_until_ports_appear() + + for sta_name in self.station_names: + sta_url = self.get_station_url(sta_name) + station_info = self.json_get(sta_url) # + "?fields=port,ip,ap") + if station_info is None: + print("unable to query %s" % sta_url) + self.resulting_stations[sta_url] = station_info + try: + ap = station_info["interface"]["ap"] + except Exception as e: + print(e) + ap = "NULL" + ip = station_info["interface"]["ip"] + if (ap != "") and (ap != "Not-Associated"): + print(" %s +AP %s, " % (sta_name, ap), end="") + if self.dut_bssid != "": + if self.dut_bssid.lower() == ap.lower(): + self._pass(sta_name+" connected to BSSID: " + ap) + # self.test_results.append("PASSED: ) + # print("PASSED: Connected to BSSID: "+ap) + else: + self._fail("%s connected to wrong BSSID, requested: %s Actual: %s" % (sta_name, self.dut_bssid, ap)) + else: + self._fail(sta_name+" did not connect to AP") + return False + + if ip == "0.0.0.0": + self._fail("%s did not get an ip. Ending test" % sta_name) + else: + self._pass("%s connected to AP: %s With IP: %s" % (sta_name, ap, ip)) + + if self.passes() == False: + if self.cleanup_on_exit: + print("Cleaning up...") + #self.remove_stations() + return False + + # start cx traffic + print("\nStarting CX Traffic") + self.l3_udp_profile.start_cx() + self.l3_tcp_profile.start_cx() + time.sleep(1) + # Refresh stats + self.l3_udp_profile.refresh_cx() + self.l3_tcp_profile.refresh_cx() + + def collect_endp_stats(self, endp_map): + print("Collecting Data") + fields="/all" + for (cx_name, endps) in endp_map.items(): + try: + endp_url = "/endp/%s%s" % (endps[0], fields) + endp_json = self.json_get(endp_url) + self.resulting_endpoints[endp_url] = endp_json + ptest_a_tx = endp_json['endpoint']['tx bytes'] + ptest_a_rx = endp_json['endpoint']['rx bytes'] + + #ptest = self.json_get("/endp/%s?fields=tx+bytes,rx+bytes" % cx_names[cx_name]["b"]) + endp_url = "/endp/%s%s" % (endps[1], fields) + endp_json = self.json_get(endp_url) + self.resulting_endpoints[endp_url] = endp_json + + ptest_b_tx = endp_json['endpoint']['tx bytes'] + ptest_b_rx = endp_json['endpoint']['rx bytes'] + + self.compare_vals("testTCP-A TX", ptest_a_tx) + self.compare_vals("testTCP-A RX", ptest_a_rx) + + self.compare_vals("testTCP-B TX", ptest_b_tx) + self.compare_vals("testTCP-B RX", ptest_b_rx) + + except Exception as e: + self.error(e) + + + def stop(self): + # stop cx traffic + print("Stopping CX Traffic") + self.l3_udp_profile.stop_cx() + self.l3_tcp_profile.stop_cx() + + # Refresh stats + print("\nRefresh CX stats") + self.l3_udp_profile.refresh_cx() + self.l3_tcp_profile.refresh_cx() + + print("Sleeping for 5 seconds") + time.sleep(5) + + # get data for endpoints JSON + self.collect_endp_stats(self.l3_udp_profile.created_cx) + self.collect_endp_stats(self.l3_tcp_profile.created_cx) + # print("\n") + + def cleanup(self): + # remove all endpoints and cxs + if self.cleanup_on_exit: + for sta_name in self.station_names: + LFUtils.removePort(self.resource, sta_name, self.lfclient_url) + curr_endp_names = [] + removeCX(self.lfclient_url, self.l3_udp_profile.get_cx_names()) + removeCX(self.lfclient_url, self.l3_tcp_profile.get_cx_names()) + for (cx_name, endp_names) in self.l3_udp_profile.created_cx.items(): + curr_endp_names.append(endp_names[0]) + curr_endp_names.append(endp_names[1]) + for (cx_name, endp_names) in self.l3_tcp_profile.created_cx.items(): + curr_endp_names.append(endp_names[0]) + curr_endp_names.append(endp_names[1]) + removeEndps(self.lfclient_url, curr_endp_names, debug= self.debug) + +# ~class + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def main(): + lfjson_host = "localhost" + lfjson_port = 8080 + parser = argparse.ArgumentParser( + prog="sta_connect2.py", + formatter_class=argparse.RawTextHelpFormatter, + description="""LANforge Unit Test: Connect Station to AP +Example: +./sta_connect2.py --dest 192.168.100.209 --dut_ssid OpenWrt-2 --dut_bssid 24:F5:A2:08:21:6C +""") + parser.add_argument("-d", "--dest", type=str, help="address of the LANforge GUI machine (localhost is default)") + parser.add_argument("-o", "--port", type=int, help="IP Port the LANforge GUI is listening on (8080 is default)") + parser.add_argument("-u", "--user", type=str, help="TBD: credential login/username") + parser.add_argument("-p", "--passwd", type=str, help="TBD: credential password") + parser.add_argument("--resource", type=str, help="LANforge Station resource ID to use, default is 1") + parser.add_argument("--upstream_resource", type=str, help="LANforge Ethernet port resource ID to use, default is 1") + parser.add_argument("--upstream_port", type=str, help="LANforge Ethernet port name, default is eth2") + parser.add_argument("--radio", type=str, help="LANforge radio to use, default is wiphy0") + parser.add_argument("--sta_mode", type=str, + help="LANforge station-mode setting (see add_sta LANforge CLI documentation, default is 0 (auto))") + parser.add_argument("--dut_ssid", type=str, help="DUT SSID") + parser.add_argument("--dut_security", type=str, help="DUT security: openLF, wpa, wpa2, wpa3") + parser.add_argument("--dut_passwd", type=str, help="DUT PSK password. Do not set for OPEN auth") + parser.add_argument("--dut_bssid", type=str, help="DUT BSSID to which we expect to connect.") + parser.add_argument("--debug", type=str, help="enable debugging") + parser.add_argument("--prefix", type=str, help="Station prefix. Default: 'sta'", default='sta') + parser.add_argument("--bringup_time", type=int, help="Seconds to wait for stations to associate and aquire IP. Default: 300", default=300) + parser.add_argument('--influx_user', help='Username for your Influx database', default=None) + parser.add_argument('--influx_passwd', help='Password for your Influx database', default=None) + parser.add_argument('--influx_db', help='Name of your Influx database', default=None) + parser.add_argument('--influx_host', help='Host of your influx database if different from the system you are running on', default='localhost') + parser.add_argument('--monitor_interval', help='How frequently you want to append to your database', default='5s') + + args = parser.parse_args() + if args.dest is not None: + lfjson_host = args.dest + if args.port is not None: + lfjson_port = args.port + + on_flags = [ 1, "1", "on", "yes", "true" ] + debug_v = False + if args.debug is not None: + if args.debug in on_flags: + debug_v = True + + staConnect = StaConnect2(lfjson_host, lfjson_port, + debug_=True, + _influx_db = args.influx_db, + _influx_passwd = args.influx_passwd, + _influx_user = args.influx_user, + _influx_host = args.influx_host, + _exit_on_fail=True, + _exit_on_error=False) + + if args.user is not None: + staConnect.user = args.user + if args.passwd is not None: + staConnect.passwd = args.passwd + if args.sta_mode is not None: + staConnect.sta_mode = args.sta_mode + if args.upstream_resource is not None: + staConnect.upstream_resource = args.upstream_resource + if args.upstream_port is not None: + staConnect.upstream_port = args.upstream_port + if args.radio is not None: + staConnect.radio = args.radio + if args.resource is not None: + staConnect.resource = args.resource + if args.dut_ssid is not None: + staConnect.dut_ssid = args.dut_ssid + if args.dut_passwd is not None: + staConnect.dut_passwd = args.dut_passwd + if args.dut_bssid is not None: + staConnect.dut_bssid = args.dut_bssid + if args.dut_security is not None: + staConnect.dut_security = args.dut_security + if (args.prefix is not None) or (args.prefix != "sta"): + staConnect.sta_prefix = args.prefix + staConnect.station_names = [ "%s0000"%args.prefix ] + staConnect.bringup_time_sec = args.bringup_time + + # staConnect.cleanup() + staConnect.setup() + staConnect.start() + print("napping %f sec" % staConnect.runtime_secs) + + time.sleep(staConnect.runtime_secs) + staConnect.stop() + run_results = staConnect.get_result_list() + is_passing = staConnect.passes() + if is_passing == False: + print("FAIL: Some tests failed") + else: + print("PASS: All tests pass") + print(staConnect.get_all_message()) + + staConnect.cleanup() + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/sta_connect_bssid_mac.py b/lanforge/lanforge-scripts/py-scripts/sta_connect_bssid_mac.py new file mode 100644 index 000000000..e0da58c1f --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sta_connect_bssid_mac.py @@ -0,0 +1,130 @@ +""" +Name: sta_connect_bssid_mac.py +Purpose: + This script can create stations and can be used to set multiple BSSID, MAC to each individual station. + Also use-bss-transition | 0x80000000000 # Enable BSS transition. flag can be set here. +Example: + ./sta_connect_bssid_mac.py + --mgr localhost --mgr_port 8080 + --ssid "TestAP" #ssid + --radio wiphy0 #radio + --security "open" // "wpa" // "wpa2" #security + --passwd "BLANK" #password + --bssid 78:d2:94:4f:20:c5,78:d2:94:4f:20:c5 #bssid names + --sta_name "sta001,sta002" #station names + --mac 04:f0:21:89:3e:ea,04:f0:21:89:4e:ea #mac + --bss_trans #flag to set BSS transition on all stations +""" +import sys +import os +import importlib + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class client_connect(Realm): + def __init__(self, lfclient_host="localhost", lfclient_port=8080, radio=None, sta_name=None, + ssid=None, security=None, paswd=None, bssid=None, mac=None, bss_trans=False): + super().__init__(lfclient_host, lfclient_port) + self.station_profile = self.new_station_profile() + self.sta_name = sta_name + self.bssid = bssid + self.radio = radio + self.security = security + self.ssid = ssid + self.paswd = paswd + self.mac = mac + self.bss_trans = bss_trans + + def setup(self): + station_ = self.sta_name.split(",") + print(station_) + mac_list = self.mac.split(",") + bssid_list = self.bssid.split(",") + + self.station_profile.use_security(self.security, self.ssid, self.paswd) + + if self.bss_trans: + self.station_profile.desired_add_sta_flags = ["use-bss-transition"] + self.station_profile.desired_add_sta_flags_mask = ["use-bss-transition"] + + for station_name in range(len(station_)): + stat_list = [] + + stat_list.append(station_[station_name]) + print(station_name) + self.station_profile.cleanup(stat_list) + + try: + if self.bssid[station_name] is not None or self.bssid[station_name] != "": + self.station_profile.set_command_param("add_sta", "ap", bssid_list[station_name]) + except: + self.station_profile.set_command_param("add_sta", "ap", "DEFAULT") + + try: + if self.mac[station_name] is not None or self.mac[station_name] != "": + self.station_profile.add_sta_data["mac"] = mac_list[station_name] + print(self.mac[station_name]) + except: + self.station_profile.add_sta_data["mac"] = "xx:xx:xx:xx:*:*" + + print(stat_list) + print(self.radio) + self.station_profile.create(radio=self.radio, sta_names_=stat_list, debug=self.debug) + + self.station_profile.admin_up() + self.wait_for_ip(station_list=station_) + print("stations created") + + +def main(): + # This has --mgr, --mgr_port and --debug + parser = LFCliBase.create_bare_argparse(prog="sta_connect_bssid_mac.py", + description= + """ + --mgr localhost --mgr_port 8080 + --ssid "TestAP-Jitendra" + --radio wiphy0 + --security "open" // "wpa" // "wpa2" + --passwd "BLANK" + --bssid 78:d2:94:4f:20:c5,78:d2:94:4f:20:c5 + --sta_name "sta001,sta002" + --mac 04:f0:21:89:3e:ea,04:f0:21:89:4e:ea + --bss_trans + """ + ) + + # Adding More Arguments for custom use + parser.add_argument('--ssid', type=str, help='--ssid', default="") + parser.add_argument('--passwd', type=str, help='--passwd', default="BLANK") + parser.add_argument('--security', type=str, help='--security', default="open") + parser.add_argument('--radio', type=str, help='--radio to use', + default="wiphy0") + parser.add_argument('--sta_name', type=str, help='--num_client is number of stations', default="sta001") + parser.add_argument("--bssid", type=str, help='DUT BSSID to which we expect to connect', default="DEFAULT") + parser.add_argument('--mac', type=str, help='--mac to stations', + default="xx:xx:xx:xx:*:*") + parser.add_argument("-bt", "--bss_trans", default=False, action='store_true', + help="To enable BSS transition.(by default: False)") + + args = parser.parse_args() + obj = client_connect(lfclient_host=args.mgr, lfclient_port=args.mgr_port, radio=args.radio, sta_name=args.sta_name, + ssid=args.ssid, security=args.security, paswd=args.passwd, bssid=args.bssid, + mac=args.mac, bss_trans=args.bss_trans + ) + obj.setup() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/sta_connect_example.py b/lanforge/lanforge-scripts/py-scripts/sta_connect_example.py new file mode 100755 index 000000000..2365858b4 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sta_connect_example.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Example of how to instantiate StaConnect and run the test +import sys +import os +import importlib +import time +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +# if you lack __init__.py in this directory you will not find sta_connect module +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +sta_connect = importlib.import_module("py-scripts.sta_connect") +StaConnect = sta_connect.StaConnect + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='sta_connect_example.py', + formatter_class=argparse.RawTextHelpFormatter + ) + required_args=None + for group in parser._action_groups: + if group.title == "required arguments": + required_args=group + break; + + optional_args=None + for group in parser._action_groups: + if group.title == "optional arguments": + optional_args=group + break; + + args = parser.parse_args() + if args.upstream_port is None: + args.upstream_port = "eth2" + if args.ssid is None: + args.ssid = "Default-SSID-2g" + if args.passwd is None: + args.passwd = "12345678" + if args.security is None: + args.security = sta_connect.WPA2 + if args.radio is None: + args.radio = "wiphy0" + staConnect = StaConnect("localhost", 8080, _debugOn=False) + staConnect.sta_mode = 0 + staConnect.upstream_resource = 1 + staConnect.upstream_port = args.upstream_port + staConnect.radio = args.radio + staConnect.resource = 1 + staConnect.dut_security = args.security + staConnect.dut_ssid = args.ssid + staConnect.dut_passwd = args.passwd + staConnect.station_names = [ "sta000" ] + staConnect.setup() + staConnect.start() + time.sleep(20) + staConnect.stop() + #staConnect.finish() + staConnect.cleanup() + is_passing = staConnect.passes() + if is_passing == False: + # run_results = staConnect.get_failed_result_list() + fail_message = staConnect.get_fail_message() + print("Some tests failed:\n" + fail_message) + else: + print("Tests pass") + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +if __name__ == '__main__': + main() + +# diff --git a/lanforge/lanforge-scripts/py-scripts/sta_connect_multi_example.py b/lanforge/lanforge-scripts/py-scripts/sta_connect_multi_example.py new file mode 100755 index 000000000..9c5dd0402 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sta_connect_multi_example.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# Example of how to instantiate StaConnect and run the test +import sys +import os +import importlib + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +# if you lack __init__.py in this directory you will not find sta_connect module +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm +sta_connect = importlib.import_module("py-scripts.sta_connect") +StaConnect = sta_connect.StaConnect + + +def main(): + # create multiple OPEN stations + station_names = LFUtils.port_name_series(start_id=0, end_id=1) + + test = StaConnect("localhost", 8080, _debugOn=False, _exit_on_error=True, + _cleanup_on_exit=False, _runtime_sec=360, _exit_on_fail=True) + test.sta_mode = sta_connect.MODE_AUTO + test.upstream_resource = 1 + test.upstream_port = "eth1" + test.radio = "wiphy0" + test.resource = 1 + test.dut_security = sta_connect.OPEN + test.dut_ssid = "jedway-open" + test.dut_passwd = "[BLANK]" + test.station_names = station_names + test.runtime_secs = 5 + test.cleanup_on_exit = True + test.run() + is_passing = test.passes() + + # recorded stations and endpoints can be retrieved this way: + ''' + for sta_name in test.resulting_stations: + print("** recorded: "+sta_name) + pprint.pprint(test.resulting_stations[sta_name]) + + for endp_name in test.resulting_endpoints: + print("** endp: "+endp_name) + pprint.pprint(test.resulting_endpoints[endp_name]) + ''' + if is_passing == False: + # run_results = staConnect.get_failed_result_list() + fail_message = test.get_fail_message() + print("Some tests failed:\n" + fail_message) + return + else: + print("Tests pass") + + # Stations use WPA2 + test.dut_security = sta_connect.WPA2 + test.dut_ssid = "jedway-wpa2-x2048-5-1" + test.dut_passwd = "jedway-wpa2-x2048-5-1" + test.run() + is_passing = test.passes() + if is_passing == False: + # run_results = staConnect.get_failed_result_list() + fail_message = test.get_fail_message() + print("Some tests failed:\n" + fail_message) + return + else: + print("Tests pass") + + if test.cleanup_on_exit == True: + test.remove_stations() + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +if __name__ == '__main__': + main() + +# diff --git a/lanforge/lanforge-scripts/py-scripts/sta_scan_test.py b/lanforge/lanforge-scripts/py-scripts/sta_scan_test.py new file mode 100755 index 000000000..e9a9a86c2 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/sta_scan_test.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 + +""" +NAME: sta_scan_test.py + +PURPOSE: +Creates a station with specified ssid info (can be real or fake ssid, if fake use open for security), then +starts a scan and waits 15 seconds, finally scan results are printed to console + +Use './sta_scan_test.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys +import os + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: +sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + +if 'py-dashboard' not in sys.path: +sys.path.append(os.path.join(os.path.abspath('..'), 'py-dashboard')) + +import argparse +from LANforge import LFUtils +from realm import Realm +import time + + +class StaScan(Realm): + def __init__(self, + ssid=None, + security=None, + password=None, + sta_list=[], + upstream=None, + radio=None, + host="localhost", + port=8080, + mode=0, + number_template="00000", + use_ht160=False, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(lfclient_host=host, + lfclient_port=port), + self.upstream = upstream + self.host = host + self.port = port + self.ssid = ssid + self.sta_list = sta_list + self.security = security + self.password = password + self.radio = radio + self.mode = mode + self.number_template = number_template + self.debug = _debug_on + self.station_profile = self.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.debug = self.debug + + self.station_profile.use_ht160 = use_ht160 + if self.station_profile.use_ht160: + self.station_profile.mode = 9 + self.station_profile.mode = mode + + def start(self): + self.station_profile.admin_up() + print(self.sta_list) + print("Sleeping 15s while waiting for scan") + data = { + "shelf": 1, + "resource": 1, + "port": self.sta_list + } + self.json_post("/cli-json/scan_wifi", data) + time.sleep(15) + scan_results = self.json_get("scanresults/1/1/%s" % ','.join(self.sta_list)) + + print("{0:<23}".format("BSS"), "{0:<7}".format("Signal"), "{0:<5}".format("SSID")) + for result in scan_results['scan-results']: + for name, info in result.items(): + print("%s\t%s\t%s" % (info['bss'], info['signal'], info['ssid'])) + + + + def pre_cleanup(self): + for sta in self.sta_list: + self.rm_port(sta, check_exists=True) + + def cleanup(self): + self.station_profile.cleanup() + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + + def build(self): + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, num_stations=1, debug=self.debug) + self._pass("PASS: Station build finished") + LFUtils.wait_until_ports_appear(','.join(self.sta_list)) + + +def main(): + parser = Realm.create_basic_argparse( + prog='sta_scan_test.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Used to scan for ssids after creating a station + ''', + description='''\ + Creates a station with specified ssid info (can be real or fake ssid, if fake use open for security), then + starts a scan and waits 15 seconds, finally scan results are printed to console + + Example: + ./sta_scan_test.py --ssid test_name --security open --radio wiphy0 + ''') + + parser.add_argument('--mode', help='Used to force mode of stations') + parser.add_argument('--sta_name', help='Name of station to be used', default=["sta0000"]) + + args = parser.parse_args() + + station_list = args.sta_name + sta_scan_test = StaScan(host=args.mgr, + port=args.mgr_port, + number_template="0000", + sta_list=station_list, + upstream=args.upstream_port, + ssid=args.ssid, + password=args.passwd, + radio=args.radio, + security=args.security, + use_ht160=False, + mode=args.mode, + _debug_on=args.debug) + + sta_scan_test.pre_cleanup() + + sta_scan_test.build() + # exit() + if not sta_scan_test.passes(): + print(sta_scan_test.get_fail_message()) + sta_scan_test.exit_fail() + + sta_scan_test.start() + sta_scan_test.cleanup() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/station_layer3.py b/lanforge/lanforge-scripts/py-scripts/station_layer3.py new file mode 100644 index 000000000..7563d34fc --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/station_layer3.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +''' +this script creates 1 station on given arguments +how to run - [lanforge@LF4-Node2 py-scripts]$ python3 station_banao.py -hst localhost -s TestAP22 -pwd [BLANK] -sec open -rad wiphy0 +''' +import sys +import os +import importlib +import argparse +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class STATION(LFCliBase): + def __init__(self, lfclient_host, lfclient_port, ssid, paswd, security, radio, sta_list=None, name_prefix="L3Test", upstream="eth2"): + super().__init__(lfclient_host, lfclient_port) + self.host = lfclient_host + self.port = lfclient_port + self.ssid = ssid + self.paswd = paswd + self.security = security + self.radio = radio + self.sta_list = sta_list + self.name_prefix = name_prefix + self.upstream = upstream + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.paswd, + self.station_profile.security = self.security + self.cx_profile = self.local_realm.new_l3_cx_profile() + self.cx_profile.host = self.host + self.cx_profile.port = self.port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = 1000000 + self.cx_profile.side_a_max_bps = 1000000 + self.cx_profile.side_b_min_bps = 1000000 + self.cx_profile.side_b_max_bps = 1000000 + + + def precleanup(self, sta_list): + self.cx_profile.cleanup_prefix() + for sta in self.sta_list: + self.local_realm.rm_port(sta, check_exists=True) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=sta_list, + debug=self.debug) + time.sleep(1) + + def build(self): + self.station_profile.use_security(self.security, self.ssid, self.paswd) + self.station_profile.create(radio=self.radio) + self.cx_profile.create(endp_type="lf_udp", side_a=self.station_profile.station_names, side_b=self.upstream, + sleep_time=0) + + def start(self, sta_list): + self.station_profile.admin_up() + temp_stas = self.station_profile.station_names.copy() + if self.local_realm.wait_for_ip(temp_stas): + self._pass("All stations got IPs") + else: + self._fail("Stations failed to get IPs") + self.exit_fail() + self.cx_profile.start_cx() + + + def stop(self): + # Bring stations down + self.station_profile.admin_down() + self.cx_profile.stop_cx() + + + + + +def main(): + parser = argparse.ArgumentParser( + prog='station_layer3.py', + formatter_class=argparse.RawTextHelpFormatter, + description="Netgear AP DFS Test Script") + parser.add_argument('-hst', '--host', type=str, help='host name') + parser.add_argument('-s', '--ssid', type=str, help='ssid for client') + parser.add_argument('-pwd', '--passwd', type=str, help='password to connect to ssid') + parser.add_argument('-sec', '--security', type=str, help='security') + parser.add_argument('-rad', '--radio', type=str, help='radio at which client will be connected') + #parser.add_argument() + args = parser.parse_args() + num_sta = 1 + station_list = LFUtils.port_name_series(prefix="sta", + start_id=0, + end_id=num_sta - 1, + padding_number=10000, + radio=args.radio) + obj = STATION(lfclient_host= args.host, lfclient_port=8080, ssid=args.ssid , paswd=args.passwd, security=args.security, radio=args.radio, sta_list=station_list) + obj.precleanup(station_list) + obj.build() + obj.start(station_list) + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/stations_connected.py b/lanforge/lanforge-scripts/py-scripts/stations_connected.py new file mode 100755 index 000000000..74070fbcc --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/stations_connected.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Contains examples of using realm to query stations and get specific information from them +import sys +import os +import importlib + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class StationsConnected(LFCliBase): + def __init__(self, lfjson_host, lfjson_port): + super().__init__(_lfjson_host=lfjson_host, _lfjson_port=lfjson_port, _debug=False) + self.localrealm = Realm(lfclient_host=lfjson_host, lfclient_port=lfjson_port, debug=False) + self.check_connect() + + def run(self): + self.clear_test_results() + fields = "_links,port,alias,ip,ap,port+type" + self.station_results = self.localrealm.find_ports_like("sta*", fields, debug_=False) + #pprint(self.station_results) + if (self.station_results is None) or (len(self.station_results) < 1): + self.get_failed_result_list() + return False + return True + + def num_associated(self, bssid): + counter = 0 + # print("there are %d results" % len(self.station_results)) + for eid,record in self.station_results.items(): + #print("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ") + #pprint(eid) + #pprint(record) + if record["ap"] == bssid: + counter += 1 + #print("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ") + return counter + +def main(): + qstationsx = StationsConnected("localhost", 8080) + bssid = "00:0E:8E:7B:DF:9B" + if qstationsx.run(): + associated_stations = qstationsx.num_associated(bssid) + print("Number of stations associated to %s: %s" % (bssid, associated_stations)) + else: + print("problem querying for stations for %s" % bssid) + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_1k_clients_jedtest.py b/lanforge/lanforge-scripts/py-scripts/test_1k_clients_jedtest.py new file mode 100755 index 000000000..cde6597cd --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_1k_clients_jedtest.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +import sys +import os +import importlib +import argparse +import time +import datetime + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class Test1KClients(LFCliBase): + def __init__(self, + upstream, + host="localhost", + port=8080, + side_a_min_rate= 0,side_a_max_rate= 56000, + side_b_min_rate= 0,side_b_max_rate= 56000, + num_sta_=200, + test_duration="2d", + _debug_on=True, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, + port, + _debug=_debug_on, + _local_realm=Realm(lfclient_host=host, lfclient_port=port), + _exit_on_fail=_exit_on_fail) + self.ssid_radio_map = { + '1.1.wiphy0' : ("wpa2", "jedway-wpa2-x2048-4-4", "jedway-wpa2-x2048-4-4"), + '1.1.wiphy1' : ("wpa2", "jedway-wpa2-x2048-5-1", "jedway-wpa2-x2048-5-1"), + '1.1.wiphy2' : ("wpa2", "jedway-wpa2-x2048-4-1", "jedway-wpa2-x2048-4-1"), + + '1.2.wiphy0' : ("wpa2", "jedway-wpa2-x2048-5-3", "jedway-wpa2-x2048-5-3"), + '1.2.wiphy1' : ("wpa2", "jedway-wpa2-x2048-4-4", "jedway-wpa2-x2048-4-4"), + '1.2.wiphy2' : ("wpa2", "jedway-wpa2-x2048-4-1", "jedway-wpa2-x2048-4-1"), + } + if num_sta_ is None: + raise ValueError("need a number of stations per radio") + self.num_sta = int(num_sta_) + self.station_radio_map = { + # port_name_series(prefix=prefix_, start_id=start_id_, end_id=end_id_, padding_number=padding_number_, radio=radio) + "1.1.wiphy0" : LFUtils.port_name_series(start_id=0, end_id=self.num_sta-1, padding_number=10000, radio="1.1.wiphy0"), + "1.1.wiphy1" : LFUtils.port_name_series(start_id=1000, end_id=1000+self.num_sta-1, padding_number=10000, radio="1.1.wiphy1"), + "1.1.wiphy2" : LFUtils.port_name_series(start_id=2000, end_id=2000+self.num_sta-1, padding_number=10000, radio="1.1.wiphy2"), + + "1.2.wiphy0" : LFUtils.port_name_series(start_id=3000, end_id=3000+self.num_sta-1, padding_number=10000, radio="1.2.wiphy0"), + "1.2.wiphy1" : LFUtils.port_name_series(start_id=4000, end_id=4000+self.num_sta-1, padding_number=10000, radio="1.2.wiphy1"), + "1.2.wiphy2" : LFUtils.port_name_series(start_id=5000, end_id=5000+self.num_sta-1, padding_number=10000, radio="1.2.wiphy2") + } + self.test_duration=test_duration + self.upstream=upstream + self.name_prefix = "1k" + self.cx_profile = self.local_realm.new_l3_cx_profile() + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = side_a_min_rate + self.cx_profile.side_a_max_bps = side_a_max_rate + self.cx_profile.side_b_min_bps = side_b_min_rate + self.cx_profile.side_b_max_bps = side_b_max_rate + + self.station_profile_map = {} + #change resource admin_up rate + self.local_realm.json_post("/cli-json/set_resource", { + "shelf":1, + "resource":all, + "max_staged_bringup": 30, + "max_trying_ifup": 15, + "max_station_bringup": 6 + }) + + + def build(self): + for (radio, name_series) in self.station_radio_map.items(): + print("building stations for %s"%radio) + if (name_series is None) or len(name_series) < 1: + print("No name series for %s"%radio) + continue + station_profile = self.local_realm.new_station_profile() + station_profile.use_security(self.ssid_radio_map[radio][0], + self.ssid_radio_map[radio][1], + self.ssid_radio_map[radio][2]) + self.station_profile_map[radio] = station_profile + + self._pass("defined %s station profiles" % len(self.station_radio_map)) + for (radio, station_profile) in self.station_profile_map.items(): + station_profile.create(radio=radio, + sta_names_=self.station_radio_map[radio], + dry_run=False, + up_=False, + debug=self.debug, + suppress_related_commands_=True, + use_radius=False, + hs20_enable=False, + sleep_time=.02) + station_profile.set_command_param("set_port", "report_timer", 1500) + station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.cx_profile.create(endp_type="lf_udp", side_a=station_profile.station_names, side_b=self.upstream, sleep_time=0) + + self._pass("built stations on %s radios" % len(self.station_radio_map)) + + def __get_rx_values(self): + cx_list = self.json_get("endp?fields=name,rx+bytes", debug_=self.debug) + if self.debug: + print("==============\n", cx_list, "\n==============") + cx_rx_map = {} + for cx_name in cx_list['endpoint']: + if cx_name != 'uri' and cx_name != 'handler': + for item, value in cx_name.items(): + for value_name, value_rx in value.items(): + if value_name == 'rx bytes' and item in self.cx_profile.created_cx.values(): + cx_rx_map[item] = value_rx + return cx_rx_map + + def __compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + if len(old_list) == len(new_list): + for item, value in old_list.items(): + expected_passes += 1 + if new_list[item] > old_list[item]: + passes += 1 + if passes == expected_passes: + return True + else: + return False + else: + return False + + def start(self): + print("Bringing stations up...") + prev_ip_num=0 + for (radio, station_profile) in self.station_profile_map.items(): + station_profile.admin_up() + total_num_sta=6*self.num_sta + self.local_realm.wait_for_ip(station_list=self.station_radio_map[radio], debug=self.debug, timeout_sec=30) + curr_ip_num = self.local_realm.get_curr_num_ips(num_sta_with_ips=prev_ip_num,station_list=self.station_radio_map[radio], debug=self.debug) + while ((prev_ip_num < curr_ip_num) and (curr_ip_num < total_num_sta)): + self.local_realm.wait_for_ip(station_list=self.station_radio_map[radio], debug=self.debug, timeout_sec=90) + prev_ip_num = curr_ip_num + curr_ip_num = self.local_realm.get_curr_num_ips(num_sta_with_ips=prev_ip_num,station_list=self.station_radio_map[radio], debug=self.debug) + if curr_ip_num == total_num_sta: + self._pass("stations on radio %s up" % radio) + else: + self._fail("FAIL: Not all stations on radio %s up" % radio) + self.exit_fail() + + + old_cx_rx_values = self.__get_rx_values() + self.cx_profile.start_cx() + + passes = 0 + expected_passes = 0 + curr_time = datetime.datetime.now() + end_time = self.local_realm.parse_time(self.test_duration) + curr_time + sleep_interval = self.local_realm.parse_time(self.test_duration) // 3 + + while curr_time < end_time: + + time.sleep(sleep_interval.total_seconds()) + + new_cx_rx_values = self.__get_rx_values() + if self.debug: + print(old_cx_rx_values, new_cx_rx_values) + print("\n-----------------------------------") + print(curr_time, end_time) + print("-----------------------------------\n") + expected_passes += 1 + if self.__compare_vals(old_cx_rx_values, new_cx_rx_values): + passes += 1 + else: + self._fail("FAIL: Not all stations increased traffic") + self.exit_fail() + + old_cx_rx_values = new_cx_rx_values + curr_time = datetime.datetime.now() + + if passes == expected_passes: + self._pass("PASS: All tests passed") + + + def stop(self): + self.cx_profile.stop_cx() + + def pre_cleanup(self): + self.cx_profile.cleanup_prefix() + for (radio, name_series) in self.station_radio_map.items(): + sta_list= self.station_radio_map[radio] + for sta in sta_list: + self.local_realm.rm_port(sta, check_exists=True) + + def post_cleanup(self): + self.cx_profile.cleanup() + for (radio, name_series) in self.station_radio_map.items(): + sta_list= self.station_radio_map[radio] + for sta in sta_list: + self.local_realm.rm_port(sta, check_exists=True) + + +def main(): + + + parser = LFCliBase.create_bare_argparse(prog=__file__, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + creates lots of stations across multiple radios. + ''', + description='''\ + test_1k_clients_jedtest.py: + -------------------- + Generic command layout: + python3 ./test_1k_clients_jedtest.py + --mgr localhost + --mgr_port 8080 + --sta_per_radio 300 + --test_duration 3m + --a_min 1000 + --b_min 1000 + --a_max 0 + --b_max 0 + --debug ''' + ) + + required_args=None + for group in parser._action_groups: + if group.title == "required arguments": + required_args=group + break + + if required_args is not None: + required_args.add_argument("--sta_per_radio",type=int,help="number of stations per radio") + + optional_args=None + for group in parser._action_groups: + if group.title == "optional arguments": + optional_args=group + break + if optional_args is not None: + optional_args.add_argument('--a_min', help='--a_min bps rate minimum for side_a', default=0) + optional_args.add_argument('--b_min', help='--b_min bps rate minimum for side_b', default=0) + optional_args.add_argument('--a_max', help='--a_min bps rate minimum for side_a', default=256000) + optional_args.add_argument('--b_max', help='--b_min bps rate minimum for side_b', default=256000) + optional_args.add_argument('--test_duration', help='--test_duration sets the duration of the test', default="2m") + optional_args.add_argument('-u', '--upstream_port',help='non-station port that generates traffic: ., e.g: 1.eth1',default='1.eth1') + + + args = parser.parse_args() + + kilo_test = Test1KClients(host=args.mgr, + port=args.mgr_port, + upstream=args.upstream_port, + num_sta_=args.sta_per_radio, + side_a_max_rate=args.a_max, + side_a_min_rate=args.a_min, + side_b_max_rate=args.b_max, + side_b_min_rate=args.b_min, + _debug_on=args.debug) + + kilo_test.pre_cleanup() + kilo_test.build() + if not kilo_test.passes(): + kilo_test.exit_failed() + kilo_test.start() + if not kilo_test.passes(): + kilo_test.exit_failed() + kilo_test.stop() + if not kilo_test.passes(): + kilo_test.exit_failed() + time.sleep(60) + kilo_test.post_cleanup() + kilo_test.exit_success() + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_all_scripts.sh b/lanforge/lanforge-scripts/py-scripts/test_all_scripts.sh new file mode 100644 index 000000000..66106e4a6 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_all_scripts.sh @@ -0,0 +1,100 @@ +#!/bin/bash +#This bash script aims to automate the test process of all Candela Technologies's test_* scripts in the lanforge-scripts directory. The script can be run 2 ways and may include (via user input) the "start_num" and "stop_num" variables to select which tests should be run. +# OPTION ONE: ./test_all_scripts.sh : this command runs all the scripts in the array "testCommands" +# OPTION TWO: ./test_all_scripts.sh 4 5 : this command runs py-script commands (in testCommands array) that include the py-script options beginning with 4 and 5 (inclusive) in case function ret_case_num. +#Variables +NUM_STA=4 +SSID_USED="jedway-wpa2-x2048-5-3" +PASSWD_USED="jedway-wpa2-x2048-5-3" +RADIO_USED="wiphy1" +SECURITY="wpa2" + +START_NUM=0 +CURR_TEST_NUM=0 +CURR_TEST_NAME="BLANK" +STOP_NUM=9 + +#Test array +testCommands=("./example_security_connection.py --num_stations $NUM_STA --ssid jedway-r8000-36 --passwd jedway-r8000-36 --radio $RADIO_USED --security wpa " + "./example_security_connection.py --num_stations $NUM_STA --ssid $SSID_USED --passwd $SSID_USED --radio $RADIO_USED --security wpa2" + "./example_security_connection.py --num_stations $NUM_STA --ssid jedway-wep-48 --passwd jedway-wep-48 --radio $RADIO_USED --security wep" + "./example_security_connection.py --num_stations $NUM_STA --ssid jedway-wpa3-1 --passwd jedway-wpa3-1 --radio $RADIO_USED --security wpa3" + "./test_ipv4_connection.py --radio wiphy2 --num_stations $NUM_STA --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY --debug --upstream_port eth1" + "./test_generic.py --mgr localhost --mgr_port 4122 --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --num_stations $NUM_STA --type lfping --dest 10.40.0.1 --security $SECURITY" + "./test_generic.py --mgr localhost --mgr_port 4122 --radio $RADIO_USED --ssid $SSID_USED --passwd $PASSWD_USED --num_stations $NUM_STA --type speedtest --speedtest_min_up 20 --speedtest_min_dl 20 --speedtest_max_ping 150 --security $SECURITY" + "./test_ipv4_l4_urls_per_ten.py --upstream_port eth1 --radio $RADIO_USED --num_stations $NUM_STA --security $SECURITY --ssid $SSID_USED --passwd $PASSWD_USED --num_tests 1 --requests_per_ten 600 --target_per_ten 600" + "./test_ipv4_l4_wifi.py --upstream_port eth1 --radio wiphy0 --num_stations $NUM_STA --security $SECURITY --ssid jedway-wpa2-x2048-4-4 --passwd jedway-wpa2-x2048-4-4 --test_duration 3m" + "./test_ipv4_l4.py --radio wiphy3 --num_stations 4 --security wpa2 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --url \"dl http://10.40.0.1 /dev/null\" --test_duration 2m --debug" + "./test_ipv4_variable_time.py --radio wiphy1 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --security wpa2 --mode 4 --ap 00:0e:8e:ff:86:e6 --test_duration 30s --output_format excel" + "./test_ipv4_variable_time.py --radio wiphy1 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --security wpa2 --mode 4 --ap 00:0e:8e:ff:86:e6 --test_duration 30s --output_format csv" + "./create_bridge.py --radio wiphy1 --upstream_port eth1" + "./create_l3.py --radio wiphy1 --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY" + "./create_l4.py --radio wiphy1 --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY" + "./create_macvlan.py --radio wiphy1" + "./create_station.py --radio wiphy1 --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY" + "./create_vap.py --radio wiphy1 --ssid $SSID_USED --passwd $PASSWD_USED --security $SECURITY" +) +declare -A name_to_num +name_to_num=( + ["example_security_connection"]=1 + ["test_ipv4_connection"]=2 + ["test_generic"]=3 + ["test_ipv4_l4_urls_per_ten"]=4 + ["test_ipv4_l4_wifi"]=5 + ["test_ipv4_l4"]=6 + ["test_ipv4_variable_time"]=7 + ["create_bridge"]=8 + ["create_l3"]=9 + ["create_l4"]=10 + ["create_macvlan"]=10 + ["create_station"]=11 + ["create_vap"]=12 +) + +function blank_db() { + echo "Loading blank scenario..." >>~/test_all_output_file.txt + ./scenario.py --load BLANK >>~/test_all_output_file.txt + #check_blank.py +} +function echo_print() { + echo "Beginning $CURR_TEST_NAME test..." >>~/test_all_output_file.txt +} +function run_test() { + for i in "${testCommands[@]}"; do + CURR_TEST_NAME=${i%%.py*} + CURR_TEST_NAME=${CURR_TEST_NAME#./*} + CURR_TEST_NUM="${name_to_num[$CURR_TEST_NAME]}" + echo "$CURR_TEST_NAME $CURR_TEST_NUM" + + if (( $CURR_TEST_NUM > $STOP_NUM )) || (( $STOP_NUM == $CURR_TEST_NUM )) && (( $STOP_NUM != 0 )); then + exit 1 + fi + if (( $CURR_TEST_NUM > $START_NUM )) || (( $CURR_TEST_NUM == $START_NUM )); then + echo_print + echo "$i" + [[ x$DEBUG != x ]] && sleep 2 + eval $i >>~/test_all_output_file.txt + if [ $? -ne 0 ]; then + echo $CURR_TEST_NAME failure + [[ x$DEBUG != x ]] && exit 1 + else + echo $CURR_TEST_NAME success + fi + if [[ "${CURR_TEST_NAME}" = @(example_wpa_connection|example_wpa2_connection|example_wpa3_connection|example_wep_connection) ]]; then + blank_db + fi + fi + done +} +function check_args() { + if [ ! -z $1 ]; then + START_NUM=$1 + fi + if [ ! -z $2 ]; then + STOP_NUM=$2 + fi +} +true >~/test_all_output_file.txt +check_args $1 $2 +run_test +#test generic and fileio are for macvlans diff --git a/lanforge/lanforge-scripts/py-scripts/test_client_admission.py b/lanforge/lanforge-scripts/py-scripts/test_client_admission.py new file mode 100755 index 000000000..d19d36d7a --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_client_admission.py @@ -0,0 +1,127 @@ +""" This script will create one station at a time and generate downstream traffic at 5Mbps then again create next station create layer3 and will continue doing same until Ap stops admiting client + This script can be used for for client admission test for particular AP + + arguements = >python3 load_21.py -hst 192.168.200.13 -s Nikita -pwd [BLANK] -sec open -rad wiphy1 --num_sta 60 + -Nikita Yadav + -date: 23-02-2021 +""" +import sys +import os +import importlib +import argparse +import time + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class LoadLayer3(Realm): + def __init__(self, lfclient_host, lfclient_port, ssid, paswd, security, radio, num_sta, name_prefix="L3", upstream="eth2"): + + self.host = lfclient_host + self.port = lfclient_port + self.ssid = ssid + self.paswd = paswd + self.security = security + self.radio = radio + self.num_sta = num_sta + + self.name_prefix = name_prefix + self.upstream = upstream + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.paswd, + self.station_profile.security = self.security + self.cx_profile = self.local_realm.new_l3_cx_profile() + self.cx_profile.host = self.host + self.cx_profile.port = self.port + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = 5000000 + self.cx_profile.side_a_max_bps = 5000000 + self.cx_profile.side_b_min_bps = 0 + self.cx_profile.side_b_max_bps = 0 + + def precleanup(self, num_sta): + num_sta = self.num_sta + station_list = LFUtils.port_name_series(prefix="sta", + start_id=0, + end_id=num_sta - 1, + padding_number=100, + radio=self.radio) + self.cx_profile.cleanup_prefix() + + for sta in station_list: + self.local_realm.rm_port(sta, check_exists=True) + LFUtils.wait_until_ports_disappear(base_url=self.local_realm.lfclient_url, port_list=station_list, + debug=self.local_realm.debug) + time.sleep(1) + + def build(self, sta_name): + self.station_profile.use_security(self.security, self.ssid, self.paswd) + self.station_profile.create(radio=self.radio, sta_names_=[sta_name], debug=self.local_realm.debug) + self.station_profile.admin_up() + if self.local_realm.wait_for_ip([sta_name]): + self.local_realm._pass("All stations got IPs", print_=True) + + self.cx_profile.create(endp_type="lf_udp", side_a=self.upstream, side_b=[sta_name], + sleep_time=0) + self.cx_profile.start_cx() + + return 1 + else: + self.local_realm._fail("Stations failed to get IPs", print_=True) + return 0 + + def start(self, num_sta): + num_sta = self.num_sta + station_list = LFUtils.port_name_series(prefix="sta", + start_id=0, + end_id=num_sta - 1, + padding_number=100, + radio=self.radio) + + for i in station_list: + # self.build(i) + if self.build(i) == 0: + print("station not created") + break + else: + print("station created") + + def stop(self): + # Bring stations down + self.station_profile.admin_down() + self.cx_profile.stop_cx() + + +def main(): + parser = argparse.ArgumentParser( + prog="test_client_admission.py", + formatter_class=argparse.RawTextHelpFormatter, + description="Client Admission Test Script") + parser.add_argument('-hst', '--host', type=str, help='host name') + parser.add_argument('-s', '--ssid', type=str, help='ssid for client') + parser.add_argument('-pwd', '--passwd', type=str, help='password to connect to ssid') + parser.add_argument('-sec', '--security', type=str, help='security') + parser.add_argument('-rad', '--radio', type=str, help='radio at which client will be connected') + parser.add_argument('-num_sta', '--num_sta', type=int, help='provide number of stations you want to create', default=60) + # parser.add_argument() + args = parser.parse_args() + + obj = LoadLayer3(lfclient_host=args.host, lfclient_port=8080, ssid=args.ssid, paswd=args.passwd, + security=args.security, radio=args.radio, num_sta=args.num_sta) + obj.precleanup(num_sta=args.num_sta) + + obj.start(num_sta=args.num_sta) + + +if __name__ == '__main__': + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_fileio.py b/lanforge/lanforge-scripts/py-scripts/test_fileio.py new file mode 100755 index 000000000..6148a78c7 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_fileio.py @@ -0,0 +1,788 @@ +#!/usr/bin/env python3 +""" +NAME: test_fileio.py + +PURPOSE: +test_fileio.py will create stations or macvlans with matching fileio endpoints to generate and verify fileio related traffic. + +This script will create a variable number of stations or macvlans to test fileio traffic. Pre-existing stations and +macvlans can be used as well. Command line options are available to update cross-connects as well as using a list of +existing cross-connects if desired. if none are given, cross-connects and endpoints will be created by the script. +Modes such as read-only, write-only, or both can be specified as well as ip addresses and starting numbers for sequential +stations or macvlans that are created in case of limited or pre-existing configurations. The test that is run during +this script will depend on the mode used, a read-only test will check the read-bps attribute, write-only will check write-bps +and both will check both attributes. If the relevant attributes increase over the duration of the test it will pass, +otherwise it will fail. + +EXAMPLE: +./test_fileio.py --macvlan_parent --num_ports --use_macvlans + --first_mvlan_ip --netmask --gateway + +./test_fileio.py --macvlan_parent eth2 --num_ports 3 --use_macvlans --first_mvlan_ip 192.168.92.13 + --netmask 255.255.255.0 --gateway 192.168.92.1 + + +Use './test_fileio.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import argparse +import time +import datetime + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +add_file_endp = importlib.import_module("py-json.LANforge.add_file_endp") +fe_fstype = add_file_endp.fe_fstype +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class FileIOTest(LFCliBase): + def __init__(self, host, port, ssid, security, password, + number_template="00000", + radio="wiphy0", + fs_type=fe_fstype.EP_FE_NFS4.name, + min_rw_size=64*1024, + max_rw_size=64*1024, + min_file_size=25*1024*1024, + max_file_size=25*1024*1024, + min_read_rate_bps=1000*1000, + max_read_rate_bps=1000*1000, + min_write_rate_bps="1G", + max_write_rate_bps=1000*1000, + directory="AUTO", + test_duration="5m", + upstream_port="eth1", + num_ports=1, + server_mount="10.40.0.1:/var/tmp/test", + macvlan_parent=None, + first_mvlan_ip=None, + netmask=None, + gateway=None, + dhcp=True, + use_macvlans=False, + use_test_groups=False, + write_only_test_group=None, + read_only_test_group=None, + port_list=[], + ip_list=None, + connections_per_port=1, + mode="both", + update_group_args={"name": None, "action": None, "cxs": None}, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.radio = radio + self.upstream_port = upstream_port + self.ssid = ssid + self.security = security + self.password = password + self.number_template = number_template + self.test_duration = test_duration + self.port_list = [] + self.connections_per_port = connections_per_port + self.use_macvlans = use_macvlans + self.mode = mode.lower() + self.ip_list = ip_list + self.netmask = netmask + self.gateway = gateway + if self.use_macvlans: + if macvlan_parent is not None: + self.macvlan_parent = macvlan_parent + self.port_list = port_list + else: + self.port_list = port_list + + self.use_test_groups = use_test_groups + if self.use_test_groups: + if self.mode == "write": + if write_only_test_group is not None: + self.write_only_test_group = write_only_test_group + else: + raise ValueError("--write_only_test_group must be used to set test group name") + if self.mode == "read": + if read_only_test_group is not None: + self.read_only_test_group = read_only_test_group + else: + raise ValueError("--read_only_test_group must be used to set test group name") + if self.mode == "both": + if write_only_test_group is not None and read_only_test_group is not None: + self.write_only_test_group = write_only_test_group + self.read_only_test_group = read_only_test_group + else: + raise ValueError("--write_only_test_group and --read_only_test_group " + "must be used to set test group names") + + #self.min_rw_size = self.parse_size(min_rw_size) + #self.max_rw_size = self.parse_size(max_rw_size) + #self.min_file_size = self.parse_size(min_file_size) + #self.min_file_size = self.parse_size(min_file_size) + #self.min_read_rate_bps = self.parse_size_bps(min_read_rate_bps) + # self.max_read_rate_bps = self.sisize_bps(max_read_rate_bps) + # self.min_write_rate_bps = self.parse_size_bps(min_write_rate_bps) + #self.max_write_rate_bps = self.parse_size_bps(max_write_rate_bps) + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.wo_profile = self.local_realm.new_fio_endp_profile() + self.mvlan_profile = self.local_realm.new_mvlan_profile() + + if not self.use_macvlans and len(self.port_list) > 0: + self.station_profile = self.local_realm.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + + self.wo_profile.fs_type = fs_type + self.wo_profile.min_rw_size = LFUtils.parse_size(min_rw_size) + self.wo_profile.max_rw_size = LFUtils.parse_size(max_rw_size) + self.wo_profile.min_file_size = LFUtils.parse_size(min_file_size) + self.wo_profile.max_file_size = LFUtils.parse_size(max_file_size) + self.wo_profile.min_read_rate_bps = LFUtils.parse_size(min_read_rate_bps) + self.wo_profile.max_read_rate_bps = LFUtils.parse_size(max_read_rate_bps) + self.wo_profile.min_write_rate_bps = LFUtils.parse_size(min_write_rate_bps) + self.wo_profile.max_write_rate_bps = LFUtils.parse_size(max_write_rate_bps) + self.wo_profile.directory = directory + self.wo_profile.server_mount = server_mount + self.wo_profile.num_connections_per_port = connections_per_port + + self.ro_profile = self.wo_profile.create_ro_profile() + + if self.use_macvlans: + self.mvlan_profile.num_macvlans = int(num_ports) + self.mvlan_profile.desired_macvlans = self.port_list + self.mvlan_profile.macvlan_parent = self.macvlan_parent + self.mvlan_profile.dhcp = dhcp + self.mvlan_profile.netmask = netmask + self.mvlan_profile.first_ip_addr = first_mvlan_ip + self.mvlan_profile.gateway = gateway + + self.created_ports = [] + if self.use_test_groups: + if self.mode is not None: + if self.mode == "write": + self.wo_tg_profile = self.local_realm.new_test_group_profile() + self.wo_tg_profile.group_name = self.write_only_test_group + elif self.mode == "read": + self.ro_tg_profile = self.local_realm.new_test_group_profile() + self.ro_tg_profile.group_name = self.read_only_test_group + elif self.mode == "both": + self.wo_tg_profile = self.local_realm.new_test_group_profile() + self.ro_tg_profile = self.local_realm.new_test_group_profile() + self.wo_tg_profile.group_name = self.write_only_test_group + self.ro_tg_profile.group_name = self.read_only_test_group + else: + raise ValueError("Unknown mode given ", self.mode) + else: + raise ValueError("Mode ( read, write, or both ) must be specified") + + if update_group_args is not None and update_group_args['name'] is not None: + temp_tg = self.local_realm.new_test_group_profile() + temp_cxs = update_group_args['cxs'].split(',') + if update_group_args['action'] == "add": + temp_tg.group_name = update_group_args['name'] + if not temp_tg.check_group_exists(): + temp_tg.create_group() + for cx in temp_cxs: + if "CX_" not in cx: + cx = "CX_" + cx + temp_tg.add_cx(cx) + if update_group_args['action'] == "del": + temp_tg.group_name = update_group_args['name'] + if temp_tg.check_group_exists(): + for cx in temp_cxs: + temp_tg.rm_cx(cx) + time.sleep(5) + + self.wo_tg_exists = False + self.ro_tg_exists = False + self.wo_tg_cx_exists = False + self.ro_tg_cx_exists = False + print("Checking for pre-existing test groups and cxs") + if self.use_test_groups: + if self.mode == "write": + if self.wo_tg_profile.check_group_exists(): + self.wo_tg_exists = True + if len(self.wo_tg_profile.list_cxs()) > 0: + self.wo_tg_cx_exists = True + elif self.mode == "read": + if self.ro_tg_profile.check_group_exists(): + self.ro_tg_exists = True + if len(self.ro_tg_profile.list_cxs()) > 0: + self.ro_tg_cx_exists = True + elif self.mode == "both": + if self.wo_tg_profile.check_group_exists(): + self.wo_tg_exists = True + if len(self.wo_tg_profile.list_cxs()) > 0: + self.wo_tg_cx_exists = True + if self.ro_tg_profile.check_group_exists(): + self.ro_tg_exists = True + if len(self.ro_tg_profile.list_cxs()) > 0: + self.ro_tg_cx_exists = True + + def __compare_vals(self, val_list): + passes = 0 + expected_passes = 0 + # print(val_list) + for item in val_list: + expected_passes += 1 + # print(item) + if item[0] == 'r': + # print("TEST", item, + # val_list[item]['read-bps'], + # self.ro_profile.min_read_rate_bps, + # val_list[item]['read-bps'] > self.ro_profile.min_read_rate_bps) + + if val_list[item]['read-bps'] > self.wo_profile.min_read_rate_bps: + passes += 1 + else: + # print("TEST", item, + # val_list[item]['write-bps'], + # self.wo_profile.min_write_rate_bps, + # val_list[item]['write-bps'] > self.wo_profile.min_write_rate_bps) + + if val_list[item]['write-bps'] > self.wo_profile.min_write_rate_bps: + passes += 1 + if passes == expected_passes: + return True + else: + return False + else: + return False + + def __get_values(self): + time.sleep(3) + if self.mode == "write": + cx_list = self.json_get("fileio/%s?fields=write-bps,read-bps" % ( + ','.join(self.wo_profile.created_cx.keys())), debug_=self.debug) + elif self.mode == "read": + cx_list = self.json_get("fileio/%s?fields=write-bps,read-bps" % ( + ','.join(self.ro_profile.created_cx.keys())), debug_=self.debug) + else: + cx_list = self.json_get("fileio/%s,%s?fields=write-bps,read-bps" % ( + ','.join(self.wo_profile.created_cx.keys()), + ','.join(self.ro_profile.created_cx.keys())), debug_=self.debug) + # print(cx_list) + # print("==============\n", cx_list, "\n==============") + cx_map = {} + # pprint.pprint(cx_list) + if cx_list is not None: + cx_list = cx_list['endpoint'] + for i in cx_list: + for item, value in i.items(): + # print(item, value) + cx_map[self.local_realm.name_to_eid(item)[2]] = {"read-bps": value['read-bps'], "write-bps": value['write-bps']} + # print(cx_map) + return cx_map + + def build(self): + # Build stations + if self.use_macvlans: + print("Creating MACVLANs") + self.mvlan_profile.create(admin_down=False, sleep_time=.5, debug=self.debug) + self._pass("PASS: MACVLAN build finished") + self.created_ports += self.mvlan_profile.created_macvlans + elif not self.use_macvlans and self.ip_list is None: + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.port_list, debug=self.debug) + self._pass("PASS: Station build finished") + self.created_ports += self.station_profile.station_names + + if len(self.ip_list) > 0: + # print("++++++++++++++++\n", self.ip_list, "++++++++++++++++\n") + for num_port in range(len(self.port_list)): + if self.ip_list[num_port] != 0: + if self.gateway is not None and self.netmask is not None: + shelf = self.local_realm.name_to_eid(self.port_list[num_port])[0] + resource = self.local_realm.name_to_eid(self.port_list[num_port])[1] + port = self.local_realm.name_to_eid(self.port_list[num_port])[2] + req_url = "/cli-json/set_port" + data = { + "shelf": shelf, + "resource": resource, + "port": port, + "ip_addr": self.ip_list[num_port], + "netmask": self.netmask, + "gateway": self.gateway + } + self.local_realm.json_post(req_url, data) + self.created_ports.append("%s.%s.%s" % (shelf, resource, port)) + else: + raise ValueError("Netmask and gateway must be specified") + + # if use test groups and test group does not exist, create cxs, create test group, assign to test group + # if use test groups and test group exists and no cxs, create cxs, assign to test group + # if use test groups and test group exist and cxs exist, do nothing + # if not use test groups, create cxs + if self.mode is not None: + if self.use_test_groups: + if self.mode == "write": + if self.wo_tg_exists: + if not self.wo_tg_cx_exists: + print("Creating Write Only CXs") + self.wo_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + time.sleep(1) + print("Adding cxs to %s" % self.wo_tg_profile.group_name) + for cx in self.wo_profile.created_cx.values(): + self.wo_tg_profile.add_cx(cx) + else: + print("Creating Write Only CXs") + self.wo_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + time.sleep(1) + print("Creating Write Only test group") + self.wo_tg_profile.create_group() + print("Adding cxs to %s" % self.wo_tg_profile.group_name) + for cx in self.wo_profile.created_cx.values(): + self.wo_tg_profile.add_cx(cx) + elif self.mode == "read": + if self.ro_tg_exists: + if not self.ro_tg_cx_exists: + print("Creating Read Only CXs") + self.ro_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + time.sleep(1) + print("Adding cxs to %s" % self.ro_tg_profile.group_name) + for cx in self.ro_profile.created_cx.values(): + self.ro_tg_profile.add_cx(cx) + else: + print("Creating Read Only CXs") + self.ro_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + time.sleep(1) + print("Creating Read Only test group") + self.ro_tg_profile.create_group() + print("Adding cxs to %s" % self.ro_tg_profile.group_name) + for cx in self.ro_profile.created_cx.values(): + self.ro_tg_profile.add_cx(cx) + elif self.mode == "both": + if self.wo_tg_exists: + if not self.wo_tg_cx_exists: + print("Creating Write Only CXs") + self.wo_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + time.sleep(1) + print("Adding cxs to %s" % self.wo_tg_profile.group_name) + for cx in self.wo_profile.created_cx.values(): + self.wo_tg_profile.add_cx(cx) + else: + print("Creating Write Only CXs") + self.wo_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + time.sleep(1) + print("Creating Write Only test group") + self.wo_tg_profile.create_group() + print("Adding cxs to %s" % self.wo_tg_profile.group_name) + for cx in self.wo_profile.created_cx.values(): + self.wo_tg_profile.add_cx(cx) + if self.ro_tg_exists: + if not self.ro_tg_cx_exists: + print("Creating Read Only CXs") + self.ro_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + time.sleep(1) + print("Adding cxs to %s" % self.ro_tg_profile.group_name) + for cx in self.ro_profile.created_cx.values(): + self.ro_tg_profile.add_cx(cx) + else: + print("Creating Read Only CXs") + self.ro_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + time.sleep(1) + print("Creating Read Only test group") + self.ro_tg_profile.create_group() + print("Adding cxs to %s" % self.ro_tg_profile.group_name) + for cx in self.ro_profile.created_cx.values(): + self.ro_tg_profile.add_cx(cx) + else: + raise ValueError("Uknown mode used, must be (read, write, both)") + else: + if self.mode == "write": + print("Creating Write Only CXs") + self.wo_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + elif self.mode == "read": + print("Creating Read Only CXs") + self.ro_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + elif self.mode == "both": + print("Creating Write Only CXs") + self.wo_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + print("Creating Read Only CXs") + self.ro_profile.create(ports=self.created_ports, connections_per_port=self.connections_per_port, + sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + else: + raise ValueError("Unknown mode used, must be (read, write, both)") + else: + raise ValueError("Mode must be set (read, write, both)") + + def start(self, print_pass=False, print_fail=False): + temp_ports = self.created_ports.copy() + #temp_stas.append(self.local_realm.name_to_eid(self.upstream_port)[2]) + if not self.use_macvlans: + self.station_profile.admin_up() + else: + self.mvlan_profile.admin_up() + if self.local_realm.wait_for_ip(temp_ports, debug=self.debug): + self._pass("All ports got IPs", print_pass) + else: + self._fail("Ports failed to get IPs", print_fail) + cur_time = datetime.datetime.now() + # print("Got Values") + end_time = self.local_realm.parse_time(self.test_duration) + cur_time + if self.use_test_groups: + if self.mode == "write": + self.wo_tg_profile.start_group() + elif self.mode == "read": + self.ro_tg_profile.start_group() + else: + self.wo_tg_profile.start_group() + time.sleep(2) + self.ro_tg_profile.start_group() + else: + if self.mode == "write": + self.wo_profile.start_cx() + elif self.mode == "read": + self.ro_profile.start_cx() + else: + self.wo_profile.start_cx() + time.sleep(2) + self.ro_profile.start_cx() + + passes = 0 + expected_passes = 0 + print("Starting Test...") + while cur_time < end_time: + interval_time = cur_time + datetime.timedelta(seconds=1) + while cur_time < interval_time: + cur_time = datetime.datetime.now() + time.sleep(1) + + new_rx_values = self.__get_values() + # exit(1) + # print(new_rx_values) + # print("\n-----------------------------------") + # print(cur_time, end_time, cur_time + datetime.timedelta(minutes=1)) + # print("-----------------------------------\n") + expected_passes += 1 + if self.__compare_vals(new_rx_values): + passes += 1 + else: + self._fail("FAIL: Not all stations increased traffic", print_fail) + # break + # old_rx_values = new_rx_values + cur_time = datetime.datetime.now() + if passes == expected_passes: + self._pass("PASS: All tests passes", print_pass) + + def stop(self): + if self.use_test_groups: + if self.mode == "write": + self.wo_tg_profile.stop_group() + elif self.mode == "read": + self.ro_tg_profile.stop_group() + else: + self.wo_tg_profile.stop_group() + time.sleep(2) + self.ro_tg_profile.stop_group() + else: + if self.mode == "write": + self.wo_profile.stop_cx() + elif self.mode == "read": + self.ro_profile.stop_cx() + else: + self.wo_profile.stop_cx() + time.sleep(2) + self.ro_profile.stop_cx() + + if not self.use_macvlans: + self.station_profile.admin_down() + else: + self.mvlan_profile.admin_down() + + def cleanup(self, port_list=None): + if self.use_test_groups: + if self.mode == "read": + if not self.ro_tg_exists: + if self.ro_tg_profile.check_group_exists(): + self.ro_tg_profile.rm_group() + if not self.ro_tg_cx_exists: + self.ro_profile.cleanup() + + elif self.mode == "write": + if not self.wo_tg_exists: + if self.wo_tg_profile.check_group_exists(): + self.wo_tg_profile.rm_group() + if not self.wo_tg_cx_exists: + self.wo_profile.cleanup() + + elif self.mode == "both": + if not self.ro_tg_exists: + if self.ro_tg_profile.check_group_exists(): + self.ro_tg_profile.rm_group() + if not self.ro_tg_cx_exists: + self.ro_profile.cleanup() + + if not self.wo_tg_exists: + if self.wo_tg_profile.check_group_exists(): + self.wo_tg_profile.rm_group() + if not self.wo_tg_cx_exists: + self.wo_profile.cleanup() + else: + if self.mode == "read": + self.ro_profile.cleanup() + elif self.mode == "write": + self.wo_profile.cleanup() + elif self.mode == "both": + self.ro_profile.cleanup() + self.wo_profile.cleanup() + + if not self.use_macvlans and self.ip_list is None: + self.station_profile.cleanup(port_list) + elif self.use_macvlans: + self.mvlan_profile.cleanup() + + # LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=port_list, debug=self.debug) + + +def main(): + parser = LFCliBase.create_bare_argparse( + prog='test_fileio.py', + # formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Creates FileIO endpoints which can be NFS, CIFS or iSCSI endpoints.''', + + description='''\ +test_fileio.py: +-------------------- +Generic command layout: +./test_fileio.py --macvlan_parent --num_ports --use_macvlans + --first_mvlan_ip --netmask --gateway + +./test_fileio.py --macvlan_parent eth2 --num_ports 3 --use_macvlans --first_mvlan_ip 192.168.92.13 + --netmask 255.255.255.0 --gateway 192.168.92.1 + +./test_fileio.py --radio 1.wiphy0 --test_duration 1m --macvlan_parent eth1 --num_ports 3 --use_macvlans + --use_ports eth1#0,eth1#1,eth1#2 --connections_per_port 2 --mode write + +./test_fileio.py --radio 1.wiphy0 --test_duration 1m --macvlan_parent eth1 --num_ports 3 --use_macvlans + --first_mvlan_ip 10.40.3.100 --netmask 255.255.240.0 --gateway 10.40.0.1 + --use_test_groups --write_only_test_group test_wo --read_only_test_group test_ro + --add_to_group test_wo --cxs test_wo0000,test_wo0001,test_wo0002 + +./test_fileio.py --radio 1.wiphy0 --test_duration 1m --macvlan_parent eth1 --num_ports 3 --use_macvlans + --use_ports eth1#0=10.40.3.103,eth1#1,eth1#2 --connections_per_port 2 + --netmask 255.255.240.0 --gateway 10.40.0.1 + +''') + parser.add_argument('--num_stations', help='Number of stations to create', default=0) + parser.add_argument('--radio', help='radio EID, e.g: 1.wiphy2') + parser.add_argument('--ssid', help='SSID for stations to associate to') + parser.add_argument('--passwd', '--password', '--key', help='WiFi passphrase/password/key') + parser.add_argument('--security', help='security type to use for ssid { wep | wpa | wpa2 | wpa3 | open }') + parser.add_argument('-u', '--upstream_port', + help='non-station port that generates traffic: ., e.g: 1.eth1', + default='1.eth1') + parser.add_argument('--test_duration', help='sets the duration of the test', default="5m") + parser.add_argument('--fs_type', help='endpoint type', default="fe_nfs4") + parser.add_argument('--min_rw_size', help='minimum read/write size', default=64*1024) + parser.add_argument('--max_rw_size', help='maximum read/write size', default=64*1024) + parser.add_argument('--min_file_size', help='minimum file size', default=50*1024*1024) + parser.add_argument('--max_file_size', help='maximum file size', default=50*1024*1024) + parser.add_argument('--min_read_rate_bps', help='minimum bps read rate', default=10e9) + parser.add_argument('--max_read_rate_bps', help='maximum bps read rate', default=10e9) + parser.add_argument('--min_write_rate_bps', help='minimum bps write rate', default=10e9) + parser.add_argument('--max_write_rate_bps', help='maximum bps write rate', default="1G") + parser.add_argument('--directory', help='--directory directory to read/write in. Absolute path suggested', + default="AUTO") + parser.add_argument('--server_mount', help='--server_mount The server to mount, ex: 192.168.100.5/exports/test1', + default="10.40.0.1:/var/tmp/test") + + parser.add_argument('--macvlan_parent', help='specifies parent port for macvlan creation', default=None) + parser.add_argument('--first_port', help='specifies name of first port to be used', default=None) + parser.add_argument('--num_ports', help='number of ports to create', default=1) + parser.add_argument('--connections_per_port', help='specifies number of connections to be used per port', default=1, + type=int) + parser.add_argument('--use_ports', help='list of comma separated ports to use with ips, \'=\' separates name and ip' + '{ port_name1=ip_addr1,port_name1=ip_addr2 }. ' + 'Ports without ips will be left alone', default=None) + parser.add_argument('--use_macvlans', help='will create macvlans', action='store_true', default=False) + parser.add_argument('--first_mvlan_ip', help='specifies first static ip address to be used or dhcp', default=None) + parser.add_argument('--netmask', help='specifies netmask to be used with static ip addresses', default=None) + parser.add_argument('--gateway', help='specifies default gateway to be used with static addressing', default=None) + parser.add_argument('--use_test_groups', help='will use test groups to start/stop instead of single endps/cxs', + action='store_true', default=False) + parser.add_argument('--read_only_test_group', help='specifies name to use for read only test group', default=None) + parser.add_argument('--write_only_test_group', help='specifies name to use for write only test group', default=None) + parser.add_argument('--mode', help='write,read,both', default='both', type=str) + tg_group = parser.add_mutually_exclusive_group() + tg_group.add_argument('--add_to_group', help='name of test group to add cxs to', default=None) + tg_group.add_argument('--del_from_group', help='name of test group to delete cxs from', default=None) + parser.add_argument('--cxs', help='list of cxs to add/remove depending on use of --add_to_group or --del_from_group' + , default=None) + args = parser.parse_args() + + update_group_args = { + "name": None, + "action": None, + "cxs": None + } + if args.add_to_group is not None and args.cxs is not None: + update_group_args['name'] = args.add_to_group + update_group_args['action'] = "add" + update_group_args['cxs'] = args.cxs + elif args.del_from_group is not None and args.cxs is not None: + update_group_args['name'] = args.del_from_group + update_group_args['action'] = "del" + update_group_args['cxs'] = args.cxs + + port_list = [] + ip_list = [] + if args.first_port is not None and args.use_ports is not None: + if args.first_port.startswith("sta"): + if (args.num_ports is not None) and (int(args.num_ports) > 0): + start_num = int(args.first_port[3:]) + num_ports = int(args.num_ports) + port_list = LFUtils.port_name_series(prefix="sta", start_id=start_num, end_id=start_num+num_ports-1, + padding_number=10000, + radio=args.radio) + else: + if (args.num_ports is not None) and args.macvlan_parent is not None and (int(args.num_ports) > 0) \ + and args.macvlan_parent in args.first_port: + start_num = int(args.first_port[args.first_port.index('#')+1:]) + num_ports = int(args.num_ports) + port_list = LFUtils.port_name_series(prefix=args.macvlan_parent+"#", start_id=start_num, + end_id=start_num+num_ports-1, padding_number=100000, + radio=args.radio) + else: + raise ValueError("Invalid values for num_ports [%s], macvlan_parent [%s], and/or first_port [%s].\n" + "first_port must contain parent port and num_ports must be greater than 0" + % (args.num_ports, args.macvlan_parent, args.first_port)) + else: + if args.use_ports is None: + num_ports = int(args.num_ports) + if not args.use_macvlans: + port_list = LFUtils.port_name_series(prefix="sta", start_id=0, end_id=num_ports - 1, + padding_number=10000, + radio=args.radio) + else: + port_list = LFUtils.port_name_series(prefix=args.macvlan_parent + "#", start_id=0, + end_id=num_ports - 1, padding_number=100000, + radio=args.radio) + else: + temp_list = args.use_ports.split(',') + for port in temp_list: + port_list.append(port.split('=')[0]) + if '=' in port: + ip_list.append(port.split('=')[1]) + else: + ip_list.append(0) + + if len(port_list) != len(ip_list): + raise ValueError(temp_list, " ports must have matching ip addresses!") + + if args.first_mvlan_ip is not None: + if args.first_mvlan_ip.lower() == "dhcp": + dhcp = True + else: + dhcp = False + else: + dhcp = True + if 'nfs' in args.fs_type: + if len(os.popen('mount -l | grep nfs').read()) > 0: + print('Success') + else: + raise ValueError("No nfs share is mounted") + else: + exit(1) + + ip_test = FileIOTest(args.mgr, + args.mgr_port, + ssid=args.ssid, + password=args.passwd, + security=args.security, + port_list=port_list, + ip_list=ip_list, + test_duration=args.test_duration, + upstream_port=args.upstream_port, + _debug_on=args.debug, + macvlan_parent=args.macvlan_parent, + use_macvlans=args.use_macvlans, + first_mvlan_ip=args.first_mvlan_ip, + netmask=args.netmask, + gateway=args.gateway, + dhcp=dhcp, + fs_type=args.fs_type, + min_rw_size=args.min_rw_size, + max_rw_size=args.max_rw_size, + min_file_size=args.min_file_size, + max_file_size=args.max_file_size, + min_read_rate_bps=args.min_read_rate_bps, + max_read_rate_bps=args.max_read_rate_bps, + min_write_rate_bps=args.min_write_rate_bps, + max_write_rate_bps=args.max_write_rate_bps, + directory=args.directory, + server_mount=args.server_mount, + num_ports=args.num_ports, + use_test_groups=args.use_test_groups, + write_only_test_group=args.write_only_test_group, + read_only_test_group=args.read_only_test_group, + update_group_args = update_group_args, + connections_per_port=args.connections_per_port, + mode=args.mode + # want a mount options param + ) + + ip_test.cleanup(port_list) + ip_test.build() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + ip_test.start(False, False) + ip_test.stop() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + # exit(1) + time.sleep(30) + ip_test.cleanup(port_list) + if ip_test.passes(): + print("Full test passed, all endpoints had increased bytes-rd throughout test duration") + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_generic.py b/lanforge/lanforge-scripts/py-scripts/test_generic.py new file mode 100755 index 000000000..6ce023f50 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_generic.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python3 +""" +NAME: test_generic.py + +PURPOSE: +test_generic.py will create stations and endpoints to generate traffic based on a command-line specified command type. + +This script will create a variable number of stations to test generic endpoints. Multiple command types can be tested +including ping, speedtest, generic types. The test will check the last-result attribute for different things +depending on what test is being run. Ping will test for successful pings, speedtest will test for download +speed, upload speed, and ping time, generic will test for successful generic commands + +SETUP: +Enable the generic tab in LANforge GUI + +EXAMPLE: + + LFPING: + ./test_generic.py --radio wiphy1 --ssid ct523c --passwd ct523c --security wpa2 --num_stations 4 --type lfping --dest "192.168.0.104" --debug + LFCURL (under construction): + ./test_generic.py --mgr localhost --mgr_port 4122 --radio wiphy1 --num_stations 26 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --security wpa2 --type lfcurl --dest 10.40.0.1 + GENERIC: + ./test_generic.py --mgr localhost--mgr_port 4122 --radio wiphy1 --num_stations 2 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --security wpa2 --type generic + SPEEDTEST: + ./test_generic.py --mgr localhost --mgr_port 4122 --radio wiphy2 --num_stations 13 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --type speedtest --speedtest_min_up 20 + --speedtest_min_dl 20 --speedtest_max_ping 150 --security wpa2 + IPERF3 (under construction): + ./test_generic.py --mgr localhost --mgr_port 4122 --radio wiphy1 --num_stations 3 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --security wpa2 --type iperf3 + +Use './test_generic.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import pprint +import argparse +import time +import datetime + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class GenTest(LFCliBase): + def __init__(self, ssid, security, passwd, sta_list, client, name_prefix, upstream, host="localhost", port=8080, + number_template="000", test_duration="5m", type="lfping", dest=None, cmd =None, + interval=1, radio=None, speedtest_min_up=None, speedtest_min_dl=None, speedtest_max_ping=None, + file_output=None, + loop_count=None, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _local_realm=Realm(host,port), _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.ssid = ssid + self.radio = radio + self.upstream = upstream + self.sta_list = sta_list + self.security = security + self.passwd = passwd + self.number_template = number_template + self.name_prefix = name_prefix + self.test_duration = test_duration + self.debug = _debug_on + if (client is not None): + self.client_name = client + self.station_profile = self.local_realm.new_station_profile() + self.generic_endps_profile = self.local_realm.new_generic_endp_profile() + + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.passwd, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + + self.generic_endps_profile.name = name_prefix + self.generic_endps_profile.type = type + self.generic_endps_profile.dest = dest + self.generic_endps_profile.cmd = cmd + self.generic_endps_profile.interval = interval + self.generic_endps_profile.file_output= file_output + self.generic_endps_profile.loop_count = loop_count + if (speedtest_min_up is not None): + self.generic_endps_profile.speedtest_min_up = float(speedtest_min_up) + if (speedtest_min_dl is not None): + self.generic_endps_profile.speedtest_min_dl = float(speedtest_min_dl) + if (speedtest_max_ping is not None): + self.generic_endps_profile.speedtest_max_ping = float(speedtest_max_ping) + + def check_tab_exists(self): + response = self.json_get("generic") + if response is None: + return False + else: + return True + + def start(self, print_pass=False, print_fail=False): + self.station_profile.admin_up() + temp_stas = [] + for station in self.sta_list.copy(): + temp_stas.append(self.local_realm.name_to_eid(station)[2]) + if self.debug: + pprint.pprint(self.station_profile.station_names) + LFUtils.wait_until_ports_admin_up(base_url=self.lfclient_url, port_list=self.station_profile.station_names) + if self.local_realm.wait_for_ip(station_list=temp_stas, ipv4=True): + self._pass("All stations got IPs") + else: + self._fail("Stations failed to get IPs") + self.exit_fail() + + self.generic_endps_profile.start_cx() + + def stop(self): + print("Stopping Test...") + self.generic_endps_profile.stop_cx() + self.station_profile.admin_down() + + def build(self): + self.station_profile.use_security(self.security, self.ssid, self.passwd) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + + self.generic_endps_profile.create(ports=self.station_profile.station_names, sleep_time=.5) + self._pass("PASS: Station build finished") + + def cleanup(self, sta_list): + self.generic_endps_profile.cleanup() + self.station_profile.cleanup(sta_list) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, debug=self.debug) + + +def main(): + optional = [] + optional.append({'name': '--mode', 'help': 'Used to force mode of stations'}) + optional.append({'name': '--ap', 'help': 'Used to force a connection to a particular AP'}) + optional.append({'name': '--output_format', 'help': 'choose either csv or xlsx'}) + optional.append({'name': '--report_file', 'help': 'where you want to store results', 'default': None}) + optional.append({'name': '--a_min', 'help': '--a_min bps rate minimum for side_a', 'default': 256000}) + optional.append({'name': '--b_min', 'help': '--b_min bps rate minimum for side_b', 'default': 256000}) + optional.append({'name': '--gen_cols', 'help': 'Columns wished to be monitored from layer 3 endpoint tab', + 'default': ['name', 'tx bytes', 'rx bytes']}) + optional.append({'name': '--port_mgr_cols', 'help': 'Columns wished to be monitored from port manager tab', + 'default': ['ap', 'ip', 'parent dev']}) + optional.append( + {'name': '--compared_report', 'help': 'report path and file which is wished to be compared with new report', + 'default': None}) + optional.append({'name': '--monitor_interval', + 'help': 'how frequently do you want your monitor function to take measurements; 250ms, 35s, 2h', + 'default': '2s'}) + + parser = LFCliBase.create_basic_argparse( + prog='test_generic.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''Create generic endpoints and test for their ability to execute chosen commands\n''', + description=''' +test_generic.py +-------------------- +Generic command example: +python3 ./test_generic.py + --mgr localhost (optional) + --mgr_port 4122 (optional) + --upstream_port eth1 (optional) + --radio wiphy0 (required) + --num_stations 3 (optional) + --security {open|wep|wpa|wpa2|wpa3} (required) + --ssid netgear (required) + --passwd admin123 (required) + --type lfping {generic|lfping|iperf3-client | speedtest | lf_curl} (required) + --dest 10.40.0.1 (required - also target for iperf3) + --test_duration 2m + --interval 1s + --debug + + + Example commands: + LFPING: + ./test_generic.py --mgr localhost --mgr_port 4122 --radio wiphy0 --num_stations 7 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --type lfping --dest 10.40.0.1 --security wpa2 + LFCURL (under construction): + ./test_generic.py --mgr localhost --mgr_port 4122 --radio wiphy1 --num_stations 26 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --security wpa2 --type lfcurl --dest 10.40.0.1 + GENERIC: + ./test_generic.py --mgr localhost--mgr_port 4122 --radio wiphy1 --num_stations 2 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --security wpa2 --type generic + SPEEDTEST: + ./test_generic.py --mgr localhost --mgr_port 4122 --radio wiphy2 --num_stations 13 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --type speedtest --speedtest_min_up 20 + --speedtest_min_dl 20 --speedtest_max_ping 150 --security wpa2 + IPERF3 (under construction): + ./test_generic.py --mgr localhost --mgr_port 4122 --radio wiphy1 --num_stations 3 --ssid jedway-wpa2-x2048-4-1 --passwd jedway-wpa2-x2048-4-1 --security wpa2 --type iperf3 +''', + more_optional=optional) + + parser.add_argument('--type', help='type of command to run: generic, lfping, iperf3-client, iperf3-server, lfcurl', default="lfping") + parser.add_argument('--cmd', help='specifies command to be run by generic type endp', default='') + parser.add_argument('--dest', help='destination IP for command', default="10.40.0.1") + parser.add_argument('--test_duration', help='duration of the test eg: 30s, 2m, 4h', default="2m") + parser.add_argument('--interval', help='interval to use when running lfping (1s, 1m)', default=1) + parser.add_argument('--speedtest_min_up', help='sets the minimum upload threshold for the speedtest type', default=None) + parser.add_argument('--speedtest_min_dl', help='sets the minimum download threshold for the speedtest type', default=None) + parser.add_argument('--speedtest_max_ping', help='sets the minimum ping threshold for the speedtest type', default=None) + parser.add_argument('--client', help='client to the iperf3 server', default=None) + parser.add_argument('--file_output', help='location to output results of lf_curl, absolute path preferred', default=None) + parser.add_argument('--loop_count', help='determines the number of loops to use in lf_curl', default=None) + + args = parser.parse_args() + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + # Create directory + + # if file path with output file extension is not given... + # check if home/lanforge/report-data exists. if not, save + # in new folder based in current file's directory + systeminfopath = None + if args.report_file is None: + new_file_path = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-h-%M-m-%S-s")).replace(':', + '-') + '-test_generic' # create path name + try: + path = os.path.join('/home/lanforge/report-data/', new_file_path) + os.mkdir(path) + except: + curr_dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + path = os.path.join(curr_dir_path, new_file_path) + os.mkdir(path) + systeminfopath = str(path) + '/systeminfo.txt' + + if args.output_format in ['csv', 'json', 'html', 'hdf', 'stata', 'pickle', 'pdf', 'png', 'parquet', + 'xlsx']: + report_f = str(path) + '/data.' + args.output_format + output = args.output_format + else: + print( + 'Not supporting this report format or cannot find report format provided. Defaulting to csv data file output type, naming it data.csv.') + report_f = str(path) + '/data.csv' + output = 'csv' + + else: + systeminfopath = str(args.report_file).split('/')[-1] + report_f = args.report_file + if args.output_format is None: + output = str(args.report_file).split('.')[-1] + else: + output = args.output_format + print("Saving final report data in ... " + report_f) + + # Retrieve last data file + compared_rept = None + if args.compared_report: + compared_report_format = args.compared_report.split('.')[-1] + # if compared_report_format not in ['csv', 'json', 'dta', 'pkl','html','xlsx','parquet','h5']: + if compared_report_format != 'csv': + print(ValueError("Cannot process this file type. Please select a different file and re-run script.")) + exit(1) + else: + compared_rept = args.compared_report + + station_list = LFUtils.portNameSeries(radio=args.radio, + prefix_="sta", + start_id_=0, + end_id_=num_sta-1, + padding_number_=100) + + generic_test = GenTest(host=args.mgr, port=args.mgr_port, + number_template="00", + radio=args.radio, + sta_list=station_list, + name_prefix="GT", + type=args.type, + dest=args.dest, + cmd=args.cmd, + interval=1, + ssid=args.ssid, + upstream=args.upstream_port, + passwd=args.passwd, + security=args.security, + test_duration=args.test_duration, + speedtest_min_up=args.speedtest_min_up, + speedtest_min_dl=args.speedtest_min_dl, + speedtest_max_ping=args.speedtest_max_ping, + file_output=args.file_output, + loop_count=args.loop_count, + client=args.client, + _debug_on=args.debug) + + if not generic_test.check_tab_exists(): + raise ValueError("Error received from GUI, please ensure generic tab is enabled") + generic_test.cleanup(station_list) + generic_test.build() + if not generic_test.passes(): + print(generic_test.get_fail_message()) + generic_test.exit_fail() + generic_test.start() + if not generic_test.passes(): + print(generic_test.get_fail_message()) + generic_test.exit_fail() + + try: + genconnections = ','.join([[*x.keys()][0] for x in generic_test.json_get('generic')['endpoints']]) + except: + raise ValueError('1. Enable the generic tab in LANforge GUI , if still fails 2. Try setting the upstream port flag if your device does not have an eth1 port') + + if type(args.gen_cols) is not list: + generic_cols = list(args.gen_cols.split(",")) + # send col names here to file to reformat + else: + generic_cols = args.gen_cols + # send col names here to file to reformat + if type(args.port_mgr_cols) is not list: + port_mgr_cols = list(args.port_mgr_cols.split(",")) + # send col names here to file to reformat + else: + port_mgr_cols = args.port_mgr_cols + # send col names here to file to reformat + if args.debug: + print("Generic Endp column names are...") + print(generic_cols) + print("Port Manager column names are...") + print(port_mgr_cols) + try: + monitor_interval = Realm.parse_time(args.monitor_interval).total_seconds() + except ValueError as error: + print(ValueError("The time string provided for monitor_interval argument is invalid. Please see supported time stamp increments and inputs for monitor_interval in --help. ")) + exit(1) + generic_test.start(False, False) + generic_test.generic_endps_profile.monitor(generic_cols=generic_cols, + sta_list=station_list, + #port_mgr_cols=port_mgr_cols, + report_file=report_f, + systeminfopath=systeminfopath, + duration_sec=Realm.parse_time(args.test_duration).total_seconds(), + monitor_interval_ms=monitor_interval, + created_cx=genconnections, + output_format=output, + compared_report=compared_rept, + script_name='test_generic', + arguments=args, + debug=args.debug) + + generic_test.stop() + time.sleep(30) + generic_test.cleanup(station_list) + if generic_test.passes(): + generic_test.exit_success() + + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ip_connection.py b/lanforge/lanforge-scripts/py-scripts/test_ip_connection.py new file mode 100755 index 000000000..0d7c21964 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ip_connection.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python3 +""" +NAME: test_ip_connection.py +This script combines functionality of test_ipv4_connection.py and test_ipv6_connection.py. +test_ipv4_connection.py and test_ipv6_connection.py are located in py-scripts/scripts_deprecated + +PURPOSE: +test_ip_connection.py will create stations and attempt to connect to an SSID. WPA, WPA2, WPA3, WEP, and Open connection types are supported + +Script for creating a variable number of stations and attempting to connect them to an SSID. +A test will run to verify stations are associated and get an IP, if these conditions are both true, the test will +pass, otherwise, the test will fail. + +EXAMPLE: +./test_ip_connection.py --upstream_port eth1 --radio wiphy0 --num_stations 3 --security open --ssid netgear --passwd BLANK --debug + ./test_ip_connection.py --upstream_port eth1 --ipv6 --radio wiphy0 --num_stations 3 --proxy --security {open|wep|wpa|wpa2|wpa3} + --ssid netgear --passwd admin123 --mode 1 --ap "00:0e:8e:78:e1:76" --test_id --timeout 120 --debug + +Use './test_ip_connection.py' --help to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import argparse +import time +import pprint + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base") +LFCliBase = lfcli_base.LFCliBase +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class ConnectTest(LFCliBase): + def __init__(self, + _ssid=None, + _security=None, + _password=None, + _host=None, + _port=None, + _sta_list=None, + _number_template="00000", + _radio="wiphy0", + _proxy_str=None, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False, + _ap=None, + _ipv6=False, + _mode=0, + _num_stations=0, + _timeout=120): + super().__init__(_host, + _port, + _proxy_str=_proxy_str, + _local_realm=realm.Realm(lfclient_host=_host, + lfclient_port=_port, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail, + _proxy_str=_proxy_str, + debug_=_debug_on), + _debug=_debug_on, + _exit_on_fail=_exit_on_fail) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.sta_list = _sta_list + self.radio = _radio + self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.ap = _ap + self.mode = _mode + self.ipv6 = _ipv6 + self.num_stations = _num_stations + + self.station_profile = self.local_realm.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + if self.debug: + print("----- Station List ----- ----- ----- ----- ----- ----- \n") + pprint.pprint(self.sta_list) + print("---- ~Station List ----- ----- ----- ----- ----- ----- \n") + + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + def start(self, sta_list, print_pass, print_fail): + self.station_profile.admin_up() + associated_map = {} + ip_map = {} + print("Starting test...") + for sec in range(self.timeout): + for sta_name in sta_list: + shelf = self.local_realm.name_to_eid(sta_name)[0] + resource = self.local_realm.name_to_eid(sta_name)[1] + name = self.local_realm.name_to_eid(sta_name)[2] + if self.ipv6: + url = "port/%s/%s/%s?fields=port,alias,ipv6+address,ap" % (shelf, resource, name) + else: + url = "port/%s/%s/%s?fields=port,alias,ip,ap" % (shelf, resource, name) + sta_status = self.json_get(url, debug_=self.debug) + if self.debug: + print(sta_status) + if sta_status is None or sta_status['interface'] is None or sta_status['interface']['ap'] is None: + continue + + if (len(sta_status['interface']['ap']) == 17) and (sta_status['interface']['ap'][-3] == ':'): + associated_map[sta_name] = 1 + if self.debug: + if self.ipv6: + print("Associated", sta_name, sta_status['interface']['ap'], sta_status['interface']['ipv6 address']) + else: + print("Associated", sta_name, sta_status['interface']['ap'], sta_status['interface']['ip']) + + if self.ipv6: + if sta_status['interface']['ipv6 address'] != 'DELETED' and \ + not sta_status['interface']['ipv6 address'].startswith('fe80') \ + and sta_status['interface']['ipv6 address'] != 'AUTO': + ip_map[sta_name] = 1 + if self.debug: + print("IPv6 address:", sta_name, sta_status['interface']['ap'], + sta_status['interface']['ipv6 address']) + else: + if sta_status['interface']['ip'] != '0.0.0.0': + ip_map[sta_name] = 1 + if self.debug: + print("IP", sta_name, sta_status['interface']['ap'], sta_status['interface']['ip']) + + if (len(sta_list) == len(ip_map)) and (len(sta_list) == len(associated_map)): + break + else: + time.sleep(1) + + if self.debug: + print("sta_list", len(sta_list), sta_list) + print("ip_map", len(ip_map), ip_map) + print("associated_map", len(associated_map), associated_map) + if (len(sta_list) == len(ip_map)) and (len(sta_list) == len(associated_map)): + self._pass("PASS: All stations associated with IP", print_pass) + else: + self._fail("FAIL: Not all stations able to associate/get IP", print_fail) + print("sta_list", sta_list) + print("ip_map", ip_map) + print("associated_map", associated_map) + + return self.passes() + + def stop(self): + # Bring stations down + self.station_profile.admin_down() + + def cleanup(self, sta_list): + self.station_profile.cleanup(sta_list, debug_=self.debug) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=sta_list, + debug=self.debug) + time.sleep(1) + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='test_ip_connection.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create stations that attempt to authenticate, associate, and receive IP addresses on the + chosen SSID + ''', + + description='''\ + test_ip_connection.py +-------------------------------------- +Generic ipv6 command example: +python3 ./test_ip_connection.py + --upstream_port eth1 + --radio wiphy0 + --num_stations 3 + --ipv6 + --proxy + --security {open|wep|wpa|wpa2|wpa3} + --ssid netgear + --passwd admin123 + --mode 1 + --ap "00:0e:8e:78:e1:76" + --test_id + --timeout 120 + --debug + +Generic ipv4 command example: +./test_ip_connection.py + --upstream_port eth1 + --radio wiphy0 + --num_stations 3 + --security open + --ssid netgear + --passwd BLANK + --debug''') + + required = None + for agroup in parser._action_groups: + if agroup.title == "required arguments": + required = agroup + # if required is not None: + + optional = None + for agroup in parser._action_groups: + if agroup.title == "optional arguments": + optional = agroup + + if optional is not None: + optional.add_argument("--ipv6", help="Use ipv6 connections instead of ipv4", action="store_true", default=False) + optional.add_argument("--ap", help="Add BSSID of access point to connect to") + optional.add_argument('--mode', help=LFCliBase.Help_Mode) + optional.add_argument('--timeout', + help='--timeout sets the length of time to wait until a connection is successful', + default=30) + + args = parser.parse_args() + + if (args.radio is None): + raise ValueError("--radio required") + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + station_list = LFUtils.port_name_series(prefix="sta", + start_id=0, + end_id=num_sta - 1, + padding_number=10000, + radio=args.radio) + if args.debug: + print("args.proxy: %s" % args.proxy) + ip_test = ConnectTest(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _sta_list=station_list, + _radio=args.radio, + _proxy_str=args.proxy, + _debug_on=args.debug, + _ipv6=args.ipv6, + _ap=args.ap, + _mode=args.mode, + _timeout=args.timeout) + + ip_test.cleanup(station_list) + ip_test.build() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + ip_test.add_event(name="test_ip_connection.py", message=ip_test.get_fail_message()) + ip_test.exit_fail() + ip_test.start(station_list, False, False) + ip_test.stop() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + ip_test.add_event(name="test_ip_connection.py", message=ip_test.get_fail_message()) + ip_test.exit_fail() + time.sleep(30) + ip_test.cleanup(station_list) + if ip_test.passes(): + ip_test.add_event(name="test_ip_connection.py", message="Full test passed, all stations associated and got IP") + ip_test.exit_success() + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ip_variable_time.py b/lanforge/lanforge-scripts/py-scripts/test_ip_variable_time.py new file mode 100755 index 000000000..246a0b1c3 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ip_variable_time.py @@ -0,0 +1,520 @@ +#!/usr/bin/env python3 +""" +NAME: test_ip_variable_time.py + +PURPOSE: +test_ip_variable_time.py will create stations and endpoints to generate and verify layer-3 traffic over ipv4 or ipv6. +This script replaces the functionality of test_ipv4_variable_time.py and test_ipv6_variable_time.py +This Script has two working modes: + Mode 1: + When station is not available, + + This script will create a variable number of stations each with their own set of cross-connects and endpoints. + It will then create layer 3 traffic over a specified amount of time, testing for increased traffic at regular intervals. + This test will pass if all stations increase traffic over the full test duration. + + Mode 2: + + When station is already available This script will create layer3 cross-connects and endpoints It will then + create layer 3 traffic over a specified amount of time, testing for increased traffic at regular intervals. + This test will pass if all stations increase traffic over the full test duration. + +Use './test_ip_variable_time.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" +import sys +import os +import importlib +import argparse +import datetime + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + + +sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) + +LFUtils = importlib.import_module("py-json.LANforge.LFUtils") +realm = importlib.import_module("py-json.realm") +Realm = realm.Realm + + +class IPVariableTime(Realm): + def __init__(self, + ssid=None, + security=None, + password=None, + sta_list=[], + create_sta=True, + name_prefix=None, + upstream=None, + radio=None, + host="localhost", + port=8080, + mode=0, + ap=None, + traffic_type=None, + side_a_min_rate=56, side_a_max_rate=0, + side_b_min_rate=56, side_b_max_rate=0, + number_template="00000", + test_duration="5m", + use_ht160=False, + ipv6=False, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(lfclient_host=host, + lfclient_port=port), + self.upstream = upstream + self.host = host + self.port = port + self.ssid = ssid + self.sta_list = sta_list + self.create_sta = create_sta + self.security = security + self.password = password + self.radio = radio + self.mode = mode + self.ap = ap + self.traffic_type = traffic_type + self.number_template = number_template + self.debug = _debug_on + # self.json_post("/cli-json/set_resource", { + # "shelf":1, + # "resource":all, + # "max_staged_bringup": 30, + # "max_trying_ifup": 15, + # "max_station_bringup": 6 + # }) + self.name_prefix = name_prefix + self.test_duration = test_duration + self.station_profile = self.new_station_profile() + self.cx_profile = self.new_l3_cx_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.debug = self.debug + + self.station_profile.use_ht160 = use_ht160 + if self.station_profile.use_ht160: + self.station_profile.mode = 9 + self.station_profile.mode = mode + if self.ap is not None: + self.station_profile.set_command_param("add_sta", "ap", self.ap) + + self.cx_profile.host = self.host + self.cx_profile.port = self.port + self.ipv6 = ipv6 + self.cx_profile.name_prefix = self.name_prefix + self.cx_profile.side_a_min_bps = side_a_min_rate + self.cx_profile.side_a_max_bps = side_a_max_rate + self.cx_profile.side_b_min_bps = side_b_min_rate + self.cx_profile.side_b_max_bps = side_b_max_rate + + def start(self, print_pass=False, print_fail=False): + if self.create_sta: + self.station_profile.admin_up() + # to-do- check here if upstream port got IP + temp_stas = self.station_profile.station_names.copy() + + if self.wait_for_ip(temp_stas, ipv4=not self.ipv6, ipv6=self.ipv6): + self._pass("All stations got IPs") + else: + self._fail("Stations failed to get IPs") + self.exit_fail() + self.cx_profile.start_cx() + + def stop(self): + self.cx_profile.stop_cx() + if self.create_sta: + self.station_profile.admin_down() + + def pre_cleanup(self): + self.cx_profile.cleanup_prefix() + if self.create_sta: + for sta in self.sta_list: + self.rm_port(sta, check_exists=True) + + def cleanup(self): + self.cx_profile.cleanup() + if self.create_sta: + self.station_profile.cleanup() + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=self.station_profile.station_names, + debug=self.debug) + + def build(self): + if self.create_sta: + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + self.cx_profile.create(endp_type=self.traffic_type, side_a=self.sta_list, + side_b=self.upstream, + sleep_time=0) + + +def main(): + parser = Realm.create_basic_argparse( + prog='test_ip_variable_time.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create stations to test connection and traffic on VAPs of varying security types (WEP, WPA, WPA2, WPA3, Open) + over ipv4 or ipv6 + ''', + description='''\ +test_ip_variable_time.py: +-------------------- +Generic command layout: + +python3 ./test_ip_variable_time.py + --upstream_port eth1 + --radio wiphy0 + --num_stations 32 + --security {open|wep|wpa|wpa2|wpa3} + --mode 1 + {"auto" : "0", + "a" : "1", + "b" : "2", + "g" : "3", + "abg" : "4", + "abgn" : "5", + "bgn" : "6", + "bg" : "7", + "abgnAC" : "8", + "anAC" : "9", + "an" : "10", + "bgnAC" : "11", + "abgnAX" : "12", + "bgnAX" : "13"} + --ssid netgear + --password admin123 + --test_duration 2m (default) + --monitor_interval_ms + --a_min 3000 + --b_min 1000 + --ap "00:0e:8e:78:e1:76" + --output_format csv + --traffic_type lf_udp + --report_file ~/Documents/results.csv (Example of csv file output - please use another extension for other file formats) + --compared_report ~/Documents/results_prev.csv (Example of csv file retrieval - please use another extension for other file formats) - UNDER CONSTRUCTION + --layer3_cols 'name','tx bytes','rx bytes','dropped' (column names from the GUI to print on report - please read below to know what to put here according to preferences) + --port_mgr_cols 'ap','ip' (column names from the GUI to print on report - please read below to know what to put here according to preferences) + --debug + + python3 ./test_ip_variable_time.py + --upstream_port eth1 (upstream Port) + --traffic_type lf_udp (traffic type, lf_udp | lf_tcp) + --test_duration 5m (duration to run traffic 5m --> 5 Minutes) + --create_sta False (False, means it will not create stations and use the sta_names specified below) + --sta_names sta000,sta001,sta002 (used if --create_sta False, comma separated names of stations) + + +=============================================================================== + ** FURTHER INFORMATION ** + Using the layer3_cols flag: + + Currently the output function does not support inputting the columns in layer3_cols the way they are displayed in the GUI. This quirk is under construction. To output + certain columns in the GUI in your final report, please match the according GUI column display to it's counterpart to have the columns correctly displayed in + your report. + + GUI Column Display Layer3_cols argument to type in (to print in report) + + Name | 'name' + EID | 'eid' + Run | 'run' + Mng | 'mng' + Script | 'script' + Tx Rate | 'tx rate' + Tx Rate (1 min) | 'tx rate (1 min)' + Tx Rate (last) | 'tx rate (last)' + Tx Rate LL | 'tx rate ll' + Rx Rate | 'rx rate' + Rx Rate (1 min) | 'rx rate (1 min)' + Rx Rate (last) | 'rx rate (last)' + Rx Rate LL | 'rx rate ll' + Rx Drop % | 'rx drop %' + Tx PDUs | 'tx pdus' + Tx Pkts LL | 'tx pkts ll' + PDU/s TX | 'pdu/s tx' + Pps TX LL | 'pps tx ll' + Rx PDUs | 'rx pdus' + Rx Pkts LL | 'pps rx ll' + PDU/s RX | 'pdu/s tx' + Pps RX LL | 'pps rx ll' + Delay | 'delay' + Dropped | 'dropped' + Jitter | 'jitter' + Tx Bytes | 'tx bytes' + Rx Bytes | 'rx bytes' + Replays | 'replays' + TCP Rtx | 'tcp rtx' + Dup Pkts | 'dup pkts' + Rx Dup % | 'rx dup %' + OOO Pkts | 'ooo pkts' + Rx OOO % | 'rx ooo %' + RX Wrong Dev | 'rx wrong dev' + CRC Fail | 'crc fail' + RX BER | 'rx ber' + CX Active | 'cx active' + CX Estab/s | 'cx estab/s' + 1st RX | '1st rx' + CX TO | 'cx to' + Pattern | 'pattern' + Min PDU | 'min pdu' + Max PDU | 'max pdu' + Min Rate | 'min rate' + Max Rate | 'max rate' + Send Buf | 'send buf' + Rcv Buf | 'rcv buf' + CWND | 'cwnd' + TCP MSS | 'tcp mss' + Bursty | 'bursty' + A/B | 'a/b' + Elapsed | 'elapsed' + Destination Addr | 'destination addr' + Source Addr | 'source addr' + ''') + + parser.add_argument('--mode', help='Used to force mode of stations') + parser.add_argument('--ap', help='Used to force a connection to a particular AP') + parser.add_argument('--traffic_type', help='Select the Traffic Type [lf_udp, lf_tcp, udp, tcp], type will be ' + 'adjusted automatically between ipv4 and ipv6 based on use of --ipv6 flag' + , required=True) + parser.add_argument('--output_format', help='choose either csv or xlsx') + parser.add_argument('--report_file', help='where you want to store results', default=None) + parser.add_argument('--a_min', help='--a_min bps rate minimum for side_a', default=256000) + parser.add_argument('--b_min', help='--b_min bps rate minimum for side_b', default=256000) + parser.add_argument('--test_duration', help='--test_duration sets the duration of the test', default="2m") + parser.add_argument('--layer3_cols', help='Columns wished to be monitored from layer 3 endpoint tab', + default=['name', 'tx bytes', 'rx bytes', 'tx rate', 'rx rate']) + parser.add_argument('--port_mgr_cols', help='Columns wished to be monitored from port manager tab', + default=['ap', 'ip', 'parent dev']) + parser.add_argument('--compared_report', help='report path and file which is wished to be compared with new report', + default=None) + parser.add_argument('--monitor_interval', + help='how frequently do you want your monitor function to take measurements; \, 35s, 2h', + default='10s') + parser.add_argument('--ipv6', help='Sets the test to use IPv6 traffic instead of IPv4', action='store_true') + parser.add_argument('--influx_host') + parser.add_argument('--influx_token', help='Username for your Influx database') + parser.add_argument('--influx_bucket', help='Password for your Influx database') + parser.add_argument('--influx_org', help='Name of your Influx database') + parser.add_argument('--influx_port', help='Port where your influx database is located', default=8086) + parser.add_argument('--influx_tag', action='append', nargs=2, + help='--influx_tag Can add more than one of these.') + parser.add_argument('--influx_mgr', + help='IP address of the server your Influx database is hosted if different from your LANforge Manager', + default=None) + parser.add_argument('--create_sta', help='Used to force a connection to a particular AP', default=True) + parser.add_argument('--sta_names', help='Used to force a connection to a particular AP', default="sta0000") + args = parser.parse_args() + create_sta = True + if args.create_sta == "False": + create_sta = False + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_sta = int(args.num_stations) + + # Create directory + + # if file path with output file extension is not given... + # check if home/lanforge/report-data exists. if not, save + # in new folder based in current file's directory + + if args.report_file is None: + new_file_path = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-h-%M-m-%S-s")).replace(':', + '-') + '_test_ip_variable_time' # create path name + try: + path = os.path.join('/home/lanforge/report-data/', new_file_path) + os.mkdir(path) + except: + curr_dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + path = os.path.join(curr_dir_path, new_file_path) + os.mkdir(path) + systeminfopath = str(path) + '/systeminfo.txt' + + if args.output_format in ['csv', 'json', 'html', 'hdf', 'stata', 'pickle', 'pdf', 'png', 'parquet', + 'xlsx']: + report_f = str(path) + '/data.' + args.output_format + output = args.output_format + else: + print( + 'Not supporting this report format or cannot find report format provided. Defaulting to csv data file ' + 'output type, naming it data.csv.') + report_f = str(path) + '/data.csv' + output = 'csv' + + else: + systeminfopath = str(args.report_file).split('/')[-1] + report_f = args.report_file + if args.output_format is None: + output = str(args.report_file).split('.')[-1] + else: + output = args.output_format + print("IP Test Report Data: {}".format(report_f)) + + # Retrieve last data file + compared_rept = None + if args.compared_report: + compared_report_format = args.compared_report.split('.')[-1] + # if compared_report_format not in ['csv', 'json', 'dta', 'pkl','html','xlsx','parquet','h5']: + if compared_report_format != 'csv': + print(ValueError("Cannot process this file type. Please select a different file and re-run script.")) + exit(1) + else: + compared_rept = args.compared_report + + if create_sta: + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=num_sta - 1, padding_number_=10000, + radio=args.radio) + else: + station_list = args.sta_names.split(",") + + CX_TYPES = ("tcp", "udp", "lf_tcp", "lf_udp") + + if (args.traffic_type is None) or (args.traffic_type not in CX_TYPES): + print("cx_type needs to be lf_tcp, lf_udp, tcp, or udp, bye") + exit(1) + + if args.ipv6: + if args.traffic_type == "tcp" or args.traffic_type == "lf_tcp": + args.traffic_type = "lf_tcp6" + if args.traffic_type == "udp" or args.traffic_type == "lf_udp": + args.traffic_type = "lf_udp6" + else: + if args.traffic_type == "tcp": + args.traffic_type = "lf_tcp" + if args.traffic_type == "udp": + args.traffic_type = "lf_udp" + + ip_var_test = IPVariableTime(host=args.mgr, + port=args.mgr_port, + number_template="0000", + sta_list=station_list, + create_sta=create_sta, + name_prefix="VT", + upstream=args.upstream_port, + ssid=args.ssid, + password=args.passwd, + radio=args.radio, + security=args.security, + test_duration=args.test_duration, + use_ht160=False, + side_a_min_rate=args.a_min, + side_b_min_rate=args.b_min, + mode=args.mode, + ap=args.ap, + ipv6=args.ipv6, + traffic_type=args.traffic_type, + _debug_on=args.debug) + + ip_var_test.pre_cleanup() + + ip_var_test.build() + # exit() + if create_sta: + if not ip_var_test.passes(): + print(ip_var_test.get_fail_message()) + ip_var_test.exit_fail() + + try: + layer3connections = ','.join([[*x.keys()][0] for x in ip_var_test.json_get('endp')['endpoint']]) + except: + raise ValueError('Try setting the upstream port flag if your device does not have an eth1 port') + + if type(args.layer3_cols) is not list: + layer3_cols = list(args.layer3_cols.split(",")) + # send col names here to file to reformat + else: + layer3_cols = args.layer3_cols + # send col names here to file to reformat + if type(args.port_mgr_cols) is not list: + port_mgr_cols = list(args.port_mgr_cols.split(",")) + # send col names here to file to reformat + else: + port_mgr_cols = args.port_mgr_cols + # send col names here to file to reformat + if args.debug: + print("Layer 3 Endp column names are...") + print(layer3_cols) + print("Port Manager column names are...") + print(port_mgr_cols) + + print("Layer 3 Endp column names are...") + print(layer3_cols) + print("Port Manager column names are...") + print(port_mgr_cols) + + try: + monitor_interval = Realm.parse_time(args.monitor_interval).total_seconds() + except ValueError as error: + print(str(error)) + print(ValueError( + "The time string provided for monitor_interval argument is invalid. Please see supported time stamp increments and inputs for monitor_interval in --help. ")) + exit(1) + ip_var_test.start(False, False) + + # if args.influx_mgr is None: + # manager = args.mgr + # else: + # manager = args.influx_mgr + + if args.influx_org is not None: + from InfluxRequest import RecordInflux + grapher = RecordInflux(_influx_host=args.influx_host, + _influx_port=args.influx_port, + _influx_org=args.influx_org, + _influx_token=args.influx_token, + _influx_bucket=args.influx_bucket) + devices = [station.split('.')[-1] for station in station_list] + tags = dict() + tags['script'] = 'test_ip_variable_time' + try: + for k in args.influx_tag: + tags[k[0]] = k[1] + except: + pass + grapher.monitor_port_data(longevity=Realm.parse_time(args.test_duration).total_seconds(), + devices=devices, + monitor_interval=Realm.parse_time(args.monitor_interval).total_seconds(), + tags=tags) + + ip_var_test.cx_profile.monitor(layer3_cols=layer3_cols, + sta_list=station_list, + # port_mgr_cols=port_mgr_cols, + report_file=report_f, + systeminfopath=systeminfopath, + duration_sec=Realm.parse_time(args.test_duration).total_seconds(), + monitor_interval_ms=monitor_interval, + created_cx=layer3connections, + output_format=output, + compared_report=compared_rept, + script_name='test_ip_variable_time', + arguments=args, + debug=args.debug) + + ip_var_test.stop() + if create_sta: + if not ip_var_test.passes(): + print(ip_var_test.get_fail_message()) + ip_var_test.exit_fail() + LFUtils.wait_until_ports_admin_up(port_list=station_list) + + if ip_var_test.passes(): + ip_var_test.success() + ip_var_test.cleanup() + print("IP Variable Time Test Report Data: {}".format(report_f)) + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ipv4_connection.py b/lanforge/lanforge-scripts/py-scripts/test_ipv4_connection.py new file mode 100755 index 000000000..733044e61 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ipv4_connection.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 + +""" +NAME: test_ipv4_connection.py + +PURPOSE: +test_ipv4_connection.py will create stations and attempt to connect to an SSID. WPA, WPA2, WPA3, WEP, and Open connection types are supported + +Script for creating a variable number of stations and attempting to connect them to an SSID. +A test will run to verify stations are associated and get an IP, if these conditions are both true, the test will +pass, otherwise, the test will fail. + +EXAMPLE: +./test_ipv4_connection.py --upstream_port eth1 --radio wiphy0 --num_stations 3 --security open --ssid netgear --passwd BLANK --debug + +Use './test_ipv4_connection.py' --help to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys +import os +import argparse + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) +import LANforge +from LANforge.lfcli_base import LFCliBase +from LANforge import LFUtils +import realm +import time +import pprint + + +class IPv4Test(LFCliBase): + def __init__(self, + _ssid=None, + _security=None, + _password=None, + _host=None, + _port=None, + _sta_list=None, + _number_template="00000", + _radio="wiphy0", + _proxy_str=None, + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(_host, + _port, + _proxy_str=_proxy_str, + _local_realm=realm.Realm(lfclient_host=_host, + lfclient_port=_port, + _exit_on_error=_exit_on_error, + _exit_on_fail=_exit_on_fail, + _proxy_str=_proxy_str, + debug_=_debug_on), + _debug=_debug_on, + _exit_on_fail=_exit_on_fail) + self.host = _host + self.port = _port + self.ssid = _ssid + self.security = _security + self.password = _password + self.sta_list = _sta_list + self.radio = _radio + self.timeout = 120 + self.number_template = _number_template + self.debug = _debug_on + self.station_profile = self.local_realm.new_station_profile() + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + if self.debug: + print("----- Station List ----- ----- ----- ----- ----- ----- \n") + pprint.pprint(self.sta_list) + print("---- ~Station List ----- ----- ----- ----- ----- ----- \n") + + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + def start(self, sta_list, print_pass, print_fail): + self.station_profile.admin_up() + associated_map = {} + ip_map = {} + print("Starting test...") + for sec in range(self.timeout): + for sta_name in sta_list: + eidn = self.local_realm.name_to_eid(sta_name) + url = "/port/1/%s/%s" % (eidn[1], eidn[2]) + sta_status = self.json_get(url + "?fields=port,alias,ip,ap", debug_=self.debug) + if self.debug: + print(sta_status) + try: + if (sta_status is None) or (sta_status['interface'] is None) or (sta_status['interface']['ap'] is None): + continue + except: + continue + if (len(sta_status['interface']['ap']) == 17) and (sta_status['interface']['ap'][-3] == ':'): + if self.debug: + print("Associated", sta_name, sta_status['interface']['ap'], sta_status['interface']['ip']) + associated_map[sta_name] = 1 + if sta_status['interface']['ip'] != '0.0.0.0': + if self.debug: + print("IP", sta_name, sta_status['interface']['ap'], sta_status['interface']['ip']) + ip_map[sta_name] = 1 + if (len(sta_list) == len(ip_map)) and (len(sta_list) == len(associated_map)): + break + else: + time.sleep(1) + + if self.debug: + print("sta_list", len(sta_list), sta_list) + print("ip_map", len(ip_map), ip_map) + print("associated_map", len(associated_map), associated_map) + if (len(sta_list) == len(ip_map)) and (len(sta_list) == len(associated_map)): + self._pass("PASS: All stations associated with IP", print_pass) + else: + self._fail("FAIL: Not all stations able to associate/get IP", print_fail) + print("sta_list", sta_list) + print("ip_map", ip_map) + print("associated_map", associated_map) + + return self.passes() + + def stop(self): + # Bring stations down + self.station_profile.admin_down() + + def cleanup(self, sta_list): + self.station_profile.cleanup(sta_list, debug_=self.debug) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, + port_list=sta_list, + debug=self.debug) + time.sleep(1) + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='test_ipv4_connection.py', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create stations that attempt to authenticate, associate, and receive IP addresses on the + chosen SSID + ''', + + description='''\ + test_ipv4_connection.py +-------------------- +Command example: +./test_ipv4_connection.py + --upstream_port eth1 + --radio wiphy0 + --num_stations 3 + --security open + --ssid netgear + --passwd BLANK + --debug + ''') + required = parser.add_argument_group('required arguments') + #required.add_argument('--security', help='WiFi Security protocol: < open | wep | wpa | wpa2 | wpa3 >', required=True) + + args = parser.parse_args() + #if args.debug: + # pprint.pprint(args) + # time.sleep(5) + if (args.radio is None): + raise ValueError("--radio required") + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + station_list = LFUtils.port_name_series(prefix="sta", + start_id=0, + end_id=num_sta-1, + padding_number=10000, + radio=args.radio) + if args.debug: + print("args.proxy: %s" % args.proxy) + ip_test = IPv4Test(_host=args.mgr, + _port=args.mgr_port, + _ssid=args.ssid, + _password=args.passwd, + _security=args.security, + _sta_list=station_list, + _radio=args.radio, + _proxy_str=args.proxy, + _debug_on=args.debug) + + ip_test.cleanup(station_list) + ip_test.build() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + ip_test.add_event(name="test_ipv4_connection.py", message=ip_test.get_fail_message()) + ip_test.exit_fail() + ip_test.start(station_list, False, False) + ip_test.stop() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + ip_test.add_event(name="test_ipv4_connection.py", message=ip_test.get_fail_message()) + ip_test.exit_fail() + time.sleep(30) + ip_test.cleanup(station_list) + if ip_test.passes(): + ip_test.add_event(name="test_ipv4_connection.py", message="Full test passed, all stations associated and got IP") + ip_test.exit_success() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4.py b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4.py new file mode 100755 index 000000000..5cea2831c --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 + +""" +NAME: test_ipv4_l4.py + +PURPOSE: +test_ipv4_l4.py will create stations and endpoints to generate and verify layer-4 traffic + +This script will monitor the bytes-rd attribute of the endpoints. If the the monitored value does not continually increase, this test will not pass. + +EXAMPLE: +./test_ipv4_l4.py --upstream_port eth1 (optional) --radio wiphy0 (required) --num_stations 3 (optional) + --security {open|wep|wpa|wpa2|wpa3} (required) --ssid netgear (required) + --url "dl http://10.40.0.1 /dev/null" (required) --password admin123 (required) + --test_duration 2m (optional) --debug (optional) + +Use './test_ipv4_l4.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append('../py-json') + +import argparse +from LANforge.lfcli_base import LFCliBase +from LANforge.LFUtils import * +from LANforge import LFUtils +import argparse +import realm +import time +import datetime + + +class IPV4L4(LFCliBase): + def __init__(self, host, port, ssid, security, password, url, + station_list, + number_template="00000", radio="wiphy0", + test_duration="5m", upstream_port="eth1", + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.radio = radio + self.upstream_port = upstream_port + self.ssid = ssid + self.security = security + self.password = password + self.url = url + self.number_template = number_template + self.sta_list = station_list + self.test_duration = test_duration + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_l4_cx_profile() + + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + + self.cx_profile.url = self.url + + def __compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + if len(old_list) == len(new_list): + for item, value in old_list.items(): + expected_passes += 1 + if new_list[item] > old_list[item]: + passes += 1 + # print(item, old_list[item], new_list[item], passes, expected_passes) + + if passes == expected_passes: + return True + else: + return False + else: + return False + + def __get_values(self): + time.sleep(1) + cx_list = self.json_get("layer4/list?fields=name,bytes-rd", debug_=self.debug) + # print("==============\n", cx_list, "\n==============") + cx_map = {} + for cx_name in cx_list['endpoint']: + if cx_name != 'uri' and cx_name != 'handler': + for item, value in cx_name.items(): + for value_name, value_rx in value.items(): + if item in self.cx_profile.created_cx.keys() and value_name == 'bytes-rd': + cx_map[item] = value_rx + return cx_map + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None) + + def start(self, print_pass=False, print_fail=False): + temp_stas = self.station_profile.station_names.copy() + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(temp_stas): + self._pass("All stations got IPs", print_pass) + else: + self._fail("Stations failed to get IPs", print_fail) + exit(1) + cur_time = datetime.datetime.now() + old_rx_values = self.__get_values() + end_time = self.local_realm.parse_time(self.test_duration) + cur_time + self.cx_profile.start_cx() + passes = 0 + expected_passes = 0 + print("Starting Test...") + while cur_time < end_time: + interval_time = cur_time + datetime.timedelta(minutes=1) + while cur_time < interval_time: + cur_time = datetime.datetime.now() + time.sleep(1) + + new_rx_values = self.__get_values() + # print(old_rx_values, new_rx_values) + # print("\n-----------------------------------") + # print(cur_time, end_time, cur_time + datetime.timedelta(minutes=1)) + # print("-----------------------------------\n") + expected_passes += 1 + if self.__compare_vals(old_rx_values, new_rx_values): + passes += 1 + else: + self._fail("FAIL: Not all stations increased traffic", print_fail) + break + old_rx_values = new_rx_values + cur_time = datetime.datetime.now() + if passes == expected_passes: + self._pass("PASS: All tests passes", print_pass) + + def stop(self): + self.cx_profile.stop_cx() + self.station_profile.admin_down() + + def cleanup(self, sta_list): + self.cx_profile.cleanup() + self.station_profile.cleanup(sta_list) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, + debug=self.debug) + + +def main(): + lfjson_host = "localhost" + lfjson_port = 8080 + + parser = LFCliBase.create_basic_argparse( + prog='test_generic.py', + # formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create layer-4 endpoints and test that the bytes-rd from the chosen URL are increasing over the + duration of the test + ''', + + description='''\ + test_ipv4_l4.py + -------------------- + Generic command layout: + python ./test_ipv4_l4.py --upstream_port --radio --debug + + Command Line Example: + python3 ./test_ipv4_l4.py + --upstream_port eth1 (optional) + --radio wiphy0 (required) + --num_stations 3 (optional) + --security {open|wep|wpa|wpa2|wpa3} (required) + --ssid netgear (required) + --url "dl http://10.40.0.1 /dev/null" (required) + --password admin123 (required) + --test_duration 2m (optional) + --debug (optional) + + ''') + + parser.add_argument('--test_duration', help='--test_duration sets the duration of the test', default="5m") + parser.add_argument('--url', help='--url specifies upload/download, address, and dest', + default="dl http://10.40.0.1 /dev/null") + + args = parser.parse_args() + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + station_list = LFUtils.portNameSeries(prefix_="sta", + start_id_=0, + end_id_=num_sta - 1, + padding_number_=10000, + radio=args.radio) + + ip_test = IPV4L4(host=args.mgr, port=args.mgr_port, + ssid=args.ssid, + radio=args.radio, + password=args.passwd, + security=args.security, + station_list=station_list, + url=args.url, + test_duration=args.test_duration, + upstream_port=args.upstream_port, + _debug_on=args.debug) + + ip_test.cleanup(station_list) + ip_test.build() + print('Stations built') + if not ip_test.passes(): + print(ip_test.get_fail_message()) + ip_test.exit_fail() + print('Starting Stations') + ip_test.start(False, False) + print('Stopping Stations') + ip_test.stop() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + ip_test.exit_fail() + time.sleep(30) + ip_test.cleanup(station_list) + if ip_test.passes(): + print("Full test passed, all endpoints had increased bytes-rd throughout test duration") + ip_test.exit_success() + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_upload.py b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_upload.py new file mode 100755 index 000000000..d437007b9 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_upload.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 + +""" +NAME: test_ipv4_l4_ftp_upload.py + +PURPOSE: +test_ipv4_l4_ftp_upload.py will create stations and endpoints to generate and verify layer-4 upload traffic over an ftp connection + +This script will monitor the bytes-wr attribute of the endpoints. If the the monitored value does not continually increase, this test will not pass. + +EXAMPLE: + ./test_ipv4_l4_ftp_upload.py --upstream_port eth1 --radio wiphy0 --num_stations 3 --security {open|wep|wpa|wpa2|wpa3} + --ssid netgear --passwd admin123 --test_duration 2m --url "ul ftp://10.40.0.1 /dev/null" --requests_per_ten 600 + --debug + +Use './test_ipv4_l4_ftp_upload.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append('../py-json') + +import argparse +from LANforge.lfcli_base import LFCliBase +from LANforge.LFUtils import * +from LANforge import LFUtils +import realm +import time +import datetime + + +class IPV4L4(LFCliBase): + def __init__(self, host, port, ssid, security, password, url, requests_per_ten, station_list, number_template="00000", + upstream_port="eth1", radio="wiphy0", + test_duration="5m", + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.ssid = ssid + self.radio = radio + self.upstream_port = upstream_port + self.security = security + self.password = password + self.url = url + self.requests_per_ten = requests_per_ten + self.number_template = number_template + self.sta_list = station_list + self.test_duration = test_duration + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_l4_cx_profile() + + self.station_profile.lfclient_url = self.lfclient_url + print("##### station_profile.lfclient_url: {}".format(self.station_profile.lfclient_url)) + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + + self.cx_profile.url = self.url + self.cx_profile.requests_per_ten = self.requests_per_ten + + self.port_util = realm.PortUtils(self.local_realm) + + def __compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + if len(old_list) == len(new_list): + for item, value in old_list.items(): + expected_passes += 1 + if new_list[item] > old_list[item]: + passes += 1 + # print(item, old_list[item], new_list[item], passes, expected_passes) + + if passes == expected_passes: + return True + else: + return False + else: + return False + + def __get_values(self): + time.sleep(1) + cx_list = self.json_get("layer4/list?fields=name,bytes-rd", debug_=self.debug) + # print("==============\n", cx_list, "\n==============") + cx_map = {} + for cx_name in cx_list['endpoint']: + if cx_name != 'uri' and cx_name != 'handler': + for item, value in cx_name.items(): + for value_name, value_rx in value.items(): + if item in self.cx_profile.created_cx.keys() and value_name == 'bytes-wr': + cx_map[item] = value_rx + return cx_map + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5, debug_=self.debug, suppress_related_commands_=True) + + def start(self, print_pass=False, print_fail=False): + self.port_util.set_ftp(port_name=self.local_realm.name_to_eid(self.upstream_port)[2], resource=1, on=True) + temp_stas = self.sta_list.copy() + self.station_profile.admin_up() + # temp_stas.append(self.local_realm.name_to_eid(self.upstream_port)[2]) + if self.local_realm.wait_for_ip(temp_stas): + self._pass("All stations got IPs", print_pass) + else: + self._fail("Stations failed to get IPs", print_fail) + exit(1) + cur_time = datetime.datetime.now() + old_rx_values = self.__get_values() + end_time = self.local_realm.parse_time(self.test_duration) + cur_time + self.cx_profile.start_cx() + passes = 0 + expected_passes = 0 + print("Starting Test...") + while cur_time < end_time: + interval_time = cur_time + datetime.timedelta(minutes=1) + while cur_time < interval_time: + cur_time = datetime.datetime.now() + time.sleep(1) + + new_rx_values = self.__get_values() + # print(old_rx_values, new_rx_values) + # print("\n-----------------------------------") + # print(cur_time, end_time, cur_time + datetime.timedelta(minutes=1)) + # print("-----------------------------------\n") + expected_passes += 1 + if self.__compare_vals(old_rx_values, new_rx_values): + passes += 1 + else: + self._fail("FAIL: Not all stations increased traffic", print_fail) + break + old_rx_values = new_rx_values + cur_time = datetime.datetime.now() + if passes == expected_passes: + self._pass("PASS: All tests passes", print_pass) + + def stop(self): + self.port_util.set_ftp(port_name=self.local_realm.name_to_eid(self.upstream_port)[2], resource=1, on=False) + self.cx_profile.stop_cx() + self.station_profile.admin_down() + + def cleanup(self, sta_list): + self.cx_profile.cleanup() + self.station_profile.cleanup(sta_list) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, + debug=self.debug) + + +def main(): + lfjson_port = 8080 + + parser = LFCliBase.create_basic_argparse( + prog='test_ipv4_l4_ftp', + # formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create layer-4 endpoints to an ftp server and test that the bytes-wr are increasing over the duration + of the test + ''', + + description='''\ + test_ipv4_l4_ftp_upload.py +-------------------- +Generic command example: +./test_ipv4_l4_ftp_upload.py --upstream_port eth1 \\ + --radio wiphy0 \\ + --num_stations 3 \\ + --security {open|wep|wpa|wpa2|wpa3} \\ + --ssid netgear \\ + --passwd admin123 \\ + --test_duration 2m \\ + --url "ul ftp://10.40.0.1 /dev/null" \\ + --requests_per_ten 600 \\ + --debug + ''') + + parser.add_argument('--test_duration', help='--test_duration sets the duration of the test', default="5m") + parser.add_argument('--requests_per_ten', help='--requests_per_ten number of request per ten minutes', default=600) + parser.add_argument('--url', help='--url specifies upload/download, address, and dest', + default="ul ftp://10.40.0.1 /dev/null") + + args = parser.parse_args() + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=num_sta-1, padding_number_=10000, + radio=args.radio) + + ip_test = IPV4L4(host=args.mgr, port=args.mgr_port, + ssid=args.ssid, + password=args.passwd, + security=args.security, + station_list=station_list, + url=args.url, + test_duration=args.test_duration, + requests_per_ten=args.requests_per_ten, + _debug_on=args.debug, + upstream_port=args.upstream_port) + ip_test.cleanup(station_list) + ip_test.build() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + exit(1) + ip_test.start(False, False) + ip_test.stop() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + exit(1) + time.sleep(30) + ip_test.cleanup(station_list) + if ip_test.passes(): + print("Full test passed, all endpoints had increased bytes-rd throughout test duration") + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_urls_per_ten.py b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_urls_per_ten.py new file mode 100755 index 000000000..893cf00a9 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_urls_per_ten.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 + +""" +NAME: test_ipv4_l4_ftp_urls_per_ten.py + +PURPOSE: +test_ipv4_l4_ftp_urls_per_ten.py will create stations and endpoints to generate and verify layer-4 traffic over an ftp connection + +This script will monitor the urls/s attribute of the endpoints. If the the monitored value does not continually increase, this test will not pass. + +EXAMPLE: + ./test_ipv4_l4_ftp_urls_per_ten.py --upstream_port eth1 --radio wiphy0 --num_stations 3 --security {open|wep|wpa|wpa2|wpa3} + --ssid netgear --passwd admin123 --test_duration 2m --interval 1s --mode 1 --ap "00:0e:8e:78:e1:76" --requests_per_ten 600 + --num_tests 1 --url "ul ftp://lanforge:lanforge@10.40.0.1/example.txt /home/lanforge/example.txt" --debug + +Use './test_ipv4_l4_ftp_urls_per_ten.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys +import os +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + +import argparse +from LANforge.lfcli_base import LFCliBase +from LANforge.LFUtils import * +from LANforge import LFUtils +import realm +import time +import datetime + + +class IPV4L4(LFCliBase): + def __init__(self, ssid, security, password, url, requests_per_ten, station_list,test_duration="2m",host="localhost", port=8080, + number_template="00000", num_tests=1, radio="wiphy0", mode=0, ap=None, + _debug_on=False, upstream_port="eth1", + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, + port, + _debug=_debug_on, + _local_realm = realm.Realm(lfclient_host=host, lfclient_port=port), + _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.radio = radio + self.upstream_port = upstream_port + self.ssid = ssid + self.security = security + self.password = password + self.mode = mode + self.ap=ap + self.url = url + self.requests_per_ten = requests_per_ten + self.number_template = number_template + self.sta_list = station_list + self.num_tests = num_tests + self.target_requests_per_ten = requests_per_ten + self.test_duration = test_duration + + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_l4_cx_profile() + + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = self.mode + if self.ap is not None: + self.station_profile.set_command_param("add_sta", "ap",self.ap) + self.cx_profile.url = self.url + print(self.cx_profile.url) + self.cx_profile.requests_per_ten = self.requests_per_ten + + self.port_util = realm.PortUtils(self.local_realm) + + def __check_request_rate(self): + endp_list = self.json_get("layer4/list?fields=urls/s") + expected_passes = 0 + passes = 0 + if endp_list is not None and endp_list['endpoint'] is not None: + endp_list = endp_list['endpoint'] + for item in endp_list: + for name, info in item.items(): + if name in self.cx_profile.created_cx.keys(): + expected_passes += 1 + if info['urls/s'] * self.requests_per_ten >= self.target_requests_per_ten * .9: + # print(name, info['urls/s'], info['urls/s'] * self.requests_per_ten, self.target_requests_per_ten * .9) + passes += 1 + + return passes == expected_passes + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5, debug_=self.debug, suppress_related_commands_=None) + + def start(self, print_pass=False, print_fail=False): + self.port_util.set_ftp(port_name=self.local_realm.name_to_eid(self.upstream_port)[2], resource=1, on=True) + temp_stas = self.sta_list.copy() + + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(temp_stas): + self._pass("All stations got IPs", print_pass) + else: + self._fail("Stations failed to get IPs", print_fail) + exit(1) + self.cx_profile.start_cx() + print("Starting test") + curr_time = datetime.datetime.now() + end_time = self.local_realm.parse_time(self.test_duration) + curr_time + sleep_interval = self.local_realm.parse_time(self.test_duration) // 5 + passes = 0 + expected_passes = 0 + for test in range(self.num_tests): + expected_passes += 1 + while curr_time < end_time: + time.sleep(sleep_interval.total_seconds()) + curr_time = datetime.datetime.now() + + if self.cx_profile.check_errors(self.debug): + if self.__check_request_rate(): + passes += 1 + else: + self._fail("FAIL: Request rate did not exceed 90% target rate", print_fail) + break + else: + self._fail("FAIL: Errors found getting to %s " % self.url, print_fail) + break + if passes == expected_passes: + self._pass("PASS: All tests passes", print_pass) + + def stop(self): + self.cx_profile.stop_cx() + self.port_util.set_ftp(port_name=self.local_realm.name_to_eid(self.upstream_port)[2], resource=1, on=False) + self.station_profile.admin_down() + + def cleanup(self, sta_list): + self.cx_profile.cleanup() + self.station_profile.cleanup(sta_list) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, + debug=self.debug) + + +def main(): + lfjson_port = 8080 + parser = LFCliBase.create_basic_argparse( + prog='test_ipv4_l4_urls_per_ten', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create layer-4 endpoints to connect to an ftp server and test that urls/s are meeting or exceeding the target rate + ''', + + description='''\ + test_ipv4_l4_ftp_urls_per_ten.py +-------------------- +Generic command example: +python3 ./test_ipv4_l4_ftp_urls_per_ten.py --upstream_port eth1 \\ + --radio wiphy0 \\ + --num_stations 3 \\ + --security {open|wep|wpa|wpa2|wpa3} \\ + --ssid netgear \\ + --passwd admin123 \\ + --test_duration 2m \\ {2m | 30s | 3h | 1d ...etc} + --interval 1s \\ + --mode 1 + {"auto" : "0", + "a" : "1", + "b" : "2", + "g" : "3", + "abg" : "4", + "abgn" : "5", + "bgn" : "6", + "bg" : "7", + "abgnAC" : "8", + "anAC" : "9", + "an" : "10", + "bgnAC" : "11", + "abgnAX" : "12", + "bgnAX" : "13", + --ap "00:0e:8e:78:e1:76" + --requests_per_ten 600 \\ + --num_tests 1 \\ + --url "ul ftp://lanforge:lanforge@10.40.0.1/example.txt /home/lanforge/example.txt" + --debug + ''') + optional = parser.add_argument_group('optional arguments') + required = parser.add_argument_group('required arguments') + required.add_argument('--security', help='WiFi Security protocol: < open | wep | wpa | wpa2 | wpa3 >', required=True) + parser.add_argument('--requests_per_ten', help='--requests_per_ten number of request per ten minutes', default=600) + parser.add_argument('--test_duration', help='--test duration of a single test', default=600) + parser.add_argument('--num_tests', help='--num_tests number of tests to run. Each test runs 10 minutes', default=1) + parser.add_argument('--url', help='--url specifies upload/download, address, and dest', + default="dl http://10.40.0.1 /dev/null") + optional.add_argument('--mode',help='Used to force mode of stations') + optional.add_argument('--ap',help='Used to force a connection to a particular AP') + #parser.add_argument('--target_per_ten', help='--target_per_ten target number of request per ten minutes. test will check for 90 percent of this value', + #default=600) + args = parser.parse_args() + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=num_sta-1, padding_number_=10000, + radio=args.radio) + + ip_test = IPV4L4(host=args.mgr, port=args.mgr_port, + ssid=args.ssid, + password=args.passwd, + upstream_port=args.upstream_port, + radio= args.radio, + security=args.security, + station_list=station_list, + url=args.url, + mode=args.mode, + ap=args.ap, + num_tests=args.num_tests, + requests_per_ten=args.requests_per_ten, + test_duration=args.test_duration, + _debug_on=args.debug) + ip_test.cleanup(station_list) + ip_test.build() + ip_test.start() + ip_test.stop() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + exit(1) + time.sleep(30) + ip_test.cleanup(station_list) + if ip_test.passes(): + print("Full test passed, all endpoints met or exceeded 90 percent of the target rate") + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_wifi.py b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_wifi.py new file mode 100755 index 000000000..bb94ac0cb --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_ftp_wifi.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 + +""" +NAME: test_ipv4_l4_ftp_wifi.py + +PURPOSE: +test_ipv4_l4_ftp_wifi.py will create stations and endpoints to generate and verify layer-4 traffic over an ftp connection + +This script will monitor the bytes-wr attribute of the endpoints. If the the monitored value does not continually increase, this test will not pass. + +EXAMPLE: +./test_ipv4_l4_ftp_wifi.py --upstream_port eth1 --radio wiphy0 --num_stations 3 --security {open|wep|wpa|wpa2|wpa3} + --ssid netgear --passwd admin123 --dest 10.40.0.1 --test_duration 2m --interval 1s --requests_per_ten 600 + --dest /var/www/html/data_slug_4K.bin --source /tmp/data_slug_4K.bin --ftp_user lanforge --ftp_passwd lanforge + --debug + +Use './test_ipv4_l4_ftp_wifi.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append('../py-json') + +import argparse +from LANforge.lfcli_base import LFCliBase +from LANforge.LFUtils import * +from LANforge import LFUtils +import realm +import time +import datetime + + +class IPV4L4(LFCliBase): + def __init__(self, host, port, ssid, security, password, requests_per_ten, station_list, number_template="00000", + upstream_port="eth1", radio="wiphy0", ftp_user="localhost", ftp_passwd="localhost", + source="", dest="", + test_duration="5m", + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.ssid = ssid + self.radio = radio + self.upstream_port = upstream_port + self.security = security + self.password = password + self.requests_per_ten = requests_per_ten + self.number_template = number_template + self.sta_list = station_list + self.test_duration = test_duration + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_http_profile() + + self.station_profile.lfclient_url = self.lfclient_url + self.ftp_user = ftp_user + self.ftp_passwd = ftp_passwd + self.source = source + self.dest = dest + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + + self.cx_profile.requests_per_ten = self.requests_per_ten + + self.port_util = realm.PortUtils(self.local_realm) + + def __compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + if len(old_list) == len(new_list): + for item, value in old_list.items(): + expected_passes += 1 + if new_list[item] > old_list[item]: + passes += 1 + # print(item, old_list[item], new_list[item], passes, expected_passes) + + if passes == expected_passes: + return True + else: + return False + else: + return False + + def __get_values(self): + time.sleep(1) + cx_list = self.json_get("layer4/list?fields=name,bytes-wr", debug_=self.debug) + # print("==============\n", cx_list, "\n==============") + cx_map = {} + for cx_name in cx_list['endpoint']: + if cx_name != 'uri' and cx_name != 'handler': + for item, value in cx_name.items(): + for value_name, value_rx in value.items(): + if item in self.cx_profile.created_cx.keys() and value_name == 'bytes-wr': + cx_map[item] = value_rx + return cx_map + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(self.sta_list): + self._pass("All stations got IPs") + else: + self._fail("Stations failed to get IPs") + exit(1) + self.cx_profile.direction = "ul" + self.cx_profile.dest = self.dest + self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5, debug_=self.debug, + suppress_related_commands_=True, ftp=True, user=self.ftp_user, passwd=self.ftp_passwd, + source=self.source) + + def start(self, print_pass=False, print_fail=False): + print("Starting Test...") + self.cx_profile.start_cx() + cur_time = datetime.datetime.now() + old_rx_values = self.__get_values() + end_time = self.local_realm.parse_time(self.test_duration) + cur_time + passes = 0 + expected_passes = 0 + while cur_time < end_time: + interval_time = cur_time + datetime.timedelta(minutes=1) + while cur_time < interval_time: + cur_time = datetime.datetime.now() + time.sleep(1) + + new_rx_values = self.__get_values() + # print(old_rx_values, new_rx_values) + # print("\n-----------------------------------") + # print(cur_time, end_time, cur_time + datetime.timedelta(minutes=1)) + # print("-----------------------------------\n") + expected_passes += 1 + if self.__compare_vals(old_rx_values, new_rx_values): + passes += 1 + else: + self._fail("FAIL: Not all stations increased traffic", print_fail) + break + old_rx_values = new_rx_values + cur_time = datetime.datetime.now() + if passes == expected_passes: + self._pass("PASS: All tests passes", print_pass) + + def stop(self): + self.cx_profile.stop_cx() + self.station_profile.admin_down() + + def cleanup(self, sta_list): + self.cx_profile.cleanup() + self.station_profile.cleanup(sta_list) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, + debug=self.debug) + + +def main(): + lfjson_port = 8080 + + parser = LFCliBase.create_basic_argparse( + prog='test_ipv4_l4_ftp_wifi.py', + # formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create layer-4 endpoints and test that the bytes-wr from the chosen URL are increasing over the + duration of the test + ''', + + description='''\ + test_ipv4_l4_ftp_wifi.py +-------------------- +Generic command example: +python3 ./test_ipv4_l4_ftp_wifi.py --upstream_port eth1 \\ + --radio wiphy0 \\ + --num_stations 3 \\ + --security {open|wep|wpa|wpa2|wpa3} \\ + --ssid netgear \\ + --passwd admin123 \\ + --dest 10.40.0.1 \\ + --test_duration 2m \\ + --interval 1s \\ + --requests_per_ten 600 \\ + --dest /var/www/html/data_slug_4K.bin \\ + --source /tmp/data_slug_4K.bin \\ + --ftp_user lanforge \\ + --ftp_passwd lanforge \\ + --debug + ''') + + parser.add_argument('--test_duration', help='--test_duration sets the duration of the test', default="5m") + parser.add_argument('--requests_per_ten', help='--requests_per_ten number of request per ten minutes', default=600) + parser.add_argument('--dest', help='--dest specifies the destination for the file', default="/var/www/html/data_slug_4K.bin") + parser.add_argument('--source', help='--source specifies the source of the file', + default="/tmp/data_slug_4K.bin") + parser.add_argument('--ftp_user', help='--ftp_user sets the username to be used for ftp', default="lanforge") + parser.add_argument('--ftp_passwd', help='--ftp_user sets the password to be used for ftp', default="lanforge") + + args = parser.parse_args() + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=num_sta-1, padding_number_=10000, + radio=args.radio) + + ip_test = IPV4L4(host=args.mgr, port=args.mgr_port, + ssid=args.ssid, + password=args.passwd, + security=args.security, + station_list=station_list, + test_duration=args.test_duration, + requests_per_ten=args.requests_per_ten, + _debug_on=args.debug, + upstream_port=args.upstream_port, + ftp_user=args.ftp_user, + ftp_passwd=args.ftp_passwd, + dest=args.dest, + source=args.source) + ip_test.cleanup(station_list) + ip_test.build() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + exit(1) + ip_test.start(False, False) + ip_test.stop() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + exit(1) + time.sleep(30) + ip_test.cleanup(station_list) + if ip_test.passes(): + print("Full test passed, all endpoints had increased bytes-wr throughout test duration") + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_urls_per_ten.py b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_urls_per_ten.py new file mode 100755 index 000000000..8c2bc0897 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_urls_per_ten.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 + +""" +NAME: test_ipv4_l4_urls_per_ten.py + +PURPOSE: +test_ipv4_l4_urls_per_ten.py will create stations and endpoints to generate and verify layer-4 traffic + +This script will monitor the urls/s attribute of the endpoints. If the the monitored value does not continually increase, this test will not pass. + +EXAMPLE: +./test_ipv4_l4_urls_per_ten.py --upstream_port eth1 --radio wiphy0 --num_stations 3 --security {open|wep|wpa|wpa2|wpa3} + --ssid netgear --passwd admin123 --requests_per_ten 600 --mode 1 --num_tests 1 --url "dl http://10.40.0.1 /dev/null" + --ap "00:0e:8e:78:e1:76" --target_per_ten 600 --output_format csv --report_file ~/Documents/results.csv --test_duration 2m + --debug + + +Use './test_ipv4_l4_urls_per_ten.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys +import os +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append(os.path.join(os.path.abspath('..'), 'py-json')) + +import argparse +from LANforge.lfcli_base import LFCliBase +from LANforge import LFUtils +import realm +import time +import datetime +from realm import TestGroupProfile + + +class IPV4L4(LFCliBase): + def __init__(self, + host="localhost", + port=8080, + ssid=None, + security=None, + password=None, + url=None, + requests_per_ten=None, + station_list=None, + test_duration="2m", + ap=None, + mode=0, + target_requests_per_ten=60, + number_template="00000", + num_tests=1, + radio="wiphy0", + _debug_on=False, + upstream_port="eth1", + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + + self.host = host + self.port = port + self.radio = radio + self.upstream_port = upstream_port + self.ssid = ssid + self.security = security + self.password = password + self.url = url + self.mode=mode + self.ap=ap + self.debug=_debug_on + self.requests_per_ten = int(requests_per_ten) + self.number_template = number_template + self.test_duration=test_duration + self.sta_list = station_list + self.num_tests = int(num_tests) + self.target_requests_per_ten = int(target_requests_per_ten) + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.l4cxprofile=realm.L4CXProfile(lfclient_host=host, + lfclient_port=port,local_realm=self.local_realm) + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_l4_cx_profile() + + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = self.mode + if self.ap is not None: + self.station_profile.set_command_param("add_sta", "ap",self.ap) + + self.cx_profile.url = self.url + self.cx_profile.requests_per_ten = self.requests_per_ten + + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5, debug_=self.debug, suppress_related_commands_=None) + + def start(self, print_pass=False, print_fail=False): + temp_stas = self.sta_list.copy() + # temp_stas.append(self.local_realm.name_to_eid(self.upstream_port)[2]) + + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(temp_stas): + self._pass("All stations got IPs", print_pass) + else: + self._fail("Stations failed to get IPs", print_fail) + exit(1) + self.cx_profile.start_cx() + print("Starting test...") + + def stop(self): + self.cx_profile.stop_cx() + self.station_profile.admin_down() + + def cleanup(self, sta_list): + self.cx_profile.cleanup() + self.station_profile.cleanup(sta_list) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, + debug=self.debug) + + +def main(): + parser = LFCliBase.create_basic_argparse( + prog='test_ipv4_l4_urls_per_ten', + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create layer-4 endpoints to connect to a url and test that urls/s are meeting or exceeding the target rate + ''', + description='''\ + test_ipv4_l4_urls_per_ten.py: +-------------------- +Generic command example: +python3 ./test_ipv4_l4_urls_per_ten.py + --upstream_port eth1 \\ + --radio wiphy0 \\ + --num_stations 3 \\ + --security {open|wep|wpa|wpa2|wpa3} \\ + --ssid netgear \\ + --passwd admin123 \\ + --requests_per_ten 600 \\ + --mode 1 + {"auto" : "0", + "a" : "1", + "b" : "2", + "g" : "3", + "abg" : "4", + "abgn" : "5", + "bgn" : "6", + "bg" : "7", + "abgnAC" : "8", + "anAC" : "9", + "an" : "10", + "bgnAC" : "11", + "abgnAX" : "12", + "bgnAX" : "13"} \\ + --num_tests 1 \\ + --url "dl http://10.40.0.1 /dev/null" \\ + --ap "00:0e:8e:78:e1:76" + --target_per_ten 600 \\ + --output_format csv \\ + --report_file ~/Documents/results.csv \\ + --test_duration 2m \\ + --debug + ''') + required = None + for agroup in parser._action_groups: + if agroup.title == "required arguments": + required = agroup + #if required is not None: + + optional = None + for agroup in parser._action_groups: + if agroup.title == "optional arguments": + optional = agroup + + if optional is not None: + optional.add_argument('--requests_per_ten', help='--requests_per_ten number of request per ten minutes', default=600) + optional.add_argument('--num_tests', help='--num_tests number of tests to run. Each test runs 10 minutes', default=1) + optional.add_argument('--url', help='--url specifies upload/download, address, and dest',default="dl http://10.40.0.1 /dev/null") + optional.add_argument('--test_duration', help='duration of test',default="2m") + optional.add_argument('--target_per_ten', help='--target_per_ten target number of request per ten minutes. test will check for 90 percent this value',default=600) + optional.add_argument('--mode',help='Used to force mode of stations') + optional.add_argument('--ap',help='Used to force a connection to a particular AP') + optional.add_argument('--report_file',help='where you want to store results') + optional.add_argument('--output_format', help='choose csv or xlsx') #update once other forms are completed + + args = parser.parse_args() + + num_sta = 2 + if (args.num_stations is not None) and (int(args.num_stations) > 0): + num_stations_converted = int(args.num_stations) + num_sta = num_stations_converted + if args.report_file is None: + if args.output_format in ['csv','json','html','hdf','stata','pickle','pdf','parquet','png','df','xlsx']: + output_form=args.output_format.lower() + print("Defaulting file output placement to /home/lanforge.") + rpt_file='/home/data.' + output_form + else: + print("Defaulting data file output type to Excel") + rpt_file='/home/lanforge/data.xlsx' + output_form='xlsx' + + else: + rpt_file=args.report_file + if args.output_format is None: + output_form=str(args.report_file).split('.')[-1] + else: + output_form=args.output_format + + + #Create directory + if args.report_file is None: + try: + homedir = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-%M")).replace(':','-')+'test_ipv4_l4_urls_per_ten' + path = os.path.join('/home/lanforge/report-data/',homedir) + os.mkdir(path) + except: + path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + print('Saving file to local directory') + else: + pass + + if args.report_file is None: + if args.output_format in ['csv','json','html','hdf','stata','pickle','pdf','png','df','parquet','xlsx']: + rpt_file=path+'/data.' + args.output_format + output=args.output_format + else: + print('Defaulting data file output type to Excel') + rpt_file=path+'/data.xlsx' + output='xlsx' + else: + rpt_file=args.report_file + if args.output_format is None: + output=str(args.report_file).split('.')[-1] + else: + output=args.output_format + + + station_list = LFUtils.portNameSeries(prefix_="sta", start_id_=0, end_id_=num_sta-1, padding_number_=10000, + radio=args.radio) + + ip_test = IPV4L4(host=args.mgr, port=args.mgr_port, + ssid=args.ssid, + password=args.passwd, + radio=args.radio, + upstream_port=args.upstream_port, + security=args.security, + station_list=station_list, + url=args.url, + mode=args.mode, + ap=args.ap, + _debug_on=args.debug, + test_duration=args.test_duration, + num_tests=args.num_tests, + target_requests_per_ten=args.target_per_ten, + requests_per_ten=args.requests_per_ten) + ip_test.cleanup(station_list) + ip_test.build() + ip_test.start() + + try: + layer4traffic=','.join([[*x.keys()][0] for x in ip_test.local_realm.json_get('layer4')['endpoint']]) + except: + pass + ip_test.l4cxprofile.monitor(col_names=['bytes-rd', 'urls/s'], + report_file=rpt_file, + duration_sec=ip_test.local_realm.parse_time(args.test_duration).total_seconds(), + created_cx=layer4traffic, + output_format=output_form, + script_name='test_ipv4_l4_urls_per_ten', + arguments=args, + debug=args.debug) + ip_test.stop() + if not ip_test.passes(): + print(ip_test.get_fail_message()) + exit(1) + time.sleep(30) + ip_test.cleanup(station_list) + if ip_test.passes(): + print("Full test passed, all endpoints met or exceeded 90 percent of the target rate") + + +if __name__ == "__main__": + main() diff --git a/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_wifi.py b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_wifi.py new file mode 100755 index 000000000..b4e342f00 --- /dev/null +++ b/lanforge/lanforge-scripts/py-scripts/test_ipv4_l4_wifi.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 + +""" +NAME: test_ipv4_l4_wifi.py + +PURPOSE: +test_ipv4_l4_wifi.py will create stations and endpoints to generate and verify layer-4 upload traffic + +This script will monitor the bytes-rd attribute of the endpoints. If the the monitored value does not continually increase, this test will not pass. + +EXAMPLE: +./test_ipv4_l4_wifi.py --upstream_port eth1 --radio wiphy0 --num_stations 3 --security open --ssid netgear + --passwd admin123 --test_duration 2m --requests_per_ten 600 --direction {ul | dl} + --dest /dev/null (or 10.40.0.1) --debug + +Use './test_ipv4_l4_wifi.py --help' to see command line usage and options +Copyright 2021 Candela Technologies Inc +License: Free to distribute and modify. LANforge systems must be licensed. +""" + +import sys + +if sys.version_info[0] != 3: + print("This script requires Python 3") + exit(1) + +if 'py-json' not in sys.path: + sys.path.append('../py-json') + +import argparse +from LANforge.lfcli_base import LFCliBase +from LANforge.LFUtils import * +from LANforge import LFUtils +import argparse +import realm +import time +import datetime + + +class IPV4L4(LFCliBase): + def __init__(self, host, port, ssid, security, password, requests_per_ten, station_list, + number_template="00000", radio="wiphy0", direction="dl", dest="/dev/null", + test_duration="5m", upstream_port="eth1", + _debug_on=False, + _exit_on_error=False, + _exit_on_fail=False): + super().__init__(host, port, _debug=_debug_on, _exit_on_fail=_exit_on_fail) + self.host = host + self.port = port + self.radio = radio + self.upstream_port = upstream_port + self.ssid = ssid + self.direction = direction + self.dest = dest + self.security = security + self.password = password + self.requests_per_ten = requests_per_ten + self.number_template = number_template + self.sta_list = station_list + self.test_duration = test_duration + + self.local_realm = realm.Realm(lfclient_host=self.host, lfclient_port=self.port) + self.station_profile = self.local_realm.new_station_profile() + self.cx_profile = self.local_realm.new_http_profile() + + self.station_profile.lfclient_url = self.lfclient_url + self.station_profile.ssid = self.ssid + self.station_profile.ssid_pass = self.password, + self.station_profile.security = self.security + self.station_profile.number_template_ = self.number_template + self.station_profile.mode = 0 + + self.cx_profile.requests_per_ten = self.requests_per_ten + + def __compare_vals(self, old_list, new_list): + passes = 0 + expected_passes = 0 + if len(old_list) == len(new_list): + for item, value in old_list.items(): + expected_passes += 1 + if new_list[item] > old_list[item]: + passes += 1 + + if passes == expected_passes: + return True + else: + return False + else: + return False + + def __get_values(self): + time.sleep(1) + cx_list = self.json_get("layer4/list?fields=name,bytes-rd", debug_=self.debug) + if self.debug: + print("==============\n", cx_list, "\n==============") + cx_map = {} + for cx_name in cx_list['endpoint']: + if cx_name != 'uri' and cx_name != 'handler': + for item, value in cx_name.items(): + for value_name, value_rx in value.items(): + if item in self.cx_profile.created_cx.keys() and value_name == 'bytes-rd': + cx_map[item] = value_rx + return cx_map + + def build(self): + # Build stations + self.station_profile.use_security(self.security, self.ssid, self.password) + self.station_profile.set_number_template(self.number_template) + print("Creating stations") + self.station_profile.set_command_flag("add_sta", "create_admin_down", 1) + self.station_profile.set_command_param("set_port", "report_timer", 1500) + self.station_profile.set_command_flag("set_port", "rpt_timer", 1) + self.station_profile.create(radio=self.radio, sta_names_=self.sta_list, debug=self.debug) + self._pass("PASS: Station build finished") + + self.station_profile.admin_up() + if self.local_realm.wait_for_ip(self.sta_list): + self._pass("All stations got IPs") + else: + self._fail("Stations failed to get IPs") + self.cx_profile.direction = self.direction + self.cx_profile.dest = self.dest + self.cx_profile.create(ports=self.station_profile.station_names, sleep_time=.5, debug_=self.debug, + suppress_related_commands_=None, http=True) + + def start(self, print_pass=False, print_fail=False): + cur_time = datetime.datetime.now() + old_rx_values = self.__get_values() + end_time = self.local_realm.parse_time(self.test_duration) + cur_time + self.cx_profile.start_cx() + passes = 0 + expected_passes = 0 + print("Starting Test...") + while cur_time < end_time: + interval_time = cur_time + datetime.timedelta(minutes=1) + while cur_time < interval_time: + cur_time = datetime.datetime.now() + time.sleep(1) + + new_rx_values = self.__get_values() + if self.debug: + print(old_rx_values, new_rx_values) + print("\n-----------------------------------") + print(cur_time, end_time, cur_time + datetime.timedelta(minutes=1)) + print("-----------------------------------\n") + expected_passes += 1 + if self.__compare_vals(old_rx_values, new_rx_values): + passes += 1 + else: + self._fail("FAIL: Not all stations increased traffic", print_fail) + break + old_rx_values = new_rx_values + cur_time = datetime.datetime.now() + if passes == expected_passes: + self._pass("PASS: All tests passes", print_pass) + + def stop(self): + self.cx_profile.stop_cx() + for sta_name in self.sta_list: + data = LFUtils.port_down_request(1, self.local_realm.name_to_eid(sta_name)[2]) + url = "cli-json/set_port" + self.json_post(url, data) + + def cleanup(self, sta_list): + self.cx_profile.cleanup() + self.station_profile.cleanup(sta_list) + LFUtils.wait_until_ports_disappear(base_url=self.lfclient_url, port_list=sta_list, + debug=self.debug) + + +def main(): + lfjson_host = "localhost" + lfjson_port = 8080 + + parser = LFCliBase.create_basic_argparse( + prog='test_ipv4_l4_wifi.py', + # formatter_class=argparse.RawDescriptionHelpFormatter, + formatter_class=argparse.RawTextHelpFormatter, + epilog='''\ + Create layer-4 endpoints and test that the bytes-rd from the chosen URL are increasing over the + duration of the test + ''', + + description='''\ + test_ipv4_l4_wifi.py: +-------------------- +Generic command example: +python3 ./test_ipv4_l4_wifi.py --upstream_port eth1 \\ + --radio wiphy0 \\ + --num_stations 3 \\ + --security {open|wep|wpa|wpa2|wpa3} \\ + --ssid netgear \\ + --passwd admin123 \\ + --test_duration 2m \\ + --requests_per_ten 600 \\ + --direction {ul | dl} \\ + --dest /dev/null (or 10.40.0.1)\\ + --debug + ''') + + parser.add_argument('--test_duration', help='--test_duration sets the duration of the test', default="5m") + parser.add_argument('--requests_per_ten', help='--requests_per_ten number of request per ten minutes', default=600) + parser.add_argument('--direction', help='--direction