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

Add a test service for the W3C validation harness #487

Merged
merged 23 commits into from
Jan 21, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,11 @@ endif()
include(CMakePackageConfigHelpers)

include_directories(api/include)
add_subdirectory(api)
include_directories(sdk/include)
include_directories(sdk)
include_directories(ext/include)

add_subdirectory(api)
add_subdirectory(sdk)
add_subdirectory(exporters)
if(WITH_EXAMPLES)
Expand Down
1 change: 1 addition & 0 deletions ext/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
add_subdirectory(zpages)
add_subdirectory(http)
add_subdirectory(w3c_tracecontext_test)
26 changes: 26 additions & 0 deletions ext/test/w3c_tracecontext_test/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
cc_binary(
name = "w3c_tracecontext_test",
srcs = [
"main.cc",
],
# TODO: Move copts/linkopts for static CURL usage into shared bzl file.
copts = [
"-DCURL_STATICLIB",
],
linkopts = select({
"//bazel:windows": [
"-DEFAULTLIB:advapi32.lib",
"-DEFAULTLIB:crypt32.lib",
"-DEFAULTLIB:Normaliz.lib",
],
"//conditions:default": [],
}),
deps = [
"//api",
"//exporters/ostream:ostream_span_exporter",
"//ext:headers",
"//sdk/src/trace",
"@curl",
"@github_nlohmann_json//:json",
],
)
16 changes: 16 additions & 0 deletions ext/test/w3c_tracecontext_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
include_directories(${CMAKE_SOURCE_DIR}/exporters/ostream/include)

find_package(CURL)
find_package(nlohmann_json)

if(NOT CURL_FOUND)
message(WARNING "Skipping example_w3c_tracecontext_test: CURL not found")
elseif(NOT nlohmann_json_FOUND)
message(
WARNING "Skipping example_w3c_tracecontext_test: nlohmann_json not found")
else()
add_executable(w3c_tracecontext_test main.cc)
target_link_libraries(
w3c_tracecontext_test ${CMAKE_THREAD_LIBS_INIT} opentelemetry_trace
opentelemetry_exporter_ostream_span ${CURL_LIBRARIES})
endif()
8 changes: 8 additions & 0 deletions ext/test/w3c_tracecontext_test/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM python

RUN pip install aiohttp
RUN git clone https://github.com/w3c/trace-context

WORKDIR ./trace-context/test

ENTRYPOINT [ "python", "test.py" ]
32 changes: 32 additions & 0 deletions ext/test/w3c_tracecontext_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Test service endpoint for W3C validation

This test application is intended to be used as a test service for the [W3C Distributed Tracing Validation Service](https://github.com/w3c/trace-context/tree/master/test).
It is implemented according to [this instructions](https://github.com/w3c/trace-context/tree/master/test#implement-test-service).

## Usage

1. Build and start the test service endpoint:
```sh
$ ./w3c_tracecontext_test
Listening to http://localhost:30000/test
```
A custom port number for the test service to listen to can be specified:
```sh
$ ./w3c_tracecontext_test 31339
Listening to http://localhost:31339/test
```
The test service will print the full URI that the validation service can connect to.
2. In a different terminal, set up and start the validation service according to
the [instructions](https://github.com/w3c/trace-context/tree/master/test#run-test-cases),
giving the address of the test service endpoint as argument:
```sh
$ python test.py http://localhost:31339/test
```
One can also use the `Dockerfile` provided in this folder to conveniently
run the validation service:
```sh
$ docker build --tag w3c_driver .
$ docker run --network host w3c_driver http://localhost:31339/test
```
3. The validation service will run the test suite and print detailed test results.
4. Stop the test service by pressing enter.
187 changes: 187 additions & 0 deletions ext/test/w3c_tracecontext_test/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#include "opentelemetry/context/runtime_context.h"
#include "opentelemetry/exporters/ostream/span_exporter.h"
#include "opentelemetry/ext/http/client/curl/http_client_curl.h"
#include "opentelemetry/ext/http/server/http_server.h"
#include "opentelemetry/sdk/trace/simple_processor.h"
#include "opentelemetry/sdk/trace/tracer_provider.h"
#include "opentelemetry/trace/propagation/http_trace_context.h"
#include "opentelemetry/trace/provider.h"
#include "opentelemetry/trace/scope.h"

#include <algorithm>
#include "nlohmann/json.hpp"

namespace
{
static opentelemetry::trace::propagation::HttpTraceContext<std::map<std::string, std::string>>
propagator_format;

void Setter(std::map<std::string, std::string> &carrier,
nostd::string_view trace_type = "traceparent",
nostd::string_view trace_description = "")
{
carrier[std::string(trace_type)] = std::string(trace_description);
}

nostd::string_view Getter(const std::map<std::string, std::string> &carrier,
nostd::string_view trace_type = "traceparent")
{
auto it = carrier.find(std::string(trace_type));
if (it != carrier.end())
{
return nostd::string_view(it->second);
}
return "";
}

void initTracer()
{
auto exporter = std::unique_ptr<sdktrace::SpanExporter>(
new opentelemetry::exporter::trace::OStreamSpanExporter);
auto processor = std::shared_ptr<sdktrace::SpanProcessor>(
new sdktrace::SimpleSpanProcessor(std::move(exporter)));
auto provider = nostd::shared_ptr<opentelemetry::trace::TracerProvider>(
new sdktrace::TracerProvider(processor));
// Set the global trace provider
opentelemetry::trace::Provider::SetTracerProvider(provider);
}

nostd::shared_ptr<opentelemetry::trace::Tracer> get_tracer()
{
auto provider = opentelemetry::trace::Provider::GetTracerProvider();
return provider->GetTracer("w3c_tracecontext_test");
}

struct Uri
{
std::string host;
uint16_t port;
std::string path;

Uri(std::string uri)
{
size_t host_end = uri.substr(7, std::string::npos).find(":");
size_t port_end = uri.substr(host_end + 1, std::string::npos).find("/");

host = uri.substr(0, host_end + 7);
port = std::stoi(uri.substr(7 + host_end + 1, port_end));
path = uri.substr(host_end + port_end + 2, std::string::npos);
}
};

// A noop event handler for making HTTP requests. We don't care about response bodies and error
// messages.
class NoopEventHandler : public opentelemetry::ext::http::client::EventHandler
{
public:
void OnEvent(opentelemetry::ext::http::client::SessionState state,
opentelemetry::nostd::string_view reason) noexcept override
{}

void OnConnecting(const opentelemetry::ext::http::client::SSLCertificate &) noexcept override {}

void OnResponse(opentelemetry::ext::http::client::Response &response) noexcept override {}
};
} // namespace

// Sends an HTTP POST request to the given url, with the given body.
void send_request(opentelemetry::ext::http::client::curl::SessionManager &client,
const std::string &url,
const std::string &body)
{
static std::unique_ptr<opentelemetry::ext::http::client::EventHandler> handler(
new NoopEventHandler());

auto request_span = get_tracer()->StartSpan(__func__);
opentelemetry::trace::Scope scope(request_span);

Uri uri{url};

auto session = client.CreateSession(uri.host, uri.port);
auto request = session->CreateRequest();

request->SetMethod(opentelemetry::ext::http::client::Method::Post);
request->SetUri(uri.path);
opentelemetry::ext::http::client::Body b = {body.c_str(), body.c_str() + body.size()};
request->SetBody(b);
request->AddHeader("Content-Type", "application/json");
request->AddHeader("Content-Length", std::to_string(body.size()));

std::map<std::string, std::string> headers;
propagator_format.Inject(Setter, headers, opentelemetry::context::RuntimeContext::GetCurrent());

for (auto const &hdr : headers)
{
request->AddHeader(hdr.first, hdr.second);
}

session->SendRequest(*handler);
session->FinishSession();
}

// This application receives requests from the W3C test service. Each request has a JSON body which
// consists of an array of objects, each containing an URL to which to post to, and arguments which
// need to be used as body when posting to the given URL.
int main(int argc, char *argv[])
{
initTracer();

constexpr char default_host[] = "localhost";
constexpr uint16_t default_port = 30000;
uint16_t port;

// The port the validation service listens to can be specified via the command line.
if (argc > 1)
{
port = atoi(argv[1]);
}
else
{
port = default_port;
}

auto root_span = get_tracer()->StartSpan(__func__);
opentelemetry::trace::Scope scope(root_span);

testing::HttpServer server(default_host, port);
opentelemetry::ext::http::client::curl::SessionManager client;

testing::HttpRequestCallback test_cb{
[&](testing::HttpRequest const &req, testing::HttpResponse &resp) {
auto body = nlohmann::json::parse(req.content);

std::cout << "Received request with body :\n" << req.content << "\n";

for (auto &part : body)
{
auto current_ctx = opentelemetry::context::RuntimeContext::GetCurrent();
auto ctx = propagator_format.Extract(Getter, req.headers, current_ctx);
auto token = opentelemetry::context::RuntimeContext::Attach(ctx);

auto url = part["url"].get<std::string>();
auto arguments = part["arguments"].dump();

std::cout << " Sending request to " << url << "\n";

send_request(client, url, arguments);
}

std::cout << "\n";

resp.code = 200;
return 0;
}};

server["/test"] = test_cb;

// Start server
server.start();

std::cout << "Listening at http://" << default_host << ":" << port << "/test\n";

// Wait for console input
std::cin.get();

// Stop server
server.stop();
}