From 0e3ea146751c586598943e72f8aeb3ae69f2d8bc Mon Sep 17 00:00:00 2001 From: Smruti Ranjan Senapati Date: Tue, 16 Jul 2024 17:24:10 +0530 Subject: [PATCH] fix(deployments): fix deployment status and wait command --- riocli/deployment/list.py | 28 ++++++++-------- riocli/deployment/status.py | 2 +- riocli/deployment/wait.py | 34 ++++--------------- riocli/v2client/client.py | 65 ++++++++++++++++++++++++++++++------- riocli/v2client/enums.py | 33 +++++++++++++++++++ riocli/v2client/error.py | 9 +++++ 6 files changed, 118 insertions(+), 53 deletions(-) create mode 100644 riocli/v2client/enums.py create mode 100644 riocli/v2client/error.py diff --git a/riocli/deployment/list.py b/riocli/deployment/list.py index 3814fef4..f2b590ab 100644 --- a/riocli/deployment/list.py +++ b/riocli/deployment/list.py @@ -15,7 +15,8 @@ import click from click_help_colors import HelpColorsCommand -from rapyuta_io.clients.deployment import Deployment, DeploymentPhaseConstants +from riocli.v2client.enums import DeploymentPhaseConstants +from rapyuta_io.clients.deployment import Deployment from riocli.config import new_v2_client from riocli.constants import Colors @@ -23,19 +24,20 @@ from riocli.utils import tabulate_data ALL_PHASES = [ - DeploymentPhaseConstants.INPROGRESS, - DeploymentPhaseConstants.PROVISIONING, - DeploymentPhaseConstants.SUCCEEDED, - DeploymentPhaseConstants.FAILED_TO_START, - DeploymentPhaseConstants.PARTIALLY_DEPROVISIONED, - DeploymentPhaseConstants.DEPLOYMENT_STOPPED, + DeploymentPhaseConstants.DeploymentPhaseInProgress, + DeploymentPhaseConstants.DeploymentPhaseProvisioning, + DeploymentPhaseConstants.DeploymentPhaseSucceeded, + DeploymentPhaseConstants.DeploymentPhaseStopped, + DeploymentPhaseConstants.DeploymentPhaseFailedToStart, + DeploymentPhaseConstants.DeploymentPhaseFailedToUpdate, ] DEFAULT_PHASES = [ - DeploymentPhaseConstants.INPROGRESS, - DeploymentPhaseConstants.PROVISIONING, - DeploymentPhaseConstants.SUCCEEDED, - DeploymentPhaseConstants.FAILED_TO_START, + DeploymentPhaseConstants.DeploymentPhaseInProgress, + DeploymentPhaseConstants.DeploymentPhaseProvisioning, + DeploymentPhaseConstants.DeploymentPhaseSucceeded, + DeploymentPhaseConstants.DeploymentPhaseFailedToStart, + DeploymentPhaseConstants.DeploymentPhaseFailedToUpdate ] @@ -46,7 +48,7 @@ help_options_color=Colors.GREEN, ) @click.option('--device', prompt_required=False, default='', type=str, - help='Filter the Deployment list by Device ID') + help='Filter the Deployment list by Device name') @click.option('--phase', prompt_required=False, multiple=True, type=click.Choice(ALL_PHASES), default=DEFAULT_PHASES, @@ -63,7 +65,7 @@ def list_deployments( """ try: client = new_v2_client(with_project=True) - deployments = client.list_deployments() + deployments = client.list_deployments(query={"phases": phase, "deviceName": device}) deployments = sorted(deployments, key=lambda d: d.metadata.name.lower()) display_deployment_list(deployments, show_header=True) except Exception as e: diff --git a/riocli/deployment/status.py b/riocli/deployment/status.py index 9e9779fa..d5636876 100644 --- a/riocli/deployment/status.py +++ b/riocli/deployment/status.py @@ -33,7 +33,7 @@ def status(deployment_name: str) -> None: try: client = new_v2_client() deployment = client.get_deployment(deployment_name) - click.secho(deployment.status) + click.secho(deployment.status.aggregateStatus) except Exception as e: click.secho(str(e), fg=Colors.RED) raise SystemExit(1) diff --git a/riocli/deployment/wait.py b/riocli/deployment/wait.py index 56d0f4fd..1a0c7d8c 100644 --- a/riocli/deployment/wait.py +++ b/riocli/deployment/wait.py @@ -12,26 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. import click -import typing -import time from click_help_colors import HelpColorsCommand -from rapyuta_io.utils import RetriesExhausted, DeploymentNotRunningException from riocli.config import new_v2_client -from riocli.constants import Colors, Symbols, Status -from riocli.deployment.util import name_to_guid +from riocli.constants import Colors, Symbols from riocli.utils.spinner import with_spinner +from riocli.v2client.error import RetriesExhausted, DeploymentNotRunning -from rapyuta_io import Client - -def poll_deployment_till_ready(client: Client, deployment: typing.Any, retry_count=50, sleep_interval=6): - for _ in range(retry_count): - if deployment.status.status == Status.RUNNING: - return deployment - - time.sleep(sleep_interval) - deployment = client.get_deployment(deployment.metadata.name) - return deployment @click.command( 'wait', @@ -50,25 +37,18 @@ def wait_for_deployment( """ try: client = new_v2_client() - deployment = client.get_deployment(deployment_name) - - retry_count = int(kwargs.get('retry_count')) - retry_interval = int(kwargs.get('retry_interval')) - deployment = poll_deployment_till_ready(client, deployment, retry_count, retry_interval) + deployment = client.poll_deployment(deployment_name) spinner.text = click.style('Deployment status: {}'.format(deployment.status.status), fg=Colors.GREEN) spinner.green.ok(Symbols.SUCCESS) except RetriesExhausted as e: spinner.write(click.style(str(e), fg=Colors.RED)) - spinner.text = click.style('Try again?', Colors.RED) + spinner.text = click.style('Try again', Colors.RED) spinner.red.fail(Symbols.ERROR) - except DeploymentNotRunningException as e: - if 'DEP_E151' in e.deployment_status.errors: - spinner.text = click.style('Device is either offline or not reachable', fg=Colors.RED) - else: - spinner.text = click.style(str(e), fg=Colors.RED) + except DeploymentNotRunning as e: + spinner.text = click.style(str(e), fg=Colors.RED) spinner.red.fail(Symbols.ERROR) raise SystemExit(1) except Exception as e: spinner.text = click.style(str(e), fg=Colors.RED) spinner.red.fail(Symbols.ERROR) - raise SystemExit(1) + raise SystemExit(1) \ No newline at end of file diff --git a/riocli/v2client/client.py b/riocli/v2client/client.py index 42fe7607..85b6bb04 100644 --- a/riocli/v2client/client.py +++ b/riocli/v2client/client.py @@ -16,20 +16,26 @@ import http import json import os +import time from hashlib import md5 from typing import List, Optional, Dict, Any +import click import magic import requests from munch import munchify, Munch - from rapyuta_io.utils.rest_client import HttpMethod, RestClient +from riocli.v2client.enums import DeploymentPhaseConstants +from riocli.v2client.error import RetriesExhausted, DeploymentNotRunning + + class DeploymentNotFound(Exception): def __init__(self, message='deployment not found!'): self.message = message super().__init__(self.message) + def handle_server_errors(response: requests.Response): status_code = response.status_code # 500 Internal Server Error @@ -51,11 +57,13 @@ def handle_server_errors(response: requests.Response): if status_code > 504: raise Exception('unknown server error') + class NetworkNotFound(Exception): def __init__(self, message='network not found!'): self.message = message super().__init__(self.message) + class Client(object): """ v2 API Client @@ -722,8 +730,6 @@ def _walk_pages(self, c: RestClient, params: dict = {}, limit: Optional[int] = N return munchify(result) - - def list_packages( self, query: dict = None @@ -816,7 +822,6 @@ def delete_package(self, package_name: str, raise Exception("package: {}".format(err_msg)) return munchify(data) - def list_networks( self, @@ -911,13 +916,13 @@ def delete_network(self, network_name: str, data = json.loads(response.text) if not response.ok: err_msg = data.get('error') - raise Exception("package: {}".format(err_msg)) + raise Exception("network: {}".format(err_msg)) return munchify(data) def list_deployments( - self, - query: dict = None + self, + query: dict = None ) -> Munch: """ List all deployments in a project @@ -947,7 +952,7 @@ def list_deployments( result.extend(deployments) return munchify(result) - + def create_deployment(self, deployment: dict) -> Munch: """ Create a new deployment @@ -965,11 +970,11 @@ def create_deployment(self, deployment: dict) -> Munch: raise Exception("deployment: {}".format(err_msg)) return munchify(data) - + def get_deployment( - self, - name: str, - query: dict = None + self, + name: str, + query: dict = None ): url = "{}/v2/deployments/{}/".format(self._host, name) headers = self._config.get_auth_header() @@ -1024,6 +1029,42 @@ def delete_deployment(self, name: str, query: dict = None) -> Munch: return munchify(data) + def poll_deployment( + self, + name: str, + retry_count: int = 50, + sleep_interval: int = 6, + ready_phases: List[str] = None, + ) -> Munch: + if ready_phases is None: + ready_phases = [] + + deployment = self.get_deployment(name) + + status = deployment.status + + for _ in range(retry_count): + if status.phase in ready_phases: + return deployment + + if status.phase == DeploymentPhaseConstants.DeploymentPhaseProvisioning.value: + errors = status.error_codes or [] + if 'DEP_E153' in errors: # DEP_E153 (image-pull error) will persist across retries + return deployment + elif status.phase == DeploymentPhaseConstants.DeploymentPhaseSucceeded.value: + return deployment + elif status.phase in [DeploymentPhaseConstants.DeploymentPhaseFailedToUpdate.value, + DeploymentPhaseConstants.DeploymentPhaseFailedToStart.value, + DeploymentPhaseConstants.DeploymentPhaseStopped.value]: + raise DeploymentNotRunning('Deployment not running. Deployment status: {}'.format(status.phase)) + + time.sleep(sleep_interval) + deployment = self.get_deployment(name) + status = deployment.status + + raise RetriesExhausted('Retried {} time done with an interval of {} seconds. Deployment status: {}'.format( + retry_count, sleep_interval, status.phase)) + def list_disks( self, query: dict = None diff --git a/riocli/v2client/enums.py b/riocli/v2client/enums.py new file mode 100644 index 00000000..1a7435fa --- /dev/null +++ b/riocli/v2client/enums.py @@ -0,0 +1,33 @@ +import enum + + +class DeploymentPhaseConstants(str, enum.Enum): + """ + Enumeration variables for the deployment phase + """ + + def __str__(self): + return str(self.value) + + DeploymentPhaseInProgress = "InProgress" + DeploymentPhaseProvisioning = "Provisioning" + DeploymentPhaseSucceeded = "Succeeded" + DeploymentPhaseFailedToUpdate = "FailedToUpdate" + DeploymentPhaseFailedToStart = "FailedToStart" + DeploymentPhaseStopped = "Stopped" + + +class DeploymentStatusConstants(str, enum.Enum): + """ + Enumeration variables for the deployment status + + """ + + def __str__(self): + return str(self.value) + + DeploymentStatusRunning = "Running" + DeploymentStatusPending = "Pending" + DeploymentStatusError = "Error" + DeploymentStatusUnknown = "Unknown" + DeploymentStatusStopped = "Stopped" \ No newline at end of file diff --git a/riocli/v2client/error.py b/riocli/v2client/error.py new file mode 100644 index 00000000..98881e67 --- /dev/null +++ b/riocli/v2client/error.py @@ -0,0 +1,9 @@ + +class RetriesExhausted(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) + + +class DeploymentNotRunning(Exception): + def __init__(self, msg=None): + Exception.__init__(self, msg) \ No newline at end of file