Update iBGP mesh workflow. Adds a new P router to the mesh of PE routers in the network.
Once a new router is added to the network, it must become reachable by all other devices. To achieve this, the
update_ibgp_mesh
workflow must be executed. This workflow will add the new P router to all PE routers in the network,
and add all existing PE routers to the new P router. The only input this workflow takes, is a trouble ticket number.
All other required information is already in the service database.
The workflow will run 5 Ansible playbooks:
- Check: add P router to all PE routers
- Deploy: add P router to all PE routers
- Check: add all PE routers to P router
- Deploy: add all PE routers to P router
- Verify: check that the iBGP has come up
Once these playbooks have been run successfully, the new P router is added to LibreNMS. Finally, the subscription model
of the router is updated such that router_access_via_ts
is set to False
. This is because the router is now reachable
by other machines by its loopback address. Using out of band access is therefore not needed anymore.
Show a confirmation window before running the workflow.
Does not allow for user input, but does run a validation check. The workflow is only allowed to run, if the router
already is connected by at least one IP trunk.
Source code in gso/workflows/router/update_ibgp_mesh.py
| def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
"""Show a confirmation window before running the workflow.
Does not allow for user input, but does run a validation check. The workflow is only allowed to run, if the router
already is connected by at least one IP trunk.
"""
subscription = Router.from_subscription(subscription_id)
class AddBGPSessionForm(SubmitFormPage):
model_config = ConfigDict(title=f"Add {subscription.router.router_fqdn} to the iBGP mesh?")
tt_number: TTNumber
@model_validator(mode="before")
def router_has_a_trunk(cls, data: Any) -> Any:
terminating_trunks = get_trunks_that_terminate_on_router(
subscription_id, SubscriptionLifecycle.PROVISIONING
) + get_trunks_that_terminate_on_router(subscription_id, SubscriptionLifecycle.ACTIVE)
if len(terminating_trunks) == 0:
msg = "Selected router does not terminate any available IP trunks."
raise ValueError(msg)
return data
user_input = yield AddBGPSessionForm
return user_input.model_dump()
|
add_p_to_mesh_dry(subscription, tt_number, process_id)
Perform a dry run of adding the new P router to the PE router mesh.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @step("[DRY RUN] Add P router to iBGP mesh")
def add_p_to_mesh_dry(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState:
"""Perform a dry run of adding the new P router to the PE router mesh."""
extra_vars = {
"dry_run": True,
"subscription": subscription,
"commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Update iBGP mesh",
"verb": "add_p_to_pe",
}
return {
"playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
"inventory": generate_inventory_for_routers(RouterRole.PE),
"extra_vars": extra_vars,
}
|
add_p_to_mesh_real(subscription, tt_number, process_id)
Add the P router to the mesh of PE routers.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @step("[FOR REAL] Add P router to iBGP mesh")
def add_p_to_mesh_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState:
"""Add the P router to the mesh of PE routers."""
extra_vars = {
"dry_run": False,
"subscription": subscription,
"commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Update iBGP mesh",
"verb": "add_p_to_pe",
}
return {
"playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
"inventory": generate_inventory_for_routers(RouterRole.PE),
"extra_vars": extra_vars,
}
|
add_all_pe_to_p_dry(subscription)
Perform a dry run of adding the list of all PE routers to the new P router.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @step("[DRY RUN] Add all PE routers to P router iBGP group")
def add_all_pe_to_p_dry(subscription: dict[str, Any]) -> LSOState:
"""Perform a dry run of adding the list of all PE routers to the new P router."""
extra_vars = {
"dry_run": True,
"subscription": subscription,
"pe_router_list": generate_inventory_for_routers(RouterRole.PE)["all"]["hosts"],
"verb": "add_pe_to_p",
}
return {
"playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
"inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
"extra_vars": extra_vars,
}
|
add_all_pe_to_p_real(subscription, tt_number, process_id)
Add the list of all PE routers to the new P router.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @step("[FOR REAL] Add all PE routers to P router iBGP group")
def add_all_pe_to_p_real(subscription: dict[str, Any], tt_number: str, process_id: UUIDstr) -> LSOState:
"""Add the list of all PE routers to the new P router."""
extra_vars = {
"dry_run": False,
"subscription": subscription,
"pe_router_list": generate_inventory_for_routers(RouterRole.PE)["all"]["hosts"],
"commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Update iBGP mesh",
"verb": "add_pe_to_p",
}
return {
"playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
"inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
"extra_vars": extra_vars,
}
|
check_ibgp_session(subscription)
Run a playbook using the provisioning proxy, to check the health of the new iBGP session.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @step("Verify iBGP session health")
def check_ibgp_session(subscription: dict[str, Any]) -> LSOState:
"""Run a playbook using the provisioning proxy, to check the health of the new iBGP session."""
return {
"playbook_name": "gap_ansible/playbooks/check_ibgp.yaml",
"inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
"extra_vars": {"subscription": subscription},
}
|
add_device_to_librenms(subscription)
Add the router as a device to LibreNMS.
If the device already exists, retrieve the device information instead of adding it again
Source code in gso/workflows/router/update_ibgp_mesh.py
| @step("Add the router to LibreNMS")
def add_device_to_librenms(subscription: Router) -> State:
"""Add the router as a device to LibreNMS.
If the device already exists, retrieve the device information instead of adding it again
"""
client = librenms_client.LibreNMSClient()
if not client.device_exists(subscription.router.router_fqdn):
librenms_result = client.add_device(subscription.router.router_fqdn, SNMPVersion.V2C)
else:
librenms_result = client.get_device(subscription.router.router_fqdn)
return {"librenms_device": librenms_result}
|
prompt_insert_in_radius()
Wait for confirmation from an operator that the router has been inserted in RADIUS.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @inputstep("Prompt RADIUS insertion", assignee=Assignee.SYSTEM)
def prompt_insert_in_radius() -> FormGenerator:
"""Wait for confirmation from an operator that the router has been inserted in RADIUS."""
class RADIUSPrompt(SubmitFormPage):
model_config = ConfigDict(title="Please update RADIUS before continuing")
info_label: Label = "Insert the router into RADIUS, and continue the workflow once this has been completed."
yield RADIUSPrompt
return {}
|
prompt_radius_login()
Wait for confirmation from an operator that the router can be logged into using RADIUS.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @inputstep("Prompt RADIUS login", assignee=Assignee.SYSTEM)
def prompt_radius_login() -> FormGenerator:
"""Wait for confirmation from an operator that the router can be logged into using RADIUS."""
class RADIUSPrompt(SubmitFormPage):
model_config = ConfigDict(title="Please check RADIUS before continuing")
info_label: Label = "Log in to the router using RADIUS, and continue the workflow when this was successful."
yield RADIUSPrompt
return {}
|
update_subscription_model(subscription)
Update the database model, such that it should not be reached via OOB access anymore.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @step("Update subscription model")
def update_subscription_model(subscription: Router) -> State:
"""Update the database model, such that it should not be reached via OOB access anymore."""
subscription.router.router_access_via_ts = False
return {"subscription": subscription}
|
update_ibgp_mesh()
Update the iBGP mesh with a new P router.
- Add the new P-router to all other PE-routers in the network, including a dry run.
- Add all PE-routers to the P-router, including a dry run.
- Verify that the iBGP session is up.
- Add the new P-router to LibreNMS.
- Update the subscription model.
Source code in gso/workflows/router/update_ibgp_mesh.py
| @workflow(
"Update iBGP mesh",
initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
target=Target.MODIFY,
)
def update_ibgp_mesh() -> StepList:
"""Update the iBGP mesh with a new P router.
* Add the new P-router to all other PE-routers in the network, including a dry run.
* Add all PE-routers to the P-router, including a dry run.
* Verify that the iBGP session is up.
* Add the new P-router to LibreNMS.
* Update the subscription model.
"""
router_is_pe = conditional(lambda state: state["subscription"]["router"]["router_role"] == RouterRole.PE)
router_is_p = conditional(lambda state: state["subscription"]["router"]["router_role"] == RouterRole.P)
return (
begin
>> store_process_subscription(Target.MODIFY)
>> unsync
>> router_is_p(lso_interaction(add_p_to_mesh_dry))
>> router_is_p(lso_interaction(add_p_to_mesh_real))
>> router_is_p(lso_interaction(add_all_pe_to_p_dry))
>> router_is_p(lso_interaction(add_all_pe_to_p_real))
>> router_is_p(lso_interaction(check_ibgp_session))
>> router_is_pe(lso_interaction(add_pe_mesh_to_pe_dry))
>> router_is_pe(lso_interaction(add_pe_mesh_to_pe_real))
>> router_is_pe(lso_interaction(add_pe_to_pe_mesh_dry))
>> router_is_pe(lso_interaction(add_pe_to_pe_mesh_real))
>> router_is_pe(lso_interaction(add_all_p_to_pe_dry))
>> router_is_pe(lso_interaction(add_all_p_to_pe_real))
>> router_is_pe(lso_interaction(add_pe_to_all_p_dry))
>> router_is_pe(lso_interaction(add_pe_to_all_p_real))
>> router_is_pe(lso_interaction(update_sdp_single_pe_dry))
>> router_is_pe(lso_interaction(update_sdp_single_pe_real))
>> router_is_pe(lso_interaction(update_sdp_mesh_dry))
>> router_is_pe(lso_interaction(update_sdp_mesh_real))
>> router_is_pe(lso_interaction(check_pe_ibgp))
>> router_is_pe(lso_interaction(check_l3_services))
>> add_device_to_librenms
>> prompt_insert_in_radius
>> prompt_radius_login
>> update_subscription_model
>> resync
>> done
)
|