def testEmptyRegistered(self):

        r = PluggableRegistry()

        assert r.pluggable_registered(t=PluggableType.AM) is False
        assert r.pluggable_registered(t=PluggableType.Broker) is False
        assert r.pluggable_registered(t=PluggableType.Orchestrator) is False
Exemplo n.º 2
0
    def __init__(self, *, actor: ABCBrokerMixin = None):
        super().__init__(actor=actor)
        self.last_allocation = -1
        self.allocation_horizon = 0
        self.ready = False

        self.delegations = {}
        self.combined_broker_model = None
        self.combined_broker_model_graph_id = None

        self.queue = FIFOQueue()
        self.inventory = Inventory()

        self.pluggable_registry = PluggableRegistry()
        self.lock = threading.Lock()
Exemplo n.º 3
0
    def __setstate__(self, state):
        self.__dict__.update(state)
        self.logger = None
        self.actor = None
        self.clock = None
        self.initialized = False

        self.delegations = {}
        self.combined_broker_model = None

        self.lock = threading.Lock()
        self.calendar = None

        self.last_allocation = -1
        self.allocation_horizon = 0
        self.ready = False

        self.queue = None
        self.pluggable_registry = PluggableRegistry()
    def testRegistrationWithParams(self):

        r = PluggableRegistry()

        class FakeActor:
            def __init__(self):
                self.val = 'actor'

            def __str__(self):
                return str(self.val)

        actor = FakeActor()

        r.register_pluggable(t=PluggableType.Broker,
                             p=MyPlugWithParams,
                             actor=actor,
                             para=6)

        methods = r.get_implemented_methods(t=PluggableType.Broker)

        assert "plug_produce_bqm" in methods

        c = r.get_method_callable(t=PluggableType.Broker,
                                  method='plug_produce_bqm')

        assert callable(c) is True

        ret = c(cbm=4)

        assert ret == 'Actor is actor, param is 6'
    def testRegistration(self):

        r = PluggableRegistry()

        r.register_pluggable(t=PluggableType.Broker, p=MyPlug)

        assert r.pluggable_registered(t=PluggableType.Broker) is True

        methods = r.get_implemented_methods(t=PluggableType.Broker)

        assert "plug_produce_bqm" in methods

        c = r.get_method_callable(t=PluggableType.Broker,
                                  method='plug_produce_bqm')

        assert callable(c) is True

        ret = c(cbm=4)

        assert ret == "Graph is 4 with param"

        r.unregister_pluggable(t=PluggableType.Broker)
Exemplo n.º 6
0
class BrokerSimplerUnitsPolicy(BrokerCalendarPolicy):
    """
        BrokerSimplerUnitsPolicy is a simple implementation of the broker policy interface.
        It buffers requests for allocation periods and when it performs allocations of resources it does so
        in FIFO order giving preference to extending requests.
    """
    # The amount of time over specific policy decisions the broker
    # must add when communicating with other brokers as a client (e.g.
    # renew()). Clock skew must be at least one if the broker is ticked after
    # the upstream broker(s). At some point in time we may want this to not
    # be static and learn it from what we see in the system, but for now it
    # is static.
    CLOCK_SKEW = 1

    # Number of cycles between two consecutive allocations.
    CALL_INTERVAL = 1

    #  How far in the future is the broker allocating resources
    ADVANCE_TIME = 3

    def __init__(self, *, actor: ABCBrokerMixin = None):
        super().__init__(actor=actor)
        self.last_allocation = -1
        self.allocation_horizon = 0
        self.ready = False

        self.delegations = {}
        self.combined_broker_model = None
        self.combined_broker_model_graph_id = None

        self.queue = FIFOQueue()
        self.inventory = Inventory()

        self.pluggable_registry = PluggableRegistry()
        self.lock = threading.Lock()

    def __getstate__(self):
        state = self.__dict__.copy()
        del state['logger']
        del state['actor']
        del state['clock']
        del state['initialized']

        del state['delegations']
        del state['combined_broker_model']
        del state['lock']

        del state['calendar']

        del state['last_allocation']
        del state['allocation_horizon']
        del state['ready']
        del state['queue']
        del state['pluggable_registry']

        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        self.logger = None
        self.actor = None
        self.clock = None
        self.initialized = False

        self.delegations = {}
        self.combined_broker_model = None

        self.lock = threading.Lock()
        self.calendar = None

        self.last_allocation = -1
        self.allocation_horizon = 0
        self.ready = False

        self.queue = None
        self.pluggable_registry = PluggableRegistry()

    def load_combined_broker_model(self):
        if self.combined_broker_model_graph_id is None:
            self.logger.debug("Creating an empty Combined Broker Model Graph")
        else:
            self.logger.debug(
                f"Loading an existing Combined Broker Model Graph: {self.combined_broker_model_graph_id}"
            )

        self.combined_broker_model = FimHelper.get_neo4j_cbm_graph(
            graph_id=self.combined_broker_model_graph_id)
        self.combined_broker_model_graph_id = self.combined_broker_model.get_graph_id(
        )
        self.logger.debug(
            f"Successfully loaded an Combined Broker Model Graph: {self.combined_broker_model_graph_id}"
        )
        self.pluggable_registry.register_pluggable(t=PluggableType.Broker,
                                                   p=AggregatedBQMPlugin,
                                                   actor=self.actor,
                                                   logger=self.logger)
        self.logger.debug(f"Registered AggregateBQMPlugin")

    def initialize(self):
        if not self.initialized:
            super().initialize()
            self.load_combined_broker_model()
            self.initialized = True

    def register_inventory(self, *, resource_type: ResourceType,
                           inventory: InventoryForType):
        """
        Registers the given inventory for the specified resource type. If the
        policy plugin has already been initialized, the inventory should be
        initialized.

        @param inventory: the inventory
        @param resource_type: resource_type
        """
        self.inventory.add_inventory_by_type(rtype=resource_type,
                                             inventory=inventory)

    def bind(self, *, reservation: ABCBrokerReservation) -> bool:
        term = reservation.get_requested_term()
        self.logger.info(
            "SlottedAgent bind arrived at cycle {} requested term {}".format(
                self.actor.get_current_cycle(), term))

        bid_cycle = self.get_allocation(reservation=reservation)

        self.calendar.add_request(reservation=reservation, cycle=bid_cycle)

        return False

    def bind_delegation(self, *, delegation: ABCDelegation) -> bool:
        try:
            self.lock.acquire()
            self.delegations[delegation.get_delegation_id()] = delegation
        finally:
            self.lock.release()

        return False

    def extend_broker(self, *, reservation: ABCBrokerReservation) -> bool:
        requested_term = reservation.get_requested_term()
        self.logger.info(
            "SlottedAgent extend arrived at cycle {} requested term {}".format(
                self.actor.get_current_cycle(), requested_term))

        source = reservation.get_source()

        if source is None:
            self.error(message="cannot find parent ticket for extend")

        if source.is_failed():
            self.error(message="parent ticket could not be renewed")

        bid_cycle = self.get_allocation(reservation=reservation)

        self.calendar.add_request(reservation=reservation,
                                  cycle=bid_cycle,
                                  source=source)
        self.calendar.add_request(reservation=reservation, cycle=bid_cycle)

        return False

    def formulate_bids(self, *, cycle: int) -> Bids:
        renewing = self.calendar.get_renewing(cycle=cycle)
        extending = self.process_renewing(renewing=renewing)
        return Bids(ticketing=ReservationSet(), extending=extending)

    def get_allocation(self, *, reservation: ABCBrokerReservation) -> int:
        if not self.ready:
            self.error(message="Agent not ready to accept bids")

        start = self.clock.cycle(
            when=reservation.get_requested_term().get_new_start_time())

        start -= self.ADVANCE_TIME

        intervals = int((start - self.last_allocation) / self.CALL_INTERVAL)

        if intervals <= 0:
            intervals = 1

        start = self.last_allocation + (intervals *
                                        self.CALL_INTERVAL) + self.ADVANCE_TIME

        return start

    @staticmethod
    def get_approved_term(*, reservation: ABCBrokerReservation) -> Term:
        return Term(
            start=reservation.get_requested_term().get_start_time(),
            end=reservation.get_requested_term().get_end_time(),
            new_start=reservation.get_requested_term().get_new_start_time())

    def get_next_allocation(self, *, cycle: int) -> int:
        return self.last_allocation + self.CALL_INTERVAL

    def get_renew(self, *, reservation: ABCClientReservation) -> int:
        new_start_cycle = self.actor.get_actor_clock().cycle(
            when=reservation.get_term().get_end_time()) + 1
        return new_start_cycle - self.ADVANCE_TIME - self.CLOCK_SKEW

    def get_start_for_allocation(self, *, allocation_cycle: int) -> int:
        return allocation_cycle + self.ADVANCE_TIME

    def get_end_for_allocation(self, *, allocation_cycle: int) -> int:
        return allocation_cycle + self.ADVANCE_TIME + self.allocation_horizon

    def prepare(self, *, cycle: int):
        if not self.ready:
            self.last_allocation = cycle - self.CALL_INTERVAL
            self.ready = True

        try:
            self.check_pending()
        except Exception as e:
            self.logger.error("Exception in prepare {}".format(e))

    def process_renewing(self, *, renewing: ReservationSet) -> ReservationSet:
        """
        Performs checks on renewing reservations. Updates the terms to
        suggest new terms, stores the extend on the pending list. Returns a
        fresh ReservationSet of expiring reservations to try to renew in this
        bidding cycle.

        @param renewing collection of the renewing reservations

        @return non-null set of renewals
        """
        result = ReservationSet()
        if renewing is None:
            return None

        #self.logger.debug("Expiring = {}".format(renewing.size()))

        for reservation in renewing.values():
            self.logger.debug("Expiring res: {}".format(reservation))

            if reservation.is_renewable():
                self.logger.debug("This is a renewable expiring reservation")

                term = reservation.get_term()

                term = term.extend()

                reservation.set_approved(term=term,
                                         approved_resources=reservation.
                                         get_resources().abstract_clone())

                result.add(reservation=reservation)
                self.calendar.add_pending(reservation=reservation)
            else:
                self.logger.debug(
                    "This is not a renewable expiring reservation")

        return result

    def allocate(self, *, cycle: int):
        if self.get_next_allocation(cycle=cycle) != cycle:
            return

        self.last_allocation = cycle

        start_cycle = self.get_start_for_allocation(allocation_cycle=cycle)
        advance_cycle = self.get_end_for_allocation(allocation_cycle=cycle)
        requests = self.calendar.get_all_requests(cycle=advance_cycle)

        if (requests is None or requests.size()
                == 0) and (self.queue is None or self.queue.size() == 0):
            self.logger.debug(f"requests: {requests} queue: {self.queue}")
            self.logger.debug(
                f"no requests for auction start cycle {start_cycle}")
            return

        self.logger.debug(f"allocating resources for cycle {start_cycle}")

        self.allocate_extending_reservation_set(requests=requests)
        self.allocate_queue(start_cycle=start_cycle)
        self.allocate_ticketing(requests=requests)

    def get_default_resource_type(self) -> ResourceType:
        result = None

        if len(self.inventory.get_inventory().keys()) > 0:
            result = self.inventory.get_inventory().keys().__iter__().__next__(
            )

        return result

    def allocate_extending_reservation_set(self, *, requests: ReservationSet):
        if requests is not None:
            for reservation in requests.values():
                if reservation.is_extending_ticket(
                ) and not reservation.is_closed():
                    start = reservation.get_requested_term(
                    ).get_new_start_time()
                    end = self.align_end(
                        when=reservation.get_requested_term().get_end_time())

                    resource_type = reservation.get_resources().get_type()

                    inv = self.inventory.get(resource_type=resource_type)

                    if inv is not None:
                        ext_term = Term(
                            start=reservation.get_term().get_start_time(),
                            end=end,
                            new_start=start)
                        self.extend_private(reservation=reservation,
                                            inv=inv,
                                            term=ext_term)
                    else:
                        reservation.fail(message=Constants.NO_POOL)

    def allocate_ticketing(self, *, requests: ReservationSet):
        if requests is not None:
            # Holds the Node Id to List of Reservation Ids allocated
            # This is used to check on the reservations allocated during this cycle to compute available resources
            # as the reservations are not updated in the database yet
            node_id_to_reservations = {}
            for reservation in requests.values():
                if not reservation.is_ticketing():
                    continue

                status, node_id_to_reservations, error_msg = self.ticket(
                    reservation=reservation,
                    node_id_to_reservations=node_id_to_reservations)
                if status:
                    continue

                if self.queue is None and not reservation.is_failed():
                    fail_message = "Insufficient resources"
                    if error_msg is not None:
                        fail_message = error_msg
                    reservation.fail(message=fail_message)
                    continue

                if not reservation.is_failed():
                    fail_message = f"Insufficient resources for specified start time, Failing reservation: " \
                                   f"{reservation.get_reservation_id()}"
                    if error_msg is not None:
                        fail_message = error_msg
                    reservation.fail(message=fail_message)

    def allocate_queue(self, *, start_cycle: int):
        if self.queue is None:
            return

        # Holds the Node Id to List of Reservation Ids allocated
        # This is used to check on the reservations allocated during this cycle to compute available resources
        # as the reservations are not updated in the database yet
        node_id_to_reservations = {}
        for reservation in self.queue.values():
            status, node_id_to_reservations, error_msg = self.ticket(
                reservation=reservation,
                node_id_to_reservations=node_id_to_reservations)
            if not status:
                # TODO
                threshold = 100
                start = self.clock.cycle(
                    when=reservation.get_requested_term().get_new_start_time())

                if threshold != 0 and ((start_cycle - start) > threshold):
                    reservation.fail_warn(
                        message=
                        f"Request has exceeded its threshold on the queue {reservation}"
                    )
                    self.queue.remove(reservation=reservation)
            else:
                self.queue.remove(reservation=reservation)

    def ticket(self, *, reservation: ABCBrokerReservation,
               node_id_to_reservations: dict) -> Tuple[bool, dict, Any]:
        self.logger.debug(
            f"cycle: {self.actor.get_current_cycle()} new ticket request: "
            f"{reservation}/{type(reservation).__name__}")

        error_msg = None
        start = self.align_start(
            when=reservation.get_requested_term().get_new_start_time())
        end = self.align_end(
            when=reservation.get_requested_term().get_end_time())

        resource_type = reservation.get_requested_resources().get_type()
        if resource_type is None or not self.inventory.contains_type(
                resource_type=resource_type):
            resource_type = self.get_default_resource_type()

        if resource_type is not None:
            inv = self.inventory.get(resource_type=resource_type)

            if inv is not None:
                self.logger.debug(f"Inventory type: {type(inv)}")
                term = Term(start=start, end=end)
                return self.ticket_inventory(
                    reservation=reservation,
                    inv=inv,
                    term=term,
                    node_id_to_reservations=node_id_to_reservations)
            else:
                reservation.fail(message=Constants.NO_POOL)
        else:
            reservation.fail(message=Constants.NO_POOL)

        return False, node_id_to_reservations, error_msg

    def __candidate_nodes(self, *, sliver: NodeSliver) -> List[str]:
        """
        Identify candidate worker nodes in this site that have at least
        as many needed components as in the sliver.
        """
        node_props = {
            ABCPropertyGraphConstants.PROP_SITE: sliver.site,
            ABCPropertyGraphConstants.PROP_TYPE: str(NodeType.Server)
        }
        return self.combined_broker_model.get_matching_nodes_with_components(
            label=ABCPropertyGraphConstants.CLASS_NetworkNode,
            props=node_props,
            comps=sliver.attached_components_info)

    def __find_first_fit(
            self, node_id_list: List[str], node_id_to_reservations: dict,
            inv: InventoryForType,
            reservation: ABCBrokerReservation) -> Tuple[str, BaseSliver, Any]:
        """
        Find First Available Node which can serve the reservation
        @param node_id_list: Candidate Nodes
        @param node_id_to_reservations:
        @param inv: Inventory
        @param reservation: Reservation
        @return tuple containing delegation id, sliver, error message if any
        """
        delegation_id = None
        sliver = None
        error_msg = None
        self.logger.debug(
            f"Possible candidates to serve {reservation} candidates# {node_id_list}"
        )
        requested_sliver = reservation.get_requested_resources().get_sliver()
        for node_id in node_id_list:
            try:
                self.logger.debug(
                    f"Attempting to allocate {reservation} via graph_node# {node_id}"
                )
                graph_node = self.get_network_node_from_graph(node_id=node_id)

                if requested_sliver.labels is not None and requested_sliver.labels.instance_parent is not None:
                    self.logger.info(
                        f"Sliver {requested_sliver} is requested on worker: "
                        f"{requested_sliver.labels.instance_parent}")
                    if graph_node.get_name(
                    ) != requested_sliver.labels.instance_parent:
                        self.logger.info(
                            f"Skipping candidate node: {graph_node}")
                        continue

                existing_reservations = self.get_existing_reservations(
                    node_id=node_id,
                    node_id_to_reservations=node_id_to_reservations)

                delegation_id, sliver = inv.allocate(
                    rid=reservation.get_reservation_id(),
                    requested_sliver=requested_sliver,
                    graph_id=self.combined_broker_model_graph_id,
                    graph_node=graph_node,
                    existing_reservations=existing_reservations)

                if delegation_id is not None and sliver is not None:
                    break
            except BrokerException as e:
                if e.error_code == ExceptionErrorCode.INSUFFICIENT_RESOURCES:
                    self.logger.error(f"Exception occurred: {e}")
                    error_msg = e.msg
                else:
                    raise e

        if delegation_id is None and requested_sliver.labels is not None and requested_sliver.labels.instance_parent is not None:
            error_msg = f"Insufficient Resources: {requested_sliver.labels.instance_parent} " \
                        f"cannot serve the requested sliver"

        return delegation_id, sliver, error_msg

    def __allocate_nodes(
            self, *, reservation: ABCBrokerReservation,
            inv: NetworkNodeInventory, sliver: NodeSliver,
            node_id_to_reservations: dict
    ) -> Tuple[str or None, BaseSliver, Any]:
        """
        Allocate Network Node Slivers
        @param reservation Reservation
        @param inv Inventory
        @param sliver Requested sliver
        @param node_id_to_reservations
        @return tuple containing delegation id, sliver, error message if any
        """
        delegation_id = None
        node_id_list = self.__candidate_nodes(sliver=sliver)

        # no candidate nodes found
        if len(node_id_list) == 0:
            error_msg = f'Insufficient resources: No candidates nodes found to serve {reservation}'
            self.logger.error(error_msg)
            return delegation_id, sliver, error_msg

        if self.get_algorithm_type().lower(
        ) == BrokerAllocationAlgorithm.FirstFit.name.lower():
            return self.__find_first_fit(
                node_id_list=node_id_list,
                node_id_to_reservations=node_id_to_reservations,
                inv=inv,
                reservation=reservation)

        else:
            raise BrokerException(
                error_code=ExceptionErrorCode.NOT_SUPPORTED,
                msg=f"Broker currently only supports First Fit")

    def __allocate_services(
            self, *, rid: ID, inv: NetworkServiceInventory,
            sliver: NetworkServiceSliver,
            node_id_to_reservations: dict) -> Tuple[str, BaseSliver, Any]:
        """
        Allocate Network Service Slivers
        @param rid Reservation Id
        @param inv Inventory
        @param sliver Requested sliver
        @param node_id_to_reservations
        @return tuple containing delegation id, sliver, error message if any
        """
        self.logger.debug(f"Processing Network Service sliver: {sliver}")
        delegation_id = None
        error_msg = None
        owner_switch = None
        owner_mpls_ns = None

        # For each Interface Sliver;
        for ifs in sliver.interface_info.interfaces.values():

            # Fetch Network Node Id and BQM Component Id
            node_id, bqm_component_id = ifs.get_node_map()
            bqm_component = self.get_component_sliver(node_id=bqm_component_id)

            # Get BQM Connection Point in Site Delegation (c)
            site_cp = FimHelper.get_site_interface_sliver(
                component=bqm_component,
                local_name=ifs.get_labels().local_name)

            self.logger.debug(
                f"Interface Sliver [Site Delegation] (C): {site_cp}")

            # Get BQM Peer Connection Point in Site Delegation (a)
            net_cp = self.get_net_interface_sliver(
                site_ifs_id=site_cp.node_id, itype=InterfaceType.TrunkPort)

            if net_cp is None:
                error_msg = "Peer Connection Point not found from Network AM"
                raise BrokerException(msg=error_msg)

            self.logger.debug(
                f"Peer Interface Sliver [Network Delegation] (A): {site_cp}")

            # need to find the owner switch of the network service in CBM and take it's name or labels.local_name
            owner_switch, owner_mpls_ns = self.get_owners(
                node_id=net_cp.node_id)

            if bqm_component.get_type() == ComponentType.SharedNIC:
                # VLAN is already set by the Orchestrator using the information from the Node Sliver Parent Reservation
                if ifs.get_labels().vlan is None:
                    message = "Shared NIC VLAN cannot be None"
                    self.logger.error(message)
                    raise BrokerException(
                        error_code=ExceptionErrorCode.FAILURE,
                        msg=f"{message}")
            else:
                existing_reservations = self.get_existing_reservations(
                    node_id=owner_mpls_ns.node_id,
                    node_id_to_reservations=node_id_to_reservations)
                # Set vlan - source: (c) - only for dedicated NICs
                ifs = inv.allocate_ifs(
                    requested_ns=sliver,
                    requested_ifs=ifs,
                    owner_switch=owner_switch,
                    mpls_ns=owner_mpls_ns,
                    bqm_ifs_id=net_cp.node_id,
                    existing_reservations=existing_reservations)

            # local_name source: (a)
            ifs_labels = ifs.get_labels()
            ifs_labels = Labels.update(ifs_labels,
                                       local_name=net_cp.get_name())

            # NSO device name source: (a) - need to find the owner switch of the network service in CBM
            # and take its name or labels.local_name
            # Set the NSO device-name
            ifs_labels = Labels.update(ifs_labels,
                                       device_name=owner_switch.get_name())
            adm_ids = owner_switch.get_structural_info().adm_graph_ids
            site_adm_ids = bqm_component.get_structural_info().adm_graph_ids

            self.logger.debug(f"Owner MPLS Network Service: {owner_mpls_ns}")
            self.logger.debug(f"Owner Switch: {owner_switch}")
            self.logger.debug(
                f"Owner Switch: {owner_switch.network_service_info}")

            net_adm_ids = [
                x for x in adm_ids
                if not x in site_adm_ids or site_adm_ids.remove(x)
            ]
            if len(net_adm_ids) != 1:
                error_msg = f"More than 1 or 0 Network Delegations found! net_adm_ids: {net_adm_ids}"
                self.logger.error(error_msg)
                raise BrokerException(msg=error_msg)

            # Update the Interface Sliver Node Map to map to (a)
            ifs.set_node_map(node_map=(self.combined_broker_model_graph_id,
                                       net_cp.node_id))

            delegation_id = net_adm_ids[0]

            ifs.labels = ifs_labels

            self.logger.debug(
                f"Allocated Interface Sliver: {ifs} delegation: {delegation_id}"
            )

        # Update the Network Service Sliver Node Map to map to parent of (a)
        sliver.set_node_map(node_map=(self.combined_broker_model_graph_id,
                                      owner_mpls_ns.node_id))

        # Set the Subnet and gateway from the Owner Switch (a)
        if sliver.get_type() == ServiceType.FABNetv6 or sliver.get_type(
        ) == ServiceType.FABNetv4:
            existing_reservations = self.get_existing_reservations(
                node_id=owner_mpls_ns.node_id,
                node_id_to_reservations=node_id_to_reservations)
            sliver = inv.allocate(rid=rid,
                                  requested_ns=sliver,
                                  owner_switch=owner_switch,
                                  existing_reservations=existing_reservations)

        return delegation_id, sliver, error_msg

    def ticket_inventory(
            self, *, reservation: ABCBrokerReservation, inv: InventoryForType,
            term: Term,
            node_id_to_reservations: dict) -> Tuple[bool, dict, Any]:
        error_msg = None
        try:
            rset = reservation.get_requested_resources()
            needed = rset.get_units()

            # for network node slivers
            # find a list of candidate worker nodes that satisfy the requirements based on delegated
            # capacities within the site
            # for network link slivers
            # orchestrator needs to provide a map to CBM guid of the node representing the
            # intended link (and possibly interfaces connected to it)

            res_sliver = rset.get_sliver()
            delegation_id = None
            sliver = None

            if isinstance(res_sliver, NodeSliver):
                delegation_id, sliver, error_msg = self.__allocate_nodes(
                    reservation=reservation,
                    inv=inv,
                    sliver=res_sliver,
                    node_id_to_reservations=node_id_to_reservations)

            elif isinstance(res_sliver, NetworkServiceSliver):
                delegation_id, sliver, error_msg = self.__allocate_services(
                    rid=reservation.get_reservation_id(),
                    inv=inv,
                    sliver=res_sliver,
                    node_id_to_reservations=node_id_to_reservations)
            else:
                self.logger.error(
                    f'Reservation {reservation} sliver type is neither Node, nor NetworkServiceSliver'
                )
                raise BrokerException(
                    msg=f"Reservation sliver type is neither Node "
                    f"nor NetworkLink for reservation# {reservation}")

            if delegation_id is not None:
                delegation = self.actor.get_delegation(did=delegation_id)
                reservation = self.issue_ticket(reservation=reservation,
                                                units=needed,
                                                rtype=rset.get_type(),
                                                term=term,
                                                source=delegation,
                                                sliver=sliver)

                node_map = sliver.get_node_map()
                node_id = node_map[1]
                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)
                return True, node_id_to_reservations, error_msg
        except Exception as e:
            self.logger.error(traceback.format_exc())
            self.logger.error(e)
            reservation.fail(message=str(e))
        return False, node_id_to_reservations, error_msg

    def extend_private(self, *, reservation: ABCBrokerReservation,
                       inv: InventoryForType, term: Term):
        try:
            self.logger.debug(f"Extend private initiated for {reservation}")
            requested_resources = reservation.get_requested_resources()
            needed = requested_resources.get_units()
            current = reservation.get_resources().get_units()
            if needed == current:
                self.issue_ticket(reservation=reservation,
                                  units=needed,
                                  rtype=requested_resources.get_type(),
                                  term=term,
                                  source=reservation.get_source(),
                                  sliver=reservation.get_resources().sliver)
            else:
                self.logger.error(
                    f"Failed to satisfy the extend for reservation# {reservation}"
                )
        except Exception as e:
            self.logger.error(e)
            self.logger.error(traceback.format_exc())
            reservation.fail(message="", exception=e)

    def issue_ticket(self, *, reservation: ABCBrokerReservation, units: int,
                     rtype: ResourceType, term: Term, source: ABCDelegation,
                     sliver: BaseSliver) -> ABCBrokerReservation:

        # make the new delegation
        resource_delegation = ResourceTicketFactory.create(
            issuer=self.actor.get_identity().get_guid(),
            units=units,
            term=term,
            rtype=rtype)

        # extract a new resource set
        mine = self.extract(source=source, delegation=resource_delegation)

        # attach the current request properties so that we can look at them in the future
        mine.set_sliver(sliver=sliver)

        if mine is not None and not reservation.is_failed():
            reservation.set_approved(term=term, approved_resources=mine)
            reservation.set_source(source=source)
            self.logger.debug(
                f"allocated: {mine.get_units()} for term: {term}")
            self.logger.debug(
                f"resourceshare= {units} mine= {mine.get_units()}")

            self.add_to_calendar(reservation=reservation)
            reservation.set_bid_pending(value=False)
        else:
            if mine is None:
                raise BrokerException(
                    msg=
                    "There was an error extracting a ticket from the source delegation"
                )
        return reservation

    def release(self, *, reservation):
        if isinstance(reservation, ABCBrokerReservation):
            self.logger.debug("Broker reservation")
            super().release(reservation=reservation)
        elif isinstance(reservation, ABCClientReservation):
            self.logger.debug("Client reservation")
            super().release(reservation=reservation)
            status = self.inventory.remove(source=reservation)
            self.logger.debug(
                f"Removing reservation: {reservation.get_reservation_id()} "
                f"from inventory status: {status}")

    def align_end(self, *, when: datetime) -> datetime:
        """
        Aligns the specified date with the end of the closest cycle.

        @param when when to align

        @return date aligned with the end of the closes cycle
        """
        cycle = self.clock.cycle(when=when)
        time = self.clock.cycle_end_in_millis(cycle=cycle)
        return ActorClock.from_milliseconds(milli_seconds=time)

    def align_start(self, *, when: datetime) -> datetime:
        """
        Aligns the specified date with the start of the closest cycle.

        @param when when to align

        @return date aligned with the start of the closes cycle
        """
        cycle = self.clock.cycle(when=when)
        time = self.clock.cycle_start_in_millis(cycle=cycle)
        return ActorClock.from_milliseconds(milli_seconds=time)

    def query(self, *, p: dict) -> dict:
        """
        Returns the Broker Query Model
        @params p : dictionary containing filters (not used currently)
        """
        result = {}
        self.logger.debug("Processing Query with properties: {}".format(p))

        query_action = self.get_query_action(properties=p)
        query_level = p.get(Constants.QUERY_DETAIL_LEVEL, None)
        if query_level is not None:
            query_level = int(query_level)

        if query_action is None:
            raise BrokerException(
                error_code=ExceptionErrorCode.INVALID_ARGUMENT,
                msg=f"query_action {query_action}")

        if query_action != Constants.QUERY_ACTION_DISCOVER_BQM:
            raise BrokerException(
                error_code=ExceptionErrorCode.INVALID_ARGUMENT,
                msg=f"query_action {query_action}")

        bqm_format = p.get(Constants.BROKER_QUERY_MODEL_FORMAT, None)
        if bqm_format is not None:
            bqm_format = GraphFormat(int(bqm_format))
        else:
            bqm_format = GraphFormat.GRAPHML

        try:
            self.lock.acquire()
            if self.combined_broker_model is not None:
                graph = self.combined_broker_model.get_bqm(
                    query_level=query_level)
                graph_string = None
                if graph is not None:
                    graph_string = graph.serialize_graph(format=bqm_format)
                if graph_string is not None:
                    result[Constants.BROKER_QUERY_MODEL] = graph_string
                    result[Constants.QUERY_RESPONSE_STATUS] = "True"
                else:
                    result[Constants.BROKER_QUERY_MODEL] = ""
                    result[Constants.QUERY_RESPONSE_STATUS] = "False"
                    result[Constants.
                           QUERY_RESPONSE_MESSAGE] = "Resource(s) not found"
                graph.delete_graph()
        except Exception as e:
            self.logger.error(traceback.format_exc())
            self.logger.error(e)
            result[Constants.BROKER_QUERY_MODEL] = ""
            result[Constants.QUERY_RESPONSE_STATUS] = "False"
            result[Constants.QUERY_RESPONSE_MESSAGE] = str(e)
        finally:
            self.lock.release()

        self.logger.debug("Returning Query Result: {}".format(result))
        return result

    def donate_delegation(self, *, delegation: ABCDelegation):
        """
        Donate an incoming delegation by merging to CBM;
        We take snapshot of CBM before merge, and rollback to snapshot in case merge fails
        :param delegation:
        :return:
        :raises: Exception in case of failure
        """
        self.logger.debug("Donate Delegation")
        self.bind_delegation(delegation=delegation)
        try:
            self.lock.acquire()
            if delegation.get_delegation_id() in self.delegations:
                self.merge_adm(adm_graph=delegation.get_graph())
                self.logger.debug(
                    f"Donated Delegation: {delegation.get_delegation_id()}")
            else:
                self.logger.warning(
                    f"Delegation ignored: {delegation.get_delegation_id()}")
                self.logger.debug(f"Active delegations: {self.delegations}")
        except Exception as e:
            self.logger.error(f"Failed to merge ADM: {delegation}")
            self.logger.error(traceback.format_exc())
            raise e
        finally:
            self.lock.release()

    def remove_delegation(self, *, delegation: ABCDelegation):
        try:
            self.lock.acquire()
            if delegation.get_delegation_id() in self.delegations:
                self.unmerge_adm(graph_id=delegation.get_delegation_id())
                self.delegations.pop(delegation.get_delegation_id())
                self.logger.debug(
                    f"Removed Delegation: {delegation.get_delegation_id()}")
            else:
                self.logger.warning(
                    f"Delegation ignored: {delegation.get_delegation_id()}")
                self.logger.debug(f"Active delegations: {self.delegations}")
        except Exception as e:
            self.logger.error(f"Failed to un-merge ADM: {delegation}")
            self.logger.error(traceback.format_exc())
            raise e
        finally:
            self.lock.release()

    def closed_delegation(self, *, delegation: ABCDelegation):
        """
        Close a delegation by un-merging from CBM
        We take snapshot of CBM before un-merge, and rollback to snapshot in case un-merge fails
        :param delegation:
        :return:
        """
        self.logger.debug("Close Delegation")
        self.remove_delegation(delegation=delegation)

    def reclaim_delegation(self, *, delegation: ABCDelegation):
        """
        Reclaim a delegation by un-merging from CBM
        We take snapshot of CBM before un-merge, and rollback to snapshot in case un-merge fails
        :param delegation:
        :return:
        """
        self.logger.debug("Reclaim Delegation")
        self.remove_delegation(delegation=delegation)

    def get_net_interface_sliver(self, *, site_ifs_id: str,
                                 itype: InterfaceType) -> InterfaceSliver:
        """
        Get Peer Interface Sliver (child of Network Service Sliver) provided node id of Interface Sliver
        (child of Component Sliver)

        E.g: Provided Connection Point which is a child of Component i.e. renc-w3-nic2-p1,
        return Peer Connection Point i.e. HundredGigE 0/0/0/25.3

        renc-w3-nic2-p1      => l10  <= HundredGigE 0/0/0/25.3
        [Connection Point]      Link    [Connection Point]

        @param site_ifs_id Interface Sliver Id
        @param itype Interface Type
        @return Interface sliver
        """
        try:
            self.lock.acquire()
            result = FimHelper.get_interface_sliver_by_id(
                ifs_node_id=site_ifs_id,
                graph=self.combined_broker_model,
                itype=itype)
            if len(result) != 1:
                raise BrokerException(
                    msg=
                    f"More than one Peer Interface Sliver of type {itype} found for "
                    f"IFS: {site_ifs_id}")
            return next(iter(result))
        finally:
            self.lock.release()

    def get_component_sliver(self, *, node_id: str) -> ComponentSliver or None:
        """
        Get Component Sliver from BQM
        @param node_id: Node Id
        @return Component Sliver
        """
        try:
            self.lock.acquire()
            if self.combined_broker_model is None:
                return None
            return self.combined_broker_model.build_deep_component_sliver(
                node_id=node_id)
        finally:
            self.lock.release()

    def get_owners(self, *,
                   node_id: str) -> Tuple[NodeSliver, NetworkServiceSliver]:
        """
        Get owner switch and network service of a Connection Point from BQM
        @param node_id Node Id of the Connection Point
        """
        try:
            self.lock.acquire()
            return FimHelper.get_owners(bqm=self.combined_broker_model,
                                        node_id=node_id)
        finally:
            self.lock.release()

    def get_network_node_from_graph(self, *,
                                    node_id: str) -> NodeSliver or None:
        """
        Get Node from CBM
        :param node_id:
        :return:
        """
        try:
            self.lock.acquire()
            if self.combined_broker_model is None:
                return None
            return self.combined_broker_model.build_deep_node_sliver(
                node_id=node_id)
        finally:
            self.lock.release()

    def get_existing_reservations(
            self, node_id: str,
            node_id_to_reservations: dict) -> List[ABCReservationMixin]:
        """
        Get existing reservations which are served by CBM node identified by node_id
        :param node_id:
        :param node_id_to_reservations:
        :return: list of reservations
        """
        states = [
            ReservationStates.Active.value,
            ReservationStates.ActiveTicketed.value,
            ReservationStates.Ticketed.value, ReservationStates.Nascent.value
        ]

        # Only get Active or Ticketing reservations
        existing_reservations = self.actor.get_plugin().get_database(
        ).get_reservations_by_graph_node_id_state(graph_node_id=node_id,
                                                  states=states)

        reservations_allocated_in_cycle = node_id_to_reservations.get(
            node_id, None)

        if reservations_allocated_in_cycle is None:
            return existing_reservations

        if existing_reservations is None:
            return reservations_allocated_in_cycle.values()

        for e in existing_reservations.copy():
            if reservations_allocated_in_cycle.contains(
                    rid=e.get_reservation_id()):
                existing_reservations.remove(e)

        for r in reservations_allocated_in_cycle.values():
            existing_reservations.append(r)

        return existing_reservations

    def set_logger(self, logger):
        """
        Set logger
        :param logger:
        :return:
        """
        super().set_logger(logger=logger)
        if self.inventory is not None:
            for inv in self.inventory.map.values():
                inv.set_logger(logger=logger)

    def merge_adm(self, *, adm_graph: ABCADMPropertyGraph):
        """
        Merge delegation model in CBM
        :param adm_graph: ADM
        :return:
        """
        snapshot_graph_id = None
        try:
            if self.combined_broker_model.graph_exists():
                snapshot_graph_id = self.combined_broker_model.snapshot()
            self.combined_broker_model.merge_adm(adm=adm_graph)
            self.combined_broker_model.validate_graph()
            # delete the snapshot
            if snapshot_graph_id is not None:
                self.combined_broker_model.importer.delete_graph(
                    graph_id=snapshot_graph_id)
        except Exception as e:
            self.logger.error(f"Exception occurred: {e}")
            self.logger.error(traceback.format_exc())
            if snapshot_graph_id is not None:
                self.logger.info(f"CBM rollback due to merge failure")
                self.combined_broker_model.rollback(graph_id=snapshot_graph_id)
            raise e

    def unmerge_adm(self, *, graph_id: str):
        """
        Unmerge delegation model from CBM
        :param graph_id:
        :return:
        """
        snapshot_graph_id = None
        try:
            if self.combined_broker_model.graph_exists():
                snapshot_graph_id = self.combined_broker_model.snapshot()
            self.combined_broker_model.unmerge_adm(graph_id=graph_id)
            if self.combined_broker_model.graph_exists():
                self.combined_broker_model.validate_graph()

            if snapshot_graph_id is not None:
                # delete the snapshot
                self.combined_broker_model.importer.delete_graph(
                    graph_id=snapshot_graph_id)
        except Exception as e:
            self.logger.error(f"Exception occurred: {e}")
            self.logger.error(traceback.format_exc())
            if snapshot_graph_id is not None:
                self.logger.info(f"CBM rollback due to un-merge failure")
                self.combined_broker_model.rollback(graph_id=snapshot_graph_id)
            raise e

    def get_algorithm_type(self) -> str:
        if self.properties is not None:
            algo_str = self.properties.get(Constants.ALGORITHM, None)
            if algo_str is not None:
                return algo_str
        return BrokerAllocationAlgorithm.FirstFit.name
    def testRegistrySingleton(self):
        r = PluggableRegistry()
        r1 = PluggableRegistry()

        assert r.instance is r1.instance