diff --git a/fabric_cf/actor/boot/configuration.py b/fabric_cf/actor/boot/configuration.py index 5171e928..d2d235bd 100644 --- a/fabric_cf/actor/boot/configuration.py +++ b/fabric_cf/actor/boot/configuration.py @@ -45,6 +45,10 @@ def __init__(self, *, config: dict): if Constants.CONFIG_SECTION_O_AUTH in config: self.oauth = config.get(Constants.CONFIG_SECTION_O_AUTH) + self.core_api = {} + if Constants.CONFIG_SECTION_CORE_API in config: + self.core_api = config.get(Constants.CONFIG_SECTION_CORE_API) + self.smtp = {} if Constants.CONFIG_SECTION_SMTP in config: self.smtp = config.get(Constants.CONFIG_SECTION_SMTP) @@ -91,6 +95,12 @@ def get_oauth(self) -> dict: """ return self.oauth + def get_core_api(self) -> dict: + """ + Return core api + """ + return self.core_api + def get_smtp(self) -> dict: """ Return smtp config @@ -425,7 +435,6 @@ def get_runtime_config(self) -> dict: """ if self.global_config is not None: return self.global_config.get_runtime() - return None def get_oauth_config(self) -> dict: """ @@ -433,7 +442,13 @@ def get_oauth_config(self) -> dict: """ if self.global_config is not None: return self.global_config.get_oauth() - return None + + def get_core_api_config(self) -> dict: + """ + Return Core API Config + """ + if self.global_config is not None: + return self.global_config.get_core_api() def get_smtp_config(self) -> dict: if self.global_config: diff --git a/fabric_cf/actor/core/apis/abc_database.py b/fabric_cf/actor/core/apis/abc_database.py index fd6a0245..54bc9c4b 100644 --- a/fabric_cf/actor/core/apis/abc_database.py +++ b/fabric_cf/actor/core/apis/abc_database.py @@ -477,4 +477,4 @@ def get_poas(self, *, poa_id: str = None, email: str = None, sliver_id: ID = Non @param offset offset @param states states @param last_update_time last update time - """ \ No newline at end of file + """ diff --git a/fabric_cf/actor/core/common/constants.py b/fabric_cf/actor/core/common/constants.py index c4b4cf3b..21aaafe1 100644 --- a/fabric_cf/actor/core/common/constants.py +++ b/fabric_cf/actor/core/common/constants.py @@ -173,6 +173,9 @@ class Constants: PROPERTY_CONF_O_AUTH_TRL_REFRESH = "trl-refresh" PROPERTY_CONF_O_AUTH_VERIFY_EXP = "verify-exp" + CONFIG_SECTION_CORE_API = "core_api" + PROPERTY_CONF_HOST = "host" + CONFIG_SECTION_SMTP = "smtp" CONFIG_SECTION_DATABASE = "database" @@ -275,6 +278,7 @@ class Constants: CLAIMS_PROJECTS = "projects" UUID = "uuid" TAGS = "tags" + TOKEN = "token" TOKEN_HASH = "token_hash" PROJECT_ID = "project_id" USERS = "users" diff --git a/fabric_cf/actor/core/container/globals.py b/fabric_cf/actor/core/container/globals.py index b0b141c7..c8e5c961 100644 --- a/fabric_cf/actor/core/container/globals.py +++ b/fabric_cf/actor/core/container/globals.py @@ -35,6 +35,7 @@ import logging import os +from fabric_cf.actor.core.util.quota_mgr import QuotaMgr from fim.graph.neo4j_property_graph import Neo4jGraphImporter from fim.graph.resources.abc_arm import ABCARMPropertyGraph from fss_utils.jwt_validate import JWTValidator @@ -74,6 +75,7 @@ def __init__(self): self.lock = threading.Lock() self.jwt_validator = None self.token_validator = None + self.quota_mgr = None def make_logger(self): """ @@ -193,6 +195,12 @@ def load_validators(self): refresh_period=timedelta(hours=t.hour, minutes=t.minute, seconds=t.second), jwt_validator=self.jwt_validator) + core_api = self.config.get_core_api_config() + if core_api.get("enable", False): + self.quota_mgr = QuotaMgr(core_api_host=core_api.get(Constants.PROPERTY_CONF_HOST), + token=core_api.get(Constants.TOKEN, ""), + logger=self.log) + def load_config(self): """ Load the configuration @@ -211,6 +219,9 @@ def get_jwt_validator(self) -> JWTValidator: def get_token_validator(self) -> TokenValidator: return self.token_validator + def get_quota_mgr(self) -> QuotaMgr: + return self.quota_mgr + def get_container(self) -> ABCActorContainer: """ Get the container diff --git a/fabric_cf/actor/core/kernel/kernel.py b/fabric_cf/actor/core/kernel/kernel.py index 4d15883e..d853cafe 100644 --- a/fabric_cf/actor/core/kernel/kernel.py +++ b/fabric_cf/actor/core/kernel/kernel.py @@ -26,6 +26,7 @@ import threading import time import traceback +from datetime import datetime, timezone from typing import List, Dict @@ -34,7 +35,7 @@ from fabric_cf.actor.core.apis.abc_delegation import ABCDelegation from fabric_cf.actor.core.apis.abc_policy import ABCPolicy from fabric_cf.actor.core.common.constants import Constants -from fabric_cf.actor.core.common.event_logger import EventLogger, EventLoggerSingleton +from fabric_cf.actor.core.common.event_logger import EventLoggerSingleton from fabric_cf.actor.core.common.exceptions import ReservationNotFoundException, DelegationNotFoundException, \ KernelException from fabric_cf.actor.core.kernel.authority_reservation import AuthorityReservation diff --git a/fabric_cf/actor/core/kernel/reservation_client.py b/fabric_cf/actor/core/kernel/reservation_client.py index 8be75693..01360a32 100644 --- a/fabric_cf/actor/core/kernel/reservation_client.py +++ b/fabric_cf/actor/core/kernel/reservation_client.py @@ -1557,16 +1557,16 @@ def validate_redeem(self): def add_redeem_predecessor(self, *, reservation: ABCReservationMixin, filters: dict = None): if reservation.get_reservation_id() not in self.redeem_predecessors: - state = PredecessorState(reservation=reservation) + state = PredecessorState(reservation=reservation, filters=filters) self.redeem_predecessors[reservation.get_reservation_id()] = state def remove_redeem_predecessor(self, *, rid: ID): if rid in self.redeem_predecessors: self.redeem_predecessors.pop(rid) - def add_join_predecessor(self, *, predecessor): + def add_join_predecessor(self, *, predecessor: ABCReservationMixin, filters: dict = None): if predecessor.get_reservation_id() not in self.redeem_predecessors: - state = PredecessorState(reservation=predecessor) + state = PredecessorState(reservation=predecessor, filters=filters) self.join_predecessors[predecessor.get_reservation_id()] = state def get_redeem_predecessors(self) -> List[PredecessorState]: diff --git a/fabric_cf/actor/core/manage/actor_management_object.py b/fabric_cf/actor/core/manage/actor_management_object.py index efc4f09c..fc06305c 100644 --- a/fabric_cf/actor/core/manage/actor_management_object.py +++ b/fabric_cf/actor/core/manage/actor_management_object.py @@ -29,6 +29,8 @@ from datetime import datetime, timezone from typing import TYPE_CHECKING, List, Dict, Tuple +from fabric_mb.message_bus.messages.lease_reservation_avro import LeaseReservationAvro + from fabric_cf.actor.fim.fim_helper import FimHelper from fabric_mb.message_bus.messages.reservation_mng import ReservationMng from fabric_mb.message_bus.messages.result_delegation_avro import ResultDelegationAvro @@ -902,4 +904,4 @@ def build_broker_query_model(self, level_0_broker_query_model: str, level: int, end=end, includes=includes, excludes=excludes) except Exception as e: self.logger.error(f"Exception occurred build_broker_query_model e: {e}") - self.logger.error(traceback.format_exc()) \ No newline at end of file + self.logger.error(traceback.format_exc()) diff --git a/fabric_cf/actor/core/manage/client_actor_management_object_helper.py b/fabric_cf/actor/core/manage/client_actor_management_object_helper.py index 662b5608..67c94be0 100644 --- a/fabric_cf/actor/core/manage/client_actor_management_object_helper.py +++ b/fabric_cf/actor/core/manage/client_actor_management_object_helper.py @@ -43,8 +43,7 @@ from fabric_cf.actor.core.apis.abc_actor_runnable import ABCActorRunnable from fabric_cf.actor.core.apis.abc_controller_reservation import ABCControllerReservation -from fabric_cf.actor.core.apis.abc_reservation_mixin import ABCReservationMixin -from fabric_cf.actor.core.common.constants import Constants, ErrorCodes +from fabric_cf.actor.core.common.constants import ErrorCodes from fabric_cf.actor.core.common.exceptions import ManageException from fabric_cf.actor.core.kernel.reservation_client import ClientReservationFactory from fabric_cf.actor.core.kernel.reservation_states import ReservationStates, ReservationPendingStates @@ -635,4 +634,4 @@ def run(self): result.set_message(ErrorCodes.ErrorInternalError.interpret(exception=e)) result = ManagementObject.set_exception_details(result=result, e=e) - return result \ No newline at end of file + return result diff --git a/fabric_cf/actor/core/plugins/db/actor_database.py b/fabric_cf/actor/core/plugins/db/actor_database.py index 934eb264..1c58b010 100644 --- a/fabric_cf/actor/core/plugins/db/actor_database.py +++ b/fabric_cf/actor/core/plugins/db/actor_database.py @@ -28,7 +28,8 @@ import time import traceback from datetime import datetime -from typing import List, Union, Tuple, Dict +from typing import List, Union, Dict + from fabric_cf.actor.core.apis.abc_actor_mixin import ABCActorMixin, ActorType from fabric_cf.actor.core.apis.abc_broker_proxy import ABCBrokerProxy diff --git a/fabric_cf/actor/core/policy/broker_simpler_units_policy.py b/fabric_cf/actor/core/policy/broker_simpler_units_policy.py index 8ad4e5b8..e157a055 100644 --- a/fabric_cf/actor/core/policy/broker_simpler_units_policy.py +++ b/fabric_cf/actor/core/policy/broker_simpler_units_policy.py @@ -1075,8 +1075,20 @@ def ticket_inventory(self, *, reservation: ABCBrokerReservation, inv: InventoryF try: if operation == ReservationOperation.Extend: rset = reservation.get_resources() + duration = Term.delta(reservation.get_term().get_end_time(), term.get_end_time()) else: rset = reservation.get_requested_resources() + duration = term.get_full_length() + + from fabric_cf.actor.core.container.globals import GlobalsSingleton + if GlobalsSingleton.get().get_quota_mgr(): + status, error_msg = GlobalsSingleton.get().get_quota_mgr().enforce_quota_limits(reservation=reservation, + duration=duration) + self.logger.info(f"Quota enforcement status: {status}, error: {error_msg}") + # TODO: enable enforcement action later + #if not status: + # return status, node_id_to_reservations, error_msg + needed = rset.get_units() # for network node slivers @@ -1115,6 +1127,11 @@ def ticket_inventory(self, *, reservation: ABCBrokerReservation, inv: InventoryF if node_id_to_reservations.get(node_id, None) is None: node_id_to_reservations[node_id] = ReservationSet() node_id_to_reservations[node_id].add(reservation=reservation) + + from fabric_cf.actor.core.container.globals import GlobalsSingleton + if GlobalsSingleton.get().get_quota_mgr(): + GlobalsSingleton.get().get_quota_mgr().update_quota(reservation=reservation, duration=duration) + self.logger.debug(f"Ticket Inventory returning: True {error_msg}") return True, node_id_to_reservations, error_msg except Exception as e: @@ -1204,6 +1221,13 @@ def issue_ticket(self, *, reservation: ABCBrokerReservation, units: int, rtype: return reservation def release(self, *, reservation): + duration = reservation.get_term().get_remaining_length() + if duration > 0: + from fabric_cf.actor.core.container.globals import GlobalsSingleton + if GlobalsSingleton.get().get_quota_mgr(): + GlobalsSingleton.get().get_quota_mgr().update_quota(reservation=reservation, + duration=duration) + if isinstance(reservation, ABCBrokerReservation): self.logger.debug("Broker reservation") super().release(reservation=reservation) diff --git a/fabric_cf/actor/core/policy/inventory_for_type.py b/fabric_cf/actor/core/policy/inventory_for_type.py index a8ec6b5c..5875ac5c 100644 --- a/fabric_cf/actor/core/policy/inventory_for_type.py +++ b/fabric_cf/actor/core/policy/inventory_for_type.py @@ -59,7 +59,7 @@ def free(self, *, count: int, request: dict = None, resource: dict = None) -> di """ @staticmethod - def _get_allocated_sliver(reservation: ABCReservationMixin) -> BaseSliver: + def get_allocated_sliver(reservation: ABCReservationMixin) -> BaseSliver: """ Retrieve the allocated sliver from the reservation. @@ -70,3 +70,5 @@ def _get_allocated_sliver(reservation: ABCReservationMixin) -> BaseSliver: return reservation.get_approved_resources().get_sliver() if (reservation.is_active() or reservation.is_ticketed()) and reservation.get_resources() is not None: return reservation.get_resources().get_sliver() + if (reservation.is_closed()) and reservation.get_resources() is not None: + return reservation.get_resources().get_sliver() diff --git a/fabric_cf/actor/core/policy/network_node_inventory.py b/fabric_cf/actor/core/policy/network_node_inventory.py index df3e4895..055f1845 100644 --- a/fabric_cf/actor/core/policy/network_node_inventory.py +++ b/fabric_cf/actor/core/policy/network_node_inventory.py @@ -69,7 +69,7 @@ def check_capacities(*, rid: ID, requested_capacities: Capacities, delegated: De if rid == reservation.get_reservation_id(): continue # For Active or Ticketed or Ticketing reservations; reduce the counts from available - resource_sliver = InventoryForType._get_allocated_sliver(reservation=reservation) + resource_sliver = InventoryForType.get_allocated_sliver(reservation=reservation) if resource_sliver is not None and isinstance(resource_sliver, NodeSliver): logger.debug( @@ -380,7 +380,7 @@ def __exclude_components_for_existing_reservations(*, rid: ID, graph_node: NodeS (operation == ReservationOperation.Extend or not reservation.is_ticketed()): continue # For Active or Ticketed or Ticketing reservations; reduce the counts from available - allocated_sliver = InventoryForType._get_allocated_sliver(reservation=reservation) + allocated_sliver = InventoryForType.get_allocated_sliver(reservation=reservation) if reservation.is_extending_ticket() and reservation.get_requested_resources() is not None and \ reservation.get_requested_resources().get_sliver() is not None: diff --git a/fabric_cf/actor/core/policy/network_service_inventory.py b/fabric_cf/actor/core/policy/network_service_inventory.py index 86612fe3..91f44613 100644 --- a/fabric_cf/actor/core/policy/network_service_inventory.py +++ b/fabric_cf/actor/core/policy/network_service_inventory.py @@ -76,7 +76,7 @@ def __exclude_allocated_vlans(self, *, rid: ID, available_vlan_range: List[int], continue # For Active or Ticketed or Ticketing reservations; reduce the counts from available - allocated_sliver = self._get_allocated_sliver(reservation=reservation) + allocated_sliver = self.get_allocated_sliver(reservation=reservation) self.logger.debug( f"Existing res# {reservation.get_reservation_id()} state:{reservation.get_state()} " @@ -278,7 +278,7 @@ def allocate_vnic(self, *, rid: ID, requested_ns: NetworkServiceSliver, owner_ns continue # For Active or Ticketed or Ticketing reservations; reduce the counts from available - allocated_sliver = self._get_allocated_sliver(reservation=reservation) + allocated_sliver = self.get_allocated_sliver(reservation=reservation) self.logger.debug(f"Existing res# {reservation.get_reservation_id()} " f"allocated: {allocated_sliver}") @@ -413,7 +413,7 @@ def _exclude_allocated_subnets(self, *, subnet_list: List, requested_ns_type: st if rid == reservation.get_reservation_id(): continue - allocated_sliver = self._get_allocated_sliver(reservation) + allocated_sliver = self.get_allocated_sliver(reservation) if allocated_sliver is None: continue diff --git a/fabric_cf/actor/core/time/term.py b/fabric_cf/actor/core/time/term.py index 3b18ecf9..dd97f859 100644 --- a/fabric_cf/actor/core/time/term.py +++ b/fabric_cf/actor/core/time/term.py @@ -331,6 +331,18 @@ def get_full_length(self) -> int: return end_ms - start_ms + 1 + def get_remaining_length(self) -> int: + """ + Returns the length of remaining term in milliseconds. The length of a term is the + number of milliseconds in the closed interval [now, end] + @returns term length + """ + now = datetime.now(timezone.utc) + current_ms = ActorClock.to_milliseconds(when=now) + end_ms = ActorClock.to_milliseconds(when=self.end_time) + + return end_ms - current_ms + 1 + def get_length(self) -> int: """ Returns the length of a term in milliseconds. The length of a term is the diff --git a/fabric_cf/actor/core/util/quota_mgr.py b/fabric_cf/actor/core/util/quota_mgr.py new file mode 100644 index 00000000..da2982d4 --- /dev/null +++ b/fabric_cf/actor/core/util/quota_mgr.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# MIT License +# +# Copyright (c) 2020 FABRIC Testbed +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# +# Author: Komal Thareja (kthare10@renci.org) +import logging +import threading +from typing import Any + +from fabrictestbed.external_api.core_api import CoreApi +from fabrictestbed.slice_editor import InstanceCatalog +from fim.slivers.network_node import NodeSliver + +from fabric_cf.actor.core.apis.abc_reservation_mixin import ABCReservationMixin +from fabric_cf.actor.core.policy.inventory_for_type import InventoryForType + + +class QuotaMgr: + """ + Manages resource quotas for projects, including listing, updating, and enforcing limits. + """ + def __init__(self, *, core_api_host: str, token: str, logger: logging.Logger): + """ + Initialize the Quota Manager. + + @param core_api_host: The API host for core services. + @param token: Authentication token for API access. + @param logger: Logger instance for logging messages. + """ + self.core_api = CoreApi(core_api_host=core_api_host, token=token) + self.logger = logger + self.lock = threading.Lock() + + def list_quotas(self, project_uuid: str, offset: int = 0, limit: int = 200) -> dict[tuple[str, str], dict]: + """ + Retrieve the list of quotas for a given project. + + @param project_uuid: Unique identifier for the project. + @param offset: Pagination offset for results (default: 0). + @param limit: Maximum number of quotas to fetch (default: 200). + @return: A dictionary mapping resource type/unit pairs to their quota details. + """ + quota_list = self.core_api.list_quotas(project_uuid=project_uuid, offset=offset, limit=limit) + quotas = {} + for q in quota_list: + quotas[(q.get("resource_type").lower(), q.get("resource_unit").lower())] = q + return quotas + + def update_quota(self, reservation: ABCReservationMixin, duration: float): + """ + Update the quota usage for a given reservation. + + @param reservation: Reservation object containing resource usage details. + @param duration: Duration in milliseconds for which the reservation was held. + """ + try: + with self.lock: # Locking critical section + self.logger.debug("Acquired lock for quota update.") + + slice_object = reservation.get_slice() + if not slice_object: + return + project_id = slice_object.get_project_id() + if not project_id: + return + + sliver = InventoryForType.get_allocated_sliver(reservation=reservation) + if not sliver: + return + + if duration < 60: + return + + existing_quotas = self.list_quotas(project_uuid=project_id) + + sliver_quota_usage = self.extract_quota_usage(sliver=sliver, duration=duration) + + self.logger.debug(f"Existing: {existing_quotas}") + self.logger.debug(f"Updated by: {sliver_quota_usage}") + + # Check each accumulated resource usage against its quota + for quota_key, total_duration in sliver_quota_usage.items(): + existing = existing_quotas.get(quota_key) + if not existing: + self.logger.debug("Existing not found, skipping!") + continue + + # Return resource hours for a sliver deleted before expiry + usage = -total_duration if reservation.is_closing() or reservation.is_closed() else total_duration + + self.core_api.update_quota_usage(uuid=existing.get("uuid"), project_uuid=project_id, + quota_used=usage) + + except Exception as e: + self.logger.error(f"Failed to update Quota: {e}") + finally: + self.logger.debug("Released lock for quota update.") + + @staticmethod + def extract_quota_usage(sliver: NodeSliver, duration: float) -> dict[tuple[str, str], float]: + """ + Extract resource usage details from a given sliver. + + @param sliver: The sliver object representing allocated resources. + @param duration: Duration in milliseconds for which resources are requested. + @return: A dictionary mapping resource type/unit pairs to usage amounts. + """ + unit = "HOURS".lower() + requested_resources = {} + + duration /= 3600000 + + # Check if the sliver is a NodeSliver + if not isinstance(sliver, NodeSliver): + return requested_resources + + allocations = sliver.get_capacity_allocations() + if not allocations and sliver.get_capacity_hints(): + catalog = InstanceCatalog() + allocations = catalog.get_instance_capacities(instance_type= + sliver.get_capacity_hints().instance_type) + else: + allocations = sliver.get_capacities() + + # Extract Core, Ram, Disk Hours + requested_resources[("core", unit)] = requested_resources.get(("core", unit), 0) + \ + (duration * allocations.core) + requested_resources[("ram", unit)] = requested_resources.get(("ram", unit), 0) +\ + (duration * allocations.ram) + requested_resources[("disk", unit)] = requested_resources.get(("disk", unit), 0) + \ + (duration * allocations.disk) + + # Extract component hours (e.g., GPU, FPGA, SmartNIC) + if sliver.attached_components_info: + for c in sliver.attached_components_info.devices.values(): + component_type = str(c.get_type()).lower() + requested_resources[(component_type, unit)] = ( + requested_resources.get((component_type, unit), 0) + duration + ) + + return requested_resources + + def enforce_quota_limits(self, reservation: ABCReservationMixin, duration: float) -> tuple[bool, Any]: + """ + Verify whether a reservation's requested resources fit within the project's quota limits. + + @param reservation: The reservation to check against available quotas. + @param duration: Duration in milliseconds for the reservation request. + @return: Tuple (True, None) if within limits, (False, message) if quota is exceeded. + @throws: Exception if an error occurs during database interaction. + """ + try: + slice_object = reservation.get_slice() + if not slice_object: + return False, None + project_uuid = slice_object.get_project_id() + if not project_uuid: + return False, None + + sliver = InventoryForType.get_allocated_sliver(reservation=reservation) + + if not sliver and reservation.is_ticketing() and reservation.get_requested_resources(): + sliver = reservation.get_requested_resources().get_sliver() + + if not sliver: + self.logger.info("No sliver found!") + return False, None + + sliver_resources = self.extract_quota_usage(sliver, duration) + quotas = self.list_quotas(project_uuid=project_uuid) + + # Check each accumulated resource usage against its quota + for quota_key, total_requested_duration in sliver_resources.items(): + if quota_key not in quotas: + return False, f"Quota not defined for resource: {quota_key[0]} ({quota_key[1]})." + + quota_info = quotas[quota_key] + available_quota = quota_info["quota_limit"] - quota_info["quota_used"] + + if total_requested_duration > available_quota: + return False, ( + f"Requested {total_requested_duration} {quota_key[1]} of {quota_key[0]}, " + f"but only {available_quota} is available." + ) + + # If all checks pass + return True, None + except Exception as e: + self.logger.error(f"Error while checking reservation: {str(e)}") diff --git a/fabric_cf/actor/core/util/utils.py b/fabric_cf/actor/core/util/utils.py index 90da469b..54cfb1d2 100644 --- a/fabric_cf/actor/core/util/utils.py +++ b/fabric_cf/actor/core/util/utils.py @@ -24,13 +24,14 @@ # Author Komal Thareja (kthare10@renci.org) import hashlib from bisect import bisect_left +from typing import Tuple, Dict from fabric_mb.message_bus.messages.abc_message_avro import AbcMessageAvro +from fabric_mb.message_bus.messages.lease_reservation_avro import LeaseReservationAvro from fim.slivers.base_sliver import BaseSliver from fim.slivers.network_node import NodeSliver from fim.slivers.network_service import NetworkServiceSliver -from fim.user import ComponentType -from fim.user.topology import TopologyDiff, TopologyDiffTuple +from fim.user import ComponentType, InstanceCatalog from fabric_cf.actor.security.pdp_auth import ActionId @@ -118,3 +119,4 @@ def generate_sha256(*, token: str): sha256_hex = sha256_hash.hexdigest() return sha256_hex + diff --git a/fabric_cf/actor/db/__init__.py b/fabric_cf/actor/db/__init__.py index 11f85f9b..cbd8071a 100644 --- a/fabric_cf/actor/db/__init__.py +++ b/fabric_cf/actor/db/__init__.py @@ -24,12 +24,12 @@ # # Author: Komal Thareja (kthare10@renci.org) -from sqlalchemy import JSON, ForeignKey, LargeBinary, Index, TIMESTAMP -from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy import JSON, ForeignKey, LargeBinary, Index, TIMESTAMP, UUID, func, text, Float from sqlalchemy.orm import declarative_base from sqlalchemy import Column, String, Integer, Sequence from sqlalchemy.orm import relationship + Base = declarative_base() FOREIGN_KEY_ACTOR_ID = 'Actors.act_id' @@ -240,4 +240,3 @@ class Components(Base): component = Column(String, primary_key=True, index=True) bdf = Column(String, primary_key=True, index=True) reservation = relationship('Reservations', back_populates='components') - diff --git a/fabric_cf/actor/db/psql_database.py b/fabric_cf/actor/db/psql_database.py index fc4cfb0b..860ca43d 100644 --- a/fabric_cf/actor/db/psql_database.py +++ b/fabric_cf/actor/db/psql_database.py @@ -1898,8 +1898,6 @@ def test3(): if __name__ == '__main__': test2() - #test() - #test3() logger = logging.getLogger('PsqlDatabase') db = PsqlDatabase(user='fabric', password='fabric', database='orchestrator', db_host='127.0.0.1:5432', diff --git a/fabric_cf/authority/config.al2s.am.yaml b/fabric_cf/authority/config.al2s.am.yaml index 2bd34e83..e4b98609 100644 --- a/fabric_cf/authority/config.al2s.am.yaml +++ b/fabric_cf/authority/config.al2s.am.yaml @@ -81,6 +81,11 @@ oauth: trl-refresh: 00:10:00 verify-exp: True +core_api: + host: https://uis.fabric-testbed.net + token: + enable: False + database: db-user: fabric db-password: fabric diff --git a/fabric_cf/authority/config.net.am.yaml b/fabric_cf/authority/config.net.am.yaml index 7cd92d5d..39992fbd 100644 --- a/fabric_cf/authority/config.net.am.yaml +++ b/fabric_cf/authority/config.net.am.yaml @@ -81,6 +81,11 @@ oauth: trl-refresh: 00:10:00 verify-exp: True +core_api: + host: https://uis.fabric-testbed.net + token: + enable: False + database: db-user: fabric db-password: fabric diff --git a/fabric_cf/authority/config.site.am.geni.yaml b/fabric_cf/authority/config.site.am.geni.yaml index e6cbcce1..2dc278cf 100644 --- a/fabric_cf/authority/config.site.am.geni.yaml +++ b/fabric_cf/authority/config.site.am.geni.yaml @@ -84,6 +84,11 @@ oauth: trl-refresh: 00:10:00 verify-exp: True +core_api: + host: https://uis.fabric-testbed.net + token: + enable: False + database: db-user: fabric db-password: fabric diff --git a/fabric_cf/authority/config.site.am.yaml b/fabric_cf/authority/config.site.am.yaml index 1d754062..c0c6e552 100644 --- a/fabric_cf/authority/config.site.am.yaml +++ b/fabric_cf/authority/config.site.am.yaml @@ -84,6 +84,11 @@ oauth: trl-refresh: 00:10:00 verify-exp: True +core_api: + host: https://uis.fabric-testbed.net + token: + enable: False + database: db-user: fabric db-password: fabric diff --git a/fabric_cf/authority/switch_handler_config.yml b/fabric_cf/authority/switch_handler_config.yml index b43dec81..fb5a907a 100644 --- a/fabric_cf/authority/switch_handler_config.yml +++ b/fabric_cf/authority/switch_handler_config.yml @@ -25,7 +25,7 @@ runtime: ssh_retries: 10 playbooks: - admin_ssh_key: /root/.ssh/id_rsa_nova + admin_ssh_key: /root/.ssh/id_rsa_p4_fabric location: /etc/fabric/actor/playbooks inventory_location: /etc/fabric/actor/playbooks/inventory Switch: head_switch_provisioning.yml diff --git a/fabric_cf/authority/test/test.yaml b/fabric_cf/authority/test/test.yaml index 41a638d6..533405ba 100644 --- a/fabric_cf/authority/test/test.yaml +++ b/fabric_cf/authority/test/test.yaml @@ -89,6 +89,11 @@ database: container: container.guid: site1-am-conainer +core_api: + host: https://alpha-6.fabric-testbed.net + token: + enable: False + time: # This section controls settings, which are generally useful # when running under emulation. These settings allow you to diff --git a/fabric_cf/broker/config.broker.yaml b/fabric_cf/broker/config.broker.yaml index 468169ee..acea9a27 100644 --- a/fabric_cf/broker/config.broker.yaml +++ b/fabric_cf/broker/config.broker.yaml @@ -82,6 +82,11 @@ oauth: trl-refresh: 00:10:00 verify-exp: True +core_api: + enable: True + host: https://uis.fabric-testbed.net + token: + database: db-user: fabric db-password: fabric diff --git a/fabric_cf/broker/test/test.yaml b/fabric_cf/broker/test/test.yaml index de894fa3..6de9624c 100644 --- a/fabric_cf/broker/test/test.yaml +++ b/fabric_cf/broker/test/test.yaml @@ -72,6 +72,11 @@ logging: logger: broker +core_api: + host: https://alpha-6.fabric-testbed.net + token: + enable: True + oauth: jwks-url: https://alpha-2.fabric-testbed.net/credmgr/certs # Uses HH:MM:SS (less than 24 hours) diff --git a/fabric_cf/orchestrator/config.orchestrator.yaml b/fabric_cf/orchestrator/config.orchestrator.yaml index 4d1f6a10..6f18e525 100644 --- a/fabric_cf/orchestrator/config.orchestrator.yaml +++ b/fabric_cf/orchestrator/config.orchestrator.yaml @@ -86,6 +86,11 @@ oauth: key-refresh: 00:10:00 verify-exp: True +core_api: + enable: False + host: https://uis.fabric-testbed.net + token: + smtp: smtp_server: mail.fabric-testbed.net smtp_port: 587 diff --git a/fabric_cf/orchestrator/core/orchestrator_handler.py b/fabric_cf/orchestrator/core/orchestrator_handler.py index 9905a47f..427d8c86 100644 --- a/fabric_cf/orchestrator/core/orchestrator_handler.py +++ b/fabric_cf/orchestrator/core/orchestrator_handler.py @@ -27,7 +27,7 @@ import traceback from datetime import datetime, timedelta, timezone from http.client import NOT_FOUND, BAD_REQUEST, UNAUTHORIZED -from typing import List, Tuple, Union +from typing import List, Union from fabric_mb.message_bus.messages.auth_avro import AuthAvro from fabric_mb.message_bus.messages.poa_avro import PoaAvro @@ -313,7 +313,6 @@ def create_slice(self, *, token: str, slice_name: str, slice_graph: str, ssh_key new_slice_object = OrchestratorSliceWrapper(controller=controller, broker=broker, slice_obj=slice_obj, logger=self.logger) - create_ts = time.time() new_slice_object.lock() # Create Slivers from Slice Graph; Compute Reservations from Slivers; @@ -326,22 +325,6 @@ def create_slice(self, *, token: str, slice_name: str, slice_graph: str, ssh_key # Check if Testbed in Maintenance or Site in Maintenance self.check_maintenance_mode(token=fabric_token, reservations=computed_reservations) - # TODO Future Slice - ''' - if lease_start_time and lease_end_time and lifetime: - future_start, future_end = self.controller_state.determine_future_lease_time(computed_reservations=computed_reservations, - start=lease_start_time, end=lease_end_time, - duration=lifetime) - self.logger.debug(f"Advanced Scheduling: Slice: {slice_name}({slice_id}) lifetime: {future_start} to {future_end}") - slice_obj.set_lease_start(lease_start=future_start) - slice_obj.set_lease_end(lease_end=future_end) - self.logger.debug(f"Update Slice {slice_name}") - slice_id = controller.update_slice(slice_obj=slice_obj) - for r in computed_reservations: - r.set_start(value=ActorClock.to_milliseconds(when=future_start)) - r.set_end(value=ActorClock.to_milliseconds(when=future_end)) - ''' - create_ts = time.time() if lease_start_time and lease_end_time and lifetime: # Enqueue future slices on Advanced Scheduling Thread to determine possible start time @@ -855,7 +838,7 @@ def validate_lease_time(lease_time: str) -> Union[datetime, None]: def __compute_lease_end_time(self, lease_end_time: datetime = None, allow_long_lived: bool = False, project_id: str = None, - lifetime: int = Constants.DEFAULT_LEASE_IN_HOURS) -> Tuple[datetime, datetime]: + lifetime: int = Constants.DEFAULT_LEASE_IN_HOURS) -> tuple[datetime, datetime]: """ Validate and compute Lease End Time. @@ -931,7 +914,7 @@ def check_maintenance_mode(self, *, token: FabricToken, reservations: List[Reser raise OrchestratorException(message=message, http_error_code=Constants.INTERNAL_SERVER_ERROR_MAINT_MODE) - def poa(self, *, token: str, sliver_id: str, poa: PoaAvro) -> Tuple[str, str]: + def poa(self, *, token: str, sliver_id: str, poa: PoaAvro) -> tuple[str, str]: try: controller = self.controller_state.get_management_actor() self.logger.debug(f"poa invoked for Controller: {controller}") diff --git a/fabric_cf/orchestrator/test/test.yaml b/fabric_cf/orchestrator/test/test.yaml index 4560906d..0280c048 100644 --- a/fabric_cf/orchestrator/test/test.yaml +++ b/fabric_cf/orchestrator/test/test.yaml @@ -70,6 +70,11 @@ logging: logger: orchestrator +core_api: + host: https://alpha-6.fabric-testbed.net + token: + enable: False + oauth: jwks-url: https://alpha-2.fabric-testbed.net/credmgr/certs # Uses HH:MM:SS (less than 24 hours) diff --git a/pyproject.toml b/pyproject.toml index 9f1cb336..9ad62b1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,8 +28,8 @@ dependencies = [ "PyYAML", "fabric_fss_utils==1.6.0", "fabric-message-bus==1.7.0", - "fabric-fim==1.8.0", - "fabric-credmgr-client==1.6.1", + "fabric-fim==1.8.1", + "fabrictestbed==1.8.2", "ansible" ]