3434from django .core .validators import validate_ipv4_address
3535from django .core .exceptions import ValidationError
3636import logging
37- from .serializers import BridgeSerializer , ControllerSerializer
37+ from .serializers import BridgeSerializer , ControllerSerializer , PortSerializer , PortUpdateSerializer
3838from .models import Controller , Plugins
3939from .serializers import DeviceSerializer
4040
4444from rest_framework .permissions import IsAuthenticated
4545from knox .auth import TokenAuthentication
4646from utils .ansible_utils import run_playbook_with_extravars , create_temp_inv , create_inv_data
47+ from utils .ansible_formtter import get_single_port_speed_from_results , get_port_status_from_results
4748# Import the model manager
4849from classifier .model_manager import model_manager
4950from odl .models import Category
@@ -615,4 +616,132 @@ def bridges(self, request, pk=None):
615616 return Response ({"bridges" : data }, status = 200 )
616617 except Exception as e :
617618 logger .exception ('Error getting switch bridges...' )
618- return Response ({"error" : str (e )}, status = 500 )
619+ return Response ({"error" : str (e )}, status = status .HTTP_500_INTERNAL_SERVER_ERROR )
620+
621+ @action (detail = True , methods = ['get' ], url_path = 'bridge-ports' )
622+ def bridge_ports (self , request , pk = None ):
623+ """
624+ Custom action to fetch all ports on a switch that are assigned to bridges.
625+ Returns ports where bridge is not null.
626+ """
627+ try :
628+ switch = self .get_object ()
629+ # Get all ports on this switch that have a bridge assigned
630+ ports = Port .objects .filter (device = switch , bridge__isnull = False )
631+ serializer = PortSerializer (ports , many = True )
632+ return Response ({"ports" : serializer .data }, status = status .HTTP_200_OK )
633+ except Exception as e :
634+ logger .exception ('Error getting switch bridge ports...' )
635+ return Response ({"error" : str (e )}, status = status .HTTP_500_INTERNAL_SERVER_ERROR )
636+
637+
638+ # ----- PORT VIEWS ------
639+ class PortViewSet (ModelViewSet ):
640+ """
641+ A viewset that provides actions for Port model.
642+ """
643+ queryset = Port .objects .all ()
644+ serializer_class = PortSerializer
645+ authentication_classes = (TokenAuthentication ,)
646+ permission_classes = (IsAuthenticated ,)
647+
648+ def get_serializer_class (self ):
649+ """
650+ Use PortUpdateSerializer for partial updates (PATCH).
651+ """
652+ if self .action == 'partial_update' :
653+ return PortUpdateSerializer
654+ return PortSerializer
655+
656+ def partial_update (self , request , pk = None ):
657+ """
658+ Update port link_speed only.
659+ All other fields are read-only.
660+ """
661+ try :
662+ port = self .get_object ()
663+ serializer = PortUpdateSerializer (port , data = request .data , partial = True )
664+
665+ if serializer .is_valid ():
666+ # Only allow link_speed to be updated
667+ if 'link_speed' in serializer .validated_data :
668+ updated_port = serializer .save ()
669+ return Response (PortSerializer (updated_port ).data , status = status .HTTP_200_OK )
670+ else :
671+ return Response (
672+ {"error" : "Only link_speed can be updated" },
673+ status = status .HTTP_400_BAD_REQUEST
674+ )
675+ else :
676+ return Response (serializer .errors , status = status .HTTP_400_BAD_REQUEST )
677+ except Exception as e :
678+ logger .exception (f'Error updating port: { str (e )} ' )
679+ return Response ({"error" : str (e )}, status = status .HTTP_500_INTERNAL_SERVER_ERROR )
680+
681+ @action (detail = True , methods = ['post' ], url_path = 'sync' )
682+ def sync (self , request , pk = None ):
683+ """
684+ Sync port details (speed and status) from the switch.
685+ Connects to the device and checks the current port speed and status.
686+ """
687+ try :
688+ port = self .get_object ()
689+ device = port .device
690+
691+ if device .device_type != 'switch' :
692+ return Response (
693+ {"error" : "Port sync is only available for switch devices" },
694+ status = status .HTTP_400_BAD_REQUEST
695+ )
696+
697+ # Create inventory for the device
698+ inv_content = create_inv_data (device .lan_ip_address , device .username , device .password )
699+ inv_path = create_temp_inv (inv_content )
700+
701+ # Run the check-port-details playbook with port name as extravar
702+ playbook_name = "check-port-details"
703+ extra_vars = {
704+ 'port_name' : port .name ,
705+ 'ip_address' : device .lan_ip_address ,
706+ }
707+
708+ result = run_playbook_with_extravars (
709+ playbook_name ,
710+ playbook_dir_path ,
711+ inv_path ,
712+ extra_vars ,
713+ quiet = True
714+ )
715+
716+ if result .get ('status' ) != 'success' :
717+ error_msg = result .get ('error' , 'Unknown error occurred' )
718+ logger .error (f"Failed to sync port { port .name } : { error_msg } " )
719+ return Response (
720+ {"error" : f"Failed to sync port details: { error_msg } " },
721+ status = status .HTTP_500_INTERNAL_SERVER_ERROR
722+ )
723+
724+ # Parse results
725+ port_speed = get_single_port_speed_from_results (result , port .name )
726+ port_status = get_port_status_from_results (result , port .name )
727+
728+ # Update port in database
729+ update_fields = []
730+ if port_speed is not None :
731+ port .link_speed = port_speed
732+ update_fields .append ('link_speed' )
733+ if port_status is not None :
734+ port .is_up = port_status
735+ update_fields .append ('is_up' )
736+
737+ if update_fields :
738+ port .save (update_fields = update_fields )
739+ logger .debug (f"Updated port { port .name } : speed={ port_speed } , status={ port_status } " )
740+
741+ # Return updated port data
742+ serializer = PortSerializer (port )
743+ return Response (serializer .data , status = status .HTTP_200_OK )
744+
745+ except Exception as e :
746+ logger .exception (f'Error syncing port: { str (e )} ' )
747+ return Response ({"error" : str (e )}, status = status .HTTP_500_INTERNAL_SERVER_ERROR )
0 commit comments