o
    ee                     @   sL   d dl Z d dlZd dlZdZdZdZejjZej	
dZ	G dd deZdS )    Nconnect
disconnectprobe	reconnectc                   @   s  e Zd ZdZG dd deZG dd deZG dd deZG dd	 d	eZG d
d deZ	G dd deZ
G dd  d eZdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd  Zd!d" Zd#d$ Zd%d& Zd'd( Zd)d* Zd+d, Zd-d. Zd/d0 Zd1d2 Zd3d4 Zd5d6 Zd7d8 Z d9d: Z!d;d< Z"d=d> Z#d?d@ Z$dAdB Z%dCdD Z&dEdF Z'dGdH Z(dIdJ Z)dKdL Z*dMdN Z+dOdP Z,dQdR Z-dSdT Z.dUS )V	Reconnecta2  A finite-state machine for connecting and reconnecting to a network
    resource with exponential backoff.  It also provides optional support for
    detecting a connection on which the peer is no longer responding.

    The library does not implement anything networking related, only an FSM for
    networking code to use.

    Many Reconnect methods take a "now" argument.  This makes testing easier
    since there is no hidden state.  When not testing, just pass the return
    value of ovs.time.msec().  (Perhaps this design should be revisited
    later.)c                   @   ,   e Zd ZdZdZedd Zedd ZdS )zReconnect.VoidVOIDFc                 C      d S N fsmnowr   r   //usr/lib/python3/dist-packages/ovs/reconnect.pydeadline.      zReconnect.Void.deadlinec                 C   r	   r
   r   r   r   r   r   run2   r   zReconnect.Void.runN__name__
__module____qualname__nameis_connectedstaticmethodr   r   r   r   r   r   Void*       
r   c                   @   r   )zReconnect.Listening	LISTENINGFc                 C   r	   r
   r   r   r   r   r   r   :   r   zReconnect.Listening.deadlinec                 C   r	   r
   r   r   r   r   r   r   >   r   zReconnect.Listening.runNr   r   r   r   r   	Listening6   r   r   c                   @   r   )zReconnect.BackoffBACKOFFFc                 C   s   | j | j S r
   )state_enteredbackoffr   r   r   r   r   F   s   zReconnect.Backoff.deadlinec                 C      t S r
   )CONNECTr   r   r   r   r   J   r   zReconnect.Backoff.runNr   r   r   r   r   BackoffB   r   r#   c                   @   r   )zReconnect.ConnectInProgress
CONNECTINGFc                 C   s   | j td| j S )N  )r   maxr    r   r   r   r   r   R   s   z$Reconnect.ConnectInProgress.deadlinec                 C   r!   r
   
DISCONNECTr   r   r   r   r   V   r   zReconnect.ConnectInProgress.runNr   r   r   r   r   ConnectInProgressN   r   r)   c                   @   r   )zReconnect.ActiveACTIVETc                 C   sJ   | j r#t| j| j}|| j  }||k s| jd u s| j|kr|S |d S d S N   )probe_intervalr&   last_activityr   last_receive_attempt)r   r   base
expirationr   r   r   r   ^   s   


zReconnect.Active.deadlinec                 C   s4   t d| j|t| j| j f  | |tj t	S )Nz(%s: idle %d ms, sending inactivity probe)
vlogdbgr   r&   r.   r   _transitionr   IdlePROBEr   r   r   r   r   s   s   zReconnect.Active.runNr   r   r   r   r   ActiveZ   s    
r7   c                   @   r   )zReconnect.IdleIDLETc                 C   s>   | j r| j| j  }||k s| jd u s| j|kr|S |d S d S r+   )r-   r   r/   )r   r   r1   r   r   r   r      s   

zReconnect.Idle.deadlinec                 C   s"   t d| j|| j d f  tS )NzE%s: no response to inactivity probe after %.3g seconds, disconnecting     @@)r2   errr   r   r(   r   r   r   r   r      s   zReconnect.Idle.runNr   r   r   r   r   r5   {   s    
r5   c                   @   r   )zReconnect.Reconnect	RECONNECTFc                 C      | j S r
   )r   r   r   r   r   r      s   zReconnect.Reconnect.deadlinec                 C   r!   r
   r'   r   r   r   r   r      r   zReconnect.Reconnect.runNr   r   r   r   r   r      r   c                 C   s   d| _ d| _d| _d| _d| _tj| _tj	| _
|| _d| _|| _d| _d| _|| _d| _d| _|| _d| _d| _d| _d| _dS )zCreates and returns a new reconnect FSM with default settings.  The
        FSM is initially disabled.  The caller will likely want to call
        self.enable() and self.set_name() on the returned object.voidr%   @  i  Fr   N)r   min_backoffmax_backoffr-   passiver2   info
info_levelr   r   stater   r    r.   last_connectedlast_disconnectedr/   	max_triesbackoff_free_triescreation_timen_attempted_connectionsn_successful_connectionstotal_connected_durationseqnoselfr   r   r   r   __init__   s(   
zReconnect.__init__c                 C   s   |rt j| _dS t j| _dS )a  If 'quiet' is true, this object will log informational messages at
        debug level, by default keeping them out of log files.  This is
        appropriate if the connection is one that is expected to be
        short-lived, so that the log messages are merely distracting.

        If 'quiet' is false, this object logs informational messages at info
        level.  This is the default.

        This setting has no effect on the log level of debugging, warning, or
        error messages.N)r2   r3   rC   rB   )rO   quietr   r   r   	set_quiet   s   zReconnect.set_quietc                 C   r<   r
   r   rO   r   r   r   get_name   s   zReconnect.get_namec                 C   s   |du r	d| _ dS || _ dS )zSets this object's name to 'name'.  If 'name' is None, then "void"
        is used instead.

        The name is used in log messages.Nr=   rS   )rO   r   r   r   r   set_name   s   

zReconnect.set_namec                 C   r<   )zReturn the minimum number of milliseconds to back off between
        consecutive connection attempts.  The default is 1000 ms.)r?   rT   r   r   r   get_min_backoff      zReconnect.get_min_backoffc                 C   r<   )zReturn the maximum number of milliseconds to back off between
        consecutive connection attempts.  The default is 8000 ms.)r@   rT   r   r   r   get_max_backoff   rX   zReconnect.get_max_backoffc                 C   r<   )a  Returns the "probe interval" in milliseconds.  If this is zero, it
        disables the connection keepalive feature.  If it is nonzero, then if
        the interval passes while the FSM is connected and without
        self.activity() being called, self.run() returns ovs.reconnect.PROBE.
        If the interval passes again without self.activity() being called,
        self.run() returns ovs.reconnect.DISCONNECT.)r-   rT   r   r   r   get_probe_interval   s   zReconnect.get_probe_intervalc                 C   
   || _ dS )a,  Limits the maximum number of times that this object will ask the
        client to try to reconnect to 'max_tries'.  None (the default) means an
        unlimited number of tries.

        After the number of tries has expired, the FSM will disable itself
        instead of backing off and retrying.NrG   )rO   rG   r   r   r   set_max_tries   s   
zReconnect.set_max_triesc                 C   r<   )zeReturns the current remaining number of connection attempts,
        None if the number is unlimited.r\   rT   r   r   r   get_max_tries   rX   zReconnect.get_max_triesc                 C   sf   t |d| _| jrt |d| _nd| _| j| jkr| j| _| jtjkr/| j| jkr1| j| _dS dS dS )a,  Configures the backoff parameters for this FSM.  'min_backoff' is
        the minimum number of milliseconds, and 'max_backoff' is the maximum,
        between connection attempts.

        'min_backoff' must be at least 1000, and 'max_backoff' must be greater
        than or equal to 'min_backoff'.r%   r>   N)r&   r?   r@   rD   r   r#   r    )rO   r?   r@   r   r   r   set_backoff   s   zReconnect.set_backoffc                 C   r[   )zSets the number of connection attempts that will be made without
        backoff to 'backoff_free_tries'.  Values 0 and 1 both
        represent a single attempt.N)rH   )rO   rH   r   r   r   set_backoff_free_tries  s   
z Reconnect.set_backoff_free_triesc                 C   s   |r
t d|| _dS d| _dS )a%  Sets the "probe interval" to 'probe_interval', in milliseconds.  If
        this is zero, it disables the connection keepalive feature.  If it is
        nonzero, then if the interval passes while this FSM is connected and
        without self.activity() being called, self.run() returns
        ovs.reconnect.PROBE.  If the interval passes again without
        self.activity() being called, self.run() returns
        ovs.reconnect.DISCONNECT.

        If 'probe_interval' is nonzero, then it will be forced to a value of at
        least 1000 ms.r%   r   N)r&   r-   )rO   r-   r   r   r   set_probe_interval  s   
zReconnect.set_probe_intervalc                 C   r<   )zaReturns true if 'fsm' is in passive mode, false if 'fsm' is in
        active mode (the default).)rA   rT   r   r   r   
is_passive!  rX   zReconnect.is_passivec                 C   sf   | j |kr+|| _ |r| jtjtjfv s|s-| jtjkr/|  r1| |tj d| _dS dS dS dS dS )zConfigures this FSM for active or passive mode.  In active mode (the
        default), the FSM is attempting to connect to a remote host.  In
        passive mode, the FSM is listening for connections from a remote
        host.r   N)	rA   rD   r   r)   r   _Reconnect__may_retryr4   r#   r    )rO   rA   r   r   r   r   set_passive&  s   

zReconnect.set_passivec                 C   s   | j tjkS )zReturns true if this FSM has been enabled with self.enable().
        Calling another function that indicates a change in connection state,
        such as self.disconnected() or self.force_reconnect(), will also enable
        a reconnect FSM.)rD   r   r   rT   r   r   r   
is_enabled5  s   zReconnect.is_enabledc                 C   s4   | j tjkr|  r| |tj d| _dS dS dS )zIf this FSM is disabled (the default for newly created FSMs),
        enables it, so that the next call to reconnect_run() for 'fsm' will
        return ovs.reconnect.CONNECT.

        If this FSM is not disabled, this function has no effect.r   N)rD   r   r   rc   r4   r#   r    rN   r   r   r   enable<  s   
zReconnect.enablec                 C   s"   | j tjkr| |tj dS dS )zZDisables this FSM.  Until 'fsm' is enabled again, self.run() will
        always return 0.N)rD   r   r   r4   rN   r   r   r   disableF  s   zReconnect.disablec                 C   s,   | j tjtjtjfv r| |tj dS dS )a  If this FSM is enabled and currently connected (or attempting to
        connect), forces self.run() to return ovs.reconnect.DISCONNECT the next
        time it is called, which should cause the client to drop the connection
        (or attempt), back off, and then reconnect.N)rD   r   r)   r7   r5   r4   rN   r   r   r   force_reconnectL  s   zReconnect.force_reconnectc                 C   sx  | j tjtjfvr:| j tjtjfv r;|dkr%td| jt	
|f  nq|tkr2| d| j  nd| d| j  n[| j tjkr\|dkrStd| jt	
|f  nC| d| j  n:| j tjkrk| d| j  n+| j| jk r| jrwd}nd	}|dkrtd
| j|t	
|f  n
| d| j|f  | j tjtjfv r|| _|  s| |tj dS | jdkr|  jd8  _d| _nr| j tjtjfv r| j| j | jks| jr| jrd| _nV| j| _nQ| j| jk r| j| _nF| j| jd k r|  jd9  _| jrd}nd}| d| j| jd |f  n| j| jk r-| jr!d}nd}| d| j|f  | j| _| |tj dS dS )a_  Tell this FSM that the connection dropped or that a connection
        attempt failed.  'error' specifies the reason: a positive value
        represents an errno value, EOF indicates that the connection was closed
        by the peer (e.g. read() returned 0), and 0 indicates no specific
        error.

        The FSM will back off, then reconnect.r   z%s: connection dropped (%s)z%s: connection closed by peerz%s: connection droppedz(%s: error listening for connections (%s)z#%s: error listening for connectionsz%s: connection closed by clientlisten
connectionz%s: %s attempt failed (%s)z%s: %s attempt timed outNr,      ztrying to listen againr   z"%s: waiting %.3g seconds before %sr9   ztry to listenzF%s: continuing to %s in the background but suppressing further logging)rD   r   r#   r   r7   r5   r2   warnr   osstrerrorEOFrC   r   r    r@   rA   rF   rc   r4   rH   r.   rE   r?   )rO   r   errortype_actionr   r   r   disconnectedV  s   


zReconnect.disconnectedc                 C   sV   | j tjkr)| jr| d| j  n| j| jk r | d| j  | |tj dS dS )zTell this FSM that a connection or listening attempt is in progress.

        The FSM will start a timer, after which the connection or listening
        attempt will be aborted (by returning ovs.reconnect.DISCONNECT from
        self.run()).%s: listening...z%s: connecting...N)	rD   r   r)   rA   rC   r   r    r@   r4   rN   r   r   r   
connecting  s   zReconnect.connectingc                 C   s2   | j tjkr| d| j  | |tj dS dS )a  Tell this FSM that the client is listening for connection attempts.
        This state last indefinitely until the client reports some change.

        The natural progression from this state is for the client to report
        that a connection has been accepted or is in progress of being
        accepted, by calling self.connecting() or self.connected().

        The client may also report that listening failed (e.g. accept()
        returned an unexpected error such as ENOMEM) by calling
        self.listen_error(), in which case the FSM will back off and eventually
        return ovs.reconnect.CONNECT from self.run() to tell the client to try
        listening again.rt   N)rD   r   r   rC   r   r4   rN   r   r   r   	listening  s   zReconnect.listeningc                 C   s    | j tjkr| || dS dS )a  Tell this FSM that the client's attempt to accept a connection
        failed (e.g. accept() returned an unexpected error such as ENOMEM).

        If the FSM is currently listening (self.listening() was called), it
        will back off and eventually return ovs.reconnect.CONNECT from
        self.run() to tell the client to try listening again.  If there is an
        active connection, this will be delayed until that connection drops.N)rD   r   r   rs   rO   r   rp   r   r   r   listen_error  s   zReconnect.listen_errorc                 C   s>   | j js| | | d| j  | |tj || _dS dS )a  Tell this FSM that the connection was successful.

        The FSM will start the probe interval timer, which is reset by
        self.activity().  If the timer expires, a probe will be sent (by
        returning ovs.reconnect.PROBE from self.run().  If the timer expires
        again without being reset, the connection will be aborted (by returning
        ovs.reconnect.DISCONNECT from self.run().z%s: connectedN)	rD   r   ru   rC   r   r4   r   r7   rE   rN   r   r   r   	connected  s   

zReconnect.connectedc                 C   s   |  | | || dS )zjTell this FSM that the connection attempt failed.

        The FSM will back off and attempt to reconnect.N)ru   rs   rw   r   r   r   connect_failed  s   
zReconnect.connect_failedc                 C   s$   | j tjkr| |tj || _dS )zTell this FSM that some activity occurred on the connection.  This
        resets the probe interval timer, so that the connection is known not to
        be idle.N)rD   r   r7   r4   r.   rN   r   r   r   activity  s   
zReconnect.activityc                 C   r[   )a  Tell 'fsm' that some attempt to receive data on the connection was
        made at 'now'.  The FSM only allows probe interval timer to expire when
        some attempt to receive data on the connection was received after the
        time when it should have expired.  This helps in the case where there's
        a long delay in the poll loop and then reconnect_run() executes before
        the code to try to receive anything from the remote runs.  (To disable
        this feature, pass None for 'now'.)N)r/   rN   r   r   r   receive_attempted  s   
zReconnect.receive_attemptedc                 C   s   | j tjkr|  jd7  _|tjkr|  jd7  _| j j}|j}||kr7|r0|  j|| j 7  _|  j	d7  _	t
d| j|jf  || _ || _d S )Nr,   z%s: entering %s)rD   r   r)   rJ   r7   rK   r   rL   rE   rM   r2   r3   r   r   )rO   r   rD   connected_beforeconnected_nowr   r   r   r4     s   

zReconnect._transitionc                 C   s0   | j | |}|dur||kr| j | |S dS )a  Assesses whether any action should be taken on this FSM.  The return
        value is one of:

            - None: The client need not take any action.

            - Active client, ovs.reconnect.CONNECT: The client should start a
              connection attempt and indicate this by calling
              self.connecting().  If the connection attempt has definitely
              succeeded, it should call self.connected().  If the connection
              attempt has definitely failed, it should call
              self.connect_failed().

              The FSM is smart enough to back off correctly after successful
              connections that quickly abort, so it is OK to call
              self.connected() after a low-level successful connection
              (e.g. connect()) even if the connection might soon abort due to a
              failure at a high-level (e.g. SSL negotiation failure).

            - Passive client, ovs.reconnect.CONNECT: The client should try to
              listen for a connection, if it is not already listening.  It
              should call self.listening() if successful, otherwise
              self.connecting() or reconnected_connect_failed() if the attempt
              is in progress or definitely failed, respectively.

              A listening passive client should constantly attempt to accept a
              new connection and report an accepted connection with
              self.connected().

            - ovs.reconnect.DISCONNECT: The client should abort the current
              connection or connection attempt or listen attempt and call
              self.disconnected() or self.connect_failed() to indicate it.

            - ovs.reconnect.PROBE: The client should send some kind of request
              to the peer that will elicit a response, to ensure that the
              connection is indeed in working order.  (This will only be
              returned if the "probe interval" is nonzero--see
              self.set_probe_interval()).N)rD   r   r   )rO   r   r   r   r   r   r     s   'zReconnect.runc                 C   s0   |  |}|dur|dkr|| dS dS dS )z[Causes the next call to poller.block() to wake up when self.run()
        should be called.Nr   )timeout
timer_wait)rO   pollerr   r   r   r   r   wait8  s   
zReconnect.waitc                 C   s,   | j | |}|dur|| }td|S dS )zReturns the number of milliseconds after which self.run() should be
        called if nothing else notable happens in the meantime, or None if this
        is currently unnecessary.Nr   )rD   r   r&   )rO   r   r   	remainingr   r   r   r   ?  s
   
zReconnect.timeoutc                 C   s   | j jS )zReturns True if this FSM is currently believed to be connected, that
        is, if self.connected() was called more recently than any call to
        self.connect_failed() or self.disconnected() or self.disable(), and
        False otherwise.)rD   r   rT   r   r   r   r   J  s   zReconnect.is_connectedc                 C      | j r|| j  S dS )zwReturns the number of milliseconds since 'fsm' was last connected
        to its peer. Returns None if never connected.N)rE   rN   r   r   r   get_last_connect_elapsedQ     
z"Reconnect.get_last_connect_elapsedc                 C   r   )zReturns the number of milliseconds since 'fsm' was last disconnected
        from its peer. Returns None if never disconnected.N)rF   rN   r   r   r   get_last_disconnect_elapsedY  r   z%Reconnect.get_last_disconnect_elapsedc                 C   s   G dd dt }| }| j|_| j|_| j|_| j|_| j|_| j|_|  |_| ||_	| 
||_| j|_|  rF| j| |7  _| j|_| j|_| jj|_|| j |_|S )Nc                   @   s   e Zd ZdS )z"Reconnect.get_stats.<locals>.StatsN)r   r   r   r   r   r   r   Statsb  s    r   )objectrI   rE   rF   r.   r    rM   r   r   msec_since_connectr   msec_since_disconnectrL   rJ   rK   rD   r   r   state_elapsed)rO   r   r   statsr   r   r   	get_statsa  s*   

zReconnect.get_statsc                 C   s.   | j d u rdS | j dkr|  j d8  _ dS dS )NTr   r,   Fr\   rT   r   r   r   __may_retryx  s   

zReconnect.__may_retryN)/r   r   r   __doc__r   r   r   r#   r)   r7   r5   r   rP   rR   rU   rV   rW   rY   rZ   r]   r^   r_   r`   ra   rb   rd   re   rf   rg   rh   rs   ru   rv   rx   ry   rz   r{   r|   r4   r   r   r   r   r   r   r   rc   r   r   r   r   r      sX    !
		

S
-r   )rm   ovs.utilovsovs.vlogr"   r(   r6   utilro   r2   Vlogr   r   r   r   r   r   <module>   s   