Ejemplo n.º 1
0
    def map_growing(self, *, bids: ReservationSet):
        """
        Maps reservations that are growing in this cycle (redeems or expanding
        extends), and removes them from the bid set.
        @param bids set of deferred operations for this cycle (non-null)
        @throws Exception in case of error
        """
        # self.logger.debug("Processing growing requests")
        rids_to_remove = []
        node_id_to_reservations = {}
        for reservation in bids.values():
            if reservation.is_terminal():
                continue
            adjust = reservation.get_deficit()

            if adjust > 0:
                if reservation.is_extending_lease():
                    self.logger.debug(
                        f"**Growing reservation by {adjust}:{reservation}")
                else:
                    self.logger.debug(
                        f"**Redeeming reservation by {adjust}:{reservation}")
            node_id_to_reservations = self.map(
                reservation=reservation,
                node_id_to_reservations=node_id_to_reservations)
            rids_to_remove.append(reservation.get_reservation_id())

        for rid in rids_to_remove:
            bids.remove_by_rid(rid=rid)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    def map_shrinking(self, *, bids: ReservationSet):
        """
        Maps reservations that are shrinking or staying the same (extending with
        no flex) in this cycle, and removes them from the bid set.

        @param bids set of deferred operations for this cycle (non-null)
        @raises Exception in case of error
        """
        # self.logger.debug("Processing shrinking requests")
        rids_to_remove = []
        node_id_to_reservations = {}
        for reservation in bids.values():
            adjust = reservation.get_deficit()
            if adjust > 0:
                continue
            if not reservation.is_terminal(
            ) and reservation.is_extending_lease():
                if adjust < 0:
                    self.logger.debug(
                        f"**Shrinking reservation by {adjust}:{reservation}")
                else:
                    self.logger.debug(
                        f"**Extending reservation (no flex): {reservation}")
                node_id_to_reservations = self.map(
                    reservation=reservation,
                    node_id_to_reservations=node_id_to_reservations)
                rids_to_remove.append(reservation.get_reservation_id())

        for rid in rids_to_remove:
            bids.remove_by_rid(rid=rid)
Ejemplo n.º 4
0
    def extend_lease(self,
                     *,
                     reservation: ABCControllerReservation = None,
                     rset: ReservationSet = None):
        if reservation is not None and rset is not None:
            raise ControllerException(
                "Invalid Arguments: reservation and rset can not be both not None"
            )
        if reservation is None and rset is None:
            raise ControllerException(
                "Invalid Arguments: reservation and rset can not be both None")

        if reservation is not None:
            self.extend_lease_reservation(reservation=reservation)

        if rset is not None:
            for r in rset.values():
                try:
                    if isinstance(r, ABCControllerReservation):
                        self.extend_lease_reservation(reservation=r)
                    else:
                        self.logger.warning(
                            "Reservation #{} cannot extendLease".format(
                                r.get_reservation_id()))
                except Exception as e:
                    self.logger.error(
                        "Could not extend_lease for #{} e={}".format(
                            r.get_reservation_id(), e))
Ejemplo n.º 5
0
 def close_reservations(self, *, reservations: ReservationSet):
     for reservation in reservations.values():
         try:
             self.logger.debug("Closing reservation: {}".format(
                 reservation.get_reservation_id()))
             self.close(reservation=reservation)
         except Exception as e:
             self.logger.error(traceback.format_exc())
             self.logger.error("Could not close for #{} {}".format(
                 reservation.get_reservation_id(), e))
Ejemplo n.º 6
0
 def extend_lease_reservations(self, *, rset: ReservationSet):
     """
     Extend all reservations:
     @param rset: reservation set
     """
     for reservation in rset.values():
         try:
             self.extend_lease(reservation=reservation)
         except Exception as e:
             self.logger.error("Could not redeem for # {} {}".format(reservation.get_reservation_id(), e))
Ejemplo n.º 7
0
 def tickets_client(self, *, rset: ReservationSet):
     for reservation in rset.values():
         try:
             if isinstance(reservation, ABCBrokerReservation):
                 self.ticket_broker(reservation=reservation)
             elif isinstance(reservation, ABCClientReservation):
                 self.ticket_client(reservation=reservation)
             else:
                 self.logger.warning("Reservation #{} cannot be ticketed".format(reservation.get_reservation_id()))
         except Exception as e:
             self.logger.error("Could not ticket for #{} e: {}".format(reservation.get_reservation_id(), e))
Ejemplo n.º 8
0
 def redeem_reservations(self, *, rset: ReservationSet):
     """
     Redeem all reservations:
     @param rset: reservation set
     """
     for reservation in rset.values():
         try:
             if isinstance(reservation, ABCAuthorityReservation):
                 self.redeem(reservation=reservation)
             else:
                 self.logger.warning("Reservation # {} cannot be redeemed".format(reservation.get_reservation_id()))
         except Exception as e:
             self.logger.error("Could not redeem for # {} {}".format(reservation.get_reservation_id(), e))
Ejemplo n.º 9
0
 def redeem_reservations(self, *, rset: ReservationSet):
     for reservation in rset.values():
         try:
             if isinstance(reservation, ABCControllerReservation):
                 self.redeem(reservation=reservation)
             else:
                 self.logger.warning(
                     "Reservation #{} cannot be redeemed".format(
                         reservation.get_reservation_id()))
         except Exception as e:
             self.logger.error(traceback.format_exc())
             self.logger.error("Could not redeem for #{} {}".format(
                 reservation.get_reservation_id(), e))
Ejemplo n.º 10
0
    def all_failed(*, reservations: ReservationSet) -> bool:
        """
        We don't introduce a special state to flag when a slice is ALL FAILED, however this helper function helps decide
        when to GC a slice

        @return true or false
        """
        bins = StateBins()
        for r in reservations.values():
            bins.add(s=r.get_state())

        if not bins.has_state_other_than(ReservationStates.Failed):
            return True

        return False
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
    def check_set(self, rset: ReservationSet, check: ReservationSet):
        self.assertIsNotNone(check)
        self.assertEqual(rset.size(), check.size())

        for res in rset.values():
            self.assertTrue(check.contains(reservation=res))
Ejemplo n.º 14
0
class ControllerTicketReviewPolicy(ControllerSimplePolicy):
    """
    This implementation of a Controller policy is almost identical to the parent ControllerSimplePolicy.
    The only real difference is that it addresses that Tickets should not be redeemed if any reservations are
    currently Failed or Nascent.  This effectively acts as a "gate" between the Controller and AM.
    All reservations must be Ticketed, before any reservations are allowed to be redeemed.
    """
    def __init__(self):
        super().__init__()
        self.pending_redeem = ReservationSet()

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

        return state

    def __setstate__(self, state):
        self.__dict__.update(state)

        self.logger = None
        self.actor = None
        self.clock = None
        self.initialized = False
        self.pending_notify = ReservationSet()
        self.lazy_close = False
        self.pending_redeem = ReservationSet()

    def check_pending(self):
        """
        Check to make sure all reservations are Ticketed (not Failed or Nascent)
        before calling the parent method.

        @throws Exception in case of error
        """
        # add all of our pendingRedeem, so they can be checked
        for reservation in self.pending_redeem.values():
            self.calendar.add_pending(reservation=reservation)

        # get set of reservations that need to be redeemed
        my_pending = self.calendar.get_pending()

        # keep track of status of the slice containing each reservation
        slice_status_map = {}

        # nothing to do!
        if my_pending is None:
            return

        # check the status of the Slice of each reservation
        for reservation in my_pending.values():
            slice_obj = reservation.get_slice()
            slice_id = slice_obj.get_slice_id()

            # only want to do this for 'new' tickets
            if reservation.is_failed() or reservation.is_ticketed():
                # check if we've examined this slice already
                if slice_id not in slice_status_map:
                    # set the default status
                    slice_status_map[
                        slice_id] = TicketReviewSliceState.Redeemable
                    # examine every reservation contained within the slice,
                    # looking for either a Failed or Nascent reservation
                    # we have to look at everything in a slice once, to determine all/any Sites with failures
                    for slice_reservation in slice_obj.get_reservations(
                    ).values():

                        # If any Reservations that are being redeemed, that means the
                        # slice has already cleared TicketReview.
                        if slice_reservation.is_redeeming():
                            if slice_status_map[
                                    slice_id] == TicketReviewSliceState.Nascent:
                                # There shouldn't be any Nascent reservations, if a reservation is being Redeemed.
                                self.logger.error(
                                    "Nascent reservation found while Reservation {} in slice {} is redeeming"
                                    .format(
                                        slice_reservation.get_reservation_id(),
                                        slice_obj.get_name()))

                            # We may have previously found a Failed Reservation,
                            # but if a ticketed reservation is being redeemed,
                            # the failure _should_ be from the AM, not Controller
                            # so it should be ignored by TicketReview
                            slice_status_map[
                                slice_id] = TicketReviewSliceState.Redeemable

                            #  we don't need to look at any other reservations in this slice
                            break

                        # if any tickets are Nascent,
                        # as soon as we remove the Failed reservation,
                        # those Nascent tickets might get redeemed.
                        # we must wait to Close any failed reservations
                        # until all Nascent tickets are either Ticketed or Failed
                        if slice_reservation.is_nascent():
                            self.logger.debug(
                                "Found Nascent Reservation {} in slice {} when check_pending for {}"
                                .format(slice_reservation.get_reservation_id(),
                                        slice_obj.get_name(),
                                        reservation.get_reservation_id()))

                            slice_status_map[
                                slice_id] = TicketReviewSliceState.Nascent
                            # once we have found a Nascent reservation, that is what we treat the entire slice
                            break

                        # track Failed reservations, but need to keep looking for Nascent or Redeemable.
                        if slice_reservation.is_failed():
                            self.logger.debug(
                                "Found failed reservation {} in slice {} when check_pending for {}"
                                .format(slice_reservation.get_reservation_id(),
                                        slice_obj.get_name(),
                                        reservation.get_reservation_id()))
                            slice_status_map[
                                slice_id] = TicketReviewSliceState.Failing

                # take action on the current reservation
                if slice_status_map[
                        slice_id] == TicketReviewSliceState.Failing:
                    if reservation.get_resources(
                    ) is not None and reservation.get_resources().get_type(
                    ) is not None:
                        msg = f"TicketReviewPolicy: Closing reservation {reservation.get_reservation_id()} due to " \
                              f"failure in slice {slice_obj.get_name()}"
                        self.logger.info(msg)

                        if not reservation.is_failed():
                            update_data = UpdateData()
                            update_data.failed = True
                            update_data.message = Constants.CLOSURE_BY_TICKET_REVIEW_POLICY
                            reservation.mark_close_by_ticket_review(
                                update_data=update_data)
                        self.actor.close(reservation=reservation)
                        self.calendar.remove_pending(reservation=reservation)
                        self.pending_notify.remove(reservation=reservation)

                elif slice_status_map[
                        slice_id] == TicketReviewSliceState.Nascent:
                    self.logger.info(
                        "Moving reservation {} to pending redeem list due to nascent reservation in slice {}"
                        .format(reservation.get_reservation_id(),
                                slice_obj.get_name()))
                    self.pending_redeem.add(reservation=reservation)
                    self.calendar.remove_pending(reservation=reservation)
                else:
                    # we don't need to look at any other reservations in this slice
                    self.logger.debug(
                        "Removing from pendingRedeem: {}".format(reservation))
                    self.pending_redeem.remove(reservation=reservation)
            else:
                # Remove active or close reservations
                self.logger.debug(
                    "Removing from pendingRedeem: {}".format(reservation))
                self.pending_redeem.remove(reservation=reservation)

        super().check_pending()
Ejemplo n.º 15
0
    def transition_slice(
            self, *, operation: SliceOperation,
            reservations: ReservationSet) -> Tuple[bool, SliceState]:
        """
        Attempt to transition a slice to a new state
        @param operation slice operation
        @param reservations reservations
        @return Slice State
        @throws Exception in case of error
        """
        state_changed = False
        prev_state = self.state
        if self.state not in operation.valid_from_states:
            raise SliceException(
                f"Operation: {operation} cannot transition from state {self.state}"
            )

        if operation.command == SliceCommand.Create:
            self.state = SliceState.Configuring

        elif operation.command == SliceCommand.Modify:
            self.state = SliceState.Configuring

        elif operation.command == SliceCommand.Delete:
            if self.state != SliceState.Dead:
                self.state = SliceState.Closing

        elif operation.command == SliceCommand.Reevaluate:
            if reservations is None or reservations.size() == 0:
                return state_changed, self.state

            bins = StateBins()
            for r in reservations.values():
                bins.add(s=r.get_state())

            if self.state == SliceState.Nascent or self.state == SliceState.Configuring:
                if not bins.has_state_other_than(ReservationStates.Active,
                                                 ReservationStates.Closed):
                    self.state = SliceState.StableOK

                if (not bins.has_state_other_than(ReservationStates.Active, ReservationStates.Failed,
                                                  ReservationStates.Closed)) and \
                        bins.has_state(s=ReservationStates.Failed):
                    self.state = SliceState.StableError

                if not bins.has_state_other_than(ReservationStates.Closed,
                                                 ReservationStates.CloseWait,
                                                 ReservationStates.Failed):
                    self.state = SliceState.Closing

            elif self.state == SliceState.StableError or self.state == SliceState.StableOK:
                if not bins.has_state_other_than(ReservationStates.Closed,
                                                 ReservationStates.CloseWait,
                                                 ReservationStates.Failed):
                    self.state = SliceState.Dead

                if not bins.has_state_other_than(
                        ReservationStates.Closed, ReservationStates.CloseWait,
                        ReservationPendingStates.Closing,
                        ReservationStates.Failed):
                    self.state = SliceState.Closing

            elif self.state == SliceState.Closing and not bins.has_state_other_than(
                    ReservationStates.CloseWait, ReservationStates.Closed,
                    ReservationStates.Failed):
                self.state = SliceState.Dead
        if prev_state != self.state:
            state_changed = True

        return state_changed, self.state
Ejemplo n.º 16
0
class Slice(ABCKernelSlice):
    """
    Slice implementation. A slice has a globally unique identifier, name,
    description, property list, an owning identity, an access control list, and a
    set of reservations.
    This class is used within the Service Manager, which may hold reservations on
    many sites; on the Broker, which may have provided tickets to the slice for
    reservations at many sites; and on the site Authority, where each slice may
    hold multiple reservations for resources at that site.
    """
    def __init__(self, *, slice_id: ID = None, name: str = "unspecified"):
        # Globally unique identifier.
        self.guid = slice_id
        # Slice name. Not required to be globally or locally unique.
        self.name = name
        # Description string. Has only local meaning.
        self.description = "no description"
        # The slice type: inventory or client.
        self.type = SliceTypes.ClientSlice
        # The owner of the slice.
        self.owner = None
        # Resource type associated with this slice. Used when the slice is used to
        # represent an inventory pool.
        self.resource_type = None
        # The reservations in this slice.
        self.reservations = ReservationSet()
        self.delegations = {}
        # Neo4jGraph Id
        self.graph_id = None
        self.graph = None
        self.state_machine = SliceStateMachine(slice_id=slice_id)
        self.dirty = False
        self.config_properties = None
        self.lock = threading.Lock()
        self.lease_end = None
        self.lease_start = None

    def __getstate__(self):
        state = self.__dict__.copy()
        del state['reservations']
        del state['delegations']
        del state['graph']
        del state['lock']
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        self.reservations = ReservationSet()
        self.graph = None
        self.delegations = {}
        self.lock = threading.Lock()

    def set_graph_id(self, graph_id: str):
        self.graph_id = graph_id

    def get_graph_id(self) -> str:
        return self.graph_id

    def set_graph(self, *, graph: ABCPropertyGraph):
        self.graph = graph
        self.set_graph_id(graph_id=self.graph.get_graph_id())

    def get_graph(self) -> ABCPropertyGraph:
        return self.graph

    def clone_request(self) -> ABCSlice:
        result = Slice()
        result.name = self.name
        result.guid = self.guid
        return result

    def get_lease_end(self) -> datetime:
        return self.lease_end

    def get_lease_start(self) -> datetime:
        return self.lease_start

    def get_description(self) -> str:
        return self.description

    def get_name(self) -> str:
        return self.name

    def get_owner(self) -> AuthToken:
        return self.owner

    def get_reservations(self) -> ReservationSet:
        return self.reservations

    def get_delegations(self) -> Dict[str, ABCDelegation]:
        return self.delegations

    def get_reservations_list(self) -> list:
        return self.reservations.values()

    def get_resource_type(self) -> ResourceType:
        return self.resource_type

    def get_slice_id(self) -> ID:
        return self.guid

    def is_broker_client(self) -> bool:
        return self.type == SliceTypes.BrokerClientSlice

    def is_client(self) -> bool:
        return not self.is_inventory()

    def is_inventory(self) -> bool:
        return self.type == SliceTypes.InventorySlice

    def is_empty(self) -> bool:
        return self.reservations.is_empty()

    def prepare(self):
        self.reservations.clear()
        self.delegations.clear()
        self.state_machine.clear()
        self.transition_slice(operation=SliceStateMachine.CREATE)

    def register(self, *, reservation: ABCKernelReservation):
        if self.reservations.contains(rid=reservation.get_reservation_id()):
            raise SliceException(
                "Reservation #{} already exists in slice".format(
                    reservation.get_reservation_id()))

        self.reservations.add(reservation=reservation)

    def register_delegation(self, *, delegation: ABCDelegation):
        if delegation.get_delegation_id() in self.delegations:
            raise SliceException(
                "Delegation #{} already exists in slice".format(
                    delegation.get_delegation_id()))

        self.delegations[delegation.get_delegation_id()] = delegation

    def set_lease_end(self, *, lease_end: datetime):
        self.lease_end = lease_end

    def set_lease_start(self, *, lease_start: datetime):
        self.lease_start = lease_start

    def set_broker_client(self):
        self.type = SliceTypes.BrokerClientSlice

    def set_client(self):
        self.type = SliceTypes.ClientSlice

    def set_description(self, *, description: str):
        self.description = description

    def set_inventory(self, *, value: bool):
        if value:
            self.type = SliceTypes.InventorySlice
        else:
            self.type = SliceTypes.ClientSlice

    def get_slice_type(self) -> SliceTypes:
        return self.type

    def set_name(self, *, name: str):
        self.name = name

    def set_owner(self, *, owner: AuthToken):
        self.owner = owner

    def set_resource_type(self, *, resource_type: ResourceType):
        self.resource_type = resource_type

    def soft_lookup(self, *, rid: ID) -> ABCKernelReservation:
        return self.reservations.get(rid=rid)

    def soft_lookup_delegation(self, *, did: str) -> ABCDelegation:
        return self.delegations.get(did, None)

    def __str__(self):
        msg = "{}({})".format(self.name, str(self.guid))
        if self.graph_id is not None:
            msg += " Graph Id:{}".format(self.graph_id)
        if self.owner is not None:
            if self.owner.get_email() is not None:
                msg += " Owner:{}".format(self.owner.get_email())
            else:
                msg += " Owner:{}".format(self.owner)
        '''
        if self.state_machine is not None:
            msg += " State:{}".format(self.state_machine.get_state())
        '''
        return msg

    def unregister(self, *, reservation: ABCKernelReservation):
        self.reservations.remove(reservation=reservation)

    def unregister_delegation(self, *, delegation: ABCDelegation):
        if delegation.get_delegation_id() in self.delegations:
            self.delegations.pop(delegation.get_delegation_id())

    def get_state(self) -> SliceState:
        return self.state_machine.get_state()

    def set_dirty(self):
        self.dirty = True

    def clear_dirty(self):
        self.dirty = False

    def is_dirty(self) -> bool:
        return self.dirty

    def transition_slice(self, *,
                         operation: SliceOperation) -> Tuple[bool, SliceState]:
        return self.state_machine.transition_slice(
            operation=operation, reservations=self.reservations)

    def is_stable_ok(self) -> bool:
        state_changed, slice_state = self.transition_slice(
            operation=SliceStateMachine.REEVALUATE)

        if slice_state == SliceState.StableOk:
            return True

        return False

    def is_stable_error(self) -> bool:
        state_changed, slice_state = self.transition_slice(
            operation=SliceStateMachine.REEVALUATE)

        if slice_state == SliceState.StableError:
            return True

        return False

    def is_stable(self) -> bool:
        state_changed, slice_state = self.transition_slice(
            operation=SliceStateMachine.REEVALUATE)

        if slice_state == SliceState.StableError or slice_state == SliceState.StableOk:
            return True

        return False

    def is_dead_or_closing(self) -> bool:
        state_changed, slice_state = self.transition_slice(
            operation=SliceStateMachine.REEVALUATE)

        if slice_state == SliceState.Dead or slice_state == SliceState.Closing:
            return True

        return False

    def is_dead(self) -> bool:
        state_changed, slice_state = self.transition_slice(
            operation=SliceStateMachine.REEVALUATE)

        if slice_state == SliceState.Dead:
            return True

        return False

    def set_config_properties(self, *, value: dict):
        self.config_properties = value

    def get_config_properties(self) -> dict:
        return self.config_properties

    def update_slice_graph(self, sliver: BaseSliver, rid: str,
                           reservation_state: str) -> BaseSliver:
        try:
            self.lock.acquire()
            # Update for Orchestrator for Active / Ticketed Reservations
            if sliver is not None and self.graph_id is not None:
                if sliver.reservation_info is None:
                    sliver.reservation_info = ReservationInfo()
                sliver.reservation_info.reservation_id = rid
                sliver.reservation_info.reservation_state = reservation_state
                FimHelper.update_node(graph_id=self.graph_id, sliver=sliver)
            return sliver
        finally:
            self.lock.release()
Ejemplo n.º 17
0
class Controller(ActorMixin, ABCController):
    """
    Implements Controller
    """
    saved_extended_renewable = ReservationSet()

    def __init__(self,
                 *,
                 identity: AuthToken = None,
                 clock: ActorClock = None):
        super().__init__(auth=identity, clock=clock)
        # Recovered reservations that need to obtain tickets.
        self.ticketing = ReservationSet()
        # Recovered reservations that need to extend tickets.
        self.extending_ticket = ReservationSet()
        # Recovered reservations that need to be redeemed.
        self.redeeming = ReservationSet()
        # Recovered reservations that need to extend leases.
        self.extending_lease = ReservationSet()
        # Recovered reservations that need to modify leases
        self.modifying_lease = ReservationSet()
        # Peer registry.
        self.registry = PeerRegistry()
        # initialization status
        self.initialized = False
        self.type = ActorType.Orchestrator

    def __getstate__(self):
        state = self.__dict__.copy()
        del state['recovered']
        del state['wrapper']
        del state['logger']
        del state['clock']
        del state['current_cycle']
        del state['first_tick']
        del state['stopped']
        del state['initialized']
        del state['thread_lock']
        del state['thread']
        del state['timer_queue']
        del state['event_queue']
        del state['reservation_tracker']
        del state['subscription_id']
        del state['actor_main_lock']
        del state['closing']
        del state['message_service']

        del state['ticketing']
        del state['extending_ticket']
        del state['redeeming']
        del state['extending_lease']
        del state['modifying_lease']
        del state['registry']
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        self.recovered = False
        self.wrapper = None
        self.logger = None
        self.clock = None
        self.current_cycle = -1
        self.first_tick = True
        self.stopped = False
        self.initialized = False
        self.thread = None
        self.thread_lock = threading.Lock()
        self.timer_queue = queue.Queue()
        self.event_queue = queue.Queue()
        self.reservation_tracker = None
        self.subscription_id = None
        self.actor_main_lock = threading.Condition()
        self.closing = ReservationSet()
        self.message_service = None

        self.ticketing = ReservationSet()
        self.extending_ticket = ReservationSet()
        self.redeeming = ReservationSet()
        self.extending_lease = ReservationSet()
        self.modifying_lease = ReservationSet()
        self.registry = PeerRegistry()

    def actor_added(self):
        super().actor_added()
        self.registry.actor_added()

    def add_broker(self, *, broker: ABCBrokerProxy):
        self.registry.add_broker(broker=broker)

    def bid(self):
        """
        Bids for resources as dictated by the plugin bidding policy for the
        current cycle.

        @throws Exception in case of error
        """
        # Invoke policy module to select candidates for ticket and extend. Note
        # that candidates structure is discarded when we're done.
        candidates = self.policy.formulate_bids(cycle=self.current_cycle)
        if candidates is not None:
            # Issue new ticket requests.
            ticketing = candidates.get_ticketing()
            if ticketing is not None:
                for ticket in ticketing.values():
                    try:
                        self.wrapper.ticket(reservation=ticket,
                                            destination=self)
                    except Exception as e:
                        self.logger.error(
                            "unexpected ticket failure for #{} {}".format(
                                ticket.get_reservation_id(), e))
                        ticket.fail(
                            message="unexpected ticket failure {}".format(e))

            extending = candidates.get_extending()
            if extending is not None:
                for extend in extending.values():
                    try:
                        self.wrapper.extend_ticket(reservation=extend)
                    except Exception as e:
                        self.logger.error(
                            "unexpected extend failure for #{} {}".format(
                                extend.get_reservation_id(), e))
                        extend.fail(
                            message="unexpected extend failure {}".format(e))

    def claim_delegation_client(self,
                                *,
                                delegation_id: str = None,
                                slice_object: ABCSlice = None,
                                broker: ABCBrokerProxy = None,
                                id_token: str = None) -> ABCDelegation:
        raise ControllerException("Not implemented")

    def reclaim_delegation_client(self,
                                  *,
                                  delegation_id: str = None,
                                  slice_object: ABCSlice = None,
                                  broker: ABCBrokerProxy = None,
                                  id_token: str = None) -> ABCDelegation:
        raise ControllerException("Not implemented")

    def close_expiring(self):
        """
        Issues close requests on all reservations scheduled for closing on the
        current cycle
        """
        rset = self.policy.get_closing(cycle=self.current_cycle)

        if rset is not None and rset.size() > 0:
            self.logger.info(
                "SlottedSM close expiring for cycle {} expiring {}".format(
                    self.current_cycle, rset))
            self.close_reservations(reservations=rset)

    def demand(self, *, rid: ID):
        if rid is None:
            raise ControllerException("Invalid argument")

        reservation = self.get_reservation(rid=rid)

        if reservation is None:
            raise ControllerException("Unknown reservation {}".format(rid))

        self.policy.demand(reservation=reservation)
        reservation.set_policy(policy=self.policy)

    def extend_lease_reservation(self, *,
                                 reservation: ABCControllerReservation):
        """
        Extend Lease for a reservation
        @param reservation reservation
        """
        if not self.recovered:
            self.extending_lease.add(reservation=reservation)
        else:
            self.wrapper.extend_lease(reservation=reservation)

    def extend_lease(self,
                     *,
                     reservation: ABCControllerReservation = None,
                     rset: ReservationSet = None):
        if reservation is not None and rset is not None:
            raise ControllerException(
                "Invalid Arguments: reservation and rset can not be both not None"
            )
        if reservation is None and rset is None:
            raise ControllerException(
                "Invalid Arguments: reservation and rset can not be both None")

        if reservation is not None:
            self.extend_lease_reservation(reservation=reservation)

        if rset is not None:
            for r in rset.values():
                try:
                    if isinstance(r, ABCControllerReservation):
                        self.extend_lease_reservation(reservation=r)
                    else:
                        self.logger.warning(
                            "Reservation #{} cannot extendLease".format(
                                r.get_reservation_id()))
                except Exception as e:
                    self.logger.error(
                        "Could not extend_lease for #{} e={}".format(
                            r.get_reservation_id(), e))

    def extend_ticket_client(self, *, reservation: ABCClientReservation):
        if not self.recovered:
            self.extending_ticket.add(reservation=reservation)
        else:
            self.wrapper.extend_ticket(reservation=reservation)

    def extend_tickets_client(self, *, rset: ReservationSet):
        for reservation in rset.values():
            try:
                if isinstance(reservation, ABCClientReservation):
                    self.extend_ticket_client(reservation=reservation)
                else:
                    self.logger.warning(
                        "Reservation # {} cannot be ticketed".format(
                            reservation.get_reservation_id()))
            except Exception as e:
                self.logger.error("Could not ticket for #{} e: {}".format(
                    reservation.get_reservation_id(), e))

    def get_broker(self, *, guid: ID) -> ABCBrokerProxy:
        return self.registry.get_broker(guid=guid)

    def get_brokers(self) -> list:
        return self.registry.get_brokers()

    def get_default_broker(self) -> ABCBrokerProxy:
        return self.registry.get_default_broker()

    def initialize(self):
        if not self.initialized:
            super().initialize()

            self.registry.set_slices_plugin(plugin=self.plugin)
            self.registry.initialize()

            self.initialized = True

    def process_redeeming(self):
        """
        Issue redeem requests on all reservations scheduled for redeeming on the current cycle
        """
        rset = self.policy.get_redeeming(cycle=self.current_cycle)

        if rset is not None and rset.size() > 0:
            self.logger.info(
                "SlottedController redeem for cycle {} redeeming {}".format(
                    self.current_cycle, rset))

            self.redeem_reservations(rset=rset)

    def redeem(self, *, reservation: ABCControllerReservation):
        if not self.recovered:
            self.redeeming.add(reservation=reservation)
        else:
            self.wrapper.redeem(reservation=reservation)

    def redeem_reservations(self, *, rset: ReservationSet):
        for reservation in rset.values():
            try:
                if isinstance(reservation, ABCControllerReservation):
                    self.redeem(reservation=reservation)
                else:
                    self.logger.warning(
                        "Reservation #{} cannot be redeemed".format(
                            reservation.get_reservation_id()))
            except Exception as e:
                self.logger.error(traceback.format_exc())
                self.logger.error("Could not redeem for #{} {}".format(
                    reservation.get_reservation_id(), e))

    def ticket_client(self, *, reservation: ABCClientReservation):
        if not self.recovered:
            self.ticketing.add(reservation=reservation)
        else:
            self.wrapper.ticket(reservation=reservation, destination=self)

    def tickets_client(self, *, rset: ReservationSet):
        for reservation in rset.values():
            try:
                if isinstance(reservation, ABCClientReservation):
                    self.ticket_client(reservation=reservation)
                else:
                    self.logger.warning(
                        "Reservation #{} cannot be ticketed".format(
                            reservation.get_reservation_id()))
            except Exception as e:
                self.logger.error("Could not ticket for #{} e: {}".format(
                    reservation.get_reservation_id(), e))

    def tick_handler(self):
        self.close_expiring()
        self.process_redeeming()
        self.bid()

    def update_lease(self, *, reservation: ABCReservationMixin, update_data,
                     caller: AuthToken):
        if not self.is_recovered() or self.is_stopped():
            raise ControllerException("This actor cannot receive calls")

        self.wrapper.update_lease(reservation=reservation,
                                  update_data=update_data,
                                  caller=caller)

    def update_ticket(self, *, reservation: ABCReservationMixin, update_data,
                      caller: AuthToken):
        if not self.is_recovered() or self.is_stopped():
            raise ControllerException("This actor cannot receive calls")

        self.wrapper.update_ticket(reservation=reservation,
                                   update_data=update_data,
                                   caller=caller)

    def update_delegation(self, *, delegation: ABCDelegation, update_data,
                          caller: AuthToken):
        raise ControllerException("Not supported in controller")

    def modify(self, *, reservation_id: ID, modify_properties: dict):
        if reservation_id is None or modify_properties is None:
            self.logger.error(
                "modifyProperties argument is null or non-existing reservation"
            )

        rc = None
        try:
            rc = self.get_reservation(rid=reservation_id)
        except Exception as e:
            self.logger.error("Could not find reservation #{} e: {}".format(
                reservation_id, e))

        if rc is None:
            raise ControllerException(
                "Unknown reservation: {}".format(reservation_id))

        if rc.get_resources() is not None:
            # TODO
            print("TODO")
        else:
            self.logger.warning(
                "There are no approved resources for {}, no modify properties will be added"
                .format(reservation_id))

        if not self.recovered:
            self.modifying_lease.add(reservation=rc)
        else:
            self.wrapper.modify_lease(reservation=rc)

    def save_extending_renewable(self):
        """
        For recovery, mark extending reservations renewable or the opposite
        and save this, then restore afterwards
        """
        for reservation in self.extending_ticket.values():
            try:
                if isinstance(reservation, ABCClientReservation):
                    if not reservation.get_renewable():
                        reservation.set_renewable(renewable=True)
                        self.saved_extended_renewable.add(
                            reservation=reservation)
                else:
                    self.logger.warning(
                        "Reservation #{} cannot be remarked".format(
                            reservation.get_reservation_id()))
            except Exception as e:
                self.logger.error(
                    "Could not mark ticket renewable for #{} e: {}".format(
                        reservation.get_reservation_id(), e))

    def restore_extending_renewable(self):
        """
        Restore the value of renewable field after recovery if we changed it
        """
        for reservation in self.saved_extended_renewable.values():
            try:
                reservation.set_renewable(renewable=False)
            except Exception as e:
                self.logger.error(
                    "Could not remark ticket non renewable for #{} e: {}".
                    format(reservation.get_reservation_id(), e))

    def issue_delayed(self):
        super().issue_delayed()
        self.tickets_client(rset=self.ticketing)
        self.ticketing.clear()

        self.save_extending_renewable()
        self.extend_tickets_client(rset=self.extending_ticket)
        self.extending_ticket.clear()

        self.redeem_reservations(rset=self.redeeming)
        self.redeeming.clear()

        self.extend_lease(rset=self.extending_lease)
        self.extending_lease.clear()

    @staticmethod
    def get_management_object_class() -> str:
        return ControllerManagementObject.__name__

    @staticmethod
    def get_management_object_module() -> str:
        return ControllerManagementObject.__module__

    @staticmethod
    def get_kafka_service_class() -> str:
        return ControllerService.__name__

    @staticmethod
    def get_kafka_service_module() -> str:
        return ControllerService.__module__

    @staticmethod
    def get_mgmt_kafka_service_class() -> str:
        return KafkaControllerService.__name__

    @staticmethod
    def get_mgmt_kafka_service_module() -> str:
        return KafkaControllerService.__module__