From 9d03fceeeaa8b40bd700a63af692e3b1de1df943 Mon Sep 17 00:00:00 2001
From: Komal Thareja <kthare10@renci.org>
Date: Wed, 29 May 2024 14:47:32 -0400
Subject: [PATCH] Github checks

---
 CHANGELOG.md                                  |  2 +
 fabrictestbed_extensions/fablib/fablib.py     | 72 ++++++++++----
 .../fablib/facility_port.py                   |  8 +-
 fabrictestbed_extensions/fablib/metrics.py    | 14 +--
 .../fablib/network_service.py                 | 21 ++++-
 fabrictestbed_extensions/fablib/node.py       |  6 +-
 fabrictestbed_extensions/fablib/resources.py  | 93 ++++++++++++-------
 fabrictestbed_extensions/fablib/slice.py      | 56 +++++++----
 8 files changed, 185 insertions(+), 87 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8afa25b0..e04c0ae2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 # Unreleased
 
 ### Fixed
+- AL2S Support (Issue [#325](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/325))
+- Deny infeasible slices (Issue [#326](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/326))
 - Add display of switch port name to network service table listing (Issue [#152](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/152))
 - Error *may* be inaccurate or wrong when I issue an invalid configuration. (Issue [#304](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/304))
 - Add Facility Port to allow adding multiple interfaces (Issue [#289](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/289))
diff --git a/fabrictestbed_extensions/fablib/fablib.py b/fabrictestbed_extensions/fablib/fablib.py
index 1890f629..74fc9a9b 100644
--- a/fabrictestbed_extensions/fablib/fablib.py
+++ b/fabrictestbed_extensions/fablib/fablib.py
@@ -1122,7 +1122,12 @@ def list_sites(
 
         """
         return self.get_resources(
-            update=update, force_refresh=force_refresh, start=start, end=end, avoid=avoid, includes=includes
+            update=update,
+            force_refresh=force_refresh,
+            start=start,
+            end=end,
+            avoid=avoid,
+            includes=includes,
         ).list_sites(
             output=output,
             fields=fields,
@@ -1364,11 +1369,13 @@ def get_facility_ports(self, update: bool = True) -> FacilityPorts:
         return self.facility_ports
 
     def get_resources(
-        self, update: bool = True, force_refresh: bool = False,
-            start: datetime = None,
-            end: datetime = None,
-            avoid: List[str] = None,
-            includes: List[str] = None
+        self,
+        update: bool = True,
+        force_refresh: bool = False,
+        start: datetime = None,
+        end: datetime = None,
+        avoid: List[str] = None,
+        includes: List[str] = None,
     ) -> Resources:
         """
         Get a reference to the resources object. The resources object
@@ -1396,8 +1403,14 @@ def get_resources(
         :rtype: Resources
         """
         if not self.resources:
-            self.get_available_resources(update=update, force_refresh=force_refresh,
-                                         start=start, end=end, avoid=avoid, includes=includes)
+            self.get_available_resources(
+                update=update,
+                force_refresh=force_refresh,
+                start=start,
+                end=end,
+                avoid=avoid,
+                includes=includes,
+            )
 
         return self.resources
 
@@ -1602,11 +1615,13 @@ def get_site_advertisement(self, site: str) -> FimNode:
         return topology.sites[site]
 
     def get_available_resources(
-        self, update: bool = False, force_refresh: bool = False,
+        self,
+        update: bool = False,
+        force_refresh: bool = False,
         start: datetime = None,
         end: datetime = None,
         avoid: List[str] = None,
-        includes: List[str] = None
+        includes: List[str] = None,
     ) -> Resources:
         """
         Get the available resources.
@@ -1638,11 +1653,22 @@ def get_available_resources(
         from fabrictestbed_extensions.fablib.resources import Resources
 
         if self.resources is None:
-            self.resources = Resources(self, force_refresh=force_refresh,
-                                       start=start, end=end, avoid=avoid, includes=includes)
+            self.resources = Resources(
+                self,
+                force_refresh=force_refresh,
+                start=start,
+                end=end,
+                avoid=avoid,
+                includes=includes,
+            )
         elif update:
-            self.resources.update(force_refresh=force_refresh,
-                                  start=start, end=end, avoid=avoid, includes=includes)
+            self.resources.update(
+                force_refresh=force_refresh,
+                start=start,
+                end=end,
+                avoid=avoid,
+                includes=includes,
+            )
 
         return self.resources
 
@@ -2268,7 +2294,10 @@ def __can_allocate_node_in_worker(
         :rtype: Tuple[bool, str]
         """
         if worker is None or site is None:
-            return True, f"Ignoring validation: Worker: {worker}, Site: {site} not available."
+            return (
+                True,
+                f"Ignoring validation: Worker: {worker}, Site: {site} not available.",
+            )
 
         msg = f"Node can be allocated on the host: {worker.name}."
 
@@ -2359,8 +2388,13 @@ def validate_node(self, node: Node, allocated: dict = None) -> Tuple[bool, str]:
             site = self.get_resources().get_topology_site(site_name=node.get_site())
 
             if not site:
-                logging.warning(f"Ignoring validation: Site: {node.get_site()} not available in resources.")
-                return True, f"Ignoring validation: Site: {node.get_site()} not available in resources."
+                logging.warning(
+                    f"Ignoring validation: Site: {node.get_site()} not available in resources."
+                )
+                return (
+                    True,
+                    f"Ignoring validation: Site: {node.get_site()} not available in resources.",
+                )
 
             site_maint_info = site.maintenance_info.get(site.name)
             if site_maint_info and str(site_maint_info.state) != "Active":
@@ -2369,9 +2403,7 @@ def validate_node(self, node: Node, allocated: dict = None) -> Tuple[bool, str]:
                 return False, msg
             workers = self.get_resources().get_nodes(site=site)
             if not workers:
-                msg = (
-                    f"Node cannot be validated, host information not available for {site}."
-                )
+                msg = f"Node cannot be validated, host information not available for {site}."
                 logging.error(msg)
                 return False, msg
 
diff --git a/fabrictestbed_extensions/fablib/facility_port.py b/fabrictestbed_extensions/fablib/facility_port.py
index c94ca086..53856ae2 100644
--- a/fabrictestbed_extensions/fablib/facility_port.py
+++ b/fabrictestbed_extensions/fablib/facility_port.py
@@ -143,7 +143,9 @@ def new_facility_port(
         site: str = None,
         vlan: Union[List, str] = None,
         bandwidth: int = 10,
-        labels: Labels = None, peer_labels: Labels = None, capacities: Capacities = None
+        labels: Labels = None,
+        peer_labels: Labels = None,
+        capacities: Capacities = None,
     ):
         if capacities is None:
             if not bandwidth:
@@ -151,7 +153,7 @@ def new_facility_port(
             capacities = Capacities(bw=bandwidth)
 
         interfaces = None
-        
+
         if vlan:
             index = 1
             interfaces = []
@@ -172,7 +174,7 @@ def new_facility_port(
             capacities=capacities,
             labels=labels,
             peer_labels=peer_labels,
-            interfaces=interfaces
+            interfaces=interfaces,
         )
         return FacilityPort(slice, fim_facility_port)
 
diff --git a/fabrictestbed_extensions/fablib/metrics.py b/fabrictestbed_extensions/fablib/metrics.py
index 51e34ca4..06c61b0b 100644
--- a/fabrictestbed_extensions/fablib/metrics.py
+++ b/fabrictestbed_extensions/fablib/metrics.py
@@ -29,12 +29,12 @@ def __init__(self, fablib_manager):
         self.fablib_manager = fablib_manager
 
     def list_metrics(
-            self,
-            output=None,
-            fields=None,
-            quiet=False,
-            filter_function=None,
-            pretty_names=True
+        self,
+        output=None,
+        fields=None,
+        quiet=False,
+        filter_function=None,
+        pretty_names=True,
     ):
         table = []
         metric_dict = self.metrics_to_dict()
@@ -81,4 +81,4 @@ def metrics_to_dict(self):
         if not latlon:
             d.pop("location")
 
-        return d
\ No newline at end of file
+        return d
diff --git a/fabrictestbed_extensions/fablib/network_service.py b/fabrictestbed_extensions/fablib/network_service.py
index 6809aa32..42ac4c07 100644
--- a/fabrictestbed_extensions/fablib/network_service.py
+++ b/fabrictestbed_extensions/fablib/network_service.py
@@ -47,7 +47,7 @@
 from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
 
 import jinja2
-from fabrictestbed.slice_editor import Labels, Capacities
+from fabrictestbed.slice_editor import Capacities, Labels
 from fabrictestbed.slice_editor import NetworkService as FimNetworkService
 from fabrictestbed.slice_editor import ServiceType, UserData
 from fim.slivers.network_service import NSLayer, ServiceType
@@ -336,7 +336,7 @@ def new_l3network(
         interfaces: List[Interface] = [],
         type: str = None,
         user_data={},
-        technology: str = None
+        technology: str = None,
     ):
         """
         Not inteded for API use. See slice.add_l3network
@@ -444,7 +444,7 @@ def new_network_service(
         nstype: ServiceType = None,
         interfaces: List[Interface] = [],
         user_data: dict = {},
-        technology: str = None
+        technology: str = None,
     ):
         """
         Not intended for API use. See slice.add_l2network
@@ -1282,7 +1282,13 @@ def config(self):
                 allocated_ips.append(self.get_gateway())
             self.set_allocated_ip(self.get_gateway())
 
-    def peer(self, other: NetworkService, labels: Labels, peer_labels: Labels, capacities: Capacities):
+    def peer(
+        self,
+        other: NetworkService,
+        labels: Labels,
+        peer_labels: Labels,
+        capacities: Capacities,
+    ):
         """
         Peer a network service; used for AL2S peering between FABRIC Networks and Cloud Networks
         Peer this network service to another. A few constraints are enforced like services being
@@ -1300,4 +1306,9 @@ def peer(self, other: NetworkService, labels: Labels, peer_labels: Labels, capac
 
         """
         # Peer Cloud L3VPN with FABRIC L3VPN
-        self.get_fim().peer(other.get_fim(), labels=labels, peer_labels=peer_labels, capacities=capacities)
+        self.get_fim().peer(
+            other.get_fim(),
+            labels=labels,
+            peer_labels=peer_labels,
+            capacities=capacities,
+        )
diff --git a/fabrictestbed_extensions/fablib/node.py b/fabrictestbed_extensions/fablib/node.py
index bad67881..b9d0a907 100644
--- a/fabrictestbed_extensions/fablib/node.py
+++ b/fabrictestbed_extensions/fablib/node.py
@@ -85,7 +85,11 @@ class Node:
     default_image = "default_rocky_8"
 
     def __init__(
-        self, slice: Slice, node: FimNode, validate: bool = False, raise_exception: bool = False
+        self,
+        slice: Slice,
+        node: FimNode,
+        validate: bool = False,
+        raise_exception: bool = False,
     ):
         """
         Node constructor, usually invoked by ``Slice.add_node()``.
diff --git a/fabrictestbed_extensions/fablib/resources.py b/fabrictestbed_extensions/fablib/resources.py
index d9fc3223..f1f7a39c 100644
--- a/fabrictestbed_extensions/fablib/resources.py
+++ b/fabrictestbed_extensions/fablib/resources.py
@@ -70,7 +70,11 @@ class Resources:
     HOSTS = "Hosts"
 
     site_attribute_name_mappings = {
-        CORES.lower(): {NON_PRETTY_NAME: CORES.lower(), PRETTY_NAME: CORES, HEADER_NAME: CORES},
+        CORES.lower(): {
+            NON_PRETTY_NAME: CORES.lower(),
+            PRETTY_NAME: CORES,
+            HEADER_NAME: CORES,
+        },
         RAM.lower(): {
             NON_PRETTY_NAME: RAM.lower(),
             PRETTY_NAME: RAM,
@@ -140,21 +144,25 @@ class Resources:
         non_pretty_name = names.get(NON_PRETTY_NAME)
         pretty_name = names.get(PRETTY_NAME)
         site_pretty_names[non_pretty_name] = pretty_name
-        site_pretty_names[
-            f"{non_pretty_name}_{AVAILABLE.lower()}"
-        ] = f"{pretty_name} {AVAILABLE}"
-        site_pretty_names[
-            f"{non_pretty_name}_{CAPACITY.lower()}"
-        ] = f"{pretty_name} {CAPACITY}"
-        site_pretty_names[
-            f"{non_pretty_name}_{ALLOCATED.lower()}"
-        ] = f"{pretty_name} {ALLOCATED}"
-
-    def __init__(self, fablib_manager, force_refresh: bool = False,
-                 start: datetime = None,
-                 end: datetime = None,
-                 avoid: List[str] = None,
-                 includes: List[str] = None):
+        site_pretty_names[f"{non_pretty_name}_{AVAILABLE.lower()}"] = (
+            f"{pretty_name} {AVAILABLE}"
+        )
+        site_pretty_names[f"{non_pretty_name}_{CAPACITY.lower()}"] = (
+            f"{pretty_name} {CAPACITY}"
+        )
+        site_pretty_names[f"{non_pretty_name}_{ALLOCATED.lower()}"] = (
+            f"{pretty_name} {ALLOCATED}"
+        )
+
+    def __init__(
+        self,
+        fablib_manager,
+        force_refresh: bool = False,
+        start: datetime = None,
+        end: datetime = None,
+        avoid: List[str] = None,
+        includes: List[str] = None,
+    ):
         """
         :param fablib_manager: a :class:`FablibManager` instance.
         :type fablib_manager: fablib.FablibManager
@@ -182,7 +190,13 @@ def __init__(self, fablib_manager, force_refresh: bool = False,
 
         self.topology = None
 
-        self.update(force_refresh=force_refresh, start=start, end=end, includes=includes, avoid=avoid)
+        self.update(
+            force_refresh=force_refresh,
+            start=start,
+            end=end,
+            includes=includes,
+            avoid=avoid,
+        )
 
     def __str__(self) -> str:
         """
@@ -352,21 +366,21 @@ def get_site_info(
             nodes = self.get_nodes(site=site)
             site_info[self.CORES.lower()] = {
                 self.CAPACITY.lower(): site.capacities.core,
-                self.ALLOCATED.lower(): site.capacity_allocations.core
-                if site.capacity_allocations
-                else 0,
+                self.ALLOCATED.lower(): (
+                    site.capacity_allocations.core if site.capacity_allocations else 0
+                ),
             }
             site_info[self.RAM.lower()] = {
                 self.CAPACITY.lower(): site.capacities.ram,
-                self.ALLOCATED.lower(): site.capacity_allocations.ram
-                if site.capacity_allocations
-                else 0,
+                self.ALLOCATED.lower(): (
+                    site.capacity_allocations.ram if site.capacity_allocations else 0
+                ),
             }
             site_info[self.DISK.lower()] = {
                 self.CAPACITY.lower(): site.capacities.disk,
-                self.ALLOCATED.lower(): site.capacity_allocations.disk
-                if site.capacity_allocations
-                else 0,
+                self.ALLOCATED.lower(): (
+                    site.capacity_allocations.disk if site.capacity_allocations else 0
+                ),
             }
 
             if nodes:
@@ -757,11 +771,14 @@ def get_ptp_capable(
     def get_fablib_manager(self):
         return self.fablib_manager
 
-    def update(self, force_refresh: bool = False,
-               start: datetime = None,
-               end: datetime = None,
-               avoid: List[str] = None,
-               includes: List[str] = None):
+    def update(
+        self,
+        force_refresh: bool = False,
+        start: datetime = None,
+        end: datetime = None,
+        avoid: List[str] = None,
+        includes: List[str] = None,
+    ):
         """
         Update the available resources by querying the FABRIC services
         :param force_refresh: force a refresh of available testbed
@@ -785,8 +802,14 @@ def update(self, force_refresh: bool = False,
         return_status, topology = (
             self.get_fablib_manager()
             .get_slice_manager()
-            .resources(force_refresh=force_refresh, level=2, start=start, end=end, excludes=avoid,
-                       includes=includes)
+            .resources(
+                force_refresh=force_refresh,
+                level=2,
+                start=start,
+                end=end,
+                excludes=avoid,
+                includes=includes,
+            )
         )
         if return_status != Status.OK:
             raise Exception(
@@ -1141,7 +1164,9 @@ def fp_to_dict(self, iface: interface.Interface, name: str, site: str) -> dict:
             "site_name": site,
             "node_id": iface.node_id,
             "vlan_range": iface.labels.vlan_range if iface.labels else "N/A",
-            "allocated_vlan_range": label_allocations.vlan if label_allocations else "N/A",
+            "allocated_vlan_range": (
+                label_allocations.vlan if label_allocations else "N/A"
+            ),
             "local_name": (
                 iface.labels.local_name
                 if iface.labels and iface.labels.local_name
diff --git a/fabrictestbed_extensions/fablib/slice.py b/fabrictestbed_extensions/fablib/slice.py
index beb0f7e9..d8c8bb66 100644
--- a/fabrictestbed_extensions/fablib/slice.py
+++ b/fabrictestbed_extensions/fablib/slice.py
@@ -59,7 +59,7 @@
 from typing import TYPE_CHECKING, Tuple
 
 import pandas as pd
-from fim.user import Labels, Capacities
+from fim.user import Capacities, Labels
 from fss_utils.sshkey import FABRICSSHKey
 from IPython.core.display_functions import display
 
@@ -784,7 +784,11 @@ def is_advanced_allocation(self) -> bool:
         :rtype: Bool
         """
         now = datetime.now(timezone.utc)
-        lease_start = datetime.strptime(self.get_lease_start(), Constants.LEASE_TIME_FORMAT) if self.get_lease_start() else None
+        lease_start = (
+            datetime.strptime(self.get_lease_start(), Constants.LEASE_TIME_FORMAT)
+            if self.get_lease_start()
+            else None
+        )
         if lease_start and lease_start > now and self.is_allocated():
             return True
         return False
@@ -796,10 +800,10 @@ def is_allocated(self) -> bool:
         :return: True if slice is Allocated, False otherwise
         :rtype: Bool
         """
-        if self.get_state() in [
-            "AllocatedOK",
-            "AllocatedError"
-        ] and self.get_lease_start() :
+        if (
+            self.get_state() in ["AllocatedOK", "AllocatedError"]
+            and self.get_lease_start()
+        ):
             return True
         else:
             return False
@@ -998,7 +1002,7 @@ def add_l3network(
         interfaces: List[Interface] = [],
         type: str = "IPv4",
         user_data: dict = {},
-        technology: str = None
+        technology: str = None,
     ) -> NetworkService:
         """
         Adds a new L3 network service to this slice.
@@ -1059,8 +1063,13 @@ def add_l3network(
         )
 
     def add_facility_port(
-        self, name: str = None, site: str = None, vlan: Union[str, list] = None, labels: Labels = None,
-            peer_labels: Labels = None, capacities: Capacities = None
+        self,
+        name: str = None,
+        site: str = None,
+        vlan: Union[str, list] = None,
+        labels: Labels = None,
+        peer_labels: Labels = None,
+        capacities: Capacities = None,
     ) -> NetworkService:
         """
         Adds a new L2 facility port to this slice
@@ -1081,7 +1090,13 @@ def add_facility_port(
         :rtype: NetworkService
         """
         return FacilityPort.new_facility_port(
-            slice=self, name=name, site=site, vlan=vlan, labels=labels, peer_labels=peer_labels, capacities=capacities
+            slice=self,
+            name=name,
+            site=site,
+            vlan=vlan,
+            labels=labels,
+            peer_labels=peer_labels,
+            capacities=capacities,
         )
 
     def add_node(
@@ -1151,7 +1166,12 @@ def add_node(
         :rtype: Node
         """
         node = Node.new_node(
-            slice=self, name=name, site=site, avoid=avoid, validate=validate, raise_exception=raise_exception
+            slice=self,
+            name=name,
+            site=site,
+            avoid=avoid,
+            validate=validate,
+            raise_exception=raise_exception,
         )
 
         node.init_fablib_data()
@@ -2025,7 +2045,7 @@ def submit(
         extra_ssh_keys: List[str] = None,
         lease_start_time: datetime = None,
         lease_in_days: int = None,
-        validate: bool = False
+        validate: bool = False,
     ) -> str:
         """
         Submits a slice request to FABRIC.
@@ -2116,10 +2136,12 @@ def submit(
 
             lease_end_time = None
             if lease_in_days:
-                start_time = lease_start_time if lease_end_time else datetime.now(timezone.utc)
-                lease_end_time = (
-                    start_time + timedelta(days=lease_in_days)
-                ).strftime("%Y-%m-%d %H:%M:%S %z")
+                start_time = (
+                    lease_start_time if lease_end_time else datetime.now(timezone.utc)
+                )
+                lease_end_time = (start_time + timedelta(days=lease_in_days)).strftime(
+                    "%Y-%m-%d %H:%M:%S %z"
+                )
 
             (
                 return_status,
@@ -2129,7 +2151,7 @@ def submit(
                 slice_graph=slice_graph,
                 ssh_key=ssh_keys,
                 lease_end_time=lease_end_time,
-                lease_start_time=lease_start_time_str
+                lease_start_time=lease_start_time_str,
             )
             if return_status == Status.OK:
                 logging.info(