-# Copyright (c) 2010, 2011 Nicira Networks
+# Copyright (c) 2010, 2011, 2012 Nicira, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
import os
+import ovs.vlog
+import ovs.util
+
# Values returned by Reconnect.run()
CONNECT = 'connect'
DISCONNECT = 'disconnect'
PROBE = 'probe'
-EOF = -1
+EOF = ovs.util.EOF
+vlog = ovs.vlog.Vlog("reconnect")
+
class Reconnect(object):
"""A finite-state machine for connecting and reconnecting to a network
@staticmethod
def run(fsm, now):
- logging.debug("%s: idle %d ms, sending inactivity probe"
- % (fsm.name,
- now - max(fsm.last_received, fsm.state_entered)))
+ vlog.dbg("%s: idle %d ms, sending inactivity probe"
+ % (fsm.name,
+ now - max(fsm.last_received, fsm.state_entered)))
fsm._transition(now, Reconnect.Idle)
return PROBE
@staticmethod
def deadline(fsm):
- return fsm.state_entered + fsm.probe_interval
+ if fsm.probe_interval:
+ return fsm.state_entered + fsm.probe_interval
+ return None
@staticmethod
def run(fsm, now):
- logging.error("%s: no response to inactivity probe after %.3g "
- "seconds, disconnecting"
- % (fsm.name, (now - fsm.state_entered) / 1000.0))
+ vlog.err("%s: no response to inactivity probe after %.3g "
+ "seconds, disconnecting"
+ % (fsm.name, (now - fsm.state_entered) / 1000.0))
return DISCONNECT
- class Reconnect:
+ class Reconnect(object):
name = "RECONNECT"
is_connected = False
self.max_backoff = 8000
self.probe_interval = 5000
self.passive = False
- self.info_level = logging.info
+ self.info_level = vlog.info
self.state = Reconnect.Void
self.state_entered = now
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."""
if quiet:
- self.info_level = logging.debug
+ self.info_level = vlog.dbg
else:
- self.info_level = logging.info
+ self.info_level = vlog.info
def get_name(self):
return self.name
def set_name(self, name):
"""Sets this object's name to 'name'. If 'name' is None, then "void"
is used instead.
-
+
The name is used in log messages."""
if name is None:
self.name = "void"
if (self.state == Reconnect.Backoff and
self.backoff > self.max_backoff):
self.backoff = self.max_backoff
-
+
def set_probe_interval(self, probe_interval):
"""Sets the "probe interval" to 'probe_interval', in milliseconds. If
this is zero, it disables the connection keepalive feature. If it is
def set_passive(self, passive, now):
"""Configures 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."""
+ passive mode, the FSM is listening for connections from a remote
+ host."""
if self.passive != passive:
self.passive = passive
# Report what happened
if self.state in (Reconnect.Active, Reconnect.Idle):
if error > 0:
- logging.warning("%s: connection dropped (%s)"
- % (self.name, os.strerror(error)))
+ vlog.warn("%s: connection dropped (%s)"
+ % (self.name, os.strerror(error)))
elif error == EOF:
self.info_level("%s: connection closed by peer"
% self.name)
self.info_level("%s: connection dropped" % self.name)
elif self.state == Reconnect.Listening:
if error > 0:
- logging.warning("%s: error listening for connections (%s)"
- % (self.name, os.strerror(error)))
+ vlog.warn("%s: error listening for connections (%s)"
+ % (self.name, os.strerror(error)))
else:
self.info_level("%s: error listening for connections"
% self.name)
else:
type_ = "connection"
if error > 0:
- logging.warning("%s: %s attempt failed (%s)"
- % (self.name, type_, os.strerror(error)))
+ vlog.warn("%s: %s attempt failed (%s)"
+ % (self.name, type_, os.strerror(error)))
else:
self.info_level("%s: %s attempt timed out"
% (self.name, type_))
else:
self.info_level("%s: connecting..." % self.name)
self._transition(now, Reconnect.ConnectInProgress)
-
+
def listening(self, now):
"""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
def listen_error(self, now, error):
"""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
if connected_before:
self.total_connected_duration += now - self.last_connected
self.seqno += 1
-
- logging.debug("%s: entering %s" % (self.name, state.name))
+
+ vlog.dbg("%s: entering %s" % (self.name, state.name))
self.state = state
self.state_entered = now
def run(self, now):
"""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())."""
- if now >= self.state.deadline(self):
+
+ deadline = self.state.deadline(self)
+ if deadline is not None and now >= deadline:
return self.state.run(self, now)
else:
return None
-
+
def wait(self, poller, now):
"""Causes the next call to poller.block() to wake up when self.run()
should be called."""
def timeout(self, now):
"""Returns the number of milliseconds after which self.run() should be
- called if nothing else notable happens in the meantime, or a negative
- number if this is currently unnecessary."""
+ called if nothing else notable happens in the meantime, or None if this
+ is currently unnecessary."""
deadline = self.state.deadline(self)
if deadline is not None:
remaining = deadline - now
stats.msec_since_disconnect = self.get_last_disconnect_elapsed(now)
stats.total_connected_duration = self.total_connected_duration
if self.is_connected():
- stats.total_connected_duration += self.get_last_connect_elapsed(now)
+ stats.total_connected_duration += (
+ self.get_last_connect_elapsed(now))
stats.n_attempted_connections = self.n_attempted_connections
stats.n_successful_connections = self.n_successful_connections
stats.state = self.state.name