Skip to content

Commit

Permalink
Add support for unix domain sockets PYTHON-297
Browse files Browse the repository at this point in the history
  • Loading branch information
rozza committed Oct 23, 2012
1 parent 732eb1e commit 7577a19
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 10 deletions.
10 changes: 8 additions & 2 deletions pymongo/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ def __socket(self):
"""Get a SocketInfo from the pool.
"""
host, port = (self.__host, self.__port)
if host is None or port is None:
if host is None and port is None:
host, port = self.__find_node()

try:
Expand All @@ -690,8 +690,14 @@ def __socket(self):
sock_info = self.__pool.get_socket((host, port))
except socket.error, why:
self.disconnect()

# Check if a unix domain socket
if host.endswith('.sock'):
host_details = "%s:" % host
else:
host_details = "%s:%d:" % (host, port)
raise AutoReconnect("could not connect to "
"%s:%d: %s" % (host, port, str(why)))
"%s %s" % (host_details, str(why)))
if self.__auth_credentials:
self.__check_auth(sock_info)
return sock_info
Expand Down
16 changes: 15 additions & 1 deletion pymongo/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def __init__(self, pair, max_size, net_timeout, conn_timeout, use_ssl):
self.net_timeout = net_timeout
self.conn_timeout = conn_timeout
self.use_ssl = use_ssl

# Map self._get_thread_ident() -> request socket
self._tid_to_sock = {}

Expand Down Expand Up @@ -157,6 +157,20 @@ def create_connection(self, pair):
"""
host, port = pair or self.pair

# Check if dealing with a unix domain socket
if host.endswith('.sock'):
if not hasattr(socket, "AF_UNIX"):
raise ConnectionFailure("UNIX-sockets are not supported "
"on this system")
sock = socket.socket(socket.AF_UNIX)
try:
sock.connect(host)
return sock
except socket.error, e:
if sock is not None:
sock.close()
raise e

# Don't try IPv6 if we don't support it. Also skip it if host
# is 'localhost' (::1 is fine). Avoids slow connect issues
# like PYTHON-356.
Expand Down
19 changes: 17 additions & 2 deletions pymongo/uri_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def parse_host(entity, default_port=DEFAULT_PORT):
port = int(port)
return host, port


def validate_options(opts):
"""Validates and normalizes options passed in a MongoDB URI.
Expand Down Expand Up @@ -198,7 +199,11 @@ def split_hosts(hosts, default_port=DEFAULT_PORT):
if not entity:
raise ConfigurationError("Empty host "
"(or extra comma in host list).")
nodes.append(parse_host(entity, default_port))
port = default_port
# Unix socket entities don't have ports
if entity.endswith('.sock'):
port = None
nodes.append(parse_host(entity, port))
return nodes


Expand Down Expand Up @@ -237,7 +242,17 @@ def parse_uri(uri, default_port=DEFAULT_PORT):
collection = None
options = {}

host_part, _, path_part = _partition(scheme_free, '/')
# Check for unix domain sockets in the uri
if '.sock' in scheme_free:
host_part, _, path_part = _rpartition(scheme_free, '/')
try:
parse_uri('%s%s' % (SCHEME, host_part))
except (ConfigurationError, InvalidURI):
host_part = "%s%s%s" % (host_part, _, path_part)
path_part = ""
else:
host_part, _, path_part = _partition(scheme_free, '/')

if not path_part and '?' in host_part:
raise InvalidURI("A '/' is required between "
"the host list and any options.")
Expand Down
23 changes: 23 additions & 0 deletions test/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

import datetime
import os
import socket
import sys
import time
import thread
import unittest


sys.path[0:0] = [""]

from nose.plugins.skip import SkipTest
Expand Down Expand Up @@ -274,6 +276,27 @@ def test_from_uri(self):
c.admin.system.users.remove({})
c.pymongo_test.system.users.remove({})

def test_unix_socket(self):
if not hasattr(socket, "AF_UNIX"):
raise SkipTest("UNIX-sockets are not supported on this system")

mongodb_socket = '/tmp/mongodb-27017.sock'
if not os.access(mongodb_socket, os.R_OK):
raise SkipTest("Socket file is not accessable")

self.assertTrue(Connection("mongodb://%s" % mongodb_socket))

connection = Connection("mongodb://%s" % mongodb_socket)
connection.pymongo_test.test.save({"dummy": "object"})

# Confirm we can read via the socket
dbs = connection.database_names()
self.assertTrue("pymongo_test" in dbs)

# Confirm it fails with a missing socket
self.assertRaises(ConnectionFailure, Connection,
"mongodb:///tmp/none-existent.sock")

def test_fork(self):
# Test using a connection before and after a fork.
if sys.platform == "win32":
Expand Down
71 changes: 66 additions & 5 deletions test/test_uri_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
split_hosts,
split_options,
parse_uri)
from pymongo.errors import (ConfigurationError,
InvalidURI,
UnsupportedOption)
from pymongo.errors import ConfigurationError, InvalidURI


class TestURI(unittest.TestCase):
Expand Down Expand Up @@ -69,6 +67,16 @@ def test_split_hosts(self):
split_hosts('localhost,example.com'))
self.assertEqual([('localhost', 27018), ('example.com', 27019)],
split_hosts('localhost:27018,example.com:27019'))
self.assertEqual([('/tmp/mongodb-27017.sock', None)],
split_hosts('/tmp/mongodb-27017.sock'))
self.assertEqual([('/tmp/mongodb-27017.sock', None),
('example.com', 27017)],
split_hosts('/tmp/mongodb-27017.sock,'
'example.com:27017'))
self.assertEqual([('example.com', 27017),
('/tmp/mongodb-27017.sock', None)],
split_hosts('example.com:27017,'
'/tmp/mongodb-27017.sock'))
self.assertRaises(ConfigurationError, split_hosts, '::1', 27017)
self.assertRaises(ConfigurationError, split_hosts, '[::1:27017')
self.assertRaises(ConfigurationError, split_hosts, '::1')
Expand Down Expand Up @@ -181,11 +189,64 @@ def test_parse_uri(self):
":8a2e:0370:7334]:27017/?slaveOk=true"))

res = copy.deepcopy(orig)
res['nodelist'] = [("::1", 27017),
res['nodelist'] = [("/tmp/mongodb-27017.sock", None)]
self.assertEqual(res, parse_uri("mongodb:///tmp/mongodb-27017.sock"))

res = copy.deepcopy(orig)
res['nodelist'] = [("example2.com", 27017),
("/tmp/mongodb-27017.sock", None)]
self.assertEqual(res,
parse_uri("mongodb://example2.com,"
"/tmp/mongodb-27017.sock"))

res = copy.deepcopy(orig)
res['nodelist'] = [("shoe.sock.pants.co.uk", 27017),
("/tmp/mongodb-27017.sock", None)]
res['database'] = "nethers_db"
self.assertEqual(res,
parse_uri("mongodb://shoe.sock.pants.co.uk,"
"/tmp/mongodb-27017.sock/nethers_db"))

res = copy.deepcopy(orig)
res['nodelist'] = [("/tmp/mongodb-27017.sock", None),
("example2.com", 27017)]
res.update({'database': 'test', 'collection': 'yield_historical.in'})
self.assertEqual(res,
parse_uri("mongodb:///tmp/mongodb-27017.sock,"
"example2.com:27017"
"/test.yield_historical.in"))

res = copy.deepcopy(orig)
res['nodelist'] = [("/tmp/mongodb-27017.sock", None),
("example2.com", 27017)]
res.update({'database': 'test', 'collection': 'yield_historical.sock'})
self.assertEqual(res,
parse_uri("mongodb:///tmp/mongodb-27017.sock,"
"example2.com:27017"
"/test.yield_historical.sock"))

res = copy.deepcopy(orig)
res['nodelist'] = [("example2.com", 27017)]
res.update({'database': 'test', 'collection': 'yield_historical.sock'})
self.assertEqual(res,
parse_uri("mongodb://example2.com:27017"
"/test.yield_historical.sock"))

res = copy.deepcopy(orig)
res['nodelist'] = [("/tmp/mongodb-27017.sock", None)]
res.update({'database': 'test', 'collection': 'mongodb-27017.sock'})
self.assertEqual(res,
parse_uri("mongodb:///tmp/mongodb-27017.sock"
"/test.mongodb-27017.sock"))

res = copy.deepcopy(orig)
res['nodelist'] = [('/tmp/mongodb-27020.sock', None),
("::1", 27017),
("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 27018),
("192.168.0.212", 27019),
("localhost", 27018)]
self.assertEqual(res, parse_uri("mongodb://[::1]:27017,[2001:0db8:"
self.assertEqual(res, parse_uri("mongodb:///tmp/mongodb-27020.sock,"
"[::1]:27017,[2001:0db8:"
"85a3:0000:0000:8a2e:0370:7334],"
"192.168.0.212:27019,localhost",
27018))
Expand Down

0 comments on commit 7577a19

Please sign in to comment.