Skip to content

Commit 82398b9

Browse files
committed
Merge branch 'ol-test-allocation-endpoints' into ol-dbus-async-v2
2 parents 69ed555 + 5fe46ac commit 82398b9

File tree

7 files changed

+141
-62
lines changed

7 files changed

+141
-62
lines changed

.github/workflows/test-on-droplets-matrix.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,14 @@ jobs:
238238
-d '{"persistent_vms": [], "instances": ["${{ matrix.check_vm.item_hash }}"]}' \
239239
"http://${DROPLET_IPV4}:4020/control/allocations"
240240
241+
- name: Get system usage
242+
run: |
243+
export DROPLET_IPV4="$(doctl compute droplet get aleph-vm-ci-${{ matrix.os_config.alias }}-${{ matrix.check_vm.alias }} --output json | ./.github/scripts/extract_droplet_ipv4.py)"
244+
curl -X GET -H "Content-Type: application/json" \
245+
-H "X-Auth-Signature: test" \
246+
"http://${DROPLET_IPV4}:4020/about/usage/system"
247+
248+
241249
- name: Export aleph logs
242250
if: always()
243251
run: |

src/aleph/vm/orchestrator/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ async def run_instances(instances: list[ItemHash]) -> None:
251251
# Watching for updates on this instance will therefore not work.
252252
pubsub: Optional[PubSub] = None
253253

254-
await asyncio.gather(*[start_instance(instance_id, pubsub, pool) for instance_id in instances])
255254

255+
await asyncio.gather(*[start_instance(instance_id, pubsub, pool) for instance_id in instances])
256256
await asyncio.Event().wait() # wait forever
257257

258258

src/aleph/vm/orchestrator/resources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def get_machine_properties() -> MachineProperties:
8888
return MachineProperties(
8989
cpu=CpuProperties(
9090
architecture=cpu_info.get("raw_arch_string", cpu_info.get("arch_string_raw")),
91-
vendor=cpu_info["vendor_id_raw"],
91+
vendor=cpu_info.get("vendor_id", cpu_info.get("vendor_id_raw")),
9292
),
9393
)
9494

src/aleph/vm/orchestrator/supervisor.py

Lines changed: 57 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -74,62 +74,63 @@ async def http_not_found(request: web.Request):
7474
return web.HTTPNotFound()
7575

7676

77-
app = web.Application(middlewares=[server_version_middleware])
78-
cors = setup(
79-
app,
80-
defaults={
81-
"*": ResourceOptions(
82-
allow_credentials=True,
83-
expose_headers="*",
84-
allow_headers="*",
85-
)
86-
},
87-
)
77+
def setup_webapp():
78+
app = web.Application(middlewares=[server_version_middleware])
79+
cors = setup(
80+
app,
81+
defaults={
82+
"*": ResourceOptions(
83+
allow_credentials=True,
84+
expose_headers="*",
85+
allow_headers="*",
86+
)
87+
},
88+
)
8889

89-
# Routes that need CORS enabled
90-
cors_routes = [
91-
# /about APIs return information about the VM Orchestrator
92-
web.get("/about/login", about_login),
93-
web.get("/about/executions/list", list_executions),
94-
web.get("/about/executions/details", about_executions),
95-
web.get("/about/executions/records", about_execution_records),
96-
web.get("/about/usage/system", about_system_usage),
97-
web.get("/about/config", about_config),
98-
# /control APIs are used to control the VMs and access their logs
99-
web.post("/control/allocation/notify", notify_allocation),
100-
web.get("/control/machine/{ref}/logs", stream_logs),
101-
web.post("/control/machine/{ref}/expire", operate_expire),
102-
web.post("/control/machine/{ref}/stop", operate_stop),
103-
web.post("/control/machine/{ref}/erase", operate_erase),
104-
web.post("/control/machine/{ref}/reboot", operate_reboot),
105-
# /status APIs are used to check that the VM Orchestrator is running properly
106-
web.get("/status/check/fastapi", status_check_fastapi),
107-
web.get("/status/check/fastapi/legacy", status_check_fastapi_legacy),
108-
web.get("/status/check/host", status_check_host),
109-
web.get("/status/check/version", status_check_version),
110-
web.get("/status/check/ipv6", status_check_ipv6),
111-
web.get("/status/config", status_public_config),
112-
]
113-
routes = app.add_routes(cors_routes)
114-
for route in routes:
115-
cors.add(route)
116-
117-
118-
# Routes that don't need CORS enabled
119-
other_routes = [
120-
# /control APIs are used to control the VMs and access their logs
121-
web.post("/control/allocations", update_allocations),
122-
# Raise an HTTP Error 404 if attempting to access an unknown URL within these paths.
123-
web.get("/about/{suffix:.*}", http_not_found),
124-
web.get("/control/{suffix:.*}", http_not_found),
125-
web.get("/status/{suffix:.*}", http_not_found),
126-
# /static is used to serve static files
127-
web.static("/static", Path(__file__).parent / "views/static"),
128-
# /vm is used to launch VMs on-demand
129-
web.route("*", "/vm/{ref}{suffix:.*}", run_code_from_path),
130-
web.route("*", "/{suffix:.*}", run_code_from_hostname),
131-
]
132-
app.add_routes(other_routes)
90+
# Routes that need CORS enabled
91+
cors_routes = [
92+
# /about APIs return information about the VM Orchestrator
93+
web.get("/about/login", about_login),
94+
web.get("/about/executions/list", list_executions),
95+
web.get("/about/executions/details", about_executions),
96+
web.get("/about/executions/records", about_execution_records),
97+
web.get("/about/usage/system", about_system_usage),
98+
web.get("/about/config", about_config),
99+
# /control APIs are used to control the VMs and access their logs
100+
web.post("/control/allocation/notify", notify_allocation),
101+
web.get("/control/machine/{ref}/logs", stream_logs),
102+
web.post("/control/machine/{ref}/expire", operate_expire),
103+
web.post("/control/machine/{ref}/stop", operate_stop),
104+
web.post("/control/machine/{ref}/erase", operate_erase),
105+
web.post("/control/machine/{ref}/reboot", operate_reboot),
106+
# /status APIs are used to check that the VM Orchestrator is running properly
107+
web.get("/status/check/fastapi", status_check_fastapi),
108+
web.get("/status/check/fastapi/legacy", status_check_fastapi_legacy),
109+
web.get("/status/check/host", status_check_host),
110+
web.get("/status/check/version", status_check_version),
111+
web.get("/status/check/ipv6", status_check_ipv6),
112+
web.get("/status/config", status_public_config),
113+
]
114+
routes = app.add_routes(cors_routes)
115+
for route in routes:
116+
cors.add(route)
117+
118+
# Routes that don't need CORS enabled
119+
other_routes = [
120+
# /control APIs are used to control the VMs and access their logs
121+
web.post("/control/allocations", update_allocations),
122+
# Raise an HTTP Error 404 if attempting to access an unknown URL within these paths.
123+
web.get("/about/{suffix:.*}", http_not_found),
124+
web.get("/control/{suffix:.*}", http_not_found),
125+
web.get("/status/{suffix:.*}", http_not_found),
126+
# /static is used to serve static files
127+
web.static("/static", Path(__file__).parent / "views/static"),
128+
# /vm is used to launch VMs on-demand
129+
web.route("*", "/vm/{ref}{suffix:.*}", run_code_from_path),
130+
web.route("*", "/{suffix:.*}", run_code_from_hostname),
131+
]
132+
app.add_routes(other_routes)
133+
return app
133134

134135

135136
async def stop_all_vms(app: web.Application):
@@ -153,6 +154,7 @@ def run():
153154

154155
# Require a random token to access /about APIs
155156
secret_token = token_urlsafe(nbytes=32)
157+
app = setup_webapp()
156158
# Store app singletons. Note that app["pubsub"] will also be created.
157159
app["secret_token"] = secret_token
158160
app["vm_pool"] = pool

src/aleph/vm/orchestrator/views/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,12 @@ def authenticate_api_request(request: web.Request) -> bool:
341341

342342

343343
async def update_allocations(request: web.Request):
344+
"""Main entry for the start of persistence VM and instance, called by the CCN,
345+
346+
347+
auth via the SETTINGS.ALLOCATION_TOKEN_HASH sent in header X-Auth-Signature.
348+
Receive a list of vm and instance that should be present and then match that state by stopping and launching VMs
349+
"""
344350
if not authenticate_api_request(request):
345351
return web.HTTPUnauthorized(text="Authentication token received is invalid")
346352

tests/supervisor/test_execution.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ async def test_create_execution():
5858

5959

6060
@pytest.mark.asyncio
61-
async def test_create_execution_online():
61+
async def test_create_execution_online(vm_hash: ItemHash = None):
6262
"""
6363
Create a new VM execution without building it locally and check that it starts properly.
6464
"""
6565

66+
vm_hash = vm_hash or settings.CHECK_FASTAPI_VM_ID
67+
6668
# Ensure that the settings are correct and required files present.
6769
settings.setup()
6870
settings.check()
@@ -71,7 +73,6 @@ async def test_create_execution_online():
7173
engine = metrics.setup_engine()
7274
await metrics.create_tables(engine)
7375

74-
vm_hash = ItemHash("3fc0aa9569da840c43e7bd2033c3c580abb46b007527d6d20f2d4e98e867f7af")
7576
message = await get_message(ref=vm_hash)
7677

7778
execution = VmExecution(
@@ -93,3 +94,11 @@ async def test_create_execution_online():
9394

9495
await execution.start()
9596
await execution.stop()
97+
98+
99+
@pytest.mark.asyncio
100+
async def test_create_execution_legacy():
101+
"""
102+
Create a new VM execution based on the legacy FastAPI check and ensure that it starts properly.
103+
"""
104+
await test_create_execution_online(vm_hash=settings.LEGACY_CHECK_FASTAPI_VM_ID)

tests/supervisor/test_views.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
from aiohttp import web
33

44
from aleph.vm.conf import settings
5-
from aleph.vm.orchestrator.supervisor import app
5+
from aleph.vm.orchestrator.supervisor import setup_webapp
66

77

88
@pytest.mark.asyncio
99
async def test_allocation_fails_on_invalid_item_hash(aiohttp_client):
1010
"""Test that the allocation endpoint fails when an invalid item_hash is provided."""
11+
app = setup_webapp()
1112
client = await aiohttp_client(app)
1213
settings.ALLOCATION_TOKEN_HASH = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" # = "test"
1314
response: web.Response = await client.post(
@@ -28,12 +29,65 @@ async def test_allocation_fails_on_invalid_item_hash(aiohttp_client):
2829

2930
@pytest.mark.asyncio
3031
async def test_system_usage(aiohttp_client):
31-
"""Test that the allocation endpoint fails when an invalid item_hash is provided."""
32+
"""Test that the usage system endpoints responds. No auth needed"""
33+
app = setup_webapp()
3234
client = await aiohttp_client(app)
33-
settings.ALLOCATION_TOKEN_HASH = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" # = "test"
3435
response: web.Response = await client.get("/about/usage/system")
3536
assert response.status == 200
3637
# check if it is valid json
3738
resp = await response.json()
3839
assert "cpu" in resp
3940
assert resp["cpu"]["count"] > 0
41+
42+
43+
@pytest.mark.asyncio
44+
async def test_allocation_invalid_auth_token(aiohttp_client):
45+
"""Test that the allocation endpoint fails when an invalid auth token is provided."""
46+
settings.ALLOCATION_TOKEN_HASH = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" # = "test"
47+
app = setup_webapp()
48+
client = await aiohttp_client(app)
49+
response = await client.post(
50+
"/control/allocations",
51+
json={"persistent_vms": []},
52+
headers={"X-Auth-Signature": "notTest"},
53+
)
54+
assert response.status == 401
55+
assert await response.text() == "Authentication token received is invalid"
56+
57+
58+
@pytest.mark.asyncio
59+
async def test_allocation_missing_auth_token(aiohttp_client):
60+
"""Test that the allocation endpoint fails when auth token is not provided."""
61+
app = setup_webapp()
62+
client = await aiohttp_client(app)
63+
response: web.Response = await client.post(
64+
"/control/allocations",
65+
json={"persistent_vms": []},
66+
)
67+
assert response.status == 401
68+
assert await response.text() == "Authentication token is missing"
69+
70+
71+
@pytest.mark.asyncio
72+
async def test_allocation_valid_token(aiohttp_client):
73+
"""Test that the allocation endpoint fails when an invalid auth is provided.
74+
75+
This is a very simple test that don't start or stop any VM so the mock is minimal"""
76+
77+
class FakeVmPool:
78+
def get_persistent_executions(self):
79+
return []
80+
81+
settings.ALLOCATION_TOKEN_HASH = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" # = "test"
82+
app = setup_webapp()
83+
app["vm_pool"] = FakeVmPool()
84+
app["pubsub"] = FakeVmPool()
85+
client = await aiohttp_client(app)
86+
87+
response: web.Response = await client.post(
88+
"/control/allocations",
89+
json={"persistent_vms": []},
90+
headers={"X-Auth-Signature": "test"},
91+
)
92+
assert response.status == 200
93+
assert await response.json() == {"success": True, "successful": [], "failing": [], "errors": {}}

0 commit comments

Comments
 (0)