- 
                Notifications
    You must be signed in to change notification settings 
- 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 31 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.