-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added faux monitoring service which pulls devices from NetBox and pings
- Loading branch information
1 parent
936d253
commit cacdd57
Showing
3 changed files
with
166 additions
and
0 deletions.
There are no files selected for viewing
104 changes: 104 additions & 0 deletions
104
netbox-event-driven-architectures/agents/monitor_network/monitor_network.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import os, asyncio, nmap, time, sys, ipaddress, json | ||
from nats.aio.client import Client as NATS | ||
from dotenv import load_dotenv | ||
from netbox import NetBoxHelper | ||
from pythonping import ping | ||
from pythonping.executor import SuccessOn | ||
from prettytable import PrettyTable | ||
|
||
class MonitorNetwork(): | ||
|
||
# Class Variables | ||
nats_server = "" | ||
subscribe_subject = "" | ||
publish_subject = "" | ||
network_devices = {} | ||
netbox_url = "" | ||
netbox_token = "" | ||
nc = None | ||
|
||
# Class Functions | ||
def __init__(self): | ||
# Load Environment Variables | ||
load_dotenv() | ||
self.nats_server = os.getenv("NATS_SERVER") | ||
self.publish_subject = os.getenv("PUBLISH_SUBJECT") | ||
self.subscribe_subject = os.getenv("SUBSCRIBE_SUBJECT") | ||
self.netbox_url = os.getenv("NETBOX_URL") | ||
self.netbox_token = os.getenv("NETBOX_TOKEN") | ||
|
||
# Load devices from netbox | ||
self.network_devices = self.load_devices_from_netbox() | ||
|
||
# Report environment | ||
print(f"""Loaded environment for {os.path.basename(__file__)} | ||
NATs Server: {self.nats_server} | ||
Publishing to subject: {self.publish_subject} | ||
Monitoring Devices: {json.dumps(self.network_devices, indent=4)}""") | ||
|
||
def load_devices_from_netbox(self) -> {str, str}: | ||
# Create NetBox Helper object | ||
nb = NetBoxHelper(self.netbox_url, self.netbox_token) | ||
|
||
# Get all active devices with an IPv4 management IP | ||
print(f"Loading devices from NetBox instance at {self.netbox_url}") | ||
total_devices_count, elligible_devices_count, devices = nb.get_active_devices_with_a_mgmt_ipv4() | ||
print(f"Found {total_devices_count} devices. {elligible_devices_count} of which are elligible for monitoring.") | ||
|
||
network_devices = {} | ||
|
||
# Write each of the IPs into network_devices[] | ||
for device in devices: | ||
mgmt_ip = ipaddress.ip_interface(str(device.primary_ip4)).ip | ||
network_devices[f"{device.name}"] = str(mgmt_ip) | ||
|
||
return network_devices | ||
|
||
async def message_handler(self, msg) -> None: | ||
subject = msg.subject | ||
data = msg.data.decode() | ||
print(f"Received a message on '{subject}': {data}") | ||
|
||
table = PrettyTable(["Device Name", "IP", "Pingable?"]) | ||
|
||
|
||
# Ping all devices and output the results | ||
for device, ip in self.network_devices.items(): | ||
device_failed = False | ||
ping_status = ping(ip, | ||
verbose=False, | ||
timeout=1, | ||
count=1).success(option=SuccessOn.Most) | ||
table.add_row([device, ip, ping_status]) | ||
if ping_status == False: | ||
device_failed = True | ||
|
||
|
||
await self.nc.publish(self.publish_subject, f"Device monitoring issues ⚠️ \n {table}".encode()) | ||
|
||
print(table) | ||
|
||
async def main_loop(self) -> None: | ||
# Create a NATS client | ||
self.nc = NATS() | ||
|
||
# Connect to the NATS server | ||
await self.nc.connect(self.nats_server) | ||
|
||
# Subscribe to subject | ||
await self.nc.subscribe(self.subscribe_subject, cb=self.message_handler) | ||
print(f"Subscribed to {self.subscribe_subject}") | ||
|
||
# Keep the script running to receive messages | ||
try: | ||
await asyncio.Future() | ||
except KeyboardInterrupt: | ||
print("Disconnecting...") | ||
await self.nc.close() | ||
except Exception as e: | ||
print(e) | ||
|
||
# Run the subscriber | ||
if __name__ == "__main__": | ||
network_monitor = MonitorNetwork() | ||
asyncio.run(network_monitor.main_loop()) |
60 changes: 60 additions & 0 deletions
60
netbox-event-driven-architectures/agents/monitor_network/netbox.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import pynetbox | ||
import logging | ||
from requests.exceptions import MissingSchema | ||
|
||
class NetBoxHelper: | ||
|
||
netbox = None | ||
logger = None | ||
netbox_url = "" | ||
netbox_token = "" | ||
__instance = None | ||
|
||
# Singleton --- | ||
@staticmethod | ||
def getInstance(netbox_url: str, netbox_token: str): | ||
if NetBoxHelper.__instance == None: | ||
NetBoxHelper(netbox_url, netbox_token) | ||
return NetBoxHelper.__instance | ||
|
||
def __init__(self, netbox_url: str, netbox_token: str): | ||
if NetBoxHelper.__instance != None: | ||
raise Exception(f"{NetBoxHelper.__name__} singleton already created. Please use {NetBoxHelper.__name__}.getInstance() instead") | ||
else: | ||
NetBoxHelper.__instance = self | ||
|
||
logging.basicConfig(format='%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s', | ||
datefmt='%Y-%m-%d:%H:%M:%S', | ||
level=logging.INFO) | ||
|
||
self.logger = logging.getLogger(__name__) | ||
|
||
self.netbox_url = netbox_url | ||
self.netbox_token = netbox_token | ||
|
||
try: | ||
self.netbox = pynetbox.api(self.netbox_url, self.netbox_token) | ||
except Exception as e: | ||
self.logger.debug(f"Unable to connect to NetBox instance: {e}") | ||
return None | ||
# --- Singleton | ||
|
||
# Functions | ||
def get_active_devices_with_a_mgmt_ipv4(self) -> (int, int, pynetbox.core.response.RecordSet): | ||
total_devices_count = 0 | ||
elligible_devices_count = 0 | ||
elligible_devices = None | ||
|
||
self.logger.info("Retrieving devices from NetBox...") | ||
|
||
try: | ||
total_devices_count = len(self.netbox.dcim.devices.all()) | ||
elligible_devices = self.netbox.dcim.devices.filter(status='active', has_primary_ip='True') | ||
elligible_devices_count = len(elligible_devices) | ||
|
||
except (pynetbox.core.query.RequestError, MissingSchema) as e: | ||
self.logger.info(f"Could not establish connection with NetBox API. Please verify details. Exception details: {e}") | ||
except AttributeError as e: | ||
self.logger.critical(f"Attribute error: {e}. Did you specify an invalid API endpoint?") | ||
|
||
return total_devices_count, elligible_devices_count, elligible_devices |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,5 @@ napalm | |
pythonping | ||
ipaddress | ||
python-nmap | ||
pynetbox | ||
prettytable |