osism 0.20250709.0__py3-none-any.whl → 0.20250823.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,7 @@ from loguru import logger
11
11
 
12
12
  from osism import utils
13
13
  from osism.tasks.conductor.netbox import (
14
+ get_device_interface_ips,
14
15
  get_device_loopbacks,
15
16
  get_device_oob_ip,
16
17
  get_device_vlans,
@@ -29,6 +30,7 @@ from .interface import (
29
30
  from .connections import (
30
31
  get_connected_interfaces,
31
32
  get_connected_device_for_sonic_interface,
33
+ get_connected_interface_ipv4_address,
32
34
  )
33
35
  from .cache import get_cached_device_interfaces
34
36
 
@@ -73,6 +75,12 @@ def generate_sonic_config(device, hwsku, device_as_mapping=None):
73
75
  # Get Loopback configuration from NetBox
74
76
  loopback_info = get_device_loopbacks(device)
75
77
 
78
+ # Get interface IP addresses from NetBox
79
+ interface_ips = get_device_interface_ips(device)
80
+
81
+ # Get IPv4 addresses from transfer role prefixes
82
+ transfer_ips = _get_transfer_role_ipv4_addresses(device)
83
+
76
84
  # Get breakout port configuration from NetBox
77
85
  breakout_info = detect_breakout_ports(device)
78
86
 
@@ -186,7 +194,14 @@ def generate_sonic_config(device, hwsku, device_as_mapping=None):
186
194
  )
187
195
 
188
196
  # Add interface configurations
189
- _add_interface_configurations(config, connected_interfaces, portchannel_info)
197
+ _add_interface_configurations(
198
+ config,
199
+ connected_interfaces,
200
+ portchannel_info,
201
+ interface_ips,
202
+ netbox_interfaces,
203
+ device,
204
+ )
190
205
 
191
206
  # Add BGP configurations
192
207
  _add_bgp_configurations(
@@ -196,6 +211,10 @@ def generate_sonic_config(device, hwsku, device_as_mapping=None):
196
211
  portchannel_info,
197
212
  device,
198
213
  device_as_mapping,
214
+ interface_ips,
215
+ netbox_interfaces,
216
+ transfer_ips,
217
+ utils.nb,
199
218
  )
200
219
 
201
220
  # Add NTP server configuration (device-specific)
@@ -531,7 +550,14 @@ def _add_tagged_vlans_to_ports(config, vlan_info, netbox_interfaces, device):
531
550
  config["PORT"][port_name]["tagged_vlans"] = tagged_vlans
532
551
 
533
552
 
534
- def _add_interface_configurations(config, connected_interfaces, portchannel_info):
553
+ def _add_interface_configurations(
554
+ config,
555
+ connected_interfaces,
556
+ portchannel_info,
557
+ interface_ips,
558
+ netbox_interfaces,
559
+ device,
560
+ ):
535
561
  """Add INTERFACE configuration for connected interfaces."""
536
562
  for port_name in config["PORT"]:
537
563
  # Check if this port is in the connected interfaces set and not a port channel member
@@ -539,8 +565,148 @@ def _add_interface_configurations(config, connected_interfaces, portchannel_info
539
565
  port_name in connected_interfaces
540
566
  and port_name not in portchannel_info["member_mapping"]
541
567
  ):
542
- # Add interface to INTERFACE section with ipv6_use_link_local_only enabled
543
- config["INTERFACE"][port_name] = {"ipv6_use_link_local_only": "enable"}
568
+ # Find the NetBox interface name for this SONiC port
569
+ netbox_interface_name = None
570
+ if port_name in netbox_interfaces:
571
+ netbox_interface_name = netbox_interfaces[port_name]["netbox_name"]
572
+
573
+ # Check if this interface has an IPv4 address assigned
574
+ ipv4_address = None
575
+ if netbox_interface_name and netbox_interface_name in interface_ips:
576
+ ipv4_address = interface_ips[netbox_interface_name]
577
+ logger.info(
578
+ f"Interface {port_name} ({netbox_interface_name}) has IPv4 address: {ipv4_address}"
579
+ )
580
+
581
+ if ipv4_address:
582
+ # If IPv4 address is available, configure the interface with it
583
+ # Add base interface entry (similar to VLAN_INTERFACE and LOOPBACK_INTERFACE patterns)
584
+ config["INTERFACE"][port_name] = {}
585
+ # Add IP address suffixed entry with scope and family parameters
586
+ config["INTERFACE"][f"{port_name}|{ipv4_address}"] = {
587
+ "scope": "global",
588
+ "family": "IPv4",
589
+ }
590
+ logger.info(
591
+ f"Configured interface {port_name} with IPv4 address {ipv4_address}"
592
+ )
593
+ else:
594
+ # Add interface to INTERFACE section with ipv6_use_link_local_only enabled
595
+ config["INTERFACE"][port_name] = {"ipv6_use_link_local_only": "enable"}
596
+ logger.debug(
597
+ f"Configured interface {port_name} with IPv6 link-local only"
598
+ )
599
+
600
+
601
+ def _get_transfer_role_ipv4_addresses(device):
602
+ """Get IPv4 addresses from IP prefixes with 'transfer' role.
603
+
604
+ Args:
605
+ device: NetBox device object
606
+
607
+ Returns:
608
+ dict: Dictionary mapping interface names to their transfer role IPv4 addresses
609
+ {
610
+ 'interface_name': 'ip_address/prefix_length',
611
+ ...
612
+ }
613
+ """
614
+ transfer_ips = {}
615
+
616
+ try:
617
+ # Get all interfaces for the device
618
+ interfaces = list(utils.nb.dcim.interfaces.filter(device_id=device.id))
619
+
620
+ for interface in interfaces:
621
+ # Skip management interfaces and virtual interfaces
622
+ if interface.mgmt_only or (
623
+ hasattr(interface, "type")
624
+ and interface.type
625
+ and interface.type.value == "virtual"
626
+ ):
627
+ continue
628
+
629
+ # Get IP addresses assigned to this interface
630
+ ip_addresses = utils.nb.ipam.ip_addresses.filter(
631
+ assigned_object_id=interface.id,
632
+ )
633
+
634
+ for ip_addr in ip_addresses:
635
+ if ip_addr.address:
636
+ try:
637
+ ip_obj = ipaddress.ip_interface(ip_addr.address)
638
+ if ip_obj.version == 4:
639
+ # Check if this IP belongs to a prefix with 'transfer' role
640
+ # Query for the prefix this IP belongs to
641
+ prefixes = utils.nb.ipam.prefixes.filter(
642
+ contains=str(ip_obj.ip)
643
+ )
644
+
645
+ for prefix in prefixes:
646
+ # Check if prefix has role and it's 'transfer'
647
+ if hasattr(prefix, "role") and prefix.role:
648
+ if prefix.role.slug == "transfer":
649
+ transfer_ips[interface.name] = ip_addr.address
650
+ logger.debug(
651
+ f"Found transfer role IPv4 {ip_addr.address} on interface {interface.name} of device {device.name}"
652
+ )
653
+ break
654
+
655
+ # Break after first IPv4 found (transfer or not)
656
+ if interface.name in transfer_ips:
657
+ break
658
+ except (ValueError, ipaddress.AddressValueError):
659
+ # Skip invalid IP addresses
660
+ continue
661
+
662
+ except Exception as e:
663
+ logger.warning(
664
+ f"Failed to get transfer role IPv4 addresses for device {device.name}: {e}"
665
+ )
666
+
667
+ return transfer_ips
668
+
669
+
670
+ def _has_direct_ipv4_address(port_name, interface_ips, netbox_interfaces):
671
+ """Check if an interface has a direct IPv4 address assigned.
672
+
673
+ Args:
674
+ port_name: SONiC interface name (e.g., "Ethernet0")
675
+ interface_ips: Dict mapping NetBox interface names to IPv4 addresses
676
+ netbox_interfaces: Dict mapping SONiC names to NetBox interface info
677
+
678
+ Returns:
679
+ bool: True if interface has a direct IPv4 address, False otherwise
680
+ """
681
+ if not interface_ips or not netbox_interfaces:
682
+ return False
683
+
684
+ if port_name in netbox_interfaces:
685
+ netbox_interface_name = netbox_interfaces[port_name]["netbox_name"]
686
+ return netbox_interface_name in interface_ips
687
+
688
+ return False
689
+
690
+
691
+ def _has_transfer_role_ipv4(port_name, transfer_ips, netbox_interfaces):
692
+ """Check if an interface has an IPv4 from a transfer role prefix.
693
+
694
+ Args:
695
+ port_name: SONiC interface name (e.g., "Ethernet0")
696
+ transfer_ips: Dict mapping NetBox interface names to transfer role IPv4 addresses
697
+ netbox_interfaces: Dict mapping SONiC names to NetBox interface info
698
+
699
+ Returns:
700
+ bool: True if interface has a transfer role IPv4 address, False otherwise
701
+ """
702
+ if not transfer_ips or not netbox_interfaces:
703
+ return False
704
+
705
+ if port_name in netbox_interfaces:
706
+ netbox_interface_name = netbox_interfaces[port_name]["netbox_name"]
707
+ return netbox_interface_name in transfer_ips
708
+
709
+ return False
544
710
 
545
711
 
546
712
  def _add_bgp_configurations(
@@ -550,52 +716,189 @@ def _add_bgp_configurations(
550
716
  portchannel_info,
551
717
  device,
552
718
  device_as_mapping=None,
719
+ interface_ips=None,
720
+ netbox_interfaces=None,
721
+ transfer_ips=None,
722
+ netbox=None,
553
723
  ):
554
- """Add BGP configurations."""
724
+ """Add BGP configurations.
725
+
726
+ Args:
727
+ config: Configuration dictionary to update
728
+ connected_interfaces: Set of connected interface names
729
+ connected_portchannels: Set of connected port channel names
730
+ portchannel_info: Port channel membership information
731
+ device: NetBox device object
732
+ device_as_mapping: Mapping of device names to AS numbers
733
+ interface_ips: Dict of direct IPv4 addresses on interfaces
734
+ netbox_interfaces: Dict mapping SONiC names to NetBox interface info
735
+ transfer_ips: Dict of IPv4 addresses from transfer role prefixes
736
+ netbox: NetBox API client for querying connected interface IPs
737
+ """
555
738
  # Add BGP_NEIGHBOR_AF configuration for connected interfaces
556
739
  for port_name in config["PORT"]:
740
+ has_direct_ipv4 = _has_direct_ipv4_address(
741
+ port_name, interface_ips, netbox_interfaces
742
+ )
743
+ has_transfer_ipv4 = _has_transfer_role_ipv4(
744
+ port_name, transfer_ips, netbox_interfaces
745
+ )
746
+
557
747
  if (
558
748
  port_name in connected_interfaces
559
749
  and port_name not in portchannel_info["member_mapping"]
560
750
  ):
561
- ipv4_key = f"default|{port_name}|ipv4_unicast"
562
- ipv6_key = f"default|{port_name}|ipv6_unicast"
563
- config["BGP_NEIGHBOR_AF"][ipv4_key] = {"admin_status": "true"}
564
- config["BGP_NEIGHBOR_AF"][ipv6_key] = {"admin_status": "true"}
751
+ # Include interfaces with transfer role IPv4 or no direct IPv4
752
+ if has_transfer_ipv4 or not has_direct_ipv4:
753
+ # Try to get the IPv4 address of the connected endpoint interface
754
+ connected_ipv4 = None
755
+ if netbox:
756
+ connected_ipv4 = get_connected_interface_ipv4_address(
757
+ device, port_name, netbox
758
+ )
759
+
760
+ # For BGP_NEIGHBOR_AF, always use interface name like IPv6 does
761
+ neighbor_id = port_name
762
+
763
+ ipv4_key = f"default|{neighbor_id}|ipv4_unicast"
764
+ config["BGP_NEIGHBOR_AF"][ipv4_key] = {"admin_status": "true"}
765
+
766
+ # Only add ipv6_unicast if v6only would be true (no transfer role IPv4)
767
+ if not has_transfer_ipv4:
768
+ ipv6_key = f"default|{neighbor_id}|ipv6_unicast"
769
+ config["BGP_NEIGHBOR_AF"][ipv6_key] = {"admin_status": "true"}
770
+ logger.debug(
771
+ f"Added BGP_NEIGHBOR_AF with ipv4_unicast and ipv6_unicast for interface {port_name} (no direct IPv4)"
772
+ )
773
+ else:
774
+ logger.debug(
775
+ f"Added BGP_NEIGHBOR_AF with ipv4_unicast only for interface {port_name} (transfer role IPv4, v6only=false)"
776
+ )
777
+ elif has_direct_ipv4 and not has_transfer_ipv4:
778
+ logger.info(
779
+ f"Excluding interface {port_name} from BGP detection (has direct IPv4 address, not transfer role)"
780
+ )
565
781
 
566
782
  # Add BGP_NEIGHBOR_AF configuration for connected port channels
567
783
  for pc_name in connected_portchannels:
568
- ipv4_key = f"default|{pc_name}|ipv4_unicast"
569
- ipv6_key = f"default|{pc_name}|ipv6_unicast"
784
+ # Try to get the IPv4 address of the connected endpoint interface for port channel
785
+ connected_ipv4 = None
786
+ if netbox:
787
+ connected_ipv4 = get_connected_interface_ipv4_address(
788
+ device, pc_name, netbox
789
+ )
790
+
791
+ # For BGP_NEIGHBOR_AF, always use port channel name like interfaces
792
+ neighbor_id = pc_name
793
+
794
+ ipv4_key = f"default|{neighbor_id}|ipv4_unicast"
795
+ ipv6_key = f"default|{neighbor_id}|ipv6_unicast"
570
796
  config["BGP_NEIGHBOR_AF"][ipv4_key] = {"admin_status": "true"}
571
797
  config["BGP_NEIGHBOR_AF"][ipv6_key] = {"admin_status": "true"}
572
798
 
573
799
  # Add BGP_NEIGHBOR configuration for connected interfaces
574
800
  for port_name in config["PORT"]:
801
+ has_direct_ipv4 = _has_direct_ipv4_address(
802
+ port_name, interface_ips, netbox_interfaces
803
+ )
804
+ has_transfer_ipv4 = _has_transfer_role_ipv4(
805
+ port_name, transfer_ips, netbox_interfaces
806
+ )
807
+
575
808
  if (
576
809
  port_name in connected_interfaces
577
810
  and port_name not in portchannel_info["member_mapping"]
578
811
  ):
579
- neighbor_key = f"default|{port_name}"
812
+ # Include interfaces with transfer role IPv4 or no direct IPv4
813
+ if has_transfer_ipv4 or not has_direct_ipv4:
814
+ # Try to get the IPv4 address of the connected endpoint interface
815
+ connected_ipv4 = None
816
+ if netbox:
817
+ connected_ipv4 = get_connected_interface_ipv4_address(
818
+ device, port_name, netbox
819
+ )
580
820
 
581
- # Determine peer_type based on connected device AS
582
- peer_type = "external" # Default
583
- connected_device = get_connected_device_for_sonic_interface(
584
- device, port_name
585
- )
586
- if connected_device:
587
- peer_type = _determine_peer_type(
588
- device, connected_device, device_as_mapping
821
+ # Use the connected interface's IPv4 address if available, otherwise use interface name
822
+ if connected_ipv4:
823
+ neighbor_key = f"default|{connected_ipv4}"
824
+ logger.debug(
825
+ f"Using connected interface IPv4 address {connected_ipv4} for BGP neighbor on {port_name}"
826
+ )
827
+ else:
828
+ neighbor_key = f"default|{port_name}"
829
+ logger.debug(
830
+ f"No connected interface IPv4 found, using interface name {port_name} for BGP neighbor"
831
+ )
832
+
833
+ # Determine peer_type based on connected device AS
834
+ peer_type = "external" # Default
835
+ connected_device = get_connected_device_for_sonic_interface(
836
+ device, port_name
589
837
  )
838
+ if connected_device:
839
+ peer_type = _determine_peer_type(
840
+ device, connected_device, device_as_mapping
841
+ )
590
842
 
591
- config["BGP_NEIGHBOR"][neighbor_key] = {
592
- "peer_type": peer_type,
593
- "v6only": "true",
594
- }
843
+ # Set v6only based on whether interface has transfer role IPv4
844
+ # - Transfer role IPv4: v6only=false (dual-stack BGP)
845
+ # - No direct IPv4: v6only=true (IPv6-only BGP)
846
+ bgp_neighbor_config = {
847
+ "peer_type": peer_type,
848
+ "v6only": "false" if has_transfer_ipv4 else "true",
849
+ }
850
+
851
+ # If using IP address as key, also store the local address
852
+ if connected_ipv4:
853
+ # Get the local interface IPv4 address
854
+ local_ipv4 = None
855
+ if port_name in netbox_interfaces:
856
+ netbox_interface_name = netbox_interfaces[port_name][
857
+ "netbox_name"
858
+ ]
859
+ if netbox_interface_name in interface_ips:
860
+ local_ipv4 = interface_ips[netbox_interface_name].split(
861
+ "/"
862
+ )[0]
863
+ elif netbox_interface_name in transfer_ips:
864
+ local_ipv4 = transfer_ips[netbox_interface_name].split("/")[
865
+ 0
866
+ ]
867
+
868
+ if local_ipv4:
869
+ bgp_neighbor_config["local_addr"] = local_ipv4
870
+
871
+ config["BGP_NEIGHBOR"][neighbor_key] = bgp_neighbor_config
872
+
873
+ if has_transfer_ipv4:
874
+ logger.debug(
875
+ f"Added BGP_NEIGHBOR for interface {port_name} (transfer role IPv4, v6only=false)"
876
+ )
877
+ else:
878
+ logger.debug(
879
+ f"Added BGP_NEIGHBOR for interface {port_name} (no direct IPv4, v6only=true)"
880
+ )
595
881
 
596
882
  # Add BGP_NEIGHBOR configuration for connected port channels
597
883
  for pc_name in connected_portchannels:
598
- neighbor_key = f"default|{pc_name}"
884
+ # Try to get the IPv4 address of the connected endpoint interface for port channel
885
+ connected_ipv4 = None
886
+ if netbox:
887
+ connected_ipv4 = get_connected_interface_ipv4_address(
888
+ device, pc_name, netbox
889
+ )
890
+
891
+ # Use the connected interface's IPv4 address if available, otherwise use port channel name
892
+ if connected_ipv4:
893
+ neighbor_key = f"default|{connected_ipv4}"
894
+ logger.debug(
895
+ f"Using connected interface IPv4 address {connected_ipv4} for BGP neighbor on {pc_name}"
896
+ )
897
+ else:
898
+ neighbor_key = f"default|{pc_name}"
899
+ logger.debug(
900
+ f"No connected interface IPv4 found, using port channel name {pc_name} for BGP neighbor"
901
+ )
599
902
 
600
903
  # Determine peer_type based on connected device AS
601
904
  peer_type = "external" # Default
@@ -605,11 +908,23 @@ def _add_bgp_configurations(
605
908
  device, connected_device, device_as_mapping
606
909
  )
607
910
 
608
- config["BGP_NEIGHBOR"][neighbor_key] = {
911
+ bgp_neighbor_config = {
609
912
  "peer_type": peer_type,
610
913
  "v6only": "true",
611
914
  }
612
915
 
916
+ # If using IP address as key, also store the local address
917
+ if connected_ipv4:
918
+ # For port channels, get the local IPv4 address from interface IPs
919
+ # Note: Port channels don't have direct IP assignments in NetBox,
920
+ # so we use the connected interface IP logic
921
+ local_ipv4 = None
922
+ # Port channels don't have NetBox interface entries,
923
+ # so we skip local_addr for port channels for now
924
+ # TODO: Implement port channel local address lookup if needed
925
+
926
+ config["BGP_NEIGHBOR"][neighbor_key] = bgp_neighbor_config
927
+
613
928
 
614
929
  def _get_connected_device_for_interface(device, interface_name):
615
930
  """Get the connected device for a given interface name.
@@ -387,3 +387,126 @@ def get_device_bgp_neighbors_via_loopback(
387
387
  logger.warning(f"Could not process BGP neighbors for device {device.name}: {e}")
388
388
 
389
389
  return bgp_neighbors
390
+
391
+
392
+ def get_connected_interface_ipv4_address(device, sonic_port_name, netbox):
393
+ """
394
+ Get the IPv4 address(es) of the connected endpoint interface for a given SONiC port.
395
+ Checks for direct IP addresses first, then for FHRP VIP addresses.
396
+
397
+ Args:
398
+ device: The SONiC device
399
+ sonic_port_name: The SONiC port name
400
+ netbox: The NetBox API client
401
+
402
+ Returns:
403
+ - For direct IP: The IPv4 address string of the connected interface
404
+ - For FHRP VIP: The first VIP address found (if multiple VIPs exist, logs all but returns first)
405
+ - None if no addresses found
406
+ """
407
+ try:
408
+ interface = netbox.dcim.interfaces.get(
409
+ device_id=device.id, name=sonic_port_name
410
+ )
411
+ if not interface:
412
+ return None
413
+
414
+ # Check if interface has connected_endpoints using the modern API
415
+ if not (
416
+ hasattr(interface, "connected_endpoints") and interface.connected_endpoints
417
+ ):
418
+ return None
419
+
420
+ # Ensure connected_endpoints_reachable is True
421
+ if not getattr(interface, "connected_endpoints_reachable", False):
422
+ return None
423
+
424
+ # Process each connected endpoint to find the first valid interface
425
+ connected_interface = None
426
+ for endpoint in interface.connected_endpoints:
427
+ if hasattr(endpoint, "id"):
428
+ connected_interface = endpoint
429
+ break
430
+
431
+ if not connected_interface:
432
+ return None
433
+
434
+ # First, try to get direct IPv4 addresses assigned to the connected interface
435
+ ip_addresses = netbox.ipam.ip_addresses.filter(
436
+ assigned_object_id=connected_interface.id,
437
+ )
438
+
439
+ for ip_address in ip_addresses:
440
+ # Check if it's an IPv4 address
441
+ if "/" in str(ip_address.address):
442
+ address = str(ip_address.address).split("/")[0]
443
+ if "." in address: # IPv4 address
444
+ logger.debug(
445
+ f"Found direct IPv4 address {address} on connected interface "
446
+ f"{connected_interface.name} for port {sonic_port_name}"
447
+ )
448
+ return address
449
+
450
+ # If no direct IP found, check for FHRP group membership and VIP addresses
451
+ logger.debug(
452
+ f"No direct IPv4 found on {connected_interface.name}, checking for FHRP VIP addresses"
453
+ )
454
+
455
+ # Get FHRP group assignments for the connected interface
456
+ fhrp_assignments = netbox.ipam.fhrp_group_assignments.filter(
457
+ interface_type="dcim.interface", interface_id=connected_interface.id
458
+ )
459
+
460
+ # Get all VIP addresses once to avoid repeated API calls
461
+ try:
462
+ all_vip_addresses = netbox.ipam.ip_addresses.filter(role="vip")
463
+ except Exception as vip_e:
464
+ logger.debug(f"Could not query VIP addresses: {vip_e}")
465
+ all_vip_addresses = []
466
+
467
+ # Collect all VIP IPv4 addresses from all FHRP groups this interface belongs to
468
+ vip_addresses_found = []
469
+
470
+ for assignment in fhrp_assignments:
471
+ if not assignment.group:
472
+ continue
473
+
474
+ # Find VIP addresses assigned to this specific FHRP group
475
+ for vip in all_vip_addresses:
476
+ # Check if this VIP is assigned to the current FHRP group
477
+ if (
478
+ hasattr(vip, "assigned_object_type")
479
+ and vip.assigned_object_type == "ipam.fhrpgroup"
480
+ and hasattr(vip, "assigned_object_id")
481
+ and vip.assigned_object_id == assignment.group.id
482
+ ):
483
+ # Check if it's an IPv4 address
484
+ if "/" in str(vip.address):
485
+ address = str(vip.address).split("/")[0]
486
+ if "." in address: # IPv4 address
487
+ vip_addresses_found.append(address)
488
+ logger.debug(
489
+ f"Found FHRP VIP address {address} for connected interface "
490
+ f"{connected_interface.name} (FHRP group: {assignment.group.name or assignment.group.id}) "
491
+ f"for port {sonic_port_name}"
492
+ )
493
+
494
+ # Return the first VIP address found (for BGP neighbor compatibility)
495
+ if vip_addresses_found:
496
+ logger.debug(
497
+ f"Found {len(vip_addresses_found)} VIP addresses for port {sonic_port_name}: {vip_addresses_found}"
498
+ )
499
+ logger.debug(f"Returning first VIP address: {vip_addresses_found[0]}")
500
+ return vip_addresses_found[0]
501
+
502
+ logger.debug(
503
+ f"No IPv4 address (direct or FHRP VIP) found on connected interface "
504
+ f"{connected_interface.name} for port {sonic_port_name}"
505
+ )
506
+ return None
507
+
508
+ except Exception as e:
509
+ logger.warning(
510
+ f"Could not get connected interface IPv4 for port {sonic_port_name}: {e}"
511
+ )
512
+ return None
@@ -419,6 +419,16 @@ def _extract_port_number_from_alias(alias):
419
419
  if not alias:
420
420
  return None
421
421
 
422
+ # Try to extract number from Eth54(Port54) format first
423
+ paren_match = re.search(r"Eth(\d+)\(Port(\d+)\)", alias)
424
+ if paren_match:
425
+ port_number = int(paren_match.group(1))
426
+ logger.debug(
427
+ f"Extracted port number {port_number} from Eth(Port) alias '{alias}'"
428
+ )
429
+ return port_number
430
+
431
+ # Fallback to number at end of alias
422
432
  match = re.search(r"(\d+)$", alias)
423
433
  if match:
424
434
  port_number = int(match.group(1))
@@ -687,7 +697,9 @@ def detect_breakout_ports(device):
687
697
  )
688
698
 
689
699
  # Calculate breakout mode
690
- if interface_speed == 25000 and num_subports == 4:
700
+ if interface_speed == 10000 and num_subports == 4:
701
+ brkout_mode = "4x10G"
702
+ elif interface_speed == 25000 and num_subports == 4:
691
703
  brkout_mode = "4x25G"
692
704
  elif interface_speed == 50000 and num_subports == 4:
693
705
  brkout_mode = "4x50G"
@@ -75,8 +75,15 @@ def get_vault():
75
75
  )
76
76
  ]
77
77
  )
78
- except Exception:
79
- logger.error("Unable to get vault secret. Dropping encrypted entries")
78
+ except ValueError as exc:
79
+ # Handle specific vault password configuration errors
80
+ logger.error(f"Vault password configuration error: {exc}")
81
+ logger.error("Please check your vault password setup in Redis")
82
+ vault = VaultLib()
83
+ except Exception as exc:
84
+ # Handle other errors (file access, decryption, etc.)
85
+ logger.error(f"Unable to get vault secret: {exc}")
86
+ logger.error("Dropping encrypted entries")
80
87
  vault = VaultLib()
81
88
  return vault
82
89