Experimental memcached protocol.
Only works with python 2 now
This commit is contained in:
@@ -4,3 +4,4 @@ nose
|
|||||||
coverage
|
coverage
|
||||||
mock
|
mock
|
||||||
pyaml
|
pyaml
|
||||||
|
pylibmc
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from elasticsearch.transport import Transport
|
|||||||
from elasticsearch.connection_pool import ConnectionPool, ConnectionSelector, \
|
from elasticsearch.connection_pool import ConnectionPool, ConnectionSelector, \
|
||||||
RoundRobinSelector
|
RoundRobinSelector
|
||||||
from elasticsearch.serializer import JSONSerializer
|
from elasticsearch.serializer import JSONSerializer
|
||||||
from elasticsearch.connection import Connection, RequestsHttpConnection
|
from elasticsearch.connection import Connection, RequestsHttpConnection, \
|
||||||
|
Urllib3HttpConnection, MemcachedConnection
|
||||||
from elasticsearch.exceptions import *
|
from elasticsearch.exceptions import *
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,10 @@ class Connection(object):
|
|||||||
def _pretty_json(data):
|
def _pretty_json(data):
|
||||||
# pretty JSON in tracer curl logs
|
# pretty JSON in tracer curl logs
|
||||||
try:
|
try:
|
||||||
data = json.dumps(json.loads(data), sort_keys=True, indent=2, separators=(',', ': '))
|
return json.dumps(json.loads(data), sort_keys=True, indent=2, separators=(',', ': '))
|
||||||
except ValueError:
|
except (ValueError, TypeError):
|
||||||
# non-json data or a bulk request
|
# non-json data or a bulk request
|
||||||
pass
|
return repr(data)
|
||||||
return data
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'%s %s [status:%s request:%.3fs]', method, full_url,
|
'%s %s [status:%s request:%.3fs]', method, full_url,
|
||||||
@@ -149,3 +148,65 @@ class Urllib3HttpConnection(Connection):
|
|||||||
raw_data, duration)
|
raw_data, duration)
|
||||||
|
|
||||||
return response.status, raw_data
|
return response.status, raw_data
|
||||||
|
|
||||||
|
class MemcachedConnection(Connection):
|
||||||
|
transport_schema = 'memcached'
|
||||||
|
|
||||||
|
method_map = {
|
||||||
|
'PUT': 'set',
|
||||||
|
'POST': 'set',
|
||||||
|
'DELETE': 'delete',
|
||||||
|
'HEAD': 'get',
|
||||||
|
'GET': 'get',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, host='localhost', port=11211, **kwargs):
|
||||||
|
try:
|
||||||
|
import pylibmc
|
||||||
|
except ImportError:
|
||||||
|
raise ImproperlyConfigured("You need to install pylibmc to use the MemcachedConnection class.")
|
||||||
|
super(MemcachedConnection, self).__init__(host=host, port=port, **kwargs)
|
||||||
|
self.mc = pylibmc.Client(['%s:%s' % (host, port)],behaviors={"tcp_nodelay": True})
|
||||||
|
|
||||||
|
def perform_request(self, method, url, params=None, body=None, timeout=None):
|
||||||
|
url = self.url_prefix + url
|
||||||
|
if params:
|
||||||
|
url = '%s?%s' % (url, urlencode(params or {}))
|
||||||
|
full_url = self.host + url
|
||||||
|
|
||||||
|
mc_method = self.method_map.get(method, 'get')
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
status = 200
|
||||||
|
if mc_method == 'set':
|
||||||
|
# no response from set commands
|
||||||
|
response = ''
|
||||||
|
if not json.dumps(self.mc.set(url, body)):
|
||||||
|
status = 500
|
||||||
|
else:
|
||||||
|
response = self.mc.get(url)
|
||||||
|
|
||||||
|
duration = time.time() - start
|
||||||
|
if response:
|
||||||
|
response = response.decode('utf-8')
|
||||||
|
except Exception as e:
|
||||||
|
self.log_request_fail(method, full_url, time.time() - start, exception=e)
|
||||||
|
raise ConnectionError('N/A', str(e), e)
|
||||||
|
|
||||||
|
# try not to load the json every time
|
||||||
|
if response and response[0] == '{' and ('"status"' in response or '"error"' in response):
|
||||||
|
data = json.loads(response)
|
||||||
|
if 'status' in data:
|
||||||
|
status = data['status']
|
||||||
|
elif 'error' in data:
|
||||||
|
raise TransportError('N/A', data['error'])
|
||||||
|
|
||||||
|
if not (200 <= status < 300):
|
||||||
|
self.log_request_fail(method, url, duration, status)
|
||||||
|
self._raise_error(status, response)
|
||||||
|
|
||||||
|
self.log_request_success(method, full_url, url, body, status,
|
||||||
|
response, duration)
|
||||||
|
|
||||||
|
return status, response
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from elasticsearch import Elasticsearch, MemcachedConnection, NotFoundError
|
||||||
|
|
||||||
|
from elasticsearch.transport import ADDRESS_RE
|
||||||
|
|
||||||
|
from . import ElasticTestCase
|
||||||
|
|
||||||
|
from unittest import SkipTest
|
||||||
|
|
||||||
|
class TestMemcachedConnection(ElasticTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestMemcachedConnection, self).setUp()
|
||||||
|
nodes = self.client.cluster.node_info()
|
||||||
|
for node_id, node_info in nodes["nodes"].items():
|
||||||
|
if 'memcached_address' in node_info:
|
||||||
|
connection_info = ADDRESS_RE.search(node_info['memcached_address']).groupdict()
|
||||||
|
self.mc_client = Elasticsearch(
|
||||||
|
[connection_info],
|
||||||
|
connection_class=MemcachedConnection
|
||||||
|
)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise SkipTest("No memcached plugin.")
|
||||||
|
|
||||||
|
def test_index(self):
|
||||||
|
self.mc_client.index("test_index", "test_type", {"answer": 42}, id=1)
|
||||||
|
self.assertTrue(self.client.exists("test_index", doc_type="test_type", id=1))
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
self.client.index("test_index", "test_type", {"answer": 42}, id=1)
|
||||||
|
self.assertEquals({"answer": 42}, self.mc_client.get("test_index", doc_type="test_type", id=1)["_source"])
|
||||||
|
|
||||||
|
def test_unicode(self):
|
||||||
|
self.mc_client.index("test_index", "test_type", {"answer": u"你好"}, id=u"你好")
|
||||||
|
self.assertEquals({"answer": u"你好"}, self.mc_client.get("test_index", doc_type="test_type", id=u"你好")["_source"])
|
||||||
|
|
||||||
|
def test_missing(self):
|
||||||
|
self.assertRaises(NotFoundError, self.mc_client.get, "test_index", doc_type="test_type", id=42)
|
||||||
Reference in New Issue
Block a user