Skip to content
Snippets Groups Projects
Verified Commit 6f8260ab authored by Carolina Fernandez's avatar Carolina Fernandez
Browse files

NAT-181: interactions with LibreNMS to register, validate and disable a device

parent 45b9f41e
No related branches found
No related tags found
No related merge requests found
{ {
"GENERAL": { "GENERAL": {
"public_hostname": "https://gap.geant.org" "public_hostname": "https://gap.geant.org",
"environment": "lab"
}, },
"RESOURCE_MANAGER_API_PREFIX": "http://localhost:44444", "RESOURCE_MANAGER_API_PREFIX": "http://localhost:44444",
"IPAM": { "IPAM": {
...@@ -42,6 +43,30 @@ ...@@ -42,6 +43,30 @@
"dns_view": "default" "dns_view": "default"
} }
}, },
"MONITORING": {
"LIBRENMS": {
"endpoint": "https://librenms.lab.office.geant.net/",
"token": "<token>",
"DEVICE_GROUPS": {
"routers_lab": "lab_routers",
"routers_prod": "prod_routers"
}
},
"SNMP": {
"version": "v2c",
"V2": {
"community": "librenms-community"
},
"V3": {
"authlevel": "AuthPriv",
"authname": "librenms",
"authpass": "<password1>",
"authalgo": "sha",
"cryptopass": "<password2>",
"cryptoalgo": "aes"
}
}
},
"PROVISIONING_PROXY": { "PROVISIONING_PROXY": {
"scheme": "https", "scheme": "https",
"api_base": "localhost:44444", "api_base": "localhost:44444",
......
"""
The LibreNMS module interacts with the LibreNMS instance when
- Creating a device.
- Validating the input of a device.
- Terminating a device.
"""
import json
import logging
import requests
from gso import settings
logger = logging.getLogger(__name__)
class CfgStruct(object):
pass
def _get_cfg():
"""
Internal function to retrieve all needed configuration.
"""
oss = settings.load_oss_params()
cfg = CfgStruct()
# Hack for later ease: 1st setattr will fill in the inner's dict
setattr(cfg, "_hack", "")
# Update inner dict
cfg.__dict__.update(oss.MONITORING)
assert cfg.__dict__ is not None
# Add parameters on-the-fly
cfg.headers = {"X-Auth-Token": cfg.LIBRENMS.token}
sep = "/"
if cfg.LIBRENMS.endpoint.endswith("/"):
sep = ""
cfg.base_url = f"{cfg.LIBRENMS.endpoint}{sep}api/v0"
cfg.url_devices = f"{cfg.base_url}/devices"
cfg.url_switches = f"{cfg.base_url}/devicegroups/switches"
cfg.device_groups = cfg.LIBRENMS.DEVICE_GROUPS
cfg.environment = oss.GENERAL.environment
if cfg.environment.startswith("lab"):
cfg_dg_rtr_lab = cfg.device_groups.routers_lab
cfg.url_routers = f"{cfg.base_url}/devicegroups/{cfg_dg_rtr_lab}"
elif cfg.environment.startswith("prod"):
cfg_dg_rtr_prod = cfg.device_groups.routers_prod
cfg.url_routers = f"{cfg.base_url}/devicegroups/{cfg_dg_rtr_prod}"
return cfg
def validate_device(fqdn: str):
"""
Function that validates the existence of a device in LibreNMS.
:param FQDN of the device to validate.
"""
CFG = _get_cfg()
# Validate existence
nms_result = requests.get(
CFG.url_devices, headers=CFG.headers)
assert nms_result is not None
device_id = list(map(
lambda x: x.get("device_id"),
filter(lambda x: x.get("hostname") == fqdn,
nms_result.json().get("devices"))))
if len(device_id) != 1 or device_id[0] is None:
error_msg = f"Device with FQDN={fqdn} is not registered in LibreNMS"
print(error_msg)
raise AssertionError(error_msg)
# Validate correctness
device_id = device_id[0]
url_device = f"{CFG.url_devices}/{device_id}"
logger.debug(f"Connecting to URL: {url_device}"
f"with headers: {CFG.headers}")
nms_result = requests.get(
url_device, headers=CFG.headers)
logger.debug(f"LibreNMS response={nms_result.content}")
if nms_result.status_code != 200:
print(nms_result.content)
raise AssertionError(nms_result.content)
# nms_dev_sysname = nms_result.json().get("sysName")
nms_dev_hostname = nms_result.json().get("devices")[0].get("hostname")
if fqdn != nms_dev_hostname:
error_msg = f"Device with FQDN={fqdn} may not be correctly "\
f"registered in LibreNMS (expected FQDN: {nms_dev_hostname})"
print(error_msg)
raise AssertionError(error_msg)
def register_device(fqdn: str):
"""
Function that registers a new device in LibreNMS.
:param FQDN of the device to register.
"""
CFG = _get_cfg()
logger.debug(f"Registering FQDN={fqdn} in LibreNMS")
device_data = {
"display": fqdn,
"hostname": fqdn,
"sysName": fqdn,
# "override_icmp_disable": "true",
# IMPORTANT: uncomment if testing with FQDNs that are not reachable
# from LibreNMS (e.g. ContainerLab routers)
# "force_add": "true"
}
if CFG.SNMP.version == "v2c":
device_data.update({
"community": CFG.SNMP.V2.community
})
elif CFG.SNMP.version == "v3":
for key in [
"authlevel", "authname", "authpass", "authalgo",
"cryptopass", "cryptoalgo"]:
device_data.update({key: getattr(CFG.SNMP.V3, key)})
logger.debug(f"Connecting to URL: {CFG.url_devices}"
f"with headers: {CFG.headers} and"
f"payload: {device_data}")
nms_result = requests.post(
CFG.url_devices, headers=CFG.headers,
data=json.dumps(device_data))
logger.debug(f"LibreNMS response={nms_result.content}")
if nms_result.status_code != 200:
print(nms_result.content)
raise AssertionError(nms_result.content)
def deregister_device(fqdn: str):
"""
Function that reregisters a device from LibreNMS.
:param FQDN of the device to deregister.
"""
CFG = _get_cfg()
logger.debug(f"Deregistering FQDN={fqdn} from LibreNMS")
nms_result = requests.get(
CFG.url_devices, headers=CFG.headers)
assert nms_result is not None
device_id = list(map(
lambda x: x.get("device_id"),
filter(lambda x: x.get("hostname") == fqdn,
nms_result.json().get("devices"))))
if len(device_id) != 1:
return
device_id = device_id[0]
# https://docs.librenms.org/API/Devices/#endpoint-categories
device_data = {
"field": "disabled",
"data": "1"
}
url_device = f"{CFG.url_devices}/{device_id}"
logger.debug(f"Connecting to URL: {url_device}"
f"with headers: {CFG.headers} and"
f"payload: {device_data}")
nms_result = requests.patch(
url_device, headers=CFG.headers,
data=json.dumps(device_data))
logger.debug(f"LibreNMS response={nms_result.content}")
# Fail silently if device was not registered
if nms_result.status_code != 200:
print(nms_result.content)
...@@ -4,7 +4,7 @@ GSO settings, ensuring that the required parameters are set correctly. ...@@ -4,7 +4,7 @@ GSO settings, ensuring that the required parameters are set correctly.
import ipaddress import ipaddress
import json import json
import os import os
from pydantic import BaseSettings, Field from pydantic import BaseSettings
class GeneralParams(BaseSettings): class GeneralParams(BaseSettings):
...@@ -14,6 +14,7 @@ class GeneralParams(BaseSettings): ...@@ -14,6 +14,7 @@ class GeneralParams(BaseSettings):
#: The hostname that GSO is publicly served at, used for building the #: The hostname that GSO is publicly served at, used for building the
#: callback URL that the provisioning proxy uses. #: callback URL that the provisioning proxy uses.
public_hostname: str public_hostname: str
environment: str
class InfoBloxParams(BaseSettings): class InfoBloxParams(BaseSettings):
...@@ -68,6 +69,59 @@ class IPAMParams(BaseSettings): ...@@ -68,6 +69,59 @@ class IPAMParams(BaseSettings):
LT_IAS: ServiceNetworkParams LT_IAS: ServiceNetworkParams
class MonitoringLibreNMSDevGroupsParams(BaseSettings):
"""
Parameters related to LibreNMS' devicegroups.
"""
routers_lab: str
routers_prod: str
class MonitoringSNMPV2Params(BaseSettings):
"""
Parameters related to SNMPv2.
"""
community: str
class MonitoringSNMPV3Params(BaseSettings):
"""
Parameters related to SNMPv3.
"""
authlevel: str
authname: str
authpass: str
authalgo: str
cryptopass: str
cryptoalgo: str
class MonitoringSNMPParams(BaseSettings):
"""
Parameters related to SNMP.
"""
version: str
V2: MonitoringSNMPV2Params
V3: MonitoringSNMPV3Params
class MonitoringLibreNMSParams(BaseSettings):
"""
Parameters related to LibreNMS.
"""
endpoint: str
token: str
DEVICE_GROUPS: MonitoringLibreNMSDevGroupsParams
class MonitoringParams(BaseSettings):
"""
Parameters related to the monitoring.
"""
LIBRENMS: MonitoringLibreNMSParams
SNMP: MonitoringSNMPParams
class ProvisioningProxyParams(BaseSettings): class ProvisioningProxyParams(BaseSettings):
""" """
Parameters for the provisioning proxy. Parameters for the provisioning proxy.
...@@ -85,6 +139,7 @@ class OSSParams(BaseSettings): ...@@ -85,6 +139,7 @@ class OSSParams(BaseSettings):
GENERAL: GeneralParams GENERAL: GeneralParams
IPAM: IPAMParams IPAM: IPAMParams
RESOURCE_MANAGER_API_PREFIX: str RESOURCE_MANAGER_API_PREFIX: str
MONITORING: MonitoringParams
PROVISIONING_PROXY: ProvisioningProxyParams PROVISIONING_PROXY: ProvisioningProxyParams
......
...@@ -4,6 +4,7 @@ init class that imports all workflows into GSO. ...@@ -4,6 +4,7 @@ init class that imports all workflows into GSO.
from orchestrator.workflows import LazyWorkflowInstance from orchestrator.workflows import LazyWorkflowInstance
LazyWorkflowInstance("gso.workflows.device.create_device", "create_device") LazyWorkflowInstance("gso.workflows.device.create_device", "create_device")
LazyWorkflowInstance("gso.workflows.device.validate_device", "validate_device")
LazyWorkflowInstance("gso.workflows.device.terminate_device", LazyWorkflowInstance("gso.workflows.device.terminate_device",
"terminate_device") "terminate_device")
LazyWorkflowInstance("gso.workflows.device.get_facts", "get_facts") LazyWorkflowInstance("gso.workflows.device.get_facts", "get_facts")
...@@ -15,3 +16,4 @@ LazyWorkflowInstance("gso.workflows.iptrunk.modify_iptrunk_interface", ...@@ -15,3 +16,4 @@ LazyWorkflowInstance("gso.workflows.iptrunk.modify_iptrunk_interface",
LazyWorkflowInstance("gso.workflows.iptrunk.modify_iptrunk_isis_metric", LazyWorkflowInstance("gso.workflows.iptrunk.modify_iptrunk_isis_metric",
"modify_iptrunk_isis_metric") "modify_iptrunk_isis_metric")
LazyWorkflowInstance("gso.workflows.site.create_site", "create_site") LazyWorkflowInstance("gso.workflows.site.create_site", "create_site")
LazyWorkflowInstance("gso.workflows.site.terminate_site", "terminate_site")
...@@ -16,10 +16,11 @@ from orchestrator.workflows.utils import wrap_create_initial_input_form ...@@ -16,10 +16,11 @@ from orchestrator.workflows.utils import wrap_create_initial_input_form
from gso.products.product_blocks import device as device_pb from gso.products.product_blocks import device as device_pb
from gso.products.product_types import device from gso.products.product_types import device
from gso.products.product_types.device import DeviceInactive, \ from gso.products.product_types.device import Device, DeviceInactive, \
DeviceProvisioning DeviceProvisioning
from gso.products.product_types.site import Site from gso.products.product_types.site import Site
from gso.services import _ipam from gso.services import _ipam
from gso.services import librenms
from gso.services import provisioning_proxy from gso.services import provisioning_proxy
from gso.services.provisioning_proxy import await_pp_results, \ from gso.services.provisioning_proxy import await_pp_results, \
confirm_pp_results confirm_pp_results
...@@ -66,6 +67,10 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: ...@@ -66,6 +67,10 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
return user_input.dict() return user_input.dict()
def _fqdn_from_subscription(subscription: Device) -> str:
return subscription.device.device_fqdn
@step('Create subscription') @step('Create subscription')
def create_subscription(product: UUIDstr) -> State: def create_subscription(product: UUIDstr) -> State:
subscription = DeviceInactive.from_product_id(product, uuid4()) subscription = DeviceInactive.from_product_id(product, uuid4())
...@@ -94,13 +99,17 @@ def get_info_from_ipam(subscription: DeviceProvisioning) -> State: ...@@ -94,13 +99,17 @@ def get_info_from_ipam(subscription: DeviceProvisioning) -> State:
subscription.device.device_lo_ipv4_address = lo0_addr.v4 subscription.device.device_lo_ipv4_address = lo0_addr.v4
subscription.device.device_lo_ipv6_address = lo0_addr.v6 subscription.device.device_lo_ipv6_address = lo0_addr.v6
subscription.device.device_lo_iso_address \ subscription.device.device_lo_iso_address \
= iso_from_ipv4(str(subscription.device.device_lo_ipv4_address)) = iso_from_ipv4(
str(subscription.device.device_lo_ipv4_address))
subscription.device.device_si_ipv4_network \ subscription.device.device_si_ipv4_network \
= _ipam.allocate_service_ipv4_network(service_type='SI', comment=f"SI for {lo0_name}").v4 = _ipam.allocate_service_ipv4_network(
service_type='SI', comment=f"SI for {lo0_name}").v4
subscription.device.device_ias_lt_ipv4_network \ subscription.device.device_ias_lt_ipv4_network \
= _ipam.allocate_service_ipv4_network(service_type='LT_IAS', comment=f"LT for {lo0_name}").v4 = _ipam.allocate_service_ipv4_network(
service_type='LT_IAS', comment=f"LT for {lo0_name}").v4
subscription.device.device_ias_lt_ipv6_network \ subscription.device.device_ias_lt_ipv6_network \
= _ipam.allocate_service_ipv6_network(service_type='LT_IAS', comment=f"LT for {lo0_name}").v6 = _ipam.allocate_service_ipv6_network(
service_type='LT_IAS', comment=f"LT for {lo0_name}").v6
return {'subscription': subscription} return {'subscription': subscription}
...@@ -162,6 +171,17 @@ def provision_device_real(subscription: DeviceProvisioning, ...@@ -162,6 +171,17 @@ def provision_device_real(subscription: DeviceProvisioning,
} }
@step('Register device in LibreNMS')
def register_librenms(subscription: Device) -> State:
fqdn = _fqdn_from_subscription(subscription)
_ = librenms.register_device(fqdn)
return {
'subscription': subscription,
# TBC: wait for results to be returned from LibreNMS or not
# 'result_text': result,
}
@workflow( @workflow(
'Create device', 'Create device',
initial_input_form=wrap_create_initial_input_form( initial_input_form=wrap_create_initial_input_form(
...@@ -181,6 +201,7 @@ def create_device(): ...@@ -181,6 +201,7 @@ def create_device():
>> provision_device_real >> provision_device_real
>> await_pp_results >> await_pp_results
>> confirm_pp_results >> confirm_pp_results
>> register_librenms
>> set_status(SubscriptionLifecycle.ACTIVE) >> set_status(SubscriptionLifecycle.ACTIVE)
>> resync >> resync
>> done >> done
......
...@@ -12,6 +12,7 @@ from orchestrator.workflows.steps import ( ...@@ -12,6 +12,7 @@ from orchestrator.workflows.steps import (
from orchestrator.workflows.utils import wrap_modify_initial_input_form from orchestrator.workflows.utils import wrap_modify_initial_input_form
from gso.products.product_types.device import Device from gso.products.product_types.device import Device
from gso.services import librenms
def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm: def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm:
...@@ -19,12 +20,16 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm: ...@@ -19,12 +20,16 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm:
class TerminateForm(FormPage): class TerminateForm(FormPage):
are_you_sure: Label = ( are_you_sure: Label = (
f'Are you sure you want to remove {subscription.description}?' f"Are you sure you want to remove {subscription.description}?"
) )
return TerminateForm return TerminateForm
def _fqdn_from_subscription(subscription: Device) -> str:
return subscription.device.device_fqdn
def _deprovision_in_user_management_system(fqdn: str) -> str: def _deprovision_in_user_management_system(fqdn: str) -> str:
pass pass
...@@ -35,6 +40,17 @@ def deprovision_user(subscription: Device) -> None: ...@@ -35,6 +40,17 @@ def deprovision_user(subscription: Device) -> None:
pass pass
@step("Deregister device from LibreNMS")
def deregister_librenms(subscription: Device) -> None:
fqdn = _fqdn_from_subscription(subscription)
_ = librenms.deregister_device(fqdn)
return {
"subscription": subscription,
# TBC: wait for results to be returned from LibreNMS or not
# "result_text": result,
}
@workflow( @workflow(
"Terminate device", "Terminate device",
initial_input_form=wrap_modify_initial_input_form( initial_input_form=wrap_modify_initial_input_form(
...@@ -47,6 +63,7 @@ def terminate_device(): ...@@ -47,6 +63,7 @@ def terminate_device():
>> store_process_subscription(Target.TERMINATE) >> store_process_subscription(Target.TERMINATE)
>> unsync >> unsync
>> deprovision_user >> deprovision_user
>> deregister_librenms
>> set_status(SubscriptionLifecycle.TERMINATED) >> set_status(SubscriptionLifecycle.TERMINATED)
>> resync >> resync
>> done >> done
......
# noinspection PyProtectedMember
from orchestrator.forms import FormPage
from orchestrator.targets import Target
from orchestrator.types import InputForm, UUIDstr
from orchestrator.forms.validators import Label
from orchestrator.workflow import done, init, step, workflow
from orchestrator.workflows.steps import resync # , set_status
from orchestrator.workflows.utils import wrap_create_initial_input_form
from gso.products.product_types.device import Device
# noinspection PyProtectedMember
from gso.services import librenms
def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm:
subscription = Device.from_subscription(subscription_id)
class ValidateForm(FormPage):
are_you_sure: Label = (
f"Are you sure you want to validate {subscription.description}?"
)
return ValidateForm
@step("Validate existence of device in LibreNMS")
def device_registered_in_librenms(subscription: Device) -> None:
_ = librenms.validate_device(subscription)
return {
"subscription": subscription,
# TBC: wait for results to be returned from LibreNMS or not
# "result_text": result,
}
@workflow(
"Validate device",
initial_input_form=wrap_create_initial_input_form(
initial_input_form_generator),
target=Target.SYSTEM,
)
def validate_device():
return (
init
>> device_registered_in_librenms
>> resync
>> done
)
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Label
from orchestrator.targets import Target
from orchestrator.types import InputForm, SubscriptionLifecycle, UUIDstr
from orchestrator.workflow import done, init, step, workflow
from orchestrator.workflows.steps import (
resync,
set_status,
store_process_subscription,
unsync,
)
from orchestrator.workflows.utils import wrap_modify_initial_input_form
from gso.products.product_types.site import Site
def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm:
subscription = Site.from_subscription(subscription_id)
class TerminateForm(FormPage):
are_you_sure: Label = (
f'Are you sure you want to remove {subscription.description}?'
)
return TerminateForm
def _deprovision_in_user_management_system(fqdn: str) -> str:
pass
@step("Deprovision Site")
def deprovision_site(subscription: Site) -> None:
# _deprovision_in_user_management_system(subscription.user.user_id)
pass
@workflow(
"Terminate Site",
initial_input_form=wrap_modify_initial_input_form(
initial_input_form_generator),
target=Target.TERMINATE,
)
def terminate_site():
return (
init
>> store_process_subscription(Target.TERMINATE)
>> unsync
>> deprovision_site
>> set_status(SubscriptionLifecycle.TERMINATED)
>> resync
>> done
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment