Skip to content

Commit

Permalink
ovs python: ovs.stream.open_block() returns success even if the remot…
Browse files Browse the repository at this point in the history
…e is unreachable

The python function ovs.socket_util.check_connection_completion() uses select()
(provided by python) to monitor the socket file descriptor. The select()
returns 1 when the file descriptor becomes ready. For error cases like -
111 (Connection refused) and 113 (No route to host) (POLLERR), ovs.poller._SelectSelect.poll()
expects the exceptfds list to be set by select(). But that is not the case.
As per the select() man page, writefds list will be set for POLLERR.
Please see "Correspondence between select() and poll() notifications" section of select(2)
man page.

Because of this behavior, ovs.socket_util.check_connection_completion() returns success
even if the remote is unreachable or not listening on the port.

This patch fixes this issue by using poll() to check the connection status similar to
the C implementation of check_connection_completion().

A new function 'get_system_poll() is added in ovs/poller.py which returns the
select.poll() object. If select.poll is monkey patched by eventlet/gevent, it
gets the original select.poll() and returns it.

The test cases added in this patch fails without the fix.

Suggested-by: Ben Pfaff <[email protected]>
Signed-off-by: Numan Siddique <[email protected]>
Signed-off-by: Ben Pfaff <[email protected]>
Acked-by: Mark Michelson <[email protected]>
  • Loading branch information
numansiddique authored and blp committed Aug 14, 2018
1 parent 44a629b commit c1aa16d
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 6 deletions.
35 changes: 33 additions & 2 deletions python/ovs/poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,22 @@
SSL = None

try:
import eventlet.patcher
from eventlet import patcher as eventlet_patcher

def _using_eventlet_green_select():
return eventlet.patcher.is_monkey_patched(select)
return eventlet_patcher.is_monkey_patched(select)
except:
eventlet_patcher = None

def _using_eventlet_green_select():
return False

try:
from gevent import monkey as gevent_monkey
except:
gevent_monkey = None


vlog = ovs.vlog.Vlog("poller")

POLLIN = 0x001
Expand Down Expand Up @@ -257,3 +265,26 @@ def __log_wakeup(self, events):
def __reset(self):
self.poll = SelectPoll()
self.timeout = -1


def get_system_poll():
"""Returns the original select.poll() object. If select.poll is
monkey patched by eventlet or gevent library, it gets the original
select.poll and returns an object of it using the
eventlet.patcher.original/gevent.monkey.get_original functions.
As a last resort, if there is any exception it returns the
SelectPoll() object.
"""
try:
if _using_eventlet_green_select():
_system_poll = eventlet_patcher.original("select").poll
elif gevent_monkey and gevent_monkey.is_object_patched(
'select', 'poll'):
_system_poll = gevent_monkey.get_original('select', 'poll')
else:
_system_poll = select.poll
except:
_system_poll = SelectPoll

return _system_poll()
5 changes: 3 additions & 2 deletions python/ovs/socket_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ def make_unix_socket(style, nonblock, bind_path, connect_path, short=False):


def check_connection_completion(sock):
p = ovs.poller.SelectPoll()
if sys.platform == "win32":
p = ovs.poller.SelectPoll()
event = winutils.get_new_event(None, False, True, None)
# Receive notification of readiness for writing, of completed
# connection or multipoint join operation, and of socket closure.
Expand All @@ -173,14 +173,15 @@ def check_connection_completion(sock):
win32file.FD_CLOSE)
p.register(event, ovs.poller.POLLOUT)
else:
p = ovs.poller.get_system_poll()
p.register(sock, ovs.poller.POLLOUT)
pfds = p.poll(0)
if len(pfds) == 1:
revents = pfds[0][1]
if revents & ovs.poller.POLLERR:
try:
# The following should raise an exception.
socket.send("\0", socket.MSG_DONTWAIT)
sock.send("\0".encode(), socket.MSG_DONTWAIT)

# (Here's where we end up if it didn't.)
# XXX rate-limit
Expand Down
11 changes: 9 additions & 2 deletions python/ovs/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,15 @@ def open(name, dscp=DSCP_DEFAULT):
if error:
return error, None
else:
status = ovs.socket_util.check_connection_completion(sock)
return 0, cls(sock, name, status)
err = ovs.socket_util.check_connection_completion(sock)
if err == errno.EAGAIN or err == errno.EINPROGRESS:
status = errno.EAGAIN
err = 0
elif err == 0:
status = 0
else:
status = err
return err, cls(sock, name, status)

@staticmethod
def _open(suffix, dscp):
Expand Down
1 change: 1 addition & 0 deletions tests/automake.mk
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ CHECK_PYFILES = \
tests/test-l7.py \
tests/test-ovsdb.py \
tests/test-reconnect.py \
tests/test-stream.py \
tests/MockXenAPI.py \
tests/test-unix-socket.py \
tests/test-unixctl.py \
Expand Down
16 changes: 16 additions & 0 deletions tests/ovsdb-idl.at
Original file line number Diff line number Diff line change
Expand Up @@ -1730,3 +1730,19 @@ OVSDB_CHECK_IDL_COMPOUND_INDEX_WITH_REF([set, simple3 idl-compound-index-with-re
007: check simple4: empty
008: End test
]])

m4_define([CHECK_STREAM_OPEN_BLOCK_PY],
[AT_SETUP([$1])
AT_SKIP_IF([test $2 = no])
AT_KEYWORDS([Check PY Stream open block - $3])
AT_CHECK([ovsdb_start_idltest "ptcp:0:127.0.0.1"])
PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
WRONG_PORT=$(($TCP_PORT+1))
AT_CHECK([$3 $srcdir/test-stream.py tcp:127.0.0.1:$TCP_PORT], [0], [ignore])
AT_CHECK([$3 $srcdir/test-stream.py tcp:127.0.0.1:$WRONG_PORT], [1], [ignore])
OVSDB_SERVER_SHUTDOWN
AT_CHECK([$3 $srcdir/test-stream.py tcp:127.0.0.1:$TCP_PORT], [1], [ignore])
AT_CLEANUP])

CHECK_STREAM_OPEN_BLOCK_PY([Check PY2 Stream open block], [$HAVE_PYTHON], [$PYTHON])
CHECK_STREAM_OPEN_BLOCK_PY([Check PY3 Stream open block], [$HAVE_PYTHON], [$PYTHON3])
32 changes: 32 additions & 0 deletions tests/test-stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright (c) 2018, Red Hat Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys

import ovs.stream


def main(argv):
remote = argv[1]
err, stream = ovs.stream.Stream.open_block(
ovs.stream.Stream.open(remote))

if err or stream is None:
sys.exit(1)

sys.exit(0)


if __name__ == '__main__':
main(sys.argv)

0 comments on commit c1aa16d

Please sign in to comment.