Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
[![CodeQL](https://github.com/google/testrun/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/google/testrun/actions/workflows/github-code-scanning/codeql)
[![Testrun test suite](https://github.com/google/testrun/actions/workflows/testing.yml/badge.svg?branch=main&event=push)](https://github.com/google/testrun/actions/workflows/testing.yml)

<strong>Disclaimer</strong>: Testrun uses Google Analytics to learn about how our users use Testrun. By installing and running Testrun, you understand and accept the Terms of Service found [here](https://policies.google.com/technologies/partner-sites).

## Introduction :wave:
Testrun automates specific test cases to verify network and security functionality in IoT devices. It is an open source tool which allows manufacturers of IP capable devices to test their devices for the purposes of Device Qualification within the BOS program.

Expand Down
7 changes: 4 additions & 3 deletions docs/get_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ However, to achieve a compliant test outcome, your device must be configured cor
- Connect one USB Ethernet adapter to the internet source (e.g., router or switch) using an ethernet cable.
- Connect the other USB Ethernet adapter directly to the IoT device you want to test using an ethernet cable.

**NOTE: The device under test should be powered off until prompted**

**NOTE: Both adapters should be disabled in the host system (IPv4, IPv6 and general). You can do this by going to Settings > Network**
Some things to remember:
- The device under test should be powered off until prompted
- Both adapters should be disabled in the host system (IPv4, IPv6 and general). You can do this by going to Settings > Network
- Struggling to identify the correct interfaces? See [this guide](network/identify_interfaces.md).

2. Start Testrun.

Expand Down
5 changes: 3 additions & 2 deletions docs/network/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

## Table of Contents
1) Network Overview (this page)
2) [Addresses](addresses.md)
3) [Add a new network service](add_new_service.md)
2) [How to identify network interfaces](identify_interfaces.md)
3) [Addresses](addresses.md)
4) [Add a new network service](add_new_service.md)

Test Run provides several built-in network services that can be utilized for testing purposes. These services are already available and can be used without any additional configuration.

Expand Down
18 changes: 18 additions & 0 deletions docs/network/identify_interfaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Identifying network interfaces

For Testrun to operate correctly, you must select the correct network interfaces within the settings panel of the user interface. There are 2 methods to identify the correct network interfaces:

A) Find the printed MAC address on your interface

Some USB network interfaces will have the MAC address printed on the interface itself. This will look something like: ```00:e0:4c:02:0f:a8```.

Compare this printed MAC address against the MAC address provided in the settings panel in the user interface.

B) Connect your interfaces one at a time

1) Ensure both interfaces are disconnected from your PC and open the settings panel in the user interface.

2) Connect your internet interface to your PC and refresh the settings panel. One interface, which was not previously present, should now be visibile.

3) Repeat the previous step for the devices interface.

68 changes: 39 additions & 29 deletions framework/python/src/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Manages the Testrun API"""
"""Provides Testrun data via REST API."""

from fastapi import FastAPI, APIRouter, Response, Request, status
from fastapi.responses import FileResponse
Expand Down Expand Up @@ -39,7 +39,7 @@
DEFAULT_DEVICE_INTF = "enx123456789123"

LATEST_RELEASE_CHECK = ("https://api.github.com/repos/google/" +
"testrun/releases/latest")
"testrun/releases/latest")

class Api:
"""Provide REST endpoints to manage Testrun"""
Expand Down Expand Up @@ -126,14 +126,17 @@ def stop(self):

async def get_sys_interfaces(self):
addrs = psutil.net_if_addrs()
ifaces = []
for iface in addrs:
ifaces = {}

# pylint: disable=consider-using-dict-items
for key in addrs.keys():
nic = addrs[key]

# Ignore any interfaces that are not ethernet
if not (iface.startswith("en") or iface.startswith("eth")):
if not (key.startswith("en") or key.startswith("eth")):
continue

ifaces.append(iface)
ifaces[key] = nic[0].address

return ifaces

Expand Down Expand Up @@ -255,32 +258,38 @@ async def get_version(self, response: Response):
json_response["installed_version"] = "v" + current_version

# Check latest version number from GitHub API
version_check = requests.get(LATEST_RELEASE_CHECK, timeout=5)
try:
version_check = requests.get(LATEST_RELEASE_CHECK, timeout=5)

# Check OK response was received
if version_check.status_code != 200:
response.status_code = 500
LOGGER.error(version_check.content)
return self._generate_msg(False, "Failed to fetch latest version")

# Extract version number from response, removing the leading 'v'
latest_version_no = version_check.json()["name"].strip("v")
LOGGER.debug(f"Latest version available is {latest_version_no}")

# Craft JSON response
json_response["latest_version"] = "v" + latest_version_no
json_response["latest_version_url"] = version_check.json()["html_url"]

# String comparison between current and latest version
if latest_version_no > current_version:
json_response["update_available"] = True
LOGGER.debug("An update is available")
else:
json_response["update_available"] = False
LOGGER.debug("The latest version is installed")

# Check OK response was received
if version_check.status_code != 200:
return json_response
except Exception as e:
response.status_code = 500
LOGGER.error(version_check.content)
LOGGER.error("Failed to fetch latest version")
LOGGER.debug(e)
return self._generate_msg(False, "Failed to fetch latest version")

# Extract version number from response, removing the leading 'v'
latest_version_no = version_check.json()["name"].strip("v")
LOGGER.debug(f"Latest version available is {latest_version_no}")

# Craft JSON response
json_response["latest_version"] = "v" + latest_version_no
json_response["latest_version_url"] = version_check.json()["html_url"]

# String comparison between current and latest version
if latest_version_no > current_version:
json_response["update_available"] = True
LOGGER.debug("An update is available")
else:
json_response["update_available"] = False
LOGGER.debug("The latest version is installed")

return json_response

async def get_reports(self, request: Request):
LOGGER.debug("Received reports list request")
# Resolve the server IP from the request so we
Expand Down Expand Up @@ -460,7 +469,7 @@ async def edit_device(self, request: Request, response: Response):
check_new_device = self._session.get_device(
device_json.get(DEVICE_MAC_ADDR_KEY))

if not check_new_device is None and (device.mac_addr
if not check_new_device is None and (device.mac_addr
!= check_new_device.mac_addr):
response.status_code = status.HTTP_409_CONFLICT
return self._generate_msg(False,
Expand All @@ -483,6 +492,7 @@ async def edit_device(self, request: Request, response: Response):
response.status_code = status.HTTP_400_BAD_REQUEST
return self._generate_msg(False, "Invalid JSON received")


async def get_report(self, response: Response,
device_name, timestamp):

Expand Down
13 changes: 11 additions & 2 deletions framework/python/src/common/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(self, config_file):
self._runtime_params = []
self._device_repository = []
self._total_tests = 0
self._report_url = None

self._version = None
self._load_version()
Expand Down Expand Up @@ -238,18 +239,23 @@ def add_total_tests(self, no_tests):
def get_total_tests(self):
return self._total_tests

def get_report_url(self):
return self._report_url

def set_report_url(self, url):
self._report_url = url

def reset(self):
self.set_status('Idle')
self.set_target_device(None)
self._report_url = None
self._total_tests = 0
self._results = []
self._started = None
self._finished = None

def to_json(self):

# TODO: Add report URL

results = {
'total': self.get_total_tests(),
'results': self.get_test_results()
Expand All @@ -263,6 +269,9 @@ def to_json(self):
'tests': results
}

if self._report_url is not None:
session_json['report'] = self.get_report_url()

return session_json

def get_timezone(self):
Expand Down
6 changes: 6 additions & 0 deletions framework/python/src/common/testreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ def get_duration(self):
def add_test(self, test):
self._results.append(test)

def set_report_url(self, url):
self._report_url = url

def get_report_url(self):
return self._report_url

def to_json(self):
report_json = {}
report_json['device'] = self._device
Expand Down
28 changes: 14 additions & 14 deletions framework/python/src/common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ def run_command(cmd, output=True):
by any return code from the process other than zero."""

success = False
process = subprocess.Popen(shlex.split(cmd),
with subprocess.Popen(shlex.split(cmd),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = process.communicate()

if process.returncode != 0 and output:
err_msg = f'{stderr.strip()}. Code: {process.returncode}'
LOGGER.error('Command failed: ' + cmd)
LOGGER.error('Error: ' + err_msg)
else:
success = True
if output:
return stdout.strip().decode('utf-8'), stderr
else:
return success
stderr=subprocess.PIPE) as process:
stdout, stderr = process.communicate()

if process.returncode != 0 and output:
err_msg = f'{stderr.strip()}. Code: {process.returncode}'
LOGGER.error('Command failed: ' + cmd)
LOGGER.error('Error: ' + err_msg)
else:
success = True
if output:
return stdout.strip().decode('utf-8'), stderr
else:
return success


def interface_exists(interface):
Expand Down
4 changes: 2 additions & 2 deletions framework/python/src/net_orc/network_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ def _attach_device_to_network(self, device):

mac_addr = TR_CONTAINER_MAC_PREFIX + '10'

util.run_command('ip link set dev ' + container_intf + ' address ' +
mac_addr)
util.run_command('ip link set dev ' + container_intf +
' address ' + mac_addr)

# Add bridge interface to device bridge
util.run_command('ovs-vsctl add-port ' + DEVICE_BRIDGE + ' ' + bridge_intf)
Expand Down
2 changes: 2 additions & 0 deletions framework/python/src/test_orc/test_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def run_test_modules(self):

self._write_reports(report)
self._test_in_progress = False
self.get_session().set_report_url(report.get_report_url())

# Move testing output from runtime to local device folder
timestamp_dir = self._timestamp_results(device)
Expand Down Expand Up @@ -240,6 +241,7 @@ def _timestamp_results(self, device):
device.mac_addr.replace(":", "")
)

# Define the directory
completed_results_dir = os.path.join(
self._root_path,
LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder),
Expand Down
3 changes: 2 additions & 1 deletion modules/test/conn/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pyOpenSSL
scapy
scapy
python-dateutil
Loading