Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boto / Botocore integration #209

Merged
merged 38 commits into from
Mar 16, 2017
Merged

Boto / Botocore integration #209

merged 38 commits into from
Mar 16, 2017

Conversation

raphaelgavache
Copy link
Contributor

@raphaelgavache raphaelgavache commented Mar 2, 2017

screen shot 2017-03-06 at 5 18 13 pm

# Botocore and Boto integrations

Summary

Adds botocore and boto contribs with testing units:
boto : s3/ec2/sqs/lambda
botocore : s3/ec2/sqs/lambda/kinesis
Boto3 relies on botocore so it will also be traced

Compatibility

boto >=2.29.0
botocore >= 1.4.51

TODOS

Miscellanous notes

  • botocore is really clean, boto is a mess:
    • the first has one big class that customises itself in function of what service is called. The methods are created from JSON and the result has a standard format shared by all services.
    • boto has one specific class for each AWS and which can inherit from 2 different classes AWSQueryConnection (ec2 connector for exemple) and AWSAuthConnection (s3 connector ...). Both classes make_request() methods are patched but they do not contain the same metadata. For exemple AWSAuthConnection instance does not contain the operation name called on the s3 AWS.
  • boto both class AWSQueryConnection and AWSAuthConnection have a region attribute. For some it's a string, others have different classes (sqs has his own class...) but there's also a standard Region class -> this is why I added a function get_region_name


# Original func returns a boto.connection.HPPResponse object
result = original_func(*args, **kwargs)
span.set_tag(http.STATUS_CODE, getattr(result, "status", 500))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not have it default to 500. This is a meaningful error code that influences downstream stats collection and error reporting/alerting so we do not want to set it just because there's no better candidate. Instead it's fine to set the STATUS_CODE meta only when result.status is available

return result

def get_region_name(region):
if type(region) == str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use isinstance


def patch():
# Checking for the version compatibility before patching
if LooseVersion(pkg_resources.get_distribution("botocore").version) >= LooseVersion("1.4.51"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we want version matching code at this layer. instead let the patching function fail if a patchable isn't available - and patch_all will respond to this gracefully

@talwai talwai changed the title Raphael/contrib boto Boto / Botocore integration Mar 6, 2017

# s3, lambda
def patched_auth_request(original_func, instance, args, kwargs):
tracer = getattr(instance, 'datadog_tracer', ddtrace.tracer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


with pin.tracer.trace('boto.command', service=DEFAULT_SERVICE, span_type=SPAN_TYPE) as span:

# Original func returns a boto.connection.HPPResponse object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo HTTP

# Original func returns a boto.connection.HPPResponse object
result = original_func(*args, **kwargs)
span.set_tag(http.STATUS_CODE, getattr(result, "status"))
span.set_tag(http.METHOD, getattr(result, "_method", "unknown"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than set "unknown", it's ok to leave metadata unset in general when the needed attribute is not available


# Testing resource and service
eq_(span.service, "aws")
eq_(span.resource, "boto.command")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not the correct resource. update this to do something similar to what botocore does.
and also make sure the check span.name in this test

eq_(span.get_tag(http.STATUS_CODE), "200")
eq_(span.get_tag(http.METHOD), "GET")
eq_(span.get_tag('aws.endpoint'), "lambda")
eq_(span.get_tag('aws.region'), "us-east-2")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to check span.service , span.resource and span.name in all these tests

@palazzem palazzem mentioned this pull request Mar 9, 2017
@palazzem palazzem modified the milestone: 0.7.0 Mar 9, 2017
s3.get_all_buckets()
spans = writer.pop()
assert spans
span = spans[0]
Copy link

@b1-88er b1-88er Mar 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does length of spans matter here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one span is generated each time the function patched is called. For each request of the S3 client https://github.com/boto/boto/blob/develop/boto/s3/connection.py#L440, patched function gets called only once. On tests on our staging env there's a unique trace per s3 call. Yet in the mocking env I get 3 spans, I'll investigate on that

operation_name = None
for trace in reversed(traceback.extract_stack()):
# Going backwards in the traceback till first call outside off ddtrace before make_request
if "ddtrace" not in trace[0].split('/') and trace[2] != 'make_request':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code worries me. if there's no more reliable way to pull an operation_name, we should at least have a length check on the stacktrace before we try to index into it arbitrarily.

if endpoint_name in BLACKLIST_ENDPOINT:
return True
else:
return False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just return endpoint_name in BLACKLIST_ENDPOINT

else:
return False

def unpacking_args(args, args_name, traced_args_list):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs a docstring

@raphaelgavache raphaelgavache merged commit 38dc43d into master Mar 16, 2017
@palazzem palazzem deleted the raphael/contrib-boto branch March 16, 2017 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants