Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial Arm Virtual Hardware (AVH) workflow with end to end test #30250

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions .github/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ autogenerated
automake
autotools
avahi
AVH
avL
AwaitNextAction
AXXXF
Expand Down
96 changes: 96 additions & 0 deletions .github/workflows/avh.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Copyright (c) 2022 Project CHIP Authors
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
#
# 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
#
# http://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.

name: Arm Virtual Hardware

on:
push:
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.ref }}-${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.event.number) || (github.event_name == 'workflow_dispatch' && github.run_number) || github.sha }}
cancel-in-progress: false
andy31415 marked this conversation as resolved.
Show resolved Hide resolved

jobs:
arm_crosscompile:
name: Linux ARM Cross compile
timeout-minutes: 70

runs-on: ubuntu-latest
if: github.actor != 'restyled-io[bot]'

container:
image: ghcr.io/project-chip/chip-build-crosscompile:1
volumes:
- "/tmp/bloat_reports:/tmp/bloat_reports"

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Checkout submodules & Bootstrap
uses: ./.github/actions/checkout-submodules-and-bootstrap
with:
platform: linux

- name: Build Samples
timeout-minutes: 45
run: |
./scripts/run_in_build_env.sh \
"./scripts/build/build_examples.py \
--target linux-arm64-chip-tool-ipv6only-mbedtls-clang-minmdns-verbose \
--target linux-arm64-light-ipv6only-mbedtls-clang-minmdns-verbose \
build \
"

- name: Upload built samples
uses: actions/upload-artifact@v3
with:
name: arm_crosscompiled_samples
path: |
out/linux-arm64-chip-tool-ipv6only-mbedtls-clang-minmdns-verbose/chip-tool
out/linux-arm64-light-ipv6only-mbedtls-clang-minmdns-verbose/chip-lighting-app

arm_e2e_tests:
name: Arm Virtual Hardware End to end tests
timeout-minutes: 10

runs-on: ubuntu-latest

env:
AVH_API_TOKEN: ${{ secrets.AVH_API_TOKEN }}
AVH_API_ENDPOINT: https://csa.app.avh.arm.com/api
AVH_PROJECT_NAME: "${{ github.workflow }} #${{ github.run_number }} - End to end tests"

needs: arm_crosscompile

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Downloads Cross-compiled samples
uses: actions/download-artifact@v3
with:
name: arm_crosscompiled_samples
path: scripts/tests/avh/out

- name: Install Python dependencies
run: |
pip3 install -r scripts/tests/avh/requirements.txt
andy31415 marked this conversation as resolved.
Show resolved Hide resolved

- name: Run end to end test
run: |
cd scripts/tests/avh
python3 -u -m unittest test_lighting_app.py
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
avh-api
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
paramiko
38 changes: 38 additions & 0 deletions scripts/tests/avh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Arm Virtual Hardware (AVH) based tests

This folder contains end to end tests that use the
[Arm Virtual Hardware (AVH)](https://www.arm.com/products/development-tools/simulation/virtual-hardware)
service.

The tests require the `AVH_API_TOKEN` environment variable is set with the value
from `AVH -> Profile -> API -> API Token`.

## Current tests

- [`test_lighting_app.py`](test_lighting_app.py)
- This test uses two virtual Raspberry Pi Model 4 boards running Ubuntu
Server 22.04 and pre-built `chip-tool` and `chip-lighting-app` binaries
(`linux-arm64`), and tests commissioning and control over BLE and Wi-Fi
using the virtual Bluetooth and Wi-Fi network features of AVH.

## Running the tests

1. Install dependencies

```
pip3 install -r requirements.txt
```

2. Set AVH_API_TOKEN` environment variable

```
export AVH_API_TOKEN=<AVH API TOKEN value>
```

3. Place cross-compiled `chip-tool` and `lighting-app` binaries in `out` folder

4. Run

```
python3 -u -m unittest test_lighting_app.py
```
78 changes: 78 additions & 0 deletions scripts/tests/avh/helpers/avh_chiptool_instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright (c) 2022 Project CHIP Authors
#
# 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
#
# http://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 datetime import datetime

from .avh_instance import AvhInstance

APPLICATION_BINARY = "chip-tool"


class AvhChiptoolInstance(AvhInstance):
def __init__(self, avh_client, name, application_binary_path):
super().__init__(avh_client, name)

self.application_binary_path = application_binary_path

def upload_application_binary(self):
super().upload_application_binary(
self.application_binary_path, APPLICATION_BINARY
)

def configure_system(self):
self.log_in_to_console()

# set current date and time
self.console_exec_command("sudo timedatectl set-ntp false", timeout=300)
self.console_exec_command(
f"sudo timedatectl set-time '{datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}'",
timeout=300,
)
self.console_exec_command("sudo timedatectl set-ntp true", timeout=300)

# install network manager
self.console_exec_command("sudo apt-get update", timeout=300)
self.console_exec_command(
"sudo apt-get -y install network-manager", timeout=300
)

# connect Wi-Fi to the Arm ssid
self.console_exec_command("sudo nmcli r wifi on")
self.console_exec_command("sudo nmcli d wifi connect Arm password password")

# disable eth0
self.console_exec_command("sudo nmcli dev set eth0 managed no")
self.console_exec_command("sudo ip link set dev eth0 down")

def pairing_ble_wifi(self, node_id, ssid, password, pin_code, discriminator):
output = self.console_exec_command(
f"./{APPLICATION_BINARY} pairing ble-wifi {node_id} {ssid} {password} {pin_code} {discriminator}",
timeout=120.0,
)

return output

def on(self, node_id):
output = self.console_exec_command(
f"./{APPLICATION_BINARY} onoff on {node_id} 1", timeout=30.0
)

return output

def off(self, node_id):
output = self.console_exec_command(
f"./{APPLICATION_BINARY} onoff off {node_id} 1", timeout=30.0
)

return output
89 changes: 89 additions & 0 deletions scripts/tests/avh/helpers/avh_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright (c) 2022 Project CHIP Authors
#
# 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
#
# http://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.

import uuid

from avh_api import ApiClient as AvhApiClient
from avh_api import Configuration as AvhApiConfiguration
from avh_api.api.arm_api import ArmApi as AvhApi
from avh_api.model.project_key import ProjectKey as AvhProjectKey


class AvhClient:
def __init__(self, api_token, api_endpoint=None):
avh_api_config = AvhApiConfiguration(host=api_endpoint)
self.avh_api_client = AvhApiClient(avh_api_config)

self.avh_api = AvhApi(self.avh_api_client)

avh_api_config.access_token = self.avh_api.v1_auth_login(
{"api_token": api_token}
).token

self.default_project_id = self.avh_api.v1_get_projects()[0]["id"]

def create_project(self, name, num_cores):
return self.avh_api.v1_create_project(
{
"id": str(uuid.uuid4()),
"name": name,
"settings": {
"internet_access": True,
"dhcp": True,
},
"quotas": {"cores": num_cores},
}
)["id"]

def delete_project(self, id):
self.avh_api.v1_delete_project(id)

def create_instance(self, name, flavor, os, osbuild):
return self.avh_api.v1_create_instance(
{
"name": name,
"project": self.default_project_id,
"flavor": flavor,
"os": os,
"osbuild": osbuild,
}
)["id"]

def instance_state(self, instance_id):
return str(self.avh_api.v1_get_instance_state(instance_id))

def instance_console_log(self, instance_id):
return self.avh_api.v1_get_instance_console_log(instance_id)

def instance_quick_connect_command(self, instance_id):
return self.avh_api.v1_get_instance_quick_connect_command(instance_id)

def create_ssh_project_key(self, label, key):
return self.avh_api.v1_add_project_key(
self.default_project_id,
AvhProjectKey(kind="ssh", key=key, label=label),
)["identifier"]

def instance_console_url(self, instance_id):
return self.avh_api.v1_get_instance_console(instance_id).url

def delete_ssh_project_key(self, key_id):
self.avh_api.v1_remove_project_key(self.default_project_id, key_id)

def delete_instance(self, instance_id):
self.avh_api.v1_delete_instance(instance_id)

def close(self):
self.avh_api_client.rest_client.pool_manager.clear()
self.avh_api_client.close()
Loading
Loading