-
Couldn't load subscription status.
- Fork 5
Feature: Allow User to control their VM #124
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
Merged
Merged
Changes from 27 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
f771d21
Feature: VmClient
1yam ac78d53
Fix: Protocol (http/https) should not be hardcoded.
hoh 5672776
Fix: There was no test for `notify_allocation()`.
hoh 301814a
WIP: Copy authentication functions from aleph-vm
hoh 9abb642
Fix: vm client sessions wasn't close + authentifications for test wil…
1yam 6860015
Add: Unit test for {perform_operation, stop, reboot, erase, expire}
1yam 52faf6e
Refactor: logs didn't need to generate full header
1yam 328e087
Add: get_logs test
1yam 5c61b9b
Fix: black in aleph_vm_authentification.py
1yam a30f690
Fix: fully remove _generate_header call in get_logs
1yam fa998ae
Fix: black issue
1yam 49c81b5
Fix: test fix workflow
1yam 149778b
feat(vm_client): add missing types annotations
Psycojoker 017bf01
refactor(vm_client): remove duplicated types annotations
Psycojoker 7ec6c42
refactor(vm_client): avoid using single letter variable names
Psycojoker 93dbb22
feat(vm_client): increase test_notify_allocation precision
Psycojoker f93f202
refactor(vm_client): add empty lines for code readability
Psycojoker 3abcf36
style: run linting:fmt
Psycojoker ca16c5a
Fix: Required an old version of `aleph-message`
hoh 5162096
Fix: Newer aleph-message requires InstanceEnvironment
hoh 65a0dfe
Fix: Qemu was not the default hypervisor for instances.
hoh 225b42a
Fix: Pythom 3.12 fails setup libsecp256k1
hoh 93bffa9
doc(README): command to launch tests was incorrect
Psycojoker b53505d
Refactor: create and sign playload goes to utils and some fix
1yam 3c66af0
Fix: linting issue
1yam 0c62cd5
Fix: mypy issue
1yam 0fab7c3
fix: black
1yam 5380da5
feat: use bytes_from_hex where it makes sens
Psycojoker fc1e6af
chore: use ruff new CLI api
Psycojoker 2f180e3
feat: add unit tests for authentication mechanisms of VmClient
Psycojoker d271038
fix: debug code remove
1yam 5e88161
Update vmclient.py
1yam d9b1892
Fix: update unit test to use stream_logs endpoint instead of logs
1yam 247dbfc
Implement `VmConfidentialClient` class (#138)
nesitor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| import datetime | ||
| import json | ||
| import logging | ||
| from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple | ||
| from urllib.parse import urlparse | ||
|
|
||
| import aiohttp | ||
| from aleph_message.models import ItemHash | ||
| from eth_account.messages import encode_defunct | ||
| from jwcrypto import jwk | ||
|
|
||
| from aleph.sdk.types import Account | ||
| from aleph.sdk.utils import ( | ||
| create_vm_control_payload, | ||
| sign_vm_control_payload, | ||
| to_0x_hex, | ||
| ) | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class VmClient: | ||
| account: Account | ||
| ephemeral_key: jwk.JWK | ||
| node_url: str | ||
| pubkey_payload: Dict[str, Any] | ||
| pubkey_signature_header: str | ||
| session: aiohttp.ClientSession | ||
|
|
||
| def __init__( | ||
| self, | ||
| account: Account, | ||
| node_url: str = "", | ||
| session: Optional[aiohttp.ClientSession] = None, | ||
| ): | ||
| self.account = account | ||
| self.ephemeral_key = jwk.JWK.generate(kty="EC", crv="P-256") | ||
| self.node_url = node_url | ||
| self.pubkey_payload = self._generate_pubkey_payload() | ||
| self.pubkey_signature_header = "" | ||
| self.session = session or aiohttp.ClientSession() | ||
|
|
||
| def _generate_pubkey_payload(self) -> Dict[str, Any]: | ||
| return { | ||
| "pubkey": json.loads(self.ephemeral_key.export_public()), | ||
| "alg": "ECDSA", | ||
| "domain": urlparse(self.node_url).netloc, | ||
| "address": self.account.get_address(), | ||
| "expires": ( | ||
| datetime.datetime.utcnow() + datetime.timedelta(days=1) | ||
| ).isoformat() | ||
| + "Z", | ||
| } | ||
|
|
||
| async def _generate_pubkey_signature_header(self) -> str: | ||
| pubkey_payload = json.dumps(self.pubkey_payload).encode("utf-8").hex() | ||
| signable_message = encode_defunct(hexstr=pubkey_payload) | ||
| buffer_to_sign = signable_message.body | ||
|
|
||
| signed_message = await self.account.sign_raw(buffer_to_sign) | ||
| pubkey_signature = to_0x_hex(signed_message) | ||
|
|
||
| return json.dumps( | ||
| { | ||
| "sender": self.account.get_address(), | ||
| "payload": pubkey_payload, | ||
| "signature": pubkey_signature, | ||
| "content": {"domain": urlparse(self.node_url).netloc}, | ||
| } | ||
| ) | ||
|
|
||
| async def _generate_header( | ||
| self, vm_id: ItemHash, operation: str | ||
| ) -> Tuple[str, Dict[str, str]]: | ||
| payload = create_vm_control_payload(vm_id, operation) | ||
| signed_operation = sign_vm_control_payload(payload, self.ephemeral_key) | ||
|
|
||
| if not self.pubkey_signature_header: | ||
| self.pubkey_signature_header = ( | ||
| await self._generate_pubkey_signature_header() | ||
| ) | ||
|
|
||
| headers = { | ||
| "X-SignedPubKey": self.pubkey_signature_header, | ||
| "X-SignedOperation": signed_operation, | ||
| } | ||
|
|
||
| path = payload["path"] | ||
| return f"{self.node_url}{path}", headers | ||
|
|
||
| async def perform_operation( | ||
| self, vm_id: ItemHash, operation: str | ||
| ) -> Tuple[Optional[int], str]: | ||
| if not self.pubkey_signature_header: | ||
| self.pubkey_signature_header = ( | ||
| await self._generate_pubkey_signature_header() | ||
| ) | ||
|
|
||
| url, header = await self._generate_header(vm_id=vm_id, operation=operation) | ||
|
|
||
| try: | ||
| async with self.session.post(url, headers=header) as response: | ||
| response_text = await response.text() | ||
| return response.status, response_text | ||
|
|
||
| except aiohttp.ClientError as e: | ||
| logger.error(f"HTTP error during operation {operation}: {str(e)}") | ||
| return None, str(e) | ||
|
|
||
| async def get_logs(self, vm_id: ItemHash) -> AsyncGenerator[str, None]: | ||
| if not self.pubkey_signature_header: | ||
| self.pubkey_signature_header = ( | ||
| await self._generate_pubkey_signature_header() | ||
| ) | ||
|
|
||
| payload = create_vm_control_payload(vm_id, "logs") | ||
1yam marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| signed_operation = sign_vm_control_payload(payload, self.ephemeral_key) | ||
| path = payload["path"] | ||
| ws_url = f"{self.node_url}{path}" | ||
|
|
||
| async with self.session.ws_connect(ws_url) as ws: | ||
| auth_message = { | ||
| "auth": { | ||
| "X-SignedPubKey": self.pubkey_signature_header, | ||
| "X-SignedOperation": signed_operation, | ||
| } | ||
| } | ||
| await ws.send_json(auth_message) | ||
|
|
||
| async for msg in ws: # msg is of type aiohttp.WSMessage | ||
| if msg.type == aiohttp.WSMsgType.TEXT: | ||
| yield msg.data | ||
| elif msg.type == aiohttp.WSMsgType.ERROR: | ||
| break | ||
|
|
||
| async def start_instance(self, vm_id: ItemHash) -> Tuple[int, str]: | ||
| return await self.notify_allocation(vm_id) | ||
|
|
||
| async def stop_instance(self, vm_id: ItemHash) -> Tuple[Optional[int], str]: | ||
| return await self.perform_operation(vm_id, "stop") | ||
|
|
||
| async def reboot_instance(self, vm_id: ItemHash) -> Tuple[Optional[int], str]: | ||
| return await self.perform_operation(vm_id, "reboot") | ||
|
|
||
| async def erase_instance(self, vm_id: ItemHash) -> Tuple[Optional[int], str]: | ||
| return await self.perform_operation(vm_id, "erase") | ||
|
|
||
| async def expire_instance(self, vm_id: ItemHash) -> Tuple[Optional[int], str]: | ||
| return await self.perform_operation(vm_id, "expire") | ||
|
|
||
| async def notify_allocation(self, vm_id: ItemHash) -> Tuple[int, str]: | ||
| json_data = {"instance": vm_id} | ||
|
|
||
| async with self.session.post( | ||
| f"{self.node_url}/control/allocation/notify", json=json_data | ||
| ) as session: | ||
| form_response_text = await session.text() | ||
|
|
||
| return session.status, form_response_text | ||
|
|
||
| async def manage_instance( | ||
| self, vm_id: ItemHash, operations: List[str] | ||
| ) -> Tuple[int, str]: | ||
| for operation in operations: | ||
| status, response = await self.perform_operation(vm_id, operation) | ||
| if status != 200 and status: | ||
| return status, response | ||
| return 200, "All operations completed successfully" | ||
|
|
||
| async def close(self): | ||
| await self.session.close() | ||
|
|
||
| async def __aenter__(self): | ||
Psycojoker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return self | ||
|
|
||
| async def __aexit__(self, exc_type, exc_value, traceback): | ||
| await self.close() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.