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
26 changes: 26 additions & 0 deletions .github/workflows/test-on-droplets-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,32 @@ jobs:
run: |
sudo apt-get install libsystemd-dev cmake libdbus-1-dev libglib2.0-dev

- name: Download and build required files for running tests. Copied from packaging/Makefile.
run: |
sudo mkdir --parents /opt/firecracker/
sudo curl -fsSL -o "/opt/firecracker/vmlinux.bin" "https://ipfs.aleph.cloud/ipfs/bafybeiaj2lf6g573jiulzacvkyw4zzav7dwbo5qbeiohoduopwxs2c6vvy"

rm -fr /tmp/firecracker-release
mkdir --parents /tmp/firecracker-release /opt/firecracker
curl -fsSL https://github.com/firecracker-microvm/firecracker/releases/download/v1.5.0/firecracker-v1.5.0-x86_64.tgz | tar -xz --no-same-owner --directory /tmp/firecracker-release
# Copy binaries:
cp /tmp/firecracker-release/release-v*/firecracker-v*[!.debug] /opt/firecracker/firecracker
cp /tmp/firecracker-release/release-v*/jailer-v*[!.debug] /opt/firecracker/jailer
chmod +x /opt/firecracker/firecracker
chmod +x /opt/firecracker/jailer

find /opt

- name: "Build custom runtime"
run: |
sudo apt update
sudo apt install -y debootstrap ndppd acl cloud-image-utils qemu-utils qemu-system-x86
cd runtimes/aleph-debian-12-python && sudo ./create_disk_image.sh && cd ../..

- name: "Build example volume"
run: |
cd examples/volumes && bash build_squashfs.sh

# Unit tests create and delete network interfaces, and therefore require to run as root
- name: Run unit tests
run: |
Expand Down
46 changes: 46 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Testing aleph-vm

This procedure describes how to run tests on a local system.

Tests also run on GitHub Actions via [the following workflow](./.github/workflows/test-on-droplets-matrix.yml).

Since these tests create block devices and manipulate network interfaces, they need to run as root.
If you are not comfortable with this, run them in a virtual machine.

## 1. Clone this repository

```shell
git clone https://github.com/aleph-im/aleph-vm.git
```

## 2. Install [hatch](https://hatch.pypa.io/), the project manager

Since installing tools globally is not recommended, we will install `hatch`
in a dedicated virtual environment. Alternatives include using [pipx](https://pipx.pypa.io)
or your distribution.

```shell
python3 -m venv /opt/venv
source /opt/venv/bin/activate

# Inside the venv
pip install hatch
```

## 3. Initialize hatch for running the tests

It is required that the testing virtual environment relies on system packages
for `nftables` instead of the package obtained from `salsa.debian.org` as defined in
[pyproject.toml](./pyproject.toml).

Create the testing virtual environment:
```shell
hatch env create testing
```


## 4. Run tests

```shell
hatch run testing:test
```
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dependencies = [
"systemd-python==235",
"systemd-python==235",
"superfluid~=0.2.1",
"sqlalchemy[asyncio]",
"sqlalchemy[asyncio]>=2.0",
"aiosqlite==0.19.0",
"alembic==1.13.1",
"aiohttp_cors~=0.7.0",
Expand Down Expand Up @@ -83,6 +83,8 @@ config = "aleph-vm orchestrator config {args:--help}"
check = "aleph-vm controller run {args:--help}"

[tool.hatch.envs.testing]
type = "virtual"
system-packages = true
dependencies = [
"pytest==8.0.1",
"pytest-cov==4.1.0",
Expand Down Expand Up @@ -135,6 +137,13 @@ all = [
pythonpath = [
"src"
]
testpaths = [
"tests"
]
ignore = [
"runtimes/aleph-debian-11-python/rootfs/",
"runtimes/aleph-debian-12-python/rootfs/",
]

[tool.black]
target-version = ["py39"]
Expand Down
7 changes: 5 additions & 2 deletions runtimes/aleph-debian-11-python/create_disk_image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ pip3 install 'fastapi~=0.103.1'
echo "Pip installing aleph-client"
pip3 install 'aleph-sdk-python==0.7.0'

# Compile all Python bytecode
python3 -m compileall -f /usr/local/lib/python3.9
# Compile Python code to bytecode for faster execution
# -o2 is needed to compile with optimization level 2 which is what we launch init1.py (`python -OO`)
# otherwise they are not used
python3 -m compileall -o 2 -f /usr/local/lib/python3.9


echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config
echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
Expand Down
6 changes: 4 additions & 2 deletions runtimes/aleph-debian-12-python/create_disk_image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ mkdir -p /opt/aleph/libs
pip3 install --target /opt/aleph/libs 'aleph-sdk-python==0.9.0' 'fastapi~=0.109.2'

# Compile Python code to bytecode for faster execution
python3 -m compileall -f /usr/local/lib/python3.11
python3 -m compileall -f /opt/aleph/libs
# -o2 is needed to compile with optimization level 2 which is what we launch init1.py (`python -OO`)
# otherwise they are not used
python3 -m compileall -o 2 -f /usr/local/lib/python3.11
python3 -m compileall -o 2 -f /opt/aleph/libs

echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config
echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
Expand Down
18 changes: 13 additions & 5 deletions src/aleph/vm/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ def update(self, **kwargs):
raise ValueError(msg)

def check(self):
"""Check that the settings are valid. Call this method after self.setup()."""
assert Path("/dev/kvm").exists(), "KVM not found on `/dev/kvm`."
assert isfile(self.FIRECRACKER_PATH), f"File not found {self.FIRECRACKER_PATH}"
assert isfile(self.JAILER_PATH), f"File not found {self.JAILER_PATH}"
Expand All @@ -340,11 +341,17 @@ def check(self):
assert self.FAKE_DATA_RUNTIME, "Local runtime .squashfs build not specified"
assert self.FAKE_DATA_VOLUME, "Local data volume .squashfs not specified"

assert isdir(self.FAKE_DATA_PROGRAM), "Local fake program directory is missing"
assert isfile(self.FAKE_DATA_MESSAGE), "Local fake message is missing"
assert isdir(self.FAKE_DATA_DATA), "Local fake data directory is missing"
assert isfile(self.FAKE_DATA_RUNTIME), "Local runtime .squashfs build is missing"
assert isfile(self.FAKE_DATA_VOLUME), "Local data volume .squashfs is missing"
assert isdir(
self.FAKE_DATA_PROGRAM
), f"Local fake program directory is missing, no directory '{self.FAKE_DATA_PROGRAM}'"
assert isfile(self.FAKE_DATA_MESSAGE), f"Local fake message '{self.FAKE_DATA_MESSAGE}' not found"
assert isdir(self.FAKE_DATA_DATA), f"Local fake data directory '{self.FAKE_DATA_DATA}' is missing"
assert isfile(
self.FAKE_DATA_RUNTIME
), f"Local runtime '{self.FAKE_DATA_RUNTIME}' is missing, did you build it ?"
assert isfile(
self.FAKE_DATA_VOLUME
), f"Local data volume '{self.FAKE_DATA_VOLUME}' is missing, did you build it ?"

assert is_command_available("setfacl"), "Command `setfacl` not found, run `apt install acl`"
if self.USE_NDP_PROXY:
Expand All @@ -363,6 +370,7 @@ def check(self):
), "Command `qemu-system-x86_64` not found, run `apt install qemu-system-x86`"

def setup(self):
"""Setup the environment defined by the settings. Call this method after loading the settings."""
os.makedirs(self.MESSAGE_CACHE, exist_ok=True)
os.makedirs(self.CODE_CACHE, exist_ok=True)
os.makedirs(self.RUNTIME_CACHE, exist_ok=True)
Expand Down
7 changes: 4 additions & 3 deletions src/aleph/vm/guest_api/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging
import re
from pathlib import Path
from typing import Optional

import aiohttp
Expand Down Expand Up @@ -152,7 +153,7 @@ async def list_keys_from_cache(request: web.Request):


def run_guest_api(
unix_socket_path,
unix_socket_path: Path,
vm_hash: Optional[str] = None,
sentry_dsn: Optional[str] = None,
server_name: Optional[str] = None,
Expand Down Expand Up @@ -195,8 +196,8 @@ def run_guest_api(
app.router.add_route(method="POST", path="/api/v0/p2p/pubsub/pub", handler=repost)

# web.run_app(app=app, port=9000)
web.run_app(app=app, path=unix_socket_path)
web.run_app(app=app, path=str(unix_socket_path))


if __name__ == "__main__":
run_guest_api("/tmp/guest-api", vm_hash="vm")
run_guest_api(Path("/tmp/guest-api"), vm_hash="vm")
29 changes: 1 addition & 28 deletions src/aleph/vm/orchestrator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,3 @@
from aleph.vm.version import __version__

from . import (
messages,
metrics,
pubsub,
reactor,
resources,
run,
status,
supervisor,
tasks,
views,
vm,
)

__all__ = (
"__version__",
"messages",
"metrics",
"pubsub",
"reactor",
"resources",
"run",
"status",
"supervisor",
"tasks",
"views",
"vm",
)
__all__ = ("__version__",)
95 changes: 95 additions & 0 deletions tests/supervisor/test_execution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import asyncio
import logging

import pytest
from aleph_message.models import ItemHash

from aleph.vm.conf import settings
from aleph.vm.controllers.firecracker import AlephFirecrackerProgram
from aleph.vm.models import VmExecution
from aleph.vm.orchestrator import metrics
from aleph.vm.storage import get_message


@pytest.mark.asyncio
async def test_create_execution():
"""
Create a new VM execution and check that it starts properly.
"""

settings.FAKE_DATA_PROGRAM = settings.BENCHMARK_FAKE_DATA_PROGRAM
settings.ALLOW_VM_NETWORKING = False
settings.USE_JAILER = False

logging.basicConfig(level=logging.DEBUG)
settings.PRINT_SYSTEM_LOGS = True

# Ensure that the settings are correct and required files present.
settings.setup()
settings.check()

# The database is required for the metrics and is currently not optional.
engine = metrics.setup_engine()
await metrics.create_tables(engine)

vm_hash = ItemHash("cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe")
message = await get_message(ref=vm_hash)

execution = VmExecution(
vm_hash=vm_hash,
message=message.content,
original=message.content,
snapshot_manager=None,
systemd_manager=None,
persistent=False,
)

# Downloading the resources required may take some time, limit it to 10 seconds
await asyncio.wait_for(execution.prepare(), timeout=30)

vm = execution.create(vm_id=3, tap_interface=None)

# Test that the VM is created correctly. It is not started yet.
assert isinstance(vm, AlephFirecrackerProgram)
assert vm.vm_id == 3

await execution.start()
await execution.stop()


@pytest.mark.asyncio
async def test_create_execution_online():
"""
Create a new VM execution without building it locally and check that it starts properly.
"""

# Ensure that the settings are correct and required files present.
settings.setup()
settings.check()

# The database is required for the metrics and is currently not optional.
engine = metrics.setup_engine()
await metrics.create_tables(engine)

vm_hash = ItemHash("3fc0aa9569da840c43e7bd2033c3c580abb46b007527d6d20f2d4e98e867f7af")
message = await get_message(ref=vm_hash)

execution = VmExecution(
vm_hash=vm_hash,
message=message.content,
original=message.content,
snapshot_manager=None,
systemd_manager=None,
persistent=False,
)

# Downloading the resources required may take some time, limit it to 10 seconds
await asyncio.wait_for(execution.prepare(), timeout=30)

vm = execution.create(vm_id=3, tap_interface=None)
# Test that the VM is created correctly. It is not started yet.
assert isinstance(vm, AlephFirecrackerProgram)
assert vm.vm_id == 3

await execution.start()
await execution.stop()