diff --git a/.gitignore b/.gitignore index 6f706bf96b555012f29447cf06e44530ac2393f1..ab0739bc5673861de5f36164e4232746b0b53ebe 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ __pycache__/ coverage.xml .tox/device_vendor .vscode -venv \ No newline at end of file +venv +oss-params.json diff --git a/gso/oss-params-example.json b/gso/oss-params-example.json index 98ee26d5e8212038c0b972f01a2f78d0a75aedfd..a13415dd26ddb0a58ad7eb65abcfd191c2e40de2 100644 --- a/gso/oss-params-example.json +++ b/gso/oss-params-example.json @@ -1,4 +1,7 @@ { + "GENERAL": { + "public_hostname": "https://gap.geant.org" + }, "RESOURCE_MANAGER_API_PREFIX": "http://localhost:44444", "IPAM": { "INFOBLOX": { @@ -18,8 +21,9 @@ } }, "PROVISIONING_PROXY": { - "api_base": "http://localhost:44444", + "scheme": "https", + "api_base": "localhost:44444", "auth": "Bearer <token>", "api_version": 1123 } -} \ No newline at end of file +} diff --git a/gso/services/provisioning_proxy.py b/gso/services/provisioning_proxy.py index ec282b84bba0fcd3e39d0729fd3760312de9d1c7..1189647133fde96d469c71f32f188bdc186fa8b0 100644 --- a/gso/services/provisioning_proxy.py +++ b/gso/services/provisioning_proxy.py @@ -1,24 +1,93 @@ import logging -from gso.products.product_types.device \ - import DeviceBlock +import requests +from orchestrator import inputstep +from orchestrator.config.assignee import Assignee +from orchestrator.forms import FormPage, ReadOnlyField +from orchestrator.forms.validators import Accept, Label, LongText +from orchestrator.types import UUIDstr, State + from gso import settings -# import requests +from gso.products.product_types.device import DeviceProvisioning logger = logging.getLogger(__name__) def provision_node( - node_subscription_params: DeviceBlock, + node_subscription_params: DeviceProvisioning, + process_id: UUIDstr, dry_run: bool = True): oss = settings.load_oss_params() pp_params = oss.PROVISIONING_PROXY assert pp_params - logger.debug(f'[disabled] provisioning node {node_subscription_params}') + device_params = node_subscription_params.device + callback_url = f'{settings.load_oss_params().GENERAL.public_hostname}' \ + f'/api/processes/{process_id}/resume' + logger.debug(f'[disabled] provisioning node {device_params}') + + parameters = { + 'callback': callback_url, + 'dry_run': dry_run, + 'device': { + 'fqdn': device_params.fqdn, + 'lo_address': { + 'v4': str(device_params.lo_ipv4_address), + 'v6': str(device_params.lo_ipv6_address) + }, + 'lo_iso_address': device_params.lo_iso_address, + 'si_ipv4_network': str(device_params.si_ipv4_network), + 'ias_lt_network': { + 'v4': str(device_params.ias_lt_ipv4_network), + 'v6': str(device_params.ias_lt_ipv6_network) + }, + 'site_country_code': device_params.site_country_code, + 'site_city': device_params.site_city, + 'site_latitude': device_params.site_latitude, + 'site_longitude': device_params.site_longitude, + 'snmp_location': device_params.snmp_location, + 'device_type': node_subscription_params.device_type, + 'device_vendor': node_subscription_params.device_vendor, + 'ts_address': device_params.ts_address, + 'ts_port': device_params.ts_port + } + } + + post_request = requests.post( + f'{pp_params.scheme}://{pp_params.api_base}' + f'/api/device', + json=parameters) + post_request.raise_for_status() + + +@inputstep('Await provisioning proxy results', assignee=Assignee('SYSTEM')) +def await_pp_results() -> State: + class ProvisioningResultPage(FormPage): + class Config: + title = 'Do NOT click on confirm in this step!' + + warning_label: Label = 'This step relies on an external service to ' \ + 'send an update to the orchestrator, do not ' \ + 'interfere with this process please.' + pp_run_results: dict = {'state': 'not_ready'} + confirm: Accept = Accept('INCOMPLETE') + + result_page = yield ProvisioningResultPage + + return result_page.dict() + + +@inputstep('Confirm provisioning proxy results', assignee=Assignee('SYSTEM')) +def confirm_pp_results(state: State) -> State: + class ConfirmRunPage(FormPage): + class Config: + title = 'Execution completed, please confirm the results.' + + run_status: str = ReadOnlyField(state['pp_run_results']['status']) + run_results: LongText = ReadOnlyField( + f"{state['pp_run_results']['output']}") + confirm: Accept = Accept('INCOMPLETE') + + yield ConfirmRunPage - # r = requests.get( - # f'https://{pp_params.api_base}' - # f'/api/version', - # params=node_subscription_params.dict()) - # r.raise_for_status() + return state diff --git a/gso/settings.py b/gso/settings.py index 0846e5f2ae2f983f7216666df34a8388e0697853..11cebf9d63dcf14633cae189df201ab295d31532 100644 --- a/gso/settings.py +++ b/gso/settings.py @@ -4,6 +4,10 @@ import os from pydantic import BaseSettings +class GeneralParams(BaseSettings): + public_hostname: str + + class InfoBloxParams(BaseSettings): scheme: str wapi_version: str @@ -34,12 +38,14 @@ class IPAMParams(BaseSettings): class ProvisioningProxyParams(BaseSettings): + scheme: str api_base: str auth: str # FIXME: unfinished api_version: int class OSSParams(BaseSettings): + GENERAL: GeneralParams IPAM: IPAMParams RESOURCE_MANAGER_API_PREFIX: str # api prefix PROVISIONING_PROXY: ProvisioningProxyParams diff --git a/gso/workflows/device/create_device.py b/gso/workflows/device/create_device.py index 3f9cddceeb80b40ef79a3e73e65e05b4dd4c8047..91b4374f14098227d74aba592386163f7d1a79b7 100644 --- a/gso/workflows/device/create_device.py +++ b/gso/workflows/device/create_device.py @@ -3,8 +3,6 @@ from uuid import uuid4 from orchestrator.forms import FormPage from orchestrator.targets import Target -from orchestrator.workflow import inputstep -from orchestrator.forms.validators import Accept from orchestrator.types import FormGenerator, State from orchestrator.types import SubscriptionLifecycle, UUIDstr from orchestrator.workflow import done, init, step, workflow @@ -59,18 +57,18 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: return user_input.dict() -@step("Create subscription") +@step('Create subscription') def create_subscription(product: UUIDstr) -> State: - subscription = device.DeviceInactive.from_product_id(product, uuid4()) + subscription = DeviceInactive.from_product_id(product, uuid4()) return { - "subscription": subscription, - "subscription_id": subscription.subscription_id, + 'subscription': subscription, + 'subscription_id': subscription.subscription_id, } -@step("Get information from IPAM ") -def get_info_from_ipam(subscription: device.DeviceInactive) -> State: +@step('Get information from IPAM') +def get_info_from_ipam(subscription: DeviceInactive) -> State: # lo = ipam.new_device_lo_address() # subscription.device.lo_ipv4_address = lo.v4 # subscription.device.lo_ipv6_address = lo.v6 @@ -85,7 +83,7 @@ def get_info_from_ipam(subscription: device.DeviceInactive) -> State: return {"subscription": subscription} -@step("Initialize subscription") +@step('Initialize subscription') def initialize_subscription( subscription: device.DeviceInactive, hostname: str, @@ -199,22 +197,26 @@ def provision_device_real( #return {"return_code": 0} @workflow( - "Create Device", + 'Create device', initial_input_form=wrap_create_initial_input_form( - initial_input_form_generator), + initial_input_form_generator), target=Target.CREATE, ) def create_device(): return ( - init - >> create_subscription - >> store_process_subscription(Target.CREATE) - >> get_info_from_ipam - >> initialize_subscription - >> provision_device_dry - >> confirm_step - >> provision_device_real - >> set_status(SubscriptionLifecycle.ACTIVE) - >> resync - >> done + init + >> create_subscription + >> store_process_subscription(Target.CREATE) + >> get_info_from_ipam + >> get_snmp_info + >> initialize_subscription + >> provision_device_dry + >> await_pp_results + >> confirm_pp_results + >> provision_device_real + >> await_pp_results + >> confirm_pp_results + >> set_status(SubscriptionLifecycle.ACTIVE) + >> resync + >> done ) diff --git a/tox.ini b/tox.ini index 7973b25a04d0a5a1e560b7fe9fe03fea63887bc3..dd02509de947f75dab25e672ed28a3801dc3b28d 100644 --- a/tox.ini +++ b/tox.ini @@ -15,4 +15,3 @@ commands = # coverage report --fail-under 80 coverage report flake8 -