-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #43 from kpetremann/site_decomm
feat: site decommissioning
- Loading branch information
Showing
10 changed files
with
476 additions
and
161 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,57 +1,169 @@ | ||
from dcim.models import Device, Site | ||
from django.db import transaction | ||
from django.db.models import Q | ||
from django.http import StreamingHttpResponse | ||
from drf_yasg.utils import swagger_auto_schema | ||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired | ||
from rest_framework import serializers, status | ||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
|
||
from netbox_cmdb.models.bgp import BGPPeerGroup, BGPSession, DeviceBGPSession | ||
from netbox_cmdb.models.bgp_community_list import BGPCommunityList | ||
from netbox_cmdb.models.prefix_list import PrefixList | ||
from netbox_cmdb.models.route_policy import RoutePolicy | ||
from netbox_cmdb.models.snmp import SNMP | ||
from netbox_cmdb.helpers import cleaning | ||
|
||
|
||
class DeleteAllCMDBObjectsRelatedToDeviceSerializer(serializers.Serializer): | ||
class DeviceDecommissioningBaseSerializer(serializers.Serializer): | ||
device_name = serializers.CharField() | ||
|
||
|
||
class DeleteAllCMDBObjectsRelatedToDevice(APIView): | ||
class DeviceCMDBDecommissioningAPIView(APIView): | ||
|
||
permission_classes = [IsAuthenticatedOrLoginNotRequired] | ||
|
||
@swagger_auto_schema( | ||
request_body=DeleteAllCMDBObjectsRelatedToDeviceSerializer, | ||
request_body=DeviceDecommissioningBaseSerializer, | ||
responses={ | ||
status.HTTP_200_OK: "Objects related to device have been deleted successfully", | ||
status.HTTP_400_BAD_REQUEST: "Bad Request: Device name is required", | ||
status.HTTP_404_NOT_FOUND: "Bad Request: Device not found", | ||
status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal Server Error: Something went wrong on the server", | ||
}, | ||
) | ||
def post(self, request): | ||
def delete(self, request): | ||
device_name = request.data.get("device_name", None) | ||
if device_name is None: | ||
return Response( | ||
{"error": "Device name is required"}, status=status.HTTP_400_BAD_REQUEST | ||
{"error": "device_name is required"}, status=status.HTTP_400_BAD_REQUEST | ||
) | ||
|
||
devices = Device.objects.filter(name=device_name) | ||
device_ids = [dev.id for dev in devices] | ||
if not device_ids: | ||
return Response( | ||
{"error": "no matching devices found"}, status=status.HTTP_404_NOT_FOUND | ||
) | ||
|
||
try: | ||
with transaction.atomic(): | ||
# Delete objects in reverse order of dependencies | ||
BGPSession.objects.filter( | ||
Q(peer_a__device__name=device_name) | Q(peer_b__device__name=device_name) | ||
).delete() | ||
DeviceBGPSession.objects.filter(device__name=device_name).delete() | ||
BGPPeerGroup.objects.filter(device__name=device_name).delete() | ||
RoutePolicy.objects.filter(device__name=device_name).delete() | ||
PrefixList.objects.filter(device__name=device_name).delete() | ||
BGPCommunityList.objects.filter(device_name=device_name).delete() | ||
SNMP.objects.filter(device__name=device_name).delete() | ||
deleted = cleaning.clean_cmdb_for_devices(device_ids) | ||
except Exception as e: | ||
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) | ||
|
||
return Response( | ||
{"message": f"Objects related to device {device_name} have been deleted successfully"}, | ||
{ | ||
"message": f"CMDB cleaned for {device_name}", | ||
"deleted": deleted, | ||
}, | ||
status=status.HTTP_200_OK, | ||
) | ||
|
||
|
||
class DeviceDecommissioningAPIView(APIView): | ||
|
||
permission_classes = [IsAuthenticatedOrLoginNotRequired] | ||
|
||
@swagger_auto_schema( | ||
request_body=DeviceDecommissioningBaseSerializer, | ||
responses={ | ||
status.HTTP_200_OK: "Objects related to device have been deleted successfully", | ||
status.HTTP_400_BAD_REQUEST: "Bad Request: Device name is required", | ||
status.HTTP_404_NOT_FOUND: "Bad Request: Device not found", | ||
status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal Server Error: Something went wrong on the server", | ||
}, | ||
) | ||
def delete(self, request): | ||
device_name = request.data.get("device_name", None) | ||
if device_name is None: | ||
return Response( | ||
{"error": "device_name is required"}, status=status.HTTP_400_BAD_REQUEST | ||
) | ||
|
||
devices = Device.objects.filter(name=device_name) | ||
device_ids = [dev.id for dev in devices] | ||
if not device_ids: | ||
return Response( | ||
{"error": "no matching devices found"}, status=status.HTTP_404_NOT_FOUND | ||
) | ||
|
||
try: | ||
with transaction.atomic(): | ||
deleted = cleaning.clean_cmdb_for_devices(device_ids) | ||
for device in devices: | ||
device.delete() | ||
except Exception as e: | ||
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) | ||
|
||
return Response( | ||
{ | ||
"message": f"{device_name} decommissionned", | ||
"deleted": deleted, | ||
}, | ||
status=status.HTTP_200_OK, | ||
) | ||
|
||
|
||
class SiteDecommissioningSerializer(serializers.Serializer): | ||
site_name = serializers.CharField() | ||
|
||
|
||
class SiteDecommissioningAPIView(APIView): | ||
|
||
permission_classes = [IsAuthenticatedOrLoginNotRequired] | ||
|
||
@swagger_auto_schema( | ||
request_body=SiteDecommissioningSerializer, | ||
responses={ | ||
status.HTTP_200_OK: "Site have been deleted successfully", | ||
status.HTTP_400_BAD_REQUEST: "Bad Request: Site name is required", | ||
status.HTTP_404_NOT_FOUND: "Bad Request: Site not found", | ||
status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal Server Error: Something went wrong on the server", | ||
}, | ||
) | ||
def delete(self, request): | ||
site_name = request.data.get("site_name", None) | ||
if site_name is None: | ||
return Response({"error": "site_name is required"}, status=status.HTTP_400_BAD_REQUEST) | ||
|
||
try: | ||
site = Site.objects.get(name=site_name) | ||
except Site.DoesNotExist: | ||
return Response({"error": "site not found"}, status=status.HTTP_404_NOT_FOUND) | ||
except Exception: | ||
return Response( | ||
{"error": "internal server error"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR | ||
) | ||
|
||
devices = Device.objects.filter(site=site.id) | ||
|
||
def _start(): | ||
CHUNK_SIZE = 20 | ||
device_ids = [dev.id for dev in devices] | ||
for i in range(0, len(device_ids), CHUNK_SIZE): | ||
chunk = device_ids[i : i + CHUNK_SIZE] | ||
try: | ||
with transaction.atomic(): | ||
cleaning.clean_cmdb_for_devices(chunk) | ||
for dev in devices[i : i + CHUNK_SIZE]: | ||
dev.delete() | ||
yield f'{{"deleted": {[dev.name for dev in devices[i:i+CHUNK_SIZE]]}}}\n\n' | ||
|
||
except Exception as e: | ||
StreamingHttpResponse.status_code = 500 | ||
msg = {"error": str(e)} | ||
yield f"{msg}\n\n" | ||
return | ||
|
||
try: | ||
with transaction.atomic(): | ||
cleaning.clean_site_topology(site) | ||
yield "{{'message': 'topology cleaned'}}\n\n" | ||
except Exception as e: | ||
StreamingHttpResponse.status_code = 500 | ||
msg = {"error": str(e)} | ||
yield f"{msg}\n\n" | ||
return | ||
|
||
msg = { | ||
"message": f"site {site_name} has been deleted successfully", | ||
} | ||
yield f"{msg}\n\n" | ||
|
||
return StreamingHttpResponse(_start(), content_type="text/plain") |
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
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,58 @@ | ||
from dcim.models import Location, Rack | ||
from django.db.models import Q | ||
|
||
from netbox_cmdb.models.bgp import BGPPeerGroup, BGPSession, DeviceBGPSession | ||
from netbox_cmdb.models.bgp_community_list import BGPCommunityList | ||
from netbox_cmdb.models.prefix_list import PrefixList | ||
from netbox_cmdb.models.route_policy import RoutePolicy | ||
from netbox_cmdb.models.snmp import SNMP | ||
|
||
|
||
def clean_cmdb_for_devices(device_ids: list[int]): | ||
deleted_objects = { | ||
"bgp_sessions": [], | ||
"device_bgp_sessions": [], | ||
"bgp_peer_groups": [], | ||
"route_policies": [], | ||
"prefix_lists": [], | ||
"bgp_community_lists": [], | ||
"snmp": [], | ||
} | ||
|
||
bgp_sessions = BGPSession.objects.filter( | ||
Q(peer_a__device__id__in=device_ids) | Q(peer_b__device__id__in=device_ids) | ||
) | ||
device_bgp_sessions = DeviceBGPSession.objects.filter(device__id__in=device_ids) | ||
bgp_peer_groups = BGPPeerGroup.objects.filter(device__id__in=device_ids) | ||
route_policies = RoutePolicy.objects.filter(device__id__in=device_ids) | ||
prefix_lists = PrefixList.objects.filter(device__id__in=device_ids) | ||
bgp_community_lists = BGPCommunityList.objects.filter(device__id__in=device_ids) | ||
snmp = SNMP.objects.filter(device__id__in=device_ids) | ||
|
||
deleted_objects["bgp_sessions"] = [str(val) for val in list(bgp_sessions)] | ||
deleted_objects["device_bgp_sessions"] = [str(val) for val in list(device_bgp_sessions)] | ||
deleted_objects["bgp_peer_groups"] = [str(val) for val in list(bgp_peer_groups)] | ||
deleted_objects["route_policies"] = [str(val) for val in list(route_policies)] | ||
deleted_objects["prefix_lists"] = [str(val) for val in list(prefix_lists)] | ||
deleted_objects["bgp_community_lists"] = [str(val) for val in list(bgp_community_lists)] | ||
deleted_objects["snmp"] = [str(val) for val in list(snmp)] | ||
|
||
bgp_sessions.delete() | ||
device_bgp_sessions.delete() | ||
bgp_peer_groups.delete() | ||
route_policies.delete() | ||
prefix_lists.delete() | ||
bgp_community_lists.delete() | ||
snmp.delete() | ||
|
||
return deleted_objects | ||
|
||
|
||
def clean_site_topology(site): | ||
racks = Rack.objects.filter(site=site.id) | ||
racks.delete() | ||
|
||
locations = Location.objects.filter(site=site.id) | ||
locations.delete() | ||
|
||
site.delete() |
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 |
---|---|---|
@@ -1,15 +1,22 @@ | ||
from extras.plugins import PluginTemplateExtension | ||
|
||
|
||
class Decommisioning(PluginTemplateExtension): | ||
model = "dcim.device" | ||
|
||
class DecommissioningBase(PluginTemplateExtension): | ||
def buttons(self): | ||
return ( | ||
f'<a href="#" hx-get="/plugins/cmdb/decommisioning/{self.context["object"].id}/delete" ' | ||
'hx-target="#htmx-modal-content" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#htmx-modal" ' | ||
f'<a href="/plugins/cmdb/decommissioning/{self.obj}/{self.context["object"].id}/delete" ' | ||
'class="btn btn-sm btn-danger">Decommission</a>' | ||
) | ||
|
||
|
||
template_extensions = [Decommisioning] | ||
class DeviceDecommissioning(DecommissioningBase): | ||
model = "dcim.device" | ||
obj = "device" | ||
|
||
|
||
class SiteDecommissioning(DecommissioningBase): | ||
model = "dcim.site" | ||
obj = "site" | ||
|
||
|
||
template_extensions = [DeviceDecommissioning, SiteDecommissioning] |
43 changes: 0 additions & 43 deletions
43
netbox_cmdb/netbox_cmdb/templates/netbox_cmdb/decommissioning.html
This file was deleted.
Oops, something went wrong.
33 changes: 33 additions & 0 deletions
33
netbox_cmdb/netbox_cmdb/templates/netbox_cmdb/decommissioning/base.html
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,33 @@ | ||
{% extends "base/layout.html" %} | ||
{% block title %} | ||
{{ object.name }} decommissioning | ||
{% endblock %} | ||
|
||
{% block content-wrapper %} | ||
<div class="border-top"> | ||
<div class="container mt-5 mb-3 border rounded"> | ||
|
||
<div id="_status" class="mt-3 mb-3 fw-bold"></div> | ||
<div id="_error"></div> | ||
<div id="_history" class="ps-3"></div> | ||
{% if error %} | ||
<div class="alert alert-danger">{{ error }}</div> | ||
{% else %} | ||
<div id="_content" class="mb-3"> | ||
<p class="fw-bold"> | ||
<span class="text-danger">Warning:</span> this action will remove both | ||
CMDB assets and DCIM of concerned asset(s) | ||
</p> | ||
<form | ||
hx-target="#_content" | ||
hx-post="/plugins/cmdb/decommissioning/{{ object_type }}/{{ object.id }}/delete" | ||
> | ||
{% csrf_token %} | ||
<button class="btn btn-sm btn-danger">Confirm</button> | ||
</form> | ||
</div> | ||
{% endif %} | ||
|
||
</div> | ||
</div> | ||
{% endblock content-wrapper%} |
Oops, something went wrong.