Permalink
Browse files

Add support for unix domain sockets PYTHON-297

  • Loading branch information...
1 parent 732eb1e commit 7577a19703e0124e7b9550377fcc36c1afe31a85 @rozza rozza committed Oct 23, 2012
Showing with 129 additions and 10 deletions.
  1. +8 −2 pymongo/connection.py
  2. +15 −1 pymongo/pool.py
  3. +17 −2 pymongo/uri_parser.py
  4. +23 −0 test/test_connection.py
  5. +66 −5 test/test_uri_parser.py
View
@@ -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:
@@ -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
View
@@ -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 = {}
@@ -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.
View
@@ -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.
@@ -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
@@ -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.")
@@ -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
@@ -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":
@@ -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):
@@ -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')
@@ -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))

0 comments on commit 7577a19

Please sign in to comment.