Skip to content
Snippets Groups Projects
Verified Commit a7f2a2ce authored by Karel van Klink's avatar Karel van Klink :smiley_cat:
Browse files

Rework the proxy steps, such that they are retried up to three times if needed

Also, remove a bunch of flake errors that we are not causing
parent 0682669a
No related branches found
No related tags found
No related merge requests found
Pipeline #83582 passed
...@@ -4,6 +4,7 @@ LSO is responsible for executing Ansible playbooks, that deploy subscriptions. ...@@ -4,6 +4,7 @@ LSO is responsible for executing Ansible playbooks, that deploy subscriptions.
""" """
import json import json
import logging import logging
from typing import NoReturn
import requests import requests
from orchestrator import inputstep from orchestrator import inputstep
...@@ -20,6 +21,7 @@ from gso.products.product_types.device import DeviceProvisioning ...@@ -20,6 +21,7 @@ from gso.products.product_types.device import DeviceProvisioning
from gso.products.product_types.iptrunk import Iptrunk, IptrunkProvisioning from gso.products.product_types.iptrunk import Iptrunk, IptrunkProvisioning
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
DEFAULT_LABEL = "Provisioning proxy is running. Please come back later for the results."
class CUDOperation(strEnum): class CUDOperation(strEnum):
...@@ -140,7 +142,7 @@ def deprovision_ip_trunk(subscription: Iptrunk, process_id: UUIDstr, dry_run: bo ...@@ -140,7 +142,7 @@ def deprovision_ip_trunk(subscription: Iptrunk, process_id: UUIDstr, dry_run: bo
@inputstep("Await provisioning proxy results", assignee=Assignee("SYSTEM")) @inputstep("Await provisioning proxy results", assignee=Assignee("SYSTEM"))
def await_pp_results(subscription: SubscriptionModel, label_text: str) -> FormGenerator: def await_pp_results(subscription: SubscriptionModel, label_text: str = DEFAULT_LABEL) -> FormGenerator:
class ProvisioningResultPage(FormPage): class ProvisioningResultPage(FormPage):
class Config: class Config:
title = f"Deploying {subscription.product.name}..." title = f"Deploying {subscription.product.name}..."
...@@ -150,7 +152,7 @@ def await_pp_results(subscription: SubscriptionModel, label_text: str) -> FormGe ...@@ -150,7 +152,7 @@ def await_pp_results(subscription: SubscriptionModel, label_text: str) -> FormGe
confirm: Accept = Accept("INCOMPLETE") confirm: Accept = Accept("INCOMPLETE")
@validator("pp_run_results", allow_reuse=True, pre=True, always=True) @validator("pp_run_results", allow_reuse=True, pre=True, always=True)
def run_results_must_be_given(cls, run_results: dict) -> dict | None: def run_results_must_be_given(cls, run_results: dict) -> dict | NoReturn:
if run_results is None: if run_results is None:
raise ValueError("Run results may not be empty. Wait for the provisioning proxy to finish.") raise ValueError("Run results may not be empty. Wait for the provisioning proxy to finish.")
return run_results return run_results
...@@ -172,8 +174,9 @@ def confirm_pp_results(state: State) -> FormGenerator: ...@@ -172,8 +174,9 @@ def confirm_pp_results(state: State) -> FormGenerator:
run_status: str = ReadOnlyField(state["pp_run_results"]["status"]) run_status: str = ReadOnlyField(state["pp_run_results"]["status"])
run_results: LongText = ReadOnlyField(f"{state['pp_run_results']['output']}") run_results: LongText = ReadOnlyField(f"{state['pp_run_results']['output']}")
pp_result_good_enough: bool = state["pp_run_results"]["return_code"] == 0
confirm: Accept = Accept("INCOMPLETE") confirm: Accept = Accept("INCOMPLETE")
yield ConfirmRunPage confirmation = yield ConfirmRunPage
return state return {"pp_did_succeed": confirmation.pp_result_good_enough}
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
"confirm_info": "Please verify this form looks correct.", "confirm_info": "Please verify this form looks correct.",
"pp_run_results": "Provisioning proxy results are not ready yet.", "pp_run_results": "Provisioning proxy results are not ready yet.",
"pp_result_good_enough": "Provisioning Proxy results look good (enough).",
"pp_result_good_enough_info": "If set to false, this will retry the provisioning up to two times.",
"site_bgp_community_id": "Site BGP community ID", "site_bgp_community_id": "Site BGP community ID",
"site_internal_id": "Site internal ID", "site_internal_id": "Site internal ID",
......
...@@ -9,7 +9,7 @@ from orchestrator.forms import FormPage ...@@ -9,7 +9,7 @@ from orchestrator.forms import FormPage
from orchestrator.forms.validators import Choice from orchestrator.forms.validators import Choice
from orchestrator.targets import Target from orchestrator.targets import Target
from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
from orchestrator.workflow import StepList, done, init, step, workflow from orchestrator.workflow import StepList, conditional, done, init, step, workflow
from orchestrator.workflows.steps import resync, set_status, store_process_subscription from orchestrator.workflows.steps import resync, set_status, store_process_subscription
from orchestrator.workflows.utils import wrap_create_initial_input_form from orchestrator.workflows.utils import wrap_create_initial_input_form
...@@ -118,7 +118,7 @@ def initialize_subscription( ...@@ -118,7 +118,7 @@ def initialize_subscription(
subscription = device.DeviceProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING) subscription = device.DeviceProvisioning.from_other_lifecycle(subscription, SubscriptionLifecycle.PROVISIONING)
return {"subscription": subscription} return {"subscription": subscription, "pp_did_succeed": False}
@step("Provision device [DRY RUN]") @step("Provision device [DRY RUN]")
...@@ -153,24 +153,50 @@ def provision_device_real(subscription: DeviceProvisioning, process_id: UUIDstr) ...@@ -153,24 +153,50 @@ def provision_device_real(subscription: DeviceProvisioning, process_id: UUIDstr)
} }
@step("Reset Provisioning Proxy state")
def reset_pp_success_state() -> State:
return {"pp_did_succeed": False}
@workflow( @workflow(
"Create device", "Create device",
initial_input_form=wrap_create_initial_input_form(initial_input_form_generator), initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
target=Target.CREATE, target=Target.CREATE,
) )
def create_device() -> StepList: def create_device() -> StepList:
should_retry_pp_steps = conditional(lambda state: not state.get("pp_did_succeed"))
return ( return (
init init
>> create_subscription >> create_subscription
>> store_process_subscription(Target.CREATE) >> store_process_subscription(Target.CREATE)
>> initialize_subscription >> initialize_subscription
>> get_info_from_ipam >> get_info_from_ipam
>> provision_device_dry # First attempt at dry run
>> await_pp_results >> should_retry_pp_steps(provision_device_dry)
>> confirm_pp_results >> should_retry_pp_steps(await_pp_results)
>> provision_device_real >> should_retry_pp_steps(confirm_pp_results)
>> await_pp_results # Second attempt
>> confirm_pp_results >> should_retry_pp_steps(provision_device_dry)
>> should_retry_pp_steps(await_pp_results)
>> should_retry_pp_steps(confirm_pp_results)
# Third and last try
>> should_retry_pp_steps(provision_device_dry)
>> should_retry_pp_steps(await_pp_results)
>> should_retry_pp_steps(confirm_pp_results)
>> reset_pp_success_state
# First attempt at provisioning
>> should_retry_pp_steps(provision_device_real)
>> should_retry_pp_steps(await_pp_results)
>> should_retry_pp_steps(confirm_pp_results)
# Second attempt
>> should_retry_pp_steps(provision_device_real)
>> should_retry_pp_steps(await_pp_results)
>> should_retry_pp_steps(confirm_pp_results)
# Third and last try
>> should_retry_pp_steps(provision_device_real)
>> should_retry_pp_steps(await_pp_results)
>> should_retry_pp_steps(confirm_pp_results)
>> set_status(SubscriptionLifecycle.ACTIVE) >> set_status(SubscriptionLifecycle.ACTIVE)
>> resync >> resync
>> done >> done
......
[flake8] [flake8]
ignore = D100,D101,D102,D103,D104,D105,D106,D107,D202,E501,RST301,RST304,W503,E203,C417,T202,S101 ; Allow >> on newline (W503), and allow cls as first argument for pydantic validators (B902)
; extend-ignore = E203 ignore = B902,W503
exclude = .git,.*_cache,.eggs,*.egg-info,__pycache__,venv,.tox,gso/migrations,docs exclude = .git,.*_cache,.eggs,*.egg-info,__pycache__,venv,.tox,gso/migrations,docs
enable-extensions = G enable-extensions = G
select = B,C,D,E,F,G,I,N,S,T,W,B902,B903,R select = B,C,D,E,F,G,I,N,S,T,W,B902,B903,R
max-line-length = 120 max-line-length = 120
ban-relative-imports = true ban-relative-imports = true
per-file-ignores =
# Allow first argument to be cls instead of self for pydantic validators
gso/*: B902
[testenv] [testenv]
deps = deps =
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment