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
18 changes: 14 additions & 4 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- cron: '0 13 * * *'

jobs:
testrun:
testrun_baseline:
name: Baseline
runs-on: ubuntu-20.04
timeout-minutes: 20
Expand All @@ -17,11 +17,21 @@ jobs:
- name: Run tests
shell: bash {0}
run: testing/test_baseline


testrun_tests:
name: Tests
runs-on: ubuntu-20.04
timeout-minutes: 40
steps:
- name: Checkout source
uses: actions/checkout@v2.3.4
- name: Run tests
shell: bash {0}
run: testing/test_tests
pylint:
name: Pylint
runs-on: ubuntu-20.04
timeout-minutes: 20
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Checkout source
uses: actions/checkout@v2.3.4
Expand Down
41 changes: 41 additions & 0 deletions docs/network/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Network Overview

## Table of Contents
1) Network Overview (this page)
2) [Addresses](addresses.md)
3) [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.

The following network services are provided:

### Internet Connectivity (Gateway Service)

The gateway service provides internet connectivity to the test network. It allows devices in the network to access external resources and communicate with the internet.

### DHCPv4 Service

The DHCPv4 service provides Dynamic Host Configuration Protocol (DHCP) functionality for IPv4 addressing. It includes the following components:

- Primary DHCP Server: A primary DHCP server is available to assign IPv4 addresses to DHCP clients in the network.
- Secondary DHCP Server (Failover Configuration): A secondary DHCP server operates in failover configuration with the primary server to provide high availability and redundancy.

#### Configuration

The configuration of the DHCPv4 service can be modified using the provided GRPC (gRPC Remote Procedure Call) service.

### IPv6 SLAAC Addressing

The primary DHCP server also provides IPv6 Stateless Address Autoconfiguration (SLAAC) addressing for devices in the network. IPv6 addresses are automatically assigned to devices using SLAAC where test devices support it.

### NTP Service

The Network Time Protocol (NTP) service provides time synchronization for devices in the network. It ensures that all devices have accurate and synchronized time information.

### DNS Service

The DNS (Domain Name System) service resolves domain names to their corresponding IP addresses. It allows devices in the network to access external resources using domain names.

### 802.1x Authentication (Radius Module)

The radius module provides 802.1x authentication for devices in the network. It ensures secure and authenticated access to the network. The issuing CA (Certificate Authority) certificate can be specified by the user if required.
94 changes: 94 additions & 0 deletions docs/network/add_new_service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Adding a New Network Service

The Test Run framework allows users to add their own network services with ease. A template network service can be used to get started quickly, this can be found at [modules/network/template](../../modules/network/template). Otherwise, see below for details of the requirements for new network services.

To add a new network service to Test Run, follow the procedure below:

1. Create a folder under `modules/network/` with the name of the network service in lowercase, using only alphanumeric characters and hyphens (`-`).
2. Inside the created folder, include the following files and folders:
- `{module}.Dockerfile`: Dockerfile for building the network service image. Replace `{module}` with the name of the module.
- `conf/`: Folder containing the module configuration files.
- `bin/`: Folder containing the startup script for the network service.
- Any additional application code can be placed in its own folder.

### Example `module_config.json`

```json
{
"config": {
"meta": {
"name": "{module}",
"display_name": "Network Service Name",
"description": "Description of the network service"
},
"network": {
"interface": "veth0",
"enable_wan": false,
"ip_index": 2
},
"grpc": {
"port": 5001
},
"docker": {
"depends_on": "base",
"mounts": [
{
"source": "runtime/network",
"target": "/runtime/network"
}
]
}
}
}
```

### Example of {module}.Dockerfile

```Dockerfile
# Image name: test-run/{module}
FROM test-run/base:latest

ARG MODULE_NAME={module}
ARG MODULE_DIR=modules/network/$MODULE_NAME

# Install network service dependencies
# ...

# Copy over all configuration files
COPY $MODULE_DIR/conf /testrun/conf

# Copy over all binary files
COPY $MODULE_DIR/bin /testrun/bin

# Copy over all python files
COPY $MODULE_DIR/python /testrun/python

# Do not specify a CMD or Entrypoint as Test Run will automatically start your service as required
```

### Example of start_network_service script

```bash
#!/bin/bash

CONFIG_FILE=/etc/network_service/config.conf
# ...

echo "Starting Network Service..."

# Perform any required setup steps
# ...

# Start the network service
# ...

# Monitor for changes in the config file
# ...

# Restart the network service when the config changes
# ...
```




18 changes: 18 additions & 0 deletions docs/network/addresses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Network Addresses

Each network service is configured with an IPv4 and IPv6 address. For IPv4 addressing, the last number in the IPv4 address is fixed (ensuring the IP is unique). See below for a table of network addresses:

| Name | Mac address | IPv4 address | IPv6 address |
|---------------------|----------------------|--------------|------------------------------|
| Internet gateway | 9a:02:57:1e:8f:01 | 10.10.10.1 | fd10:77be:4186::1 |
| DHCP primary | 9a:02:57:1e:8f:02 | 10.10.10.2 | fd10:77be:4186::2 |
| DHCP secondary | 9a:02:57:1e:8f:03 | 10.10.10.3 | fd10:77be:4186::3 |
| DNS server | 9a:02:57:1e:8f:04 | 10.10.10.4 | fd10:77be:4186::4 |
| NTP server | 9a:02:57:1e:8f:05 | 10.10.10.5 | fd10:77be:4186::5 |
| Radius authenticator| 9a:02:57:1e:8f:07 | 10.10.10.7 | fd10:77be:4186::7 |
| Active test module | 9a:02:57:1e:8f:09 | 10.10.10.9 | fd10:77be:4186::9 |


The default network range is 10.10.10.0/24 and devices will be assigned addresses in that range via DHCP. The range may change when requested by a test module. In which case, network services will be restarted and accessible on the new range, with the same final host ID. The default IPv6 network is fd10:77be:4186::/64 and addresses will be assigned to devices on the network using IPv6 SLAAC.

When creating a new network module, please ensure that the ip_index value in the module_config.json is unique otherwise unexpected behaviour will occur.
12 changes: 6 additions & 6 deletions framework/python/src/net_orc/network_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def _get_os_user(self):
LOGGER.error('An OS error occurred while retrieving the login name.')
except Exception as error:
# Catch any other unexpected exceptions
LOGGER.error('An exception occurred:', error)
LOGGER.error('An exception occurred:', error)
return user

def _get_user(self):
Expand All @@ -203,15 +203,15 @@ def _get_user(self):
except (KeyError, ImportError, ModuleNotFoundError, OSError) as e:
# Handle specific exceptions individually
if isinstance(e, KeyError):
LOGGER.error("USER environment variable not set or unavailable.")
LOGGER.error('USER environment variable not set or unavailable.')
elif isinstance(e, ImportError):
LOGGER.error("Unable to import the getpass module.")
LOGGER.error('Unable to import the getpass module.')
elif isinstance(e, ModuleNotFoundError):
LOGGER.error("The getpass module was not found.")
LOGGER.error('The getpass module was not found.')
elif isinstance(e, OSError):
LOGGER.error("An OS error occurred while retrieving the username.")
LOGGER.error('An OS error occurred while retrieving the username.')
else:
LOGGER.error("An exception occurred:", e)
LOGGER.error('An exception occurred:', e)
return user

def _get_device_status(self, module):
Expand Down
17 changes: 8 additions & 9 deletions framework/python/src/test_orc/test_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@
# limitations under the License.

"""Provides high level management of the test orchestrator."""
import getpass
import os
import json
import time
import shutil
import docker
from docker.types import Mount
from common import logger
from common import logger, util
from test_orc.module import TestModule
from common import util

LOG_NAME = "test_orc"
LOGGER = logger.get_logger("test_orc")
Expand Down Expand Up @@ -61,7 +59,7 @@ def start(self):
# Setup the output directory
self._host_user = util.get_host_user()
os.makedirs(RUNTIME_DIR, exist_ok=True)
util.run_command(f'chown -R {self._host_user} {RUNTIME_DIR}')
util.run_command(f"chown -R {self._host_user} {RUNTIME_DIR}")

self._load_test_modules()
self.build_test_modules()
Expand Down Expand Up @@ -102,15 +100,15 @@ def _generate_results(self, device):
results[module.name] = module_results
except (FileNotFoundError, PermissionError,
json.JSONDecodeError) as results_error:
LOGGER.error("Error occured whilst obbtaining results for module " + module.name)
LOGGER.error(f"Error occured whilst obbtaining results for module {module.name}")
LOGGER.debug(results_error)

out_file = os.path.join(
self._root_path,
"runtime/test/" + device.mac_addr.replace(":", "") + "/results.json")
with open(out_file, "w", encoding="utf-8") as f:
json.dump(results, f, indent=2)
util.run_command(f'chown -R {self._host_user} {out_file}')
util.run_command(f"chown -R {self._host_user} {out_file}")
return results

def test_in_progress(self):
Expand Down Expand Up @@ -140,18 +138,19 @@ def _run_test_module(self, module, device):
container_runtime_dir = os.path.join(
self._root_path, "runtime/test/" + device.mac_addr.replace(":", "") +
"/" + module.name)
network_runtime_dir = os.path.join(self._root_path, "runtime/network")
os.makedirs(container_runtime_dir)

network_runtime_dir = os.path.join(self._root_path, "runtime/network")

device_startup_capture = os.path.join(
self._root_path, "runtime/test/" + device.mac_addr.replace(":", "") +
"/startup.pcap")
util.run_command(f'chown -R {self._host_user} {device_startup_capture}')
util.run_command(f"chown -R {self._host_user} {device_startup_capture}")

device_monitor_capture = os.path.join(
self._root_path, "runtime/test/" + device.mac_addr.replace(":", "") +
"/monitor.pcap")
util.run_command(f'chown -R {self._host_user} {device_monitor_capture}')
util.run_command(f"chown -R {self._host_user} {device_monitor_capture}")

client = docker.from_env()

Expand Down
18 changes: 9 additions & 9 deletions local/system.json.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"network": {
"device_intf": "enx123456789123",
"internet_intf": "enx123456789124"
},
"log_level": "INFO",
"startup_timeout": 60,
"monitor_period": 300,
"runtime": 1200
{
"network": {
"device_intf": "enx123456789123",
"internet_intf": "enx123456789124"
},
"log_level": "INFO",
"startup_timeout": 60,
"monitor_period": 300,
"runtime": 1200
}
4 changes: 4 additions & 0 deletions modules/network/base/base.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ FROM ubuntu:jammy

ARG MODULE_NAME=base
ARG MODULE_DIR=modules/network/$MODULE_NAME
ARG COMMON_DIR=framework/python/src/common

# Install common software
RUN apt-get update && apt-get install -y net-tools iputils-ping tcpdump iproute2 jq python3 python3-pip dos2unix

# Install common python modules
COPY $COMMON_DIR/ /testrun/python/src/common

# Setup the base python requirements
COPY $MODULE_DIR/python /testrun/python

Expand Down
25 changes: 25 additions & 0 deletions modules/network/base/bin/setup_python_path
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash

ROOT_DIRECTORY="/testrun/python/src"

# Function to recursively add subdirectories to PYTHONPATH
add_subdirectories_to_pythonpath() {
local directory=$1
local subdirectories=( "$directory"/* )
local subdirectory

for subdirectory in "${subdirectories[@]}"; do
if [[ -d "$subdirectory" && ! "$subdirectory" = *'__pycache__' ]]; then
export PYTHONPATH="$PYTHONPATH:$subdirectory"
add_subdirectories_to_pythonpath "$subdirectory"
fi
done
}

# Set PYTHONPATH initially to an empty string
export PYTHONPATH="$ROOT_DIRECTORY"

# Add all subdirectories to PYTHONPATH
add_subdirectories_to_pythonpath "$ROOT_DIRECTORY"

echo "$PYTHONPATH"
6 changes: 3 additions & 3 deletions modules/network/base/bin/start_grpc
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.

GRPC_DIR="/testrun/python/src/grpc"
GRPC_DIR="/testrun/python/src/grpc_server"
GRPC_PROTO_DIR="proto"
GRPC_PROTO_FILE="grpc.proto"

#Move into the grpc directory
pushd $GRPC_DIR >/dev/null 2>&1

#Build the grpc proto file every time before starting server
python3 -m grpc_tools.protoc --proto_path=. ./$GRPC_PROTO_DIR/$GRPC_PROTO_FILE --python_out=. --grpc_python_out=.
python3 -u -m grpc_tools.protoc --proto_path=. ./$GRPC_PROTO_DIR/$GRPC_PROTO_FILE --python_out=. --grpc_python_out=.

popd >/dev/null 2>&1

#Start the grpc server
python3 -u $GRPC_DIR/start_server.py $@
python3 -u $GRPC_DIR/start_server.py $@ &

Loading