Skip to content

Commit

Permalink
[core] Add HTTPS support
Browse files Browse the repository at this point in the history
  • Loading branch information
raylu committed Sep 13, 2019
1 parent 182f7de commit 8df71af
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 22 deletions.
27 changes: 20 additions & 7 deletions ddtrace/api.py
Expand Up @@ -102,8 +102,11 @@ class UDSHTTPConnection(httplib.HTTPConnection):

# It's "important" to keep the hostname and port arguments here; while there are not used by the connection
# mechanism, they are actually used as HTTP headers such as `Host`.
def __init__(self, path, *args, **kwargs):
httplib.HTTPConnection.__init__(self, *args, **kwargs)
def __init__(self, path, https, *args, **kwargs):
if https:
httplib.HTTPSConnection.__init__(self, *args, **kwargs)
else:
httplib.HTTPConnection.__init__(self, *args, **kwargs)
self.path = path

def connect(self):
Expand All @@ -123,7 +126,7 @@ class API(object):
# This ought to be enough as the agent is local
TIMEOUT = 2

def __init__(self, hostname, port, uds_path=None, headers=None, encoder=None, priority_sampling=False):
def __init__(self, hostname, port, uds_path=None, https=False, headers=None, encoder=None, priority_sampling=False):
"""Create a new connection to the Tracer API.
:param hostname: The hostname.
Expand All @@ -136,6 +139,7 @@ def __init__(self, hostname, port, uds_path=None, headers=None, encoder=None, pr
self.hostname = hostname
self.port = int(port)
self.uds_path = uds_path
self.https = https

self._headers = headers or {}
self._version = None
Expand All @@ -160,9 +164,15 @@ def __init__(self, hostname, port, uds_path=None, headers=None, encoder=None, pr
})

def __str__(self):
if self.https:
scheme = 'https://'
else:
scheme = 'http://'
if self.uds_path:
return self.uds_path
return '%s:%s' % (self.hostname, self.port)
host = self.uds_path
else:
host = '%s:%s' % (self.hostname, self.port)
return scheme + host

def _set_version(self, version, encoder=None):
if version not in _VERSIONS:
Expand Down Expand Up @@ -249,9 +259,12 @@ def _put(self, endpoint, data, count):
headers[self.TRACE_COUNT_HEADER] = str(count)

if self.uds_path is None:
conn = httplib.HTTPConnection(self.hostname, self.port, timeout=self.TIMEOUT)
if self.https:
conn = httplib.HTTPSConnection(self.hostname, self.port, timeout=self.TIMEOUT)
else:
conn = httplib.HTTPConnection(self.hostname, self.port, timeout=self.TIMEOUT)
else:
conn = UDSHTTPConnection(self.uds_path, self.hostname, self.port, timeout=self.TIMEOUT)
conn = UDSHTTPConnection(self.uds_path, self.https, self.hostname, self.port, timeout=self.TIMEOUT)

try:
conn.request('PUT', endpoint, data, headers)
Expand Down
14 changes: 9 additions & 5 deletions ddtrace/tracer.py
Expand Up @@ -101,9 +101,9 @@ def context_provider(self):
"""Returns the current Tracer Context Provider"""
return self._context_provider

def configure(self, enabled=None, hostname=None, port=None, uds_path=None, dogstatsd_host=None,
dogstatsd_port=None, sampler=None, context_provider=None, wrap_executor=None,
priority_sampling=None, settings=None, collect_metrics=None):
def configure(self, enabled=None, hostname=None, port=None, uds_path=None, https=None,
dogstatsd_host=None, dogstatsd_port=None, sampler=None, context_provider=None,
wrap_executor=None, priority_sampling=None, settings=None, collect_metrics=None):
"""
Configure an existing Tracer the easy way.
Allow to configure or reconfigure a Tracer instance.
Expand All @@ -113,6 +113,7 @@ def configure(self, enabled=None, hostname=None, port=None, uds_path=None, dogst
:param str hostname: Hostname running the Trace Agent
:param int port: Port of the Trace Agent
:param str uds_path: The Unix Domain Socket path of the agent.
:param bool https: Whether to use HTTPS or HTTP.
:param int metric_port: Port of DogStatsd
:param object sampler: A custom Sampler instance, locally deciding to totally drop the trace or not.
:param object context_provider: The ``ContextProvider`` that will be used to retrieve
Expand Down Expand Up @@ -145,18 +146,21 @@ def configure(self, enabled=None, hostname=None, port=None, uds_path=None, dogst
if isinstance(self.sampler, DatadogSampler):
self.sampler._priority_sampler = self.priority_sampler

if hostname is not None or port is not None or uds_path is not None or filters is not None or \
priority_sampling is not None:
if hostname is not None or port is not None or uds_path is not None or https is not None or \
filters is not None or priority_sampling is not None:
# Preserve hostname and port when overriding filters or priority sampling
default_hostname = self.DEFAULT_HOSTNAME
default_port = self.DEFAULT_PORT
if hasattr(self, 'writer') and hasattr(self.writer, 'api'):
default_hostname = self.writer.api.hostname
default_port = self.writer.api.port
if https is None:
https = self.writer.api.https
self.writer = AgentWriter(
hostname or default_hostname,
port or default_port,
uds_path=uds_path,
https=https,
filters=filters,
priority_sampler=self.priority_sampler,
)
Expand Down
8 changes: 4 additions & 4 deletions ddtrace/writer.py
Expand Up @@ -19,14 +19,15 @@

class AgentWriter(object):

def __init__(self, hostname='localhost', port=8126, uds_path=None, filters=None, priority_sampler=None):
def __init__(self, hostname='localhost', port=8126, uds_path=None, https=False, filters=None,
priority_sampler=None):
self._pid = None
self._traces = None
self._worker = None
self._filters = filters
self._priority_sampler = priority_sampler
priority_sampling = priority_sampler is not None
self.api = api.API(hostname, port, uds_path=uds_path, priority_sampling=priority_sampling)
self.api = api.API(hostname, port, uds_path=uds_path, https=https, priority_sampling=priority_sampling)

def write(self, spans=None, services=None):
# if the worker needs to be reset, do it.
Expand Down Expand Up @@ -153,8 +154,7 @@ def put(self, item):
return Queue.put(self, item, block=False)
except Full:
# If the queue is full, replace a random item. We need to make sure
# the queue is not emptied was emptied in the meantime, so we lock
# check qsize value.
# the queue was not emptied in the meantime, so we lock and check qsize.
with self.mutex:
qsize = self._qsize()
if qsize != 0:
Expand Down
4 changes: 2 additions & 2 deletions docs/advanced_usage.rst
Expand Up @@ -10,9 +10,9 @@ is a small example showcasing this::

from ddtrace import tracer

tracer.configure(hostname=<YOUR_HOST>, port=<YOUR_PORT>)
tracer.configure(hostname=<YOUR_HOST>, port=<YOUR_PORT>, https=<True/False>)

By default, these will be set to ``localhost`` and ``8126`` respectively.
By default, these will be set to ``localhost``, ``8126``, and ``False`` respectively.

You can also use a Unix Domain Socket to connect to the agent::

Expand Down
16 changes: 13 additions & 3 deletions tests/test_api.py
Expand Up @@ -111,10 +111,10 @@ def read(self):


def test_api_str():
api = API('localhost', 8126)
assert str(api) == 'localhost:8126'
api = API('localhost', 8126, https=True)
assert str(api) == 'https://localhost:8126'
api = API('localhost', 8126, '/path/to/uds')
assert str(api) == '/path/to/uds'
assert str(api) == 'http:///path/to/uds'


class APITests(TestCase):
Expand Down Expand Up @@ -218,6 +218,16 @@ def test_put_connection_close_exception(self, HTTPConnection):
self.conn.close.assert_called_once()


def test_https():
conn = mock.MagicMock(spec=httplib.HTTPSConnection)
api = API('localhost', 8126, https=True)
with mock.patch('ddtrace.compat.httplib.HTTPSConnection') as HTTPSConnection:
HTTPSConnection.return_value = conn
api._put('/test', '<test data>', 1)
conn.request.assert_called_once()
conn.close.assert_called_once()


def test_flush_connection_timeout_connect():
payload = mock.Mock()
payload.get_payload.return_value = 'foobar'
Expand Down
2 changes: 1 addition & 1 deletion tests/test_integration.py
Expand Up @@ -199,7 +199,7 @@ def test_worker_http_error_logging(self):

logged_errors = log_handler.messages['error']
assert len(logged_errors) == 1
assert 'Failed to send traces to Datadog Agent at localhost:8126: ' \
assert 'Failed to send traces to Datadog Agent at http://localhost:8126: ' \
'HTTP error status 400, reason Bad Request, message Content-Type:' \
in logged_errors[0]

Expand Down

0 comments on commit 8df71af

Please sign in to comment.