diff --git a/riocli/device/model.py b/riocli/device/model.py index 6b5d3c5e..3a96a2e7 100644 --- a/riocli/device/model.py +++ b/riocli/device/model.py @@ -18,11 +18,11 @@ from riocli.device.util import ( create_hwil_device, + delete_hwil_device, execute_onboard_command, find_device_guid, - update_device_labels + make_device_labels_from_hwil_device ) -from riocli.exceptions import DeviceNotFound from riocli.jsonschema.validate import load_schema from riocli.model import Model @@ -32,10 +32,6 @@ def __init__(self, *args, **kwargs): self.update(*args, **kwargs) def find_object(self, client: Client) -> bool: - # For virtual devices, always return False to ensure re-onboarding is done even if the device already exists - if self.spec.get('virtual', {}).get('enabled', False): - return False - guid, obj = self.rc.find_depends({ "kind": "device", "nameOrGUID": self.metadata.name, @@ -51,29 +47,63 @@ def create_object(self, client: Client, **kwargs) -> v1Device: device = client.create_device(self.to_v1()) return device + # Create the HWIL device. hwil_response = create_hwil_device(self.spec, self.metadata) - try: - device_uuid = find_device_guid(client, self.metadata.name) - device = client.get_device(device_uuid) - except DeviceNotFound: - update_device_labels(self.metadata, hwil_response) - device = client.create_device(self.to_v1()) - except Exception as e: - raise e + # Generate labels to store HWIL metadata in rapyuta.io device. + l = make_device_labels_from_hwil_device(hwil_response) + self.metadata.get('labels', {}).update(l) + + # Create the rapyuta.io device. + device = client.create_device(self.to_v1()) + + # On-board the HWIL device using the onboard script. onboard_script = device.onboard_script() onboard_command = onboard_script.full_command() - try: - execute_onboard_command(hwil_response.id, onboard_command) - except Exception as e: - raise e + execute_onboard_command(hwil_response.id, onboard_command) return device def update_object(self, client: Client, obj: typing.Any) -> typing.Any: - pass + if not self.spec.get('virtual', {}).get('enabled', False): + return obj + + device_uuid = find_device_guid(client, self.metadata.name) + device = client.get_device(device_uuid) + + # Nothing to do if the device is already online or initializing. + if device['status'] in ('ONLINE', 'INITIALIZING'): + return device + + device_labels = device.get('labels', {}) + # Convert list to dict for easy access. + device_labels = {l['key']: l for l in device_labels} + + # Otherwise, re-onboard the device. + hwil_response = create_hwil_device(self.spec, self.metadata) + labels = make_device_labels_from_hwil_device(hwil_response) + + # Add or update labels in the device. + for k, v in labels.items(): + if k in device_labels: + device_labels[k]['value'] = v + device.update_label(device_labels[k]) + continue + + device.add_label(k, v) + + onboard_script = device.onboard_script() + onboard_command = onboard_script.full_command() + execute_onboard_command(hwil_response.id, onboard_command) + + return device def delete_object(self, client: Client, obj: typing.Any) -> typing.Any: + if self.spec.get('virtual', {}).get('enabled', False): + device_uuid = find_device_guid(client, self.metadata.name) + device = client.get_device(device_uuid) + delete_hwil_device(device) + obj.delete() def to_v1(self) -> v1Device: diff --git a/riocli/device/util.py b/riocli/device/util.py index 0d5400f8..b600c961 100644 --- a/riocli/device/util.py +++ b/riocli/device/util.py @@ -30,7 +30,7 @@ from riocli.constants import Colors from riocli.exceptions import DeviceNotFound from riocli.hwil.util import execute_command, find_device_id -from riocli.utils import is_valid_uuid, trim_suffix, trim_prefix +from riocli.utils import is_valid_uuid, trim_prefix, trim_suffix def name_to_guid(f: typing.Callable) -> typing.Callable: @@ -178,29 +178,58 @@ def is_remote_path(src, devices=[]): def create_hwil_device(spec: dict, metadata: dict) -> Munch: """Create a new hardware-in-the-loop device.""" - os = spec['virtual']['os'] - codename = spec['virtual']['codename'] - arch = spec['virtual']['arch'] - labels = hwil_device_labels(spec.virtual.product, metadata.name) - device_name = f"{metadata['name']}-{spec['virtual']['product']}-{labels['user']}" - device_name = sanitize_hwil_device_name(device_name) + virtual = spec['virtual'] + os = virtual['os'] + codename = virtual['codename'] + arch = virtual['arch'] + product = virtual['product'] + name = metadata['name'] + + labels = hwil_device_labels(product, name) + device_name = sanitize_hwil_device_name(f"{name}-{product}-{labels['user']}") + client = new_hwil_client() try: device_id = find_device_id(client, device_name) return client.get_device(device_id) except DeviceNotFound: - pass + pass # Do nothing and proceed. - try: - response = client.create_device(device_name, arch, os, codename, labels) - client.poll_till_device_ready(response.id, sleep_interval=5, retry_limit=3) - return response - except Exception as e: - raise e + response = client.create_device(device_name, arch, os, codename, labels) + client.poll_till_device_ready(response.id, sleep_interval=5, retry_limit=12) + + if response.status == 'FAILED': + raise Exception('device has failed') + + return response + + +def delete_hwil_device(device: Device) -> None: + """Delete a hardware-in-the-loop device. + This is a helper method that deletes a HWIL device + associated with the rapyuta.io device. + """ + labels = device.get('labels', {}) + if not labels: + raise DeviceNotFound(message='hwil device not found') -def execute_onboard_command(device_id: str, onboard_command: str) -> None: + device_id = None + + for l in labels: + if l['key'] == 'hwil_device_id': + device_id = l['value'] + break + + if device_id is None: + raise DeviceNotFound(message='hwil device not found') + + client = new_hwil_client() + client.delete_device(device_id) + + +def execute_onboard_command(device_id: int, onboard_command: str) -> None: """Execute the onboard command on a hardware-in-the-loop device.""" client = new_hwil_client() try: @@ -227,18 +256,15 @@ def hwil_device_labels(product_name, device_name) -> typing.Dict: } -def update_device_labels(metadata, response) -> None: - device_labels = { - "hwil_device_id": str(response.id), - "hwil_device_name": response.name, - "arch": response.architecture, - "flavor": response.flavor, - "hwil_device_username": response.username, +def make_device_labels_from_hwil_device(d: Munch) -> dict: + return { + "hwil_device_id": str(d.id), + "hwil_device_name": d.name, + "arch": d.architecture, + "flavor": d.flavor, + "hwil_device_username": d.username, } - existing_labels = metadata.get('labels', {}) - existing_labels.update(device_labels) - def sanitize_hwil_device_name(name): if len(name) == 0: diff --git a/riocli/hwil/util.py b/riocli/hwil/util.py index 57c886a9..180bc52d 100644 --- a/riocli/hwil/util.py +++ b/riocli/hwil/util.py @@ -73,7 +73,7 @@ def find_device_id(client: Client, name: str) -> str: raise DeviceNotFound(message="HWIL device not found") -def execute_command(client: Client, device_id: str, command: str) -> typing.Tuple[int, str, str]: +def execute_command(client: Client, device_id: int, command: str) -> typing.Tuple[int, str, str]: """Executes a command and waits for it to complete.""" try: response = client.execute_command(device_id, command) diff --git a/riocli/hwilclient/client.py b/riocli/hwilclient/client.py index 3ec1b308..e83d2ce4 100644 --- a/riocli/hwilclient/client.py +++ b/riocli/hwilclient/client.py @@ -119,8 +119,7 @@ def create_device( data = json.loads(response.text) if not response.ok: - err_msg = data.get('error') - raise Exception("hwil: {}".format(err_msg)) + raise Exception("hwil: {}".format(response.text)) return munchify(data) @@ -136,11 +135,12 @@ def get_device(self: Client, device_id: int) -> Munch: url = f"{self._host}/device/{device_id}" headers = self._get_auth_header() response = RestClient(url).method(HttpMethod.GET).headers(headers).execute() + handle_server_errors(response) + data = json.loads(response.text) if not response.ok: - err_msg = data.get('error') - raise Exception("hwil: {}".format(err_msg)) + raise Exception("hwil: {}".format(response.text)) return munchify(data) @@ -163,8 +163,7 @@ def execute_command(self: Client, device_id: int, command: str) -> Munch: data = json.loads(response.text) if not response.ok: - err_msg = data.get('error') - raise Exception("hwil: {}".format(err_msg)) + raise Exception("hwil: {}".format(response.text)) return munchify(data) @@ -177,8 +176,7 @@ def get_command(self: Client, command_uuid: str) -> Munch: data = json.loads(response.text) if not response.ok: - err_msg = data.get('error') - raise Exception("hwil: {}".format(err_msg)) + raise Exception("hwil: {}".format(response.text)) return munchify(data) @@ -194,8 +192,7 @@ def poll_till_device_ready(self: Client, device_id: int, sleep_interval: int, re data = json.loads(response.text) if not response.ok: - err_msg = data.get('error') - raise Exception("hwil: {}".format(err_msg)) + raise Exception("hwil: {}".format(response.text)) device = munchify(data) if device.status != 'IDLE': @@ -216,8 +213,7 @@ def list_devices(self: Client): data = json.loads(response.text) if not response.ok: - err_msg = data.get('error') - raise Exception("hwil: {}".format(err_msg)) + raise Exception("hwil: {}".format(response.text)) return munchify(data)