Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • goat/gap/geant-service-orchestrator
1 result
Show changes
Commits on Source (7)
{ {
"GENERAL": { "GENERAL": {
"public_hostname": "https://gap.geant.org" "public_hostname": "https://gap.geant.org",
"isis_high_metric": 999999
}, },
"NETBOX": { "NETBOX": {
"api": "https://127.0.0.1:8000", "api": "https://127.0.0.1:8000",
...@@ -15,37 +16,37 @@ ...@@ -15,37 +16,37 @@
"password": "robot-user-password" "password": "robot-user-password"
}, },
"LO": { "LO": {
"V4": {"containers": [], "networks": ["1.1.0.0/24"], "mask": 0}, "V4": {"containers": [], "networks": ["10.255.255.0/26"], "mask": 32},
"V6": {"containers": [], "networks": ["dead:beef::/64"], "mask": 0}, "V6": {"containers": [], "networks": ["dead:beef::/80"], "mask": 128},
"domain_name": ".lo", "domain_name": ".geant.net",
"dns_view": "default", "dns_view": "default",
"network_view": "default" "network_view": "default"
}, },
"TRUNK": { "TRUNK": {
"V4": {"containers": ["1.1.1.0/24"], "networks": [], "mask": 31}, "V4": {"containers": ["10.255.255.0/24", "10.255.254.0/24"], "networks": [], "mask": 31},
"V6": {"containers": ["dead:beef::/64"], "networks": [], "mask": 126}, "V6": {"containers": ["dead:beef::/64", "dead:beee::/64"], "networks": [], "mask": 126},
"domain_name": ".trunk", "domain_name": ".trunk",
"dns_view": "default", "dns_view": "default",
"network_view": "default" "network_view": "default"
}, },
"GEANT_IP": { "GEANT_IP": {
"V4": {"containers": ["1.1.2.0/24"], "networks": [], "mask": 31}, "V4": {"containers": ["10.255.255.0/24", "10.255.254.0/24"], "networks": [], "mask": 31},
"V6": {"containers": ["dead:beef::/64"], "networks": [], "mask": 126}, "V6": {"containers": ["dead:beef::/64", "dead:beee::/64"], "networks": [], "mask": 126},
"domain_name": ".geantip", "domain_name": ".geantip",
"dns_view": "default", "dns_view": "default",
"network_view": "default" "network_view": "default"
}, },
"SI": { "SI": {
"V4": {"containers": ["1.1.3.0/24"], "networks": [], "mask": 31}, "V4": {"containers": ["10.255.253.128/25"], "networks": [], "mask": 31},
"V6": {"containers": ["dead:beef::/64"], "networks": [], "mask": 126}, "V6": {"containers": [], "networks": [], "mask": 126},
"domain_name": ".si", "domain_name": ".geantip",
"dns_view": "default", "dns_view": "default",
"network_view": "default" "network_view": "default"
}, },
"LT_IAS": { "LT_IAS": {
"V4": {"containers": ["1.1.4.0/24"], "networks": [], "mask": 31}, "V4": {"containers": ["10.255.255.0/24"], "networks": [], "mask": 31},
"V6": {"containers": ["dead:beef::/64"], "networks": [], "mask": 126}, "V6": {"containers": ["dead:beef:cc::/48"], "networks": [], "mask": 126},
"domain_name": ".ltias", "domain_name": ".geantip",
"dns_view": "default", "dns_view": "default",
"network_view": "default" "network_view": "default"
} }
...@@ -90,5 +91,8 @@ ...@@ -90,5 +91,8 @@
"starttls_enabled": true, "starttls_enabled": true,
"smtp_username": "username", "smtp_username": "username",
"smtp_password": "password" "smtp_password": "password"
},
"SHAREPOINT": {
"checklist_site_url": "https://example.sharepoint.com/sites/example-site"
} }
} }
...@@ -21,6 +21,7 @@ class GeneralParams(BaseSettings): ...@@ -21,6 +21,7 @@ class GeneralParams(BaseSettings):
public_hostname: str public_hostname: str
"""The hostname that :term:`GSO` is publicly served at, used for building the callback URL that the provisioning """The hostname that :term:`GSO` is publicly served at, used for building the callback URL that the provisioning
proxy uses.""" proxy uses."""
isis_high_metric: int
class CeleryParams(BaseSettings): class CeleryParams(BaseSettings):
...@@ -165,6 +166,13 @@ class EmailParams(BaseSettings): ...@@ -165,6 +166,13 @@ class EmailParams(BaseSettings):
smtp_password: str | None smtp_password: str | None
class SharepointParams(BaseSettings):
"""Settings for different Sharepoint sites."""
# TODO: Stricter typing after Pydantic 2.x upgrade
checklist_site_url: str
class OSSParams(BaseSettings): class OSSParams(BaseSettings):
"""The set of parameters required for running :term:`GSO`.""" """The set of parameters required for running :term:`GSO`."""
...@@ -176,6 +184,7 @@ class OSSParams(BaseSettings): ...@@ -176,6 +184,7 @@ class OSSParams(BaseSettings):
CELERY: CeleryParams CELERY: CeleryParams
THIRD_PARTY_API_KEYS: dict[str, str] THIRD_PARTY_API_KEYS: dict[str, str]
EMAIL: EmailParams EMAIL: EmailParams
SHAREPOINT: SharepointParams
def load_oss_params() -> OSSParams: def load_oss_params() -> OSSParams:
......
...@@ -9,6 +9,7 @@ from orchestrator.utils.json import json_dumps ...@@ -9,6 +9,7 @@ from orchestrator.utils.json import json_dumps
from gso.products.product_types.iptrunk import Iptrunk from gso.products.product_types.iptrunk import Iptrunk
from gso.services.provisioning_proxy import execute_playbook from gso.services.provisioning_proxy import execute_playbook
from gso.settings import load_oss_params
def _deploy_base_config( def _deploy_base_config(
...@@ -62,11 +63,12 @@ def deploy_base_config_real( ...@@ -62,11 +63,12 @@ def deploy_base_config_real(
return {"subscription": subscription} return {"subscription": subscription}
@step("[COMMIT] Set ISIS metric to 90.000") @step("[COMMIT] Set ISIS metric to very high value")
def set_isis_to_90000(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State: def set_isis_to_max(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
"""Workflow step for setting the :term:`ISIS` metric to 90k as an arbitrarily high value to drain a link.""" """Workflow step for setting the :term:`ISIS` metric to an arbitrarily high value to drain a link."""
old_isis_metric = subscription.iptrunk.iptrunk_isis_metric old_isis_metric = subscription.iptrunk.iptrunk_isis_metric
subscription.iptrunk.iptrunk_isis_metric = 90000 params = load_oss_params()
subscription.iptrunk.iptrunk_isis_metric = params.GENERAL.isis_high_metric
extra_vars = { extra_vars = {
"wfo_trunk_json": json.loads(json_dumps(subscription)), "wfo_trunk_json": json.loads(json_dumps(subscription)),
"dry_run": False, "dry_run": False,
......
...@@ -3,12 +3,13 @@ ...@@ -3,12 +3,13 @@
import json import json
from uuid import uuid4 from uuid import uuid4
from orchestrator.config.assignee import Assignee
from orchestrator.forms import FormPage from orchestrator.forms import FormPage
from orchestrator.forms.validators import Choice, UniqueConstrainedList from orchestrator.forms.validators import Choice, Label, UniqueConstrainedList
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.utils.json import json_dumps from orchestrator.utils.json import json_dumps
from orchestrator.workflow import StepList, conditional, done, init, step, workflow from orchestrator.workflow import StepList, conditional, done, init, inputstep, 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
from pydantic import validator from pydantic import validator
...@@ -21,12 +22,13 @@ from gso.products.product_blocks.iptrunk import ( ...@@ -21,12 +22,13 @@ from gso.products.product_blocks.iptrunk import (
IptrunkType, IptrunkType,
PhyPortCapacity, PhyPortCapacity,
) )
from gso.products.product_types.iptrunk import IptrunkInactive from gso.products.product_types.iptrunk import IptrunkInactive, IptrunkProvisioning
from gso.products.product_types.router import Router from gso.products.product_types.router import Router
from gso.services import infoblox, subscriptions from gso.services import infoblox, subscriptions
from gso.services.crm import get_customer_by_name from gso.services.crm import get_customer_by_name
from gso.services.netbox_client import NetboxClient from gso.services.netbox_client import NetboxClient
from gso.services.provisioning_proxy import execute_playbook, pp_interaction from gso.services.provisioning_proxy import execute_playbook, pp_interaction
from gso.settings import load_oss_params
from gso.utils.helpers import ( from gso.utils.helpers import (
LAGMember, LAGMember,
available_interfaces_choices, available_interfaces_choices,
...@@ -220,13 +222,14 @@ def initialize_subscription( ...@@ -220,13 +222,14 @@ def initialize_subscription(
side_b_ae_members: list[dict], side_b_ae_members: list[dict],
) -> State: ) -> State:
"""Take all input from the user, and store it in the database.""" """Take all input from the user, and store it in the database."""
oss_params = load_oss_params()
side_a = Router.from_subscription(side_a_node_id).router side_a = Router.from_subscription(side_a_node_id).router
side_b = Router.from_subscription(side_b_node_id).router side_b = Router.from_subscription(side_b_node_id).router
subscription.iptrunk.geant_s_sid = geant_s_sid subscription.iptrunk.geant_s_sid = geant_s_sid
subscription.iptrunk.iptrunk_description = iptrunk_description subscription.iptrunk.iptrunk_description = iptrunk_description
subscription.iptrunk.iptrunk_type = iptrunk_type subscription.iptrunk.iptrunk_type = iptrunk_type
subscription.iptrunk.iptrunk_speed = iptrunk_speed subscription.iptrunk.iptrunk_speed = iptrunk_speed
subscription.iptrunk.iptrunk_isis_metric = 90000 subscription.iptrunk.iptrunk_isis_metric = oss_params.GENERAL.isis_high_metric
subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = side_a subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = side_a
...@@ -398,6 +401,22 @@ def check_ip_trunk_isis( ...@@ -398,6 +401,22 @@ def check_ip_trunk_isis(
return {"subscription": subscription} return {"subscription": subscription}
@step("Register DNS records for both sides of the trunk")
def register_dns_records(subscription: IptrunkInactive) -> State:
"""Register DNS records for both sides of the newly created IPtrunk."""
for index, side in enumerate(subscription.iptrunk.iptrunk_sides):
fqdn = f"{side.iptrunk_side_ae_iface}-0.{side.iptrunk_side_node.router_fqdn}"
if not (subscription.iptrunk.iptrunk_ipv4_network and subscription.iptrunk.iptrunk_ipv6_network):
msg = f"Missing IP resources in trunk, cannot allocate DNS record for side {fqdn}!"
raise ValueError(msg)
ipv4_addr = subscription.iptrunk.iptrunk_ipv4_network[index]
ipv6_addr = subscription.iptrunk.iptrunk_ipv6_network[index]
infoblox.create_host_by_ip(fqdn, ipv4_addr, ipv6_addr, "TRUNK", str(subscription.subscription_id))
return {"subscription": subscription}
@step("NextBox integration") @step("NextBox integration")
def reserve_interfaces_in_netbox(subscription: IptrunkInactive) -> State: def reserve_interfaces_in_netbox(subscription: IptrunkInactive) -> State:
"""Create the :term:`LAG` interfaces in NetBox and attach the lag interfaces to the physical interfaces.""" """Create the :term:`LAG` interfaces in NetBox and attach the lag interfaces to the physical interfaces."""
...@@ -454,6 +473,27 @@ def netbox_allocate_side_b_interfaces(subscription: IptrunkInactive) -> None: ...@@ -454,6 +473,27 @@ def netbox_allocate_side_b_interfaces(subscription: IptrunkInactive) -> None:
_allocate_interfaces_in_netbox(subscription.iptrunk.iptrunk_sides[1]) _allocate_interfaces_in_netbox(subscription.iptrunk.iptrunk_sides[1])
@inputstep("Prompt for new Sharepoint checklist", assignee=Assignee.SYSTEM)
def prompt_start_new_checklist(subscription: IptrunkProvisioning) -> FormGenerator:
"""Prompt the operator to start a new checklist in Sharepoint for approving this new IP trunk."""
oss_params = load_oss_params()
class SharepointPrompt(FormPage):
class Config:
title = "Start new checklist"
info_label_1: Label = (
f"Visit {oss_params.SHAREPOINT.checklist_site_url} and start a new Sharepoint checklist for an IPtrunk " # type: ignore[assignment]
f"from {subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_fqdn} to "
f"{subscription.iptrunk.iptrunk_sides[1].iptrunk_side_node.router_fqdn}."
)
info_label_2: Label = "Once this is done, click proceed to finish the workflow." # type: ignore[assignment]
yield SharepointPrompt
return {}
@workflow( @workflow(
"Create IP trunk", "Create IP trunk",
initial_input_form=wrap_create_initial_input_form(initial_input_form_generator), initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
...@@ -488,9 +528,11 @@ def create_iptrunk() -> StepList: ...@@ -488,9 +528,11 @@ def create_iptrunk() -> StepList:
>> pp_interaction(provision_ip_trunk_isis_iface_dry) >> pp_interaction(provision_ip_trunk_isis_iface_dry)
>> pp_interaction(provision_ip_trunk_isis_iface_real) >> pp_interaction(provision_ip_trunk_isis_iface_real)
>> pp_interaction(check_ip_trunk_isis) >> pp_interaction(check_ip_trunk_isis)
>> register_dns_records
>> side_a_is_nokia(netbox_allocate_side_a_interfaces) >> side_a_is_nokia(netbox_allocate_side_a_interfaces)
>> side_b_is_nokia(netbox_allocate_side_b_interfaces) >> side_b_is_nokia(netbox_allocate_side_b_interfaces)
>> set_status(SubscriptionLifecycle.PROVISIONING) >> set_status(SubscriptionLifecycle.PROVISIONING)
>> prompt_start_new_checklist
>> resync >> resync
>> done >> done
) )
...@@ -41,7 +41,7 @@ from gso.utils.helpers import ( ...@@ -41,7 +41,7 @@ from gso.utils.helpers import (
validate_tt_number, validate_tt_number,
) )
from gso.utils.shared_enums import Vendor from gso.utils.shared_enums import Vendor
from gso.utils.workflow_steps import set_isis_to_90000 from gso.utils.workflow_steps import set_isis_to_max
def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
...@@ -656,7 +656,7 @@ def migrate_iptrunk() -> StepList: ...@@ -656,7 +656,7 @@ def migrate_iptrunk() -> StepList:
>> unsync >> unsync
>> new_side_is_nokia(netbox_reserve_interfaces) >> new_side_is_nokia(netbox_reserve_interfaces)
>> calculate_old_side_data >> calculate_old_side_data
>> pp_interaction(set_isis_to_90000) >> pp_interaction(set_isis_to_max)
>> pp_interaction(disable_old_config_dry) >> pp_interaction(disable_old_config_dry)
>> pp_interaction(disable_old_config_real) >> pp_interaction(disable_old_config_real)
>> pp_interaction(deploy_new_config_dry) >> pp_interaction(deploy_new_config_dry)
......
...@@ -25,7 +25,7 @@ from gso.services.netbox_client import NetboxClient ...@@ -25,7 +25,7 @@ from gso.services.netbox_client import NetboxClient
from gso.services.provisioning_proxy import execute_playbook, pp_interaction from gso.services.provisioning_proxy import execute_playbook, pp_interaction
from gso.utils.helpers import get_router_vendor, validate_tt_number from gso.utils.helpers import get_router_vendor, validate_tt_number
from gso.utils.shared_enums import Vendor from gso.utils.shared_enums import Vendor
from gso.utils.workflow_steps import set_isis_to_90000 from gso.utils.workflow_steps import set_isis_to_max
def initial_input_form_generator() -> FormGenerator: def initial_input_form_generator() -> FormGenerator:
...@@ -171,7 +171,7 @@ def terminate_iptrunk() -> StepList: ...@@ -171,7 +171,7 @@ def terminate_iptrunk() -> StepList:
config_steps = ( config_steps = (
init init
>> pp_interaction(set_isis_to_90000) >> pp_interaction(set_isis_to_max)
>> pp_interaction(deprovision_ip_trunk_dry) >> pp_interaction(deprovision_ip_trunk_dry)
>> pp_interaction(deprovision_ip_trunk_real) >> pp_interaction(deprovision_ip_trunk_real)
) )
......
...@@ -14,12 +14,13 @@ from pydantic import validator ...@@ -14,12 +14,13 @@ from pydantic import validator
from pydantic_forms.core import ReadOnlyField from pydantic_forms.core import ReadOnlyField
from gso.products.product_blocks.router import RouterRole from gso.products.product_blocks.router import RouterRole
from gso.products.product_types.router import RouterInactive from gso.products.product_types.router import RouterInactive, RouterProvisioning
from gso.products.product_types.site import Site from gso.products.product_types.site import Site
from gso.services import infoblox, subscriptions from gso.services import infoblox, subscriptions
from gso.services.crm import get_customer_by_name from gso.services.crm import get_customer_by_name
from gso.services.netbox_client import NetboxClient from gso.services.netbox_client import NetboxClient
from gso.services.provisioning_proxy import pp_interaction from gso.services.provisioning_proxy import pp_interaction
from gso.settings import load_oss_params
from gso.utils.helpers import generate_fqdn, iso_from_ipv4 from gso.utils.helpers import generate_fqdn, iso_from_ipv4
from gso.utils.shared_enums import PortNumber, Vendor from gso.utils.shared_enums import PortNumber, Vendor
from gso.utils.workflow_steps import deploy_base_config_dry, deploy_base_config_real, run_checks_after_base_config from gso.utils.workflow_steps import deploy_base_config_dry, deploy_base_config_real, run_checks_after_base_config
...@@ -203,6 +204,45 @@ def prompt_insert_in_ims() -> FormGenerator: ...@@ -203,6 +204,45 @@ def prompt_insert_in_ims() -> FormGenerator:
return {} return {}
@inputstep("Prompt RADIUS insertion", assignee=Assignee.SYSTEM)
def prompt_insert_in_radius(subscription: RouterInactive) -> FormGenerator:
"""Wait for confirmation from an operator that the router has been inserted in RADIUS."""
class RadiusPrompt(FormPage):
class Config:
title = "Update RADIUS clients"
info_label_1: Label = (
f"Please go to https://kratos.geant.org/add_radius_client and add the {subscription.router.router_fqdn}" # type: ignore[assignment]
f" - {subscription.router.router_lo_ipv4_address} to radius authentication"
)
info_label_2: Label = "This will be functionally checked later during verification work." # type: ignore[assignment]
yield RadiusPrompt
return {}
@inputstep("Prompt for new Sharepoint checklist", assignee=Assignee.SYSTEM)
def prompt_start_new_checklist(subscription: RouterProvisioning) -> FormGenerator:
"""Prompt the operator to start a new checklist in Sharepoint for approving this new router."""
oss_params = load_oss_params()
class SharepointPrompt(FormPage):
class Config:
title = "Start new checklist"
info_label_1: Label = (
f"Visit {oss_params.SHAREPOINT.checklist_site_url} and start a new Sharepoint checklist for "
f"{subscription.router.router_fqdn}." # type: ignore[assignment]
)
info_label_2: Label = "Once this is done, click proceed to finish the workflow." # type: ignore[assignment]
yield SharepointPrompt
return {}
@workflow( @workflow(
"Create router", "Create router",
initial_input_form=wrap_create_initial_input_form(initial_input_form_generator), initial_input_form=wrap_create_initial_input_form(initial_input_form_generator),
...@@ -231,9 +271,11 @@ def create_router() -> StepList: ...@@ -231,9 +271,11 @@ def create_router() -> StepList:
>> prompt_reboot_router >> prompt_reboot_router
>> prompt_console_login >> prompt_console_login
>> prompt_insert_in_ims >> prompt_insert_in_ims
>> prompt_insert_in_radius
>> router_is_nokia(create_netbox_device) >> router_is_nokia(create_netbox_device)
>> pp_interaction(run_checks_after_base_config) >> pp_interaction(run_checks_after_base_config)
>> set_status(SubscriptionLifecycle.PROVISIONING) >> set_status(SubscriptionLifecycle.PROVISIONING)
>> prompt_start_new_checklist
>> resync >> resync
>> done >> done
) )
...@@ -11,7 +11,7 @@ def test_router_subscriptions_endpoint_with_valid_api_key(test_client, nokia_rou ...@@ -11,7 +11,7 @@ def test_router_subscriptions_endpoint_with_valid_api_key(test_client, nokia_rou
nokia_router_subscription_factory(status=SubscriptionLifecycle.INITIAL) nokia_router_subscription_factory(status=SubscriptionLifecycle.INITIAL)
response = test_client.get( response = test_client.get(
ROUTER_SUBSCRIPTION_ENDPOINT, headers={"Authorization": "Bearer REALY_random_AND_3cure_T0keN"} ROUTER_SUBSCRIPTION_ENDPOINT, headers={"Authorization": "Bearer another_REALY_random_AND_3cure_T0keN"}
) )
assert response.status_code == 200 assert response.status_code == 200
......
import contextlib import contextlib
import json import ipaddress
import logging import logging
import os import os
import socket
import tempfile
from pathlib import Path from pathlib import Path
import orchestrator import orchestrator
...@@ -44,6 +42,23 @@ class UseJuniperSide(strEnum): ...@@ -44,6 +42,23 @@ class UseJuniperSide(strEnum):
class FakerProvider(BaseProvider): class FakerProvider(BaseProvider):
def ipv4_network(self, *, min_subnet=1, max_subnet=32) -> ipaddress.IPv4Network:
subnet = str(self.generator.random_int(min=min_subnet, max=max_subnet))
ipv4 = self.generator.ipv4()
interface = ipaddress.IPv4Interface(ipv4 + "/" + subnet)
# Extra step for converting ``10.53.92.39/24`` to ``10.53.92.0/24``
network = interface.network.network_address
return ipaddress.IPv4Network(str(network) + "/" + subnet)
def ipv6_network(self, *, min_subnet=1, max_subnet=128) -> ipaddress.IPv6Network:
subnet = str(self.generator.random_int(min=min_subnet, max=max_subnet))
ipv6 = self.generator.ipv6()
interface = ipaddress.IPv6Interface(ipv6 + "/" + subnet)
network = interface.network.network_address
return ipaddress.IPv6Network(str(network) + "/" + subnet)
def tt_number(self) -> str: def tt_number(self) -> str:
random_date = self.generator.date(pattern="%Y%m%d") random_date = self.generator.date(pattern="%Y%m%d")
random_int = self.generator.random_int(min=10000000, max=99999999) random_int = self.generator.random_int(min=10000000, max=99999999)
...@@ -91,149 +106,14 @@ def faker() -> Faker: ...@@ -91,149 +106,14 @@ def faker() -> Faker:
return fake return fake
@pytest.fixture(scope="session")
def configuration_data() -> dict:
with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.bind(("", 0))
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
yield {
"GENERAL": {"public_hostname": "https://orchestrator.dev.gap.geant.org"},
"NETBOX": {"api": "https://127.0.0.1:8000", "token": "TOKEN"},
"IPAM": {
"INFOBLOX": {
"scheme": "https",
"wapi_version": "2.12",
"host": "10.0.0.1",
"username": "robot-user",
"password": "robot-user-password",
},
"LO": {
"V4": {
"containers": [],
"networks": ["10.255.255.0/26"],
"mask": 32,
},
"V6": {
"containers": [],
"networks": ["dead:beef::/80"],
"mask": 128,
},
"domain_name": ".geant.net",
"dns_view": "default",
"network_view": "default",
},
"TRUNK": {
"V4": {
"containers": ["10.255.255.0/24", "10.255.254.0/24"],
"networks": [],
"mask": 31,
},
"V6": {
"containers": ["dead:beef::/64", "dead:beee::/64"],
"networks": [],
"mask": 126,
},
"domain_name": ".trunk",
"dns_view": "default",
"network_view": "default",
},
"GEANT_IP": {
"V4": {
"containers": ["10.255.255.0/24", "10.255.254.0/24"],
"networks": [],
"mask": 31,
},
"V6": {
"containers": ["dead:beef::/64", "dead:beee::/64"],
"networks": [],
"mask": 126,
},
"domain_name": ".geantip",
"dns_view": "default",
"network_view": "default",
},
"SI": {
"V4": {
"containers": ["10.255.253.128/25"],
"networks": [],
"mask": 31,
},
"V6": {"containers": [], "networks": [], "mask": 126},
"domain_name": ".geantip",
"dns_view": "default",
"network_view": "default",
},
"LT_IAS": {
"V4": {
"containers": ["10.255.255.0/24"],
"networks": [],
"mask": 31,
},
"V6": {
"containers": ["dead:beef:cc::/48"],
"networks": [],
"mask": 126,
},
"domain_name": ".geantip",
"dns_view": "default",
"network_view": "default",
},
},
"MONITORING": {
"LIBRENMS": {
"base_url": "http://librenms",
"token": "secret-token",
},
"SNMP": {
"v2c": {
"community": "fake-community",
},
"v3": {
"authlevel": "AuthPriv",
"authname": "librenms",
"authpass": "<password1>",
"authalgo": "sha",
"cryptopass": "<password2>",
"cryptoalgo": "aes",
},
},
},
"PROVISIONING_PROXY": {
"scheme": "https",
"api_base": "localhost:44444",
"auth": "Bearer <token>",
"api_version": 1123,
},
"CELERY": {
"broker_url": "redis://localhost:6379",
"result_backend": "rpc://localhost:6379/0",
"result_expires": 3600,
},
"THIRD_PARTY_API_KEYS": {
"AnsibleDynamicInventoryGenerator": "REALY_random_AND_3cure_T0keN",
"Application_2": "another_REALY_random_AND_3cure_T0keN",
},
"EMAIL": {
"from_address": "noreply@nren.local",
"smtp_host": "smtp.nren.local",
"smtp_port": 487,
"starttls_enabled": True,
"smtp_username": "username",
"smtp_password": "password",
},
}
@pytest.fixture(scope="session", autouse=True) @pytest.fixture(scope="session", autouse=True)
def data_config_filename(configuration_data) -> str: def data_config_filename() -> str:
"""Create a temporary file with configuration data and set an environment variable to its path.""" """Set an environment variable to the path of the example OSS parameters file."""
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as f: config_filename = "gso/oss-params-example.json"
json.dump(configuration_data, f, ensure_ascii=False)
os.environ["OSS_PARAMS_FILENAME"] = f.name
yield f.name os.environ["OSS_PARAMS_FILENAME"] = config_filename
yield config_filename
del os.environ["OSS_PARAMS_FILENAME"] del os.environ["OSS_PARAMS_FILENAME"]
Path(f.name).unlink()
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
......
...@@ -8,12 +8,15 @@ from gso.products.product_blocks.iptrunk import IptrunkType, PhyPortCapacity ...@@ -8,12 +8,15 @@ from gso.products.product_blocks.iptrunk import IptrunkType, PhyPortCapacity
from gso.services.subscriptions import get_product_id_by_name from gso.services.subscriptions import get_product_id_by_name
from gso.utils.helpers import LAGMember from gso.utils.helpers import LAGMember
from gso.utils.shared_enums import Vendor from gso.utils.shared_enums import Vendor
from test import USER_CONFIRM_EMPTY_FORM
from test.services.conftest import MockedNetboxClient from test.services.conftest import MockedNetboxClient
from test.workflows import ( from test.workflows import (
assert_complete, assert_complete,
assert_pp_interaction_failure, assert_pp_interaction_failure,
assert_pp_interaction_success, assert_pp_interaction_success,
assert_suspended,
extract_state, extract_state,
resume_workflow,
run_workflow, run_workflow,
) )
...@@ -97,7 +100,9 @@ def input_form_wizard_data(request, juniper_router_subscription_factory, nokia_r ...@@ -97,7 +100,9 @@ def input_form_wizard_data(request, juniper_router_subscription_factory, nokia_r
@patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook") @patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook")
@patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network")
@patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network")
@patch("gso.workflows.iptrunk.create_iptrunk.infoblox.create_host_by_ip")
def test_successful_iptrunk_creation_with_standard_lso_result( def test_successful_iptrunk_creation_with_standard_lso_result(
mock_create_host,
mock_allocate_v4_network, mock_allocate_v4_network,
mock_allocate_v6_network, mock_allocate_v6_network,
mock_execute_playbook, mock_execute_playbook,
...@@ -108,6 +113,7 @@ def test_successful_iptrunk_creation_with_standard_lso_result( ...@@ -108,6 +113,7 @@ def test_successful_iptrunk_creation_with_standard_lso_result(
data_config_filename: PathLike, data_config_filename: PathLike,
test_client, test_client,
): ):
mock_create_host.return_value = None
mock_allocate_v4_network.return_value = faker.ipv4(network=True) mock_allocate_v4_network.return_value = faker.ipv4(network=True)
mock_allocate_v6_network.return_value = faker.ipv6(network=True) mock_allocate_v6_network.return_value = faker.ipv6(network=True)
product_id = get_product_id_by_name(ProductType.IP_TRUNK) product_id = get_product_id_by_name(ProductType.IP_TRUNK)
...@@ -117,6 +123,9 @@ def test_successful_iptrunk_creation_with_standard_lso_result( ...@@ -117,6 +123,9 @@ def test_successful_iptrunk_creation_with_standard_lso_result(
for _ in range(6): for _ in range(6):
result, step_log = assert_pp_interaction_success(result, process_stat, step_log) result, step_log = assert_pp_interaction_success(result, process_stat, step_log)
assert_suspended(result)
result, step_log = resume_workflow(process_stat, step_log, input_data=USER_CONFIRM_EMPTY_FORM)
assert_complete(result) assert_complete(result)
state = extract_state(result) state = extract_state(result)
...@@ -170,7 +179,9 @@ def test_iptrunk_creation_fails_when_lso_return_code_is_one( ...@@ -170,7 +179,9 @@ def test_iptrunk_creation_fails_when_lso_return_code_is_one(
@patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook") @patch("gso.workflows.iptrunk.create_iptrunk.execute_playbook")
@patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v6_network")
@patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network") @patch("gso.workflows.iptrunk.create_iptrunk.infoblox.allocate_v4_network")
@patch("gso.workflows.iptrunk.create_iptrunk.infoblox.create_host_by_ip")
def test_successful_iptrunk_creation_with_juniper_interface_names( def test_successful_iptrunk_creation_with_juniper_interface_names(
mock_create_host,
mock_allocate_v4_network, mock_allocate_v4_network,
mock_allocate_v6_network, mock_allocate_v6_network,
mock_execute_playbook, mock_execute_playbook,
...@@ -181,8 +192,9 @@ def test_successful_iptrunk_creation_with_juniper_interface_names( ...@@ -181,8 +192,9 @@ def test_successful_iptrunk_creation_with_juniper_interface_names(
_netbox_client_mock, # noqa: PT019 _netbox_client_mock, # noqa: PT019
test_client, test_client,
): ):
mock_allocate_v4_network.return_value = faker.ipv4(network=True) mock_create_host.return_value = None
mock_allocate_v6_network.return_value = faker.ipv6(network=True) mock_allocate_v4_network.return_value = faker.ipv4_network(max_subnet=31)
mock_allocate_v6_network.return_value = faker.ipv6_network(max_subnet=127)
product_id = get_product_id_by_name(ProductType.IP_TRUNK) product_id = get_product_id_by_name(ProductType.IP_TRUNK)
initial_site_data = [{"product": product_id}, *input_form_wizard_data] initial_site_data = [{"product": product_id}, *input_form_wizard_data]
result, process_stat, step_log = run_workflow("create_iptrunk", initial_site_data) result, process_stat, step_log = run_workflow("create_iptrunk", initial_site_data)
...@@ -190,4 +202,7 @@ def test_successful_iptrunk_creation_with_juniper_interface_names( ...@@ -190,4 +202,7 @@ def test_successful_iptrunk_creation_with_juniper_interface_names(
for _ in range(6): for _ in range(6):
result, step_log = assert_pp_interaction_success(result, process_stat, step_log) result, step_log = assert_pp_interaction_success(result, process_stat, step_log)
assert_suspended(result)
result, step_log = resume_workflow(process_stat, step_log, input_data=USER_CONFIRM_EMPTY_FORM)
assert_complete(result) assert_complete(result)
...@@ -3,6 +3,7 @@ from unittest.mock import patch ...@@ -3,6 +3,7 @@ from unittest.mock import patch
import pytest import pytest
from gso.products import Iptrunk from gso.products import Iptrunk
from gso.settings import load_oss_params
from test.services.conftest import MockedNetboxClient from test.services.conftest import MockedNetboxClient
from test.workflows import ( from test.workflows import (
assert_complete, assert_complete,
...@@ -35,6 +36,7 @@ def test_successful_iptrunk_termination( ...@@ -35,6 +36,7 @@ def test_successful_iptrunk_termination(
mocked_free_interface.return_value = mocked_netbox.free_interface() mocked_free_interface.return_value = mocked_netbox.free_interface()
# Run workflow # Run workflow
oss_params = load_oss_params()
initial_iptrunk_data = [ initial_iptrunk_data = [
{"subscription_id": product_id}, {"subscription_id": product_id},
{ {
...@@ -62,4 +64,4 @@ def test_successful_iptrunk_termination( ...@@ -62,4 +64,4 @@ def test_successful_iptrunk_termination(
assert mock_execute_playbook.call_count == 2 assert mock_execute_playbook.call_count == 2
assert mock_set_isis_to_90k.call_count == 1 assert mock_set_isis_to_90k.call_count == 1
assert mock_infoblox_delete_network.call_count == 2 assert mock_infoblox_delete_network.call_count == 2
assert subscription.iptrunk.iptrunk_isis_metric == 90000 assert subscription.iptrunk.iptrunk_isis_metric == oss_params.GENERAL.isis_high_metric
...@@ -87,13 +87,16 @@ def test_create_nokia_router_success( ...@@ -87,13 +87,16 @@ def test_create_nokia_router_success(
for _ in range(2): for _ in range(2):
result, step_log = assert_pp_interaction_success(result, process_stat, step_log) result, step_log = assert_pp_interaction_success(result, process_stat, step_log)
# Handle three consecutive user input steps # Handle four consecutive user input steps
for _ in range(3): for _ in range(4):
assert_suspended(result) assert_suspended(result)
result, step_log = resume_workflow(process_stat, step_log, input_data=USER_CONFIRM_EMPTY_FORM) result, step_log = resume_workflow(process_stat, step_log, input_data=USER_CONFIRM_EMPTY_FORM)
result, step_log = assert_pp_interaction_success(result, process_stat, step_log) result, step_log = assert_pp_interaction_success(result, process_stat, step_log)
assert_suspended(result)
result, step_log = resume_workflow(process_stat, step_log, input_data=USER_CONFIRM_EMPTY_FORM)
assert_complete(result) assert_complete(result)
state = extract_state(result) state = extract_state(result)
......