Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
fe10e73
Implement test orchestrator (#4)
jhughesoti Apr 25, 2023
6f3a7fe
Add issue report templates (#7)
jboddey Apr 26, 2023
e05c383
Discover devices on the network (#5)
jboddey Apr 26, 2023
823709e
Test run sync (#8)
jhughesoti Apr 28, 2023
ba6afc4
Quick refactor (#9)
jboddey Apr 28, 2023
c87a976
Fix duplicate sleep calls
jhughesoti Apr 28, 2023
34ce211
Add net orc (#11)
jhughesoti May 2, 2023
ceba453
Add the DNS test module (#12)
jhughesoti May 4, 2023
0837a9c
Add baseline and pylint tests (#25)
noursaidi May 16, 2023
4171e5f
Discover devices on the network (#22)
jboddey May 16, 2023
be829a3
Build dependencies first (#21)
jboddey May 16, 2023
84d9ff9
Port scan test module (#23)
jhughesoti May 17, 2023
07432ee
Fix device configs
jboddey May 17, 2023
7b27e23
Remove unecessary files
jhughesoti May 17, 2023
5ac8726
Cleanup duplicate properties
jhughesoti May 17, 2023
2c4efe8
Cleanup install script
jhughesoti May 17, 2023
25fd8a5
Formatting (#26)
jhughesoti May 22, 2023
41aaaf7
Test results (#27)
jhughesoti May 23, 2023
ea60b41
Test results (#28)
jhughesoti May 25, 2023
b6a6cdc
Fix pylint test and skip internet tests so CI passes (#29)
noursaidi May 25, 2023
3d53ecb
Increase pylint score (#31)
jboddey May 25, 2023
b91fff5
Pylint (#32)
jhughesoti May 30, 2023
b84a026
Add license header (#36)
jhughesoti May 30, 2023
622b12d
merge main
jhughesoti May 31, 2023
38d71aa
Ovs (#35)
jhughesoti May 31, 2023
00be9cb
remove ovs files added back in during merge
jhughesoti May 31, 2023
f331239
Nmap (#38)
jhughesoti Jun 5, 2023
2a68fba
Create startup capture (#37)
jboddey Jun 5, 2023
8e8e154
Connection (#40)
jhughesoti Jun 7, 2023
6ff220b
Conn mac oui (#42)
jhughesoti Jun 8, 2023
4ca8f44
Con mac address (#43)
jhughesoti Jun 8, 2023
ff04f43
Dns (#44)
jhughesoti Jun 8, 2023
752f701
File permissions (#45)
jhughesoti Jun 9, 2023
f6e4e93
Add connection single ip test (#47)
jhughesoti Jun 12, 2023
bca0db8
Nmap results (#49)
jhughesoti Jun 15, 2023
5b56a79
Framework restructure (#50)
jboddey Jun 15, 2023
7bb9366
Ip control (#51)
jhughesoti Jun 20, 2023
b0d14c2
Move config to /local (#52)
jboddey Jun 23, 2023
94e937f
Add documentation (#53)
jboddey Jun 23, 2023
098de20
Sync dev to main (#56)
jboddey Jun 28, 2023
f185bb1
Fix missing results on udp tests when tcp ports are also defined (#59)
jhughesoti Jun 29, 2023
355c838
Add licence header (#61)
jboddey Jul 3, 2023
d374640
Resolve merge conflicts
jboddey Jul 3, 2023
8d65386
Resolve merge conflict
jboddey Jul 3, 2023
26f8c5b
Add network docs (#63)
jboddey Jul 4, 2023
4a5c1ea
Dhcp (#64)
jhughesoti Jul 5, 2023
af8367c
Dhcp (#67)
jhughesoti Jul 6, 2023
7dd5772
Add connection.dhcp_address test (#68)
jhughesoti Jul 6, 2023
9ef0d4f
Add NTP tests (#60)
jboddey Jul 12, 2023
2ae337d
Add ipv6 tests (#65)
jboddey Jul 12, 2023
59ab65f
Merge branch 'main' into dev
jboddey Jul 12, 2023
fe4bf43
Connection private address (#71)
jhughesoti Jul 13, 2023
0c550c8
fix windows line ending
jhughesoti Jul 13, 2023
2df6b4a
Fix python import
jboddey Jul 13, 2023
bfae1e0
move isc-dhcp service commands to their own class
jhughesoti Jul 13, 2023
4bdacf3
fix dhcp1
jhughesoti Jul 13, 2023
a57393b
Merge branch 'main' into dev
jboddey Jul 14, 2023
d376655
Initial CI testing for tests (#72)
noursaidi Jul 14, 2023
01761dd
Fix radvd conf
jboddey Jul 14, 2023
c23f258
Fix individual test disable
jhughesoti Jul 14, 2023
97c4c65
Merge branch 'dev' of https://github.com/auto-iot/test-run into dev
jhughesoti Jul 14, 2023
5d24497
Add NTP Pass CI test (#76)
noursaidi Jul 17, 2023
006aa11
add shared address test (#75)
jhughesoti Jul 17, 2023
153de19
Fix single ip test (#58)
jhughesoti Jul 17, 2023
e2c934e
Merge API into dev (#70)
jboddey Jul 24, 2023
0023003
Dhcp tests (#81)
jhughesoti Aug 2, 2023
4b1d69c
Merge tls tests into dev (#80)
jboddey Aug 2, 2023
4ce26a1
Test output restructure (#79)
jhughesoti Aug 3, 2023
c78f2de
Keep test results in memory (#82)
jboddey Aug 3, 2023
11c9740
Result descriptions (#92)
jhughesoti Aug 11, 2023
9046ffd
Misc cleanup (#93)
jhughesoti Aug 11, 2023
fca08ce
Resolve merge conflicts
jboddey Aug 15, 2023
5c9c7cc
Allow CORS (#91)
jboddey Aug 16, 2023
29242c4
Add /history and device config endpoints (#88)
jboddey Aug 16, 2023
290ba50
Re-enable actions, fix conn module (#89)
jboddey Aug 16, 2023
38792e8
Add required result to module configs (#95)
jboddey Aug 16, 2023
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
8 changes: 5 additions & 3 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ jobs:
uses: actions/checkout@v2.3.4
- name: Run tests
shell: bash {0}
run: testing/test_baseline
run: testing/baseline/test_baseline

testrun_tests:
name: Tests
runs-on: ubuntu-20.04
needs: testrun_baseline
timeout-minutes: 40
steps:
- name: Checkout source
uses: actions/checkout@v2.3.4
- name: Run tests
shell: bash {0}
run: testing/test_tests
run: testing/tests/test_tests

pylint:
name: Pylint
runs-on: ubuntu-22.04
Expand All @@ -37,4 +39,4 @@ jobs:
uses: actions/checkout@v2.3.4
- name: Run tests
shell: bash {0}
run: testing/test_pylint
run: testing/pylint/test_pylint
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ venv/
error
pylint.out
__pycache__/
build/
build/
testing/unit_test/temp/
17 changes: 10 additions & 7 deletions cmd/start → bin/testrun
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,26 @@
# limitations under the License.

if [[ "$EUID" -ne 0 ]]; then
echo "Must run as root. Use sudo cmd/start"
echo "Must run as root. Use sudo testrun"
exit 1
fi

# TODO: Obtain TESTRUNPATH from user environment variables
# TESTRUNPATH="/home/boddey/Desktop/test-run"
# cd $TESTRUNPATH

# Ensure that /var/run/netns folder exists
mkdir -p /var/run/netns
sudo mkdir -p /var/run/netns

# Clear up existing runtime files
rm -rf runtime
# Create device folder if it doesn't exist
mkdir -p local/devices

# Check if python modules exist. Install if not
[ ! -d "venv" ] && cmd/install
# Check if Python modules exist. Install if not
[ ! -d "venv" ] && sudo cmd/install

# Activate Python virtual environment
source venv/bin/activate

# TODO: Execute python code
# Set the PYTHONPATH to include the "src" directory
export PYTHONPATH="$PWD/framework/python/src"
python -u framework/python/src/core/test_runner.py $@
Expand Down
222 changes: 222 additions & 0 deletions framework/python/src/api/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.

from fastapi import FastAPI, APIRouter, Response, Request, status
from fastapi.middleware.cors import CORSMiddleware
import json
from json import JSONDecodeError
import psutil
import threading
import uvicorn

from common import logger
from common.device import Device

LOGGER = logger.get_logger("api")

DEVICE_MAC_ADDR_KEY = "mac_addr"
DEVICE_MANUFACTURER_KEY = "manufacturer"
DEVICE_MODEL_KEY = "model"

class Api:
"""Provide REST endpoints to manage Test Run"""

def __init__(self, test_run):

self._test_run = test_run
self._name = "TestRun API"
self._router = APIRouter()

self._session = self._test_run.get_session()

self._router.add_api_route("/system/interfaces", self.get_sys_interfaces)
self._router.add_api_route("/system/config", self.post_sys_config,
methods=["POST"])
self._router.add_api_route("/system/config", self.get_sys_config)
self._router.add_api_route("/system/start", self.start_test_run,
methods=["POST"])
self._router.add_api_route("/system/stop", self.stop_test_run,
methods=["POST"])
self._router.add_api_route("/system/status", self.get_status)
self._router.add_api_route("/history", self.get_history)
self._router.add_api_route("/devices", self.get_devices)
self._router.add_api_route("/device", self.save_device, methods=["POST"])

# TODO: Make this configurable in system.json
origins = ["http://localhost:4200"]

self._app = FastAPI()
self._app.include_router(self._router)
self._app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

self._api_thread = threading.Thread(target=self._start,
name="Test Run API",
daemon=True)

def start(self):
LOGGER.info("Starting API")
self._api_thread.start()
LOGGER.info("API waiting for requests")

def _start(self):
uvicorn.run(self._app, log_config=None, port=self._session.get_api_port())

def stop(self):
LOGGER.info("Stopping API")

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

async def post_sys_config(self, request: Request, response: Response):
try:
config = (await request.body()).decode("UTF-8")
config_json = json.loads(config)
self._session.set_config(config_json)
# Catch JSON Decode error etc
except JSONDecodeError:
response.status_code = status.HTTP_400_BAD_REQUEST
return self._generate_msg(False, "Invalid JSON received")
return self._session.get_config()

async def get_sys_config(self):
return self._session.get_config()

async def get_devices(self):
return self._session.get_device_repository()

async def start_test_run(self, request: Request, response: Response):

LOGGER.debug("Received start command")

# Check request is valid
body = (await request.body()).decode("UTF-8")
body_json = None

try:
body_json = json.loads(body)
except JSONDecodeError:
response.status_code = status.HTTP_400_BAD_REQUEST
return self._generate_msg(False, "Invalid JSON received")

if "device" not in body_json or not (
"mac_addr" in body_json["device"] and
"firmware" in body_json["device"]):
response.status_code = status.HTTP_400_BAD_REQUEST
return self._generate_msg(False, "Invalid request received")

device = self._session.get_device(body_json["device"]["mac_addr"])

# Check Test Run is not already running
if self._test_run.get_session().get_status() != "Idle":
LOGGER.debug("Test Run is already running. Cannot start another instance")
response.status_code = status.HTTP_409_CONFLICT
return self._generate_msg(False, "Test Run is already running")

# Check if requested device is known in the device repository
if device is None:
response.status_code = status.HTTP_404_NOT_FOUND
return self._generate_msg(False,
"A device with that MAC address could not be found")

device.firmware = body_json["device"]["firmware"]

# Check Test Run is able to start
if self._test_run.get_net_orc().check_config() is False:
response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
return self._generate_msg(False,"Configured interfaces are not ready for use. Ensure required interfaces are connected.")

self._test_run.get_session().reset()
self._test_run.get_session().set_target_device(device)
LOGGER.info(f"Starting Test Run with device target {device.manufacturer} {device.model} with MAC address {device.mac_addr}")

thread = threading.Thread(target=self._start_test_run,
name="Test Run")
thread.start()
return self._test_run.get_session().to_json()

def _generate_msg(self, success, message):
msg_type = "success"
if not success:
msg_type = "error"
return json.loads('{"' + msg_type + '": "' + message + '"}')

def _start_test_run(self):
self._test_run.start()

async def stop_test_run(self):
LOGGER.debug("Received stop command. Stopping Test Run")
self._test_run.stop()
return self._generate_msg(True, "Test Run stopped")

async def get_status(self):
return self._test_run.get_session().to_json()

async def get_history(self):
LOGGER.debug("Received history list request")
return self._session.get_all_reports()

async def save_device(self, request: Request, response: Response):
LOGGER.debug("Received device post request")

try:
device_raw = (await request.body()).decode("UTF-8")
device_json = json.loads(device_raw)

if not self._validate_device_json(device_json):
response.status_code = status.HTTP_400_BAD_REQUEST
return self._generate_msg(False, "Invalid request received")

device = self._session.get_device(device_json.get(DEVICE_MAC_ADDR_KEY))

if device is None:

# Create new device
device = Device()
device.mac_addr = device_json.get(DEVICE_MAC_ADDR_KEY)
device.manufacturer = device_json.get(DEVICE_MANUFACTURER_KEY)
device.model = device_json.get(DEVICE_MODEL_KEY)
device.device_folder = device.manufacturer + " " + device.model

self._test_run.create_device(device)
response.status_code = status.HTTP_201_CREATED

else:

self._test_run.save_device(device, device_json)
response.status_code = status.HTTP_200_OK

return device.to_config_json()

# Catch JSON Decode error etc
except JSONDecodeError:
response.status_code = status.HTTP_400_BAD_REQUEST
return self._generate_msg(False, "Invalid JSON received")

def _validate_device_json(self, json_obj):
if not (DEVICE_MAC_ADDR_KEY in json_obj and
DEVICE_MANUFACTURER_KEY in json_obj and
DEVICE_MODEL_KEY in json_obj
):
return False
return True
62 changes: 62 additions & 0 deletions framework/python/src/common/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.

"""Track device object information."""

from typing import Dict
from dataclasses import dataclass, field

@dataclass
class Device():
"""Represents a physical device and it's configuration."""

folder_url: str = None
mac_addr: str = None
manufacturer: str = None
model: str = None
test_modules: Dict = field(default_factory=dict)
ip_addr: str = None
firmware: str = None
device_folder: str = None
reports = []
max_device_reports: int = None

def add_report(self, report):
self.reports.append(report)

def get_reports(self):
return self.reports

# TODO: Add ability to remove reports once test reports have been cleaned up

def to_dict(self):
"""Returns the device as a python dictionary. This is used for the
system status API endpoint and in the report."""
device_json = {}
device_json['mac_addr'] = self.mac_addr
device_json['manufacturer'] = self.manufacturer
device_json['model'] = self.model
if self.firmware is not None:
device_json['firmware'] = self.firmware
return device_json

def to_config_json(self):
"""Returns the device as a python dictionary. Fields relevant to the device
config json file are exported."""
device_json = {}
device_json['mac_addr'] = self.mac_addr
device_json['manufacturer'] = self.manufacturer
device_json['model'] = self.model
device_json['test_modules'] = self.test_modules
return device_json
Loading