Handle bad request in Docker registry client. Review: https://reviews.apache.org/r/38289
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/d7b7a53c Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/d7b7a53c Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/d7b7a53c Branch: refs/heads/master Commit: d7b7a53c1f1be9ccc570cdfc25ac62fce3726b51 Parents: 34750ce Author: Timothy Chen <[email protected]> Authored: Wed Sep 9 17:33:22 2015 -0700 Committer: Timothy Chen <[email protected]> Committed: Mon Sep 14 13:34:08 2015 -0700 ---------------------------------------------------------------------- src/Makefile.am | 4 +- .../provisioners/docker/registry_client.cpp | 87 ++- .../provisioners/docker/registry_client.hpp | 4 +- .../containerizer/docker_provisioner_tests.cpp | 683 +++++++++++++++++++ .../provisioners/docker_provisioner_tests.cpp | 627 ----------------- 5 files changed, 748 insertions(+), 657 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/d7b7a53c/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index 8c46539..509256f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1664,13 +1664,12 @@ mesos_tests_SOURCES = \ tests/paths_tests.cpp \ tests/persistent_volume_tests.cpp \ tests/protobuf_io_tests.cpp \ - tests/provisioners/docker_provisioner_tests.cpp \ tests/rate_limiting_tests.cpp \ tests/reconciliation_tests.cpp \ tests/registrar_tests.cpp \ tests/repair_tests.cpp \ tests/reservation_tests.cpp \ - tests/reservation_endpoints_tests.cpp \ + tests/reservation_endpoints_tests.cpp \ tests/resource_offers_tests.cpp \ tests/resources_tests.cpp \ tests/scheduler_tests.cpp \ @@ -1692,6 +1691,7 @@ mesos_tests_SOURCES = \ tests/containerizer/composing_containerizer_tests.cpp \ tests/containerizer/docker_containerizer_tests.cpp \ tests/containerizer/docker_tests.cpp \ + tests/containerizer/docker_provisioner_tests.cpp \ tests/containerizer/external_containerizer_test.cpp \ tests/containerizer/isolator_tests.cpp \ tests/containerizer/memory_test_helper.cpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/d7b7a53c/src/slave/containerizer/provisioners/docker/registry_client.cpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/registry_client.cpp b/src/slave/containerizer/provisioners/docker/registry_client.cpp index fce0563..b262ef0 100644 --- a/src/slave/containerizer/provisioners/docker/registry_client.cpp +++ b/src/slave/containerizer/provisioners/docker/registry_client.cpp @@ -20,6 +20,7 @@ #include <process/defer.hpp> #include <process/dispatch.hpp> +#include <process/io.hpp> #include "slave/containerizer/provisioners/docker/registry_client.hpp" #include "slave/containerizer/provisioners/docker/token_manager.hpp" @@ -264,6 +265,45 @@ RegistryClientProcess::doHttpGet( // Set the future if we get a OK response. if (httpResponse.status == "200 OK") { return httpResponse; + } else if (httpResponse.status == "400 Bad Request") { + Try<JSON::Object> errorResponse = + JSON::parse<JSON::Object>(httpResponse.body); + + if (errorResponse.isError()) { + return Failure("Failed to parse bad request response JSON: " + + errorResponse.error()); + } + + std::ostringstream out; + bool first = true; + Result<JSON::Array> errorObjects = + errorResponse.get().find<JSON::Array>("errors"); + + if (errorObjects.isError()) { + return Failure("Failed to find 'errors' in bad request response: " + + errorObjects.error()); + } else if (errorObjects.isNone()) { + return Failure("Errors not found in bad request response"); + } + + foreach (const JSON::Value& error, errorObjects.get().values) { + Result<JSON::String> message = + error.as<JSON::Object>().find<JSON::String>("message"); + if (message.isError()) { + return Failure("Failed to parse bad request error message: " + + message.error()); + } else if (message.isNone()) { + continue; + } + + if (first) { + out << message.get().value; + first = false; + } else { + out << ", " << message.get().value; + } + } + return Failure("Received Bad request, errors: [" + out.str() + "]"); } // Prevent infinite recursion. @@ -410,6 +450,10 @@ Future<ManifestResponse> RegistryClientProcess::getManifest( auto getManifestResponse = []( const Response& httpResponse) -> Try<ManifestResponse> { + if (!httpResponse.headers.contains("Docker-Content-Digest")) { + return Error("Docker-Content-Digest header missing in response"); + } + Try<JSON::Object> responseJSON = JSON::parse<JSON::Object>(httpResponse.body); @@ -417,10 +461,6 @@ Future<ManifestResponse> RegistryClientProcess::getManifest( return Error(responseJSON.error()); } - if (!httpResponse.headers.contains("Docker-Content-Digest")) { - return Error("Docker-Content-Digest header missing in response"); - } - Result<JSON::String> name = responseJSON.get().find<JSON::String>("name"); if (name.isNone()) { return Error("Failed to find \"name\" in manifest response"); @@ -434,7 +474,7 @@ Future<ManifestResponse> RegistryClientProcess::getManifest( } vector<FileSystemLayerInfo> fsLayerInfoList; - foreach(const JSON::Value& layer, fsLayers.get().values) { + foreach (const JSON::Value& layer, fsLayers.get().values) { const JSON::Object& layerInfoJSON = layer.as<JSON::Object>(); Result<JSON::String> blobSumInfo = layerInfoJSON.find<JSON::String>("blobSum"); @@ -456,10 +496,8 @@ Future<ManifestResponse> RegistryClientProcess::getManifest( return doHttpGet(manifestURL, None(), timeout, true, None()) .then([getManifestResponse] ( - const Future<Response>& httpResponseFuture - ) -> Future<ManifestResponse> { - Try<ManifestResponse> manifestResponse = - getManifestResponse(httpResponseFuture.get()); + const Response& response) -> Future<ManifestResponse> { + Try<ManifestResponse> manifestResponse = getManifestResponse(response); if (manifestResponse.isError()) { return Failure( @@ -507,30 +545,27 @@ Future<size_t> RegistryClientProcess::getBlob( "v2/" + path + "/blobs/" + digest.getOrElse(""); auto saveBlob = [filePath]( - const Response& httpResponse) -> Try<size_t> { - Try<Nothing> writeResult = - os::write(filePath, httpResponse.body); - + const Response& httpResponse) -> Future<size_t> { // TODO(jojy): Add verification step. // TODO(jojy): Add check for max size. - - if (writeResult.isError()) { - return Error(writeResult.error()); + size_t size = httpResponse.body.length(); + Try<int> fd = os::open( + filePath.value, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (fd.isError()) { + return Failure("Failed to open file '" + filePath.value + "': " + + fd.error()); } - return httpResponse.body.length(); + return process::io::write(fd.get(), httpResponse.body) + .then([size](const Future<Nothing>&) { return size; }) + .onAny([fd]() { os::close(fd.get()); } ); }; return doHttpGet(blobURL, None(), timeout, true, None()) - .then([saveBlob]( - const Future<Response>& httpResponseFuture) -> Future<size_t> { - Try<size_t> blobSaved = saveBlob(httpResponseFuture.get()); - if (blobSaved.isError()) { - return Failure("Failed to save blob: " + blobSaved.error()); - } - - return blobSaved.get(); - }); + .then([saveBlob](const Response& response) { return saveBlob(response); }); } } // namespace registry { http://git-wip-us.apache.org/repos/asf/mesos/blob/d7b7a53c/src/slave/containerizer/provisioners/docker/registry_client.hpp ---------------------------------------------------------------------- diff --git a/src/slave/containerizer/provisioners/docker/registry_client.hpp b/src/slave/containerizer/provisioners/docker/registry_client.hpp index 184ca0f..b5e2858 100644 --- a/src/slave/containerizer/provisioners/docker/registry_client.hpp +++ b/src/slave/containerizer/provisioners/docker/registry_client.hpp @@ -54,7 +54,7 @@ public: }; /** - * Encapsulates response of "GET Mannifest" request. + * Encapsulates response of "GET Manifest" request. * * Reference: https://docs.docker.com/registry/spec/api */ @@ -78,7 +78,7 @@ public: */ const Option<std::string> password; /** - * Account for fetching data from registry. + * Account for fetching data from registry. */ const Option<std::string> account; }; http://git-wip-us.apache.org/repos/asf/mesos/blob/d7b7a53c/src/tests/containerizer/docker_provisioner_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/containerizer/docker_provisioner_tests.cpp b/src/tests/containerizer/docker_provisioner_tests.cpp new file mode 100644 index 0000000..53f15e0 --- /dev/null +++ b/src/tests/containerizer/docker_provisioner_tests.cpp @@ -0,0 +1,683 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <stout/duration.hpp> + +#include <process/address.hpp> +#include <process/clock.hpp> +#include <process/future.hpp> +#include <process/gmock.hpp> +#include <process/owned.hpp> +#include <process/socket.hpp> +#include <process/subprocess.hpp> + +#include <process/ssl/gtest.hpp> + +#include "slave/containerizer/provisioners/docker/registry_client.hpp" +#include "slave/containerizer/provisioners/docker/token_manager.hpp" + +#include "tests/mesos.hpp" + +using std::map; +using std::string; +using std::vector; + +using namespace mesos::internal::slave::docker::registry; +using namespace process; + +using ManifestResponse = RegistryClient::ManifestResponse; + +namespace mesos { +namespace internal { +namespace tests { + + +/** + * Provides token operations and defaults. + */ +class TokenHelper { +protected: + const string hdrBase64 = base64::encode( + "{ \ + \"alg\":\"ES256\", \ + \"typ\":\"JWT\", \ + \"x5c\":[\"test\"] \ + }"); + + string getClaimsBase64() const + { + return base64::encode(claimsJsonString); + } + + string getTokenString() const + { + return hdrBase64 + "." + getClaimsBase64() + "." + signBase64; + } + + string getDefaultTokenString() + { + // Construct response and send(server side). + const double expirySecs = Clock::now().secs() + Days(365).secs(); + + claimsJsonString = + "{\"access\" \ + :[ \ + { \ + \"type\":\"repository\", \ + \"name\":\"library/busybox\", \ + \"actions\":[\"pull\"]}], \ + \"aud\":\"registry.docker.io\", \ + \"exp\":" + stringify(expirySecs) + ", \ + \"iat\":1438887168, \ + \"iss\":\"auth.docker.io\", \ + \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ + \"nbf\":1438887166, \ + \"sub\":\"\" \ + }"; + + return getTokenString(); + } + + const string signBase64 = base64::encode("{\"\"}"); + string claimsJsonString; +}; + + +/** + * Fixture for testing TokenManager component. + */ +class RegistryTokenTest : public TokenHelper, public ::testing::Test +{}; + + +// Tests JSON Web Token parsing for a valid token string. +TEST_F(RegistryTokenTest, ValidToken) +{ + const double expirySecs = Clock::now().secs() + Days(365).secs(); + + claimsJsonString = + "{\"access\" \ + :[ \ + { \ + \"type\":\"repository\", \ + \"name\":\"library/busybox\", \ + \"actions\":[\"pull\"]}], \ + \"aud\":\"registry.docker.io\", \ + \"exp\":" + stringify(expirySecs) + ", \ + \"iat\":1438887168, \ + \"iss\":\"auth.docker.io\", \ + \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ + \"nbf\":1438887166, \ + \"sub\":\"\" \ + }"; + + Try<Token> token = Token::create(getTokenString()); + + ASSERT_SOME(token); +} + + +// Tests JSON Web Token parsing for a token string with expiration date in the +// past. +TEST_F(RegistryTokenTest, ExpiredToken) +{ + const double expirySecs = Clock::now().secs() - Days(365).secs(); + + claimsJsonString = + "{\"access\" \ + :[ \ + { \ + \"type\":\"repository\", \ + \"name\":\"library/busybox\", \ + \"actions\":[\"pull\"]}], \ + \"aud\":\"registry.docker.io\", \ + \"exp\":" + stringify(expirySecs) + ", \ + \"iat\":1438887166, \ + \"iss\":\"auth.docker.io\", \ + \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ + \"nbf\":1438887166, \ + \"sub\":\"\" \ + }"; + + Try<Token> token = Token::create(getTokenString()); + + EXPECT_ERROR(token); +} + + +// Tests JSON Web Token parsing for a token string with no expiration date. +TEST_F(RegistryTokenTest, NoExpiration) +{ + claimsJsonString = + "{\"access\" \ + :[ \ + { \ + \"type\":\"repository\", \ + \"name\":\"library/busybox\", \ + \"actions\":[\"pull\"]}], \ + \"aud\":\"registry.docker.io\", \ + \"iat\":1438887166, \ + \"iss\":\"auth.docker.io\", \ + \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ + \"nbf\":1438887166, \ + \"sub\":\"\" \ + }"; + + const Try<Token> token = Token::create(getTokenString()); + + ASSERT_SOME(token); +} + + +// Tests JSON Web Token parsing for a token string with not-before date in the +// future. +TEST_F(RegistryTokenTest, NotBeforeInFuture) +{ + const double expirySecs = Clock::now().secs() + Days(365).secs(); + const double nbfSecs = Clock::now().secs() + Days(7).secs(); + + claimsJsonString = + "{\"access\" \ + :[ \ + { \ + \"type\":\"repository\", \ + \"name\":\"library/busybox\", \ + \"actions\":[\"pull\"]}], \ + \"aud\":\"registry.docker.io\", \ + \"exp\":" + stringify(expirySecs) + ", \ + \"iat\":1438887166, \ + \"iss\":\"auth.docker.io\", \ + \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ + \"nbf\":" + stringify(nbfSecs) + ", \ + \"sub\":\"\" \ + }"; + + const Try<Token> token = Token::create(getTokenString()); + + ASSERT_SOME(token); + ASSERT_EQ(token.get().isValid(), false); +} + + +#ifdef USE_SSL_SOCKET + +// Test suite for docker registry tests. +class RegistryClientTest : public virtual SSLTest, public TokenHelper +{ +protected: + RegistryClientTest() {} + + static void SetUpTestCase() + { + SSLTest::SetUpTestCase(); + + if (os::mkdir(RegistryClientTest::OUTPUT_DIR).isError()) { + SSLTest::cleanup_directories(); + ABORT("Could not create temporary directory: " + + RegistryClientTest::OUTPUT_DIR); + } + } + + static void TearDownTestCase() + { + SSLTest::TearDownTestCase(); + + os::rmdir(RegistryClientTest::OUTPUT_DIR); + } + + static const string OUTPUT_DIR; +}; + +const string RegistryClientTest::OUTPUT_DIR = "output_dir"; + +// Tests TokenManager for a simple token request. +TEST_F(RegistryClientTest, SimpleGetToken) +{ + Try<Socket> server = setup_server({ + {"SSL_ENABLED", "true"}, + {"SSL_KEY_FILE", key_path().value}, + {"SSL_CERT_FILE", certificate_path().value}}); + + ASSERT_SOME(server); + ASSERT_SOME(server.get().address()); + ASSERT_SOME(server.get().address().get().hostname()); + + Future<Socket> socket = server.get().accept(); + + // Create URL from server hostname and port. + const http::URL url( + "https", + server.get().address().get().hostname().get(), + server.get().address().get().port); + + Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url); + ASSERT_SOME(tokenMgr); + + Future<Token> token = + tokenMgr.get()->getToken( + "registry.docker.io", + "repository:library/busybox:pull", + None()); + + AWAIT_ASSERT_READY(socket); + + // Construct response and send(server side). + const double expirySecs = Clock::now().secs() + Days(365).secs(); + + claimsJsonString = + "{\"access\" \ + :[ \ + { \ + \"type\":\"repository\", \ + \"name\":\"library/busybox\", \ + \"actions\":[\"pull\"]}], \ + \"aud\":\"registry.docker.io\", \ + \"exp\":" + stringify(expirySecs) + ", \ + \"iat\":1438887168, \ + \"iss\":\"auth.docker.io\", \ + \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ + \"nbf\":1438887166, \ + \"sub\":\"\" \ + }"; + + const string tokenString(getTokenString()); + const string tokenResponse = "{\"token\":\"" + tokenString + "\"}"; + + const string buffer = + string("HTTP/1.1 200 OK\r\n") + + "Content-Length : " + + stringify(tokenResponse.length()) + "\r\n" + + "\r\n" + + tokenResponse; + + AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer)); + + AWAIT_ASSERT_READY(token); + ASSERT_EQ(token.get().raw, tokenString); +} + + +// Tests TokenManager for bad token response from server. +TEST_F(RegistryClientTest, BadTokenResponse) +{ + Try<Socket> server = setup_server({ + {"SSL_ENABLED", "true"}, + {"SSL_KEY_FILE", key_path().value}, + {"SSL_CERT_FILE", certificate_path().value}}); + + ASSERT_SOME(server); + ASSERT_SOME(server.get().address()); + ASSERT_SOME(server.get().address().get().hostname()); + + Future<Socket> socket = server.get().accept(); + + // Create URL from server hostname and port. + const http::URL url( + "https", + server.get().address().get().hostname().get(), + server.get().address().get().port); + + Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url); + ASSERT_SOME(tokenMgr); + + Future<Token> token = + tokenMgr.get()->getToken( + "registry.docker.io", + "repository:library/busybox:pull", + None()); + + AWAIT_ASSERT_READY(socket); + + const string tokenString("bad token"); + const string tokenResponse = "{\"token\":\"" + tokenString + "\"}"; + + const string buffer = + string("HTTP/1.1 200 OK\r\n") + + "Content-Length : " + + stringify(tokenResponse.length()) + "\r\n" + + "\r\n" + + tokenResponse; + + AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer)); + + AWAIT_FAILED(token); +} + + +// Tests TokenManager for request to invalid server. +TEST_F(RegistryClientTest, BadTokenServerAddress) +{ + // Create an invalid URL with current time. + const http::URL url("https", stringify(Clock::now().secs()), 0); + + Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url); + ASSERT_SOME(tokenMgr); + + Future<Token> token = + tokenMgr.get()->getToken( + "registry.docker.io", + "repository:library/busybox:pull", + None()); + + AWAIT_FAILED(token); +} + + +// Tests docker registry's getManifest API. +TEST_F(RegistryClientTest, SimpleGetManifest) +{ + Try<Socket> server = setup_server({ + {"SSL_ENABLED", "true"}, + {"SSL_KEY_FILE", key_path().value}, + {"SSL_CERT_FILE", certificate_path().value}}); + + ASSERT_SOME(server); + ASSERT_SOME(server.get().address()); + ASSERT_SOME(server.get().address().get().hostname()); + + Future<Socket> socket = server.get().accept(); + + const http::URL url( + "https", + server.get().address().get().hostname().get(), + server.get().address().get().port); + + Try<Owned<RegistryClient>> registryClient = + RegistryClient::create(url, url, None()); + + ASSERT_SOME(registryClient); + + Future<ManifestResponse> manifestResponseFuture = + registryClient.get()->getManifest("library/busybox", "latest", None()); + + const string unauthResponseHeaders = "Www-Authenticate: Bearer" + " realm=\"https://auth.docker.io/token\"," + "service=" + stringify(server.get().address().get()) + "," + "scope=\"repository:library/busybox:pull\""; + + const string unauthHttpResponse = + string("HTTP/1.1 401 Unauthorized\r\n") + + unauthResponseHeaders + "\r\n" + + "\r\n"; + + AWAIT_ASSERT_READY(socket); + + // Send 401 Unauthorized response for a manifest request. + Future<string> manifestHttpRequestFuture = Socket(socket.get()).recv(); + AWAIT_ASSERT_READY(manifestHttpRequestFuture); + AWAIT_ASSERT_READY(Socket(socket.get()).send(unauthHttpResponse)); + + // Token response. + socket = server.get().accept(); + AWAIT_ASSERT_READY(socket); + + Future<string> tokenRequestFuture = Socket(socket.get()).recv(); + AWAIT_ASSERT_READY(tokenRequestFuture); + + const string tokenResponse = + "{\"token\":\"" + getDefaultTokenString() + "\"}"; + + const string tokenHttpResponse = + string("HTTP/1.1 200 OK\r\n") + + "Content-Length : " + + stringify(tokenResponse.length()) + "\r\n" + + "\r\n" + + tokenResponse; + + AWAIT_ASSERT_READY(Socket(socket.get()).send(tokenHttpResponse)); + + // Manifest response. + socket = server.get().accept(); + AWAIT_ASSERT_READY(socket); + + manifestHttpRequestFuture = Socket(socket.get()).recv(); + AWAIT_ASSERT_READY(manifestHttpRequestFuture); + + const string manifestResponse = " \ + { \ + \"schemaVersion\": 1, \ + \"name\": \"library/busybox\", \ + \"tag\": \"latest\", \ + \"architecture\": \"amd64\", \ + \"fsLayers\": [ \ + { \ + \"blobSum\": \ + \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\" \ + }, \ + { \ + \"blobSum\": \ + \"sha256:1db09adb5ddd7f1a07b6d585a7db747a51c7bd17418d47e91f901bdf420abd66\" \ + }, \ + { \ + \"blobSum\": \ + \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\" \ + } \ + ], \ + \"signatures\": [ \ + { \ + \"header\": { \ + \"jwk\": { \ + \"crv\": \"P-256\", \ + \"kid\": \ + \"OOI5:SI3T:LC7D:O7DX:FY6S:IAYW:WDRN:VQEM:BCFL:OIST:Q3LO:GTQQ\", \ + \"kty\": \"EC\", \ + \"x\": \"J2N5ePGhlblMI2cdsR6NrAG_xbNC_X7s1HRtk5GXvzM\", \ + \"y\": \"Idr-tEBjnNnfq6_71aeXBi3Z9ah_rrE209l4wiaohk0\" \ + }, \ + \"alg\": \"ES256\" \ + }, \ + \"signature\": \ +\"65vq57TakC_yperuhfefF4uvTbKO2L45gYGDs5bIEgOEarAs7_" +"4dbEV5u-W7uR8gF6EDKfowUCmTq3a5vEOJ3w\", \ + \"protected\": \ + \"eyJmb3JtYXRMZW5ndGgiOjUwNTgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS" + "0wOC0xMVQwMzo0Mjo1OVoifQ\" \ + } \ + ] \ + }"; + + const string manifestHttpResponse = + string("HTTP/1.1 200 OK\r\n") + + "Content-Length : " + + stringify(manifestResponse.length()) + "\r\n" + + "Docker-Content-Digest: " + "sha256:df9e13f36d2d5b30c16bfbf2a6110c45ebed0bfa1ea42d357651bc6c736d5322" + + "\r\n" + + "\r\n" + + manifestResponse; + + AWAIT_ASSERT_READY(Socket(socket.get()).send(manifestHttpResponse)); + + AWAIT_ASSERT_READY(manifestResponseFuture); +} + + +// Tests docker registry's getBlob API. +TEST_F(RegistryClientTest, SimpleGetBlob) +{ + Try<Socket> server = setup_server({ + {"SSL_ENABLED", "true"}, + {"SSL_KEY_FILE", key_path().value}, + {"SSL_CERT_FILE", certificate_path().value}}); + + ASSERT_SOME(server); + ASSERT_SOME(server.get().address()); + ASSERT_SOME(server.get().address().get().hostname()); + + Future<Socket> socket = server.get().accept(); + + const http::URL url( + "https", + server.get().address().get().hostname().get(), + server.get().address().get().port); + + Try<Owned<RegistryClient>> registryClient = + RegistryClient::create(url, url, None()); + + ASSERT_SOME(registryClient); + + const Path blobPath(RegistryClientTest::OUTPUT_DIR + "/blob"); + + Future<size_t> resultFuture = + registryClient.get()->getBlob( + "/blob", + "digest", + blobPath, + None(), + None()); + + const string unauthResponseHeaders = "WWW-Authenticate: Bearer" + " realm=\"https://auth.docker.io/token\"," + "service=" + stringify(server.get().address().get()) + "," + "scope=\"repository:library/busybox:pull\""; + + const string unauthHttpResponse = + string("HTTP/1.1 401 Unauthorized\r\n") + + unauthResponseHeaders + "\r\n" + + "\r\n"; + + AWAIT_ASSERT_READY(socket); + + // Send 401 Unauthorized response. + Future<string> blobHttpRequestFuture = Socket(socket.get()).recv(); + AWAIT_ASSERT_READY(blobHttpRequestFuture); + AWAIT_ASSERT_READY(Socket(socket.get()).send(unauthHttpResponse)); + + // Send token response. + socket = server.get().accept(); + AWAIT_ASSERT_READY(socket); + + Future<string> tokenRequestFuture = Socket(socket.get()).recv(); + AWAIT_ASSERT_READY(tokenRequestFuture); + + const string tokenResponse = + "{\"token\":\"" + getDefaultTokenString() + "\"}"; + + const string tokenHttpResponse = + string("HTTP/1.1 200 OK\r\n") + + "Content-Length : " + + stringify(tokenResponse.length()) + "\r\n" + + "\r\n" + + tokenResponse; + + AWAIT_ASSERT_READY(Socket(socket.get()).send(tokenHttpResponse)); + + // Send redirect. + socket = server.get().accept(); + AWAIT_ASSERT_READY(socket); + + blobHttpRequestFuture = Socket(socket.get()).recv(); + AWAIT_ASSERT_READY(blobHttpRequestFuture); + + const string redirectHttpResponse = + string("HTTP/1.1 307 Temporary Redirect\r\n") + + "Location: https://" + + stringify(server.get().address().get()) + "\r\n" + + "\r\n"; + + AWAIT_ASSERT_READY(Socket(socket.get()).send(redirectHttpResponse)); + + // Finally send blob response. + socket = server.get().accept(); + AWAIT_ASSERT_READY(socket); + + blobHttpRequestFuture = Socket(socket.get()).recv(); + AWAIT_ASSERT_READY(blobHttpRequestFuture); + + const string blobResponse = stringify(Clock::now()); + + const string blobHttpResponse = + string("HTTP/1.1 200 OK\r\n") + + "Content-Length : " + + stringify(blobResponse.length()) + "\r\n" + + "\r\n" + + blobResponse; + + AWAIT_ASSERT_READY(Socket(socket.get()).send(blobHttpResponse)); + + AWAIT_ASSERT_READY(resultFuture); + + Try<string> blob = os::read(blobPath); + ASSERT_SOME(blob); + ASSERT_EQ(blob.get(), blobResponse); +} + + +TEST_F(RegistryClientTest, BadRequest) +{ + Try<Socket> server = setup_server({ + {"SSL_ENABLED", "true"}, + {"SSL_KEY_FILE", key_path().value}, + {"SSL_CERT_FILE", certificate_path().value}}); + + ASSERT_SOME(server); + ASSERT_SOME(server.get().address()); + ASSERT_SOME(server.get().address().get().hostname()); + + Future<Socket> socket = server.get().accept(); + + const http::URL url( + "https", + server.get().address().get().hostname().get(), + server.get().address().get().port); + + Try<Owned<RegistryClient>> registryClient = + RegistryClient::create(url, url, None()); + + ASSERT_SOME(registryClient); + + const Path blobPath(RegistryClientTest::OUTPUT_DIR + "/blob"); + + Future<size_t> resultFuture = + registryClient.get()->getBlob( + "/blob", + "digest", + blobPath, + None(), + None()); + + const string badRequestResponse = + "{\"errors\": [{\"message\": \"Error1\" }, {\"message\": \"Error2\"}]}"; + + const string badRequestHttpResponse = + string("HTTP/1.1 400 Bad Request\r\n") + + "Content-Length : " + stringify(badRequestResponse.length()) + "\r\n" + + "\r\n" + + badRequestResponse; + + AWAIT_ASSERT_READY(socket); + + // Send 400 Bad Request. + Future<string> blobHttpRequestFuture = Socket(socket.get()).recv(); + AWAIT_ASSERT_READY(blobHttpRequestFuture); + AWAIT_ASSERT_READY(Socket(socket.get()).send(badRequestHttpResponse)); + + AWAIT_FAILED(resultFuture); + + ASSERT_TRUE(strings::contains(resultFuture.failure(), "Error1")); + ASSERT_TRUE(strings::contains(resultFuture.failure(), "Error2")); +} + +#endif // USE_SSL_SOCKET + +} // namespace tests { +} // namespace internal { +} // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/d7b7a53c/src/tests/provisioners/docker_provisioner_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/provisioners/docker_provisioner_tests.cpp b/src/tests/provisioners/docker_provisioner_tests.cpp deleted file mode 100644 index 91ac343..0000000 --- a/src/tests/provisioners/docker_provisioner_tests.cpp +++ /dev/null @@ -1,627 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gmock/gmock.h> -#include <gtest/gtest.h> - -#include <stout/duration.hpp> - -#include <process/address.hpp> -#include <process/clock.hpp> -#include <process/future.hpp> -#include <process/gmock.hpp> -#include <process/owned.hpp> -#include <process/socket.hpp> -#include <process/subprocess.hpp> - -#include <process/ssl/gtest.hpp> - -#include "slave/containerizer/provisioners/docker/registry_client.hpp" -#include "slave/containerizer/provisioners/docker/token_manager.hpp" - -#include "tests/mesos.hpp" - -using std::map; -using std::string; -using std::vector; - -using namespace mesos::internal::slave::docker::registry; -using namespace process; - -using ManifestResponse = RegistryClient::ManifestResponse; - -namespace mesos { -namespace internal { -namespace tests { - - -/** - * Provides token operations and defaults. - */ -class TokenHelper { -protected: - const string hdrBase64 = base64::encode( - "{ \ - \"alg\":\"ES256\", \ - \"typ\":\"JWT\", \ - \"x5c\":[\"test\"] \ - }"); - - string getClaimsBase64() const - { - return base64::encode(claimsJsonString); - } - - string getTokenString() const - { - return hdrBase64 + "." + getClaimsBase64() + "." + signBase64; - } - - string getDefaultTokenString() - { - // Construct response and send(server side). - const double expirySecs = Clock::now().secs() + Days(365).secs(); - - claimsJsonString = - "{\"access\" \ - :[ \ - { \ - \"type\":\"repository\", \ - \"name\":\"library/busybox\", \ - \"actions\":[\"pull\"]}], \ - \"aud\":\"registry.docker.io\", \ - \"exp\":" + stringify(expirySecs) + ", \ - \"iat\":1438887168, \ - \"iss\":\"auth.docker.io\", \ - \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ - \"nbf\":1438887166, \ - \"sub\":\"\" \ - }"; - - return getTokenString(); - } - - const string signBase64 = base64::encode("{\"\"}"); - string claimsJsonString; -}; - - -/** - * Fixture for testing TokenManager component. - */ -class RegistryTokenTest : public TokenHelper, public ::testing::Test -{}; - - -// Tests JSON Web Token parsing for a valid token string. -TEST_F(RegistryTokenTest, ValidToken) -{ - const double expirySecs = Clock::now().secs() + Days(365).secs(); - - claimsJsonString = - "{\"access\" \ - :[ \ - { \ - \"type\":\"repository\", \ - \"name\":\"library/busybox\", \ - \"actions\":[\"pull\"]}], \ - \"aud\":\"registry.docker.io\", \ - \"exp\":" + stringify(expirySecs) + ", \ - \"iat\":1438887168, \ - \"iss\":\"auth.docker.io\", \ - \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ - \"nbf\":1438887166, \ - \"sub\":\"\" \ - }"; - - Try<Token> token = Token::create(getTokenString()); - - ASSERT_SOME(token); -} - - -// Tests JSON Web Token parsing for a token string with expiration date in the -// past. -TEST_F(RegistryTokenTest, ExpiredToken) -{ - const double expirySecs = Clock::now().secs() - Days(365).secs(); - - claimsJsonString = - "{\"access\" \ - :[ \ - { \ - \"type\":\"repository\", \ - \"name\":\"library/busybox\", \ - \"actions\":[\"pull\"]}], \ - \"aud\":\"registry.docker.io\", \ - \"exp\":" + stringify(expirySecs) + ", \ - \"iat\":1438887166, \ - \"iss\":\"auth.docker.io\", \ - \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ - \"nbf\":1438887166, \ - \"sub\":\"\" \ - }"; - - Try<Token> token = Token::create(getTokenString()); - - EXPECT_ERROR(token); -} - - -// Tests JSON Web Token parsing for a token string with no expiration date. -TEST_F(RegistryTokenTest, NoExpiration) -{ - claimsJsonString = - "{\"access\" \ - :[ \ - { \ - \"type\":\"repository\", \ - \"name\":\"library/busybox\", \ - \"actions\":[\"pull\"]}], \ - \"aud\":\"registry.docker.io\", \ - \"iat\":1438887166, \ - \"iss\":\"auth.docker.io\", \ - \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ - \"nbf\":1438887166, \ - \"sub\":\"\" \ - }"; - - const Try<Token> token = Token::create(getTokenString()); - - ASSERT_SOME(token); -} - - -// Tests JSON Web Token parsing for a token string with not-before date in the -// future. -TEST_F(RegistryTokenTest, NotBeforeInFuture) -{ - const double expirySecs = Clock::now().secs() + Days(365).secs(); - const double nbfSecs = Clock::now().secs() + Days(7).secs(); - - claimsJsonString = - "{\"access\" \ - :[ \ - { \ - \"type\":\"repository\", \ - \"name\":\"library/busybox\", \ - \"actions\":[\"pull\"]}], \ - \"aud\":\"registry.docker.io\", \ - \"exp\":" + stringify(expirySecs) + ", \ - \"iat\":1438887166, \ - \"iss\":\"auth.docker.io\", \ - \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ - \"nbf\":" + stringify(nbfSecs) + ", \ - \"sub\":\"\" \ - }"; - - const Try<Token> token = Token::create(getTokenString()); - - ASSERT_SOME(token); - ASSERT_EQ(token.get().isValid(), false); -} - - -#ifdef USE_SSL_SOCKET - -// Test suite for docker registry tests. -class RegistryClientTest : public virtual SSLTest, public TokenHelper -{ -protected: - RegistryClientTest() {} - - static void SetUpTestCase() - { - SSLTest::SetUpTestCase(); - - if (os::mkdir(RegistryClientTest::OUTPUT_DIR).isError()) { - SSLTest::cleanup_directories(); - ABORT("Could not create temporary directory: " + - RegistryClientTest::OUTPUT_DIR); - } - } - - static void TearDownTestCase() - { - SSLTest::TearDownTestCase(); - - os::rmdir(RegistryClientTest::OUTPUT_DIR); - } - - static const string OUTPUT_DIR; -}; - -const string RegistryClientTest::OUTPUT_DIR = "output_dir"; - -// Tests TokenManager for a simple token request. -TEST_F(RegistryClientTest, SimpleGetToken) -{ - Try<Socket> server = setup_server({ - {"SSL_ENABLED", "true"}, - {"SSL_KEY_FILE", key_path().value}, - {"SSL_CERT_FILE", certificate_path().value}}); - - ASSERT_SOME(server); - ASSERT_SOME(server.get().address()); - ASSERT_SOME(server.get().address().get().hostname()); - - Future<Socket> socket = server.get().accept(); - - // Create URL from server hostname and port. - const http::URL url( - "https", - server.get().address().get().hostname().get(), - server.get().address().get().port); - - Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url); - ASSERT_SOME(tokenMgr); - - Future<Token> token = - tokenMgr.get()->getToken( - "registry.docker.io", - "repository:library/busybox:pull", - None()); - - AWAIT_ASSERT_READY(socket); - - // Construct response and send(server side). - const double expirySecs = Clock::now().secs() + Days(365).secs(); - - claimsJsonString = - "{\"access\" \ - :[ \ - { \ - \"type\":\"repository\", \ - \"name\":\"library/busybox\", \ - \"actions\":[\"pull\"]}], \ - \"aud\":\"registry.docker.io\", \ - \"exp\":" + stringify(expirySecs) + ", \ - \"iat\":1438887168, \ - \"iss\":\"auth.docker.io\", \ - \"jti\":\"l2PJDFkzwvoL7-TajJF7\", \ - \"nbf\":1438887166, \ - \"sub\":\"\" \ - }"; - - const string tokenString(getTokenString()); - const string tokenResponse = "{\"token\":\"" + tokenString + "\"}"; - - const string buffer = - string("HTTP/1.1 200 OK\r\n") + - "Content-Length : " + - stringify(tokenResponse.length()) + "\r\n" + - "\r\n" + - tokenResponse; - - AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer)); - - AWAIT_ASSERT_READY(token); - ASSERT_EQ(token.get().raw, tokenString); -} - - -// Tests TokenManager for bad token response from server. -TEST_F(RegistryClientTest, BadTokenResponse) -{ - Try<Socket> server = setup_server({ - {"SSL_ENABLED", "true"}, - {"SSL_KEY_FILE", key_path().value}, - {"SSL_CERT_FILE", certificate_path().value}}); - - ASSERT_SOME(server); - ASSERT_SOME(server.get().address()); - ASSERT_SOME(server.get().address().get().hostname()); - - Future<Socket> socket = server.get().accept(); - - // Create URL from server hostname and port. - const http::URL url( - "https", - server.get().address().get().hostname().get(), - server.get().address().get().port); - - Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url); - ASSERT_SOME(tokenMgr); - - Future<Token> token = - tokenMgr.get()->getToken( - "registry.docker.io", - "repository:library/busybox:pull", - None()); - - AWAIT_ASSERT_READY(socket); - - const string tokenString("bad token"); - const string tokenResponse = "{\"token\":\"" + tokenString + "\"}"; - - const string buffer = - string("HTTP/1.1 200 OK\r\n") + - "Content-Length : " + - stringify(tokenResponse.length()) + "\r\n" + - "\r\n" + - tokenResponse; - - AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer)); - - AWAIT_FAILED(token); -} - - -// Tests TokenManager for request to invalid server. -TEST_F(RegistryClientTest, BadTokenServerAddress) -{ - // Create an invalid URL with current time. - const http::URL url("https", stringify(Clock::now().secs()), 0); - - Try<Owned<TokenManager>> tokenMgr = TokenManager::create(url); - ASSERT_SOME(tokenMgr); - - Future<Token> token = - tokenMgr.get()->getToken( - "registry.docker.io", - "repository:library/busybox:pull", - None()); - - AWAIT_FAILED(token); -} - - -// Tests docker registry's getManifest API. -TEST_F(RegistryClientTest, SimpleGetManifest) -{ - Try<Socket> server = setup_server({ - {"SSL_ENABLED", "true"}, - {"SSL_KEY_FILE", key_path().value}, - {"SSL_CERT_FILE", certificate_path().value}}); - - ASSERT_SOME(server); - ASSERT_SOME(server.get().address()); - ASSERT_SOME(server.get().address().get().hostname()); - - Future<Socket> socket = server.get().accept(); - - const http::URL url( - "https", - server.get().address().get().hostname().get(), - server.get().address().get().port); - - Try<Owned<RegistryClient>> registryClient = - RegistryClient::create(url, url, None()); - - ASSERT_SOME(registryClient); - - Future<ManifestResponse> manifestResponseFuture = - registryClient.get()->getManifest("library/busybox", "latest", None()); - - const string unauthResponseHeaders = "Www-Authenticate: Bearer" - " realm=\"https://auth.docker.io/token\"," - "service=" + stringify(server.get().address().get()) + "," - "scope=\"repository:library/busybox:pull\""; - - const string unauthHttpResponse = - string("HTTP/1.1 401 Unauthorized\r\n") + - unauthResponseHeaders + "\r\n" + - "\r\n"; - - AWAIT_ASSERT_READY(socket); - - // Send 401 Unauthorized response for a manifest request. - Future<string> manifestHttpRequestFuture = Socket(socket.get()).recv(); - AWAIT_ASSERT_READY(manifestHttpRequestFuture); - AWAIT_ASSERT_READY(Socket(socket.get()).send(unauthHttpResponse)); - - // Token response. - socket = server.get().accept(); - AWAIT_ASSERT_READY(socket); - - Future<string> tokenRequestFuture = Socket(socket.get()).recv(); - AWAIT_ASSERT_READY(tokenRequestFuture); - - const string tokenResponse = - "{\"token\":\"" + getDefaultTokenString() + "\"}"; - - const string tokenHttpResponse = - string("HTTP/1.1 200 OK\r\n") + - "Content-Length : " + - stringify(tokenResponse.length()) + "\r\n" + - "\r\n" + - tokenResponse; - - AWAIT_ASSERT_READY(Socket(socket.get()).send(tokenHttpResponse)); - - // Manifest response. - socket = server.get().accept(); - AWAIT_ASSERT_READY(socket); - - manifestHttpRequestFuture = Socket(socket.get()).recv(); - AWAIT_ASSERT_READY(manifestHttpRequestFuture); - - const string manifestResponse = " \ - { \ - \"schemaVersion\": 1, \ - \"name\": \"library/busybox\", \ - \"tag\": \"latest\", \ - \"architecture\": \"amd64\", \ - \"fsLayers\": [ \ - { \ - \"blobSum\": \ - \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\" \ - }, \ - { \ - \"blobSum\": \ - \"sha256:1db09adb5ddd7f1a07b6d585a7db747a51c7bd17418d47e91f901bdf420abd66\" \ - }, \ - { \ - \"blobSum\": \ - \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\" \ - } \ - ], \ - \"signatures\": [ \ - { \ - \"header\": { \ - \"jwk\": { \ - \"crv\": \"P-256\", \ - \"kid\": \ - \"OOI5:SI3T:LC7D:O7DX:FY6S:IAYW:WDRN:VQEM:BCFL:OIST:Q3LO:GTQQ\", \ - \"kty\": \"EC\", \ - \"x\": \"J2N5ePGhlblMI2cdsR6NrAG_xbNC_X7s1HRtk5GXvzM\", \ - \"y\": \"Idr-tEBjnNnfq6_71aeXBi3Z9ah_rrE209l4wiaohk0\" \ - }, \ - \"alg\": \"ES256\" \ - }, \ - \"signature\": \ -\"65vq57TakC_yperuhfefF4uvTbKO2L45gYGDs5bIEgOEarAs7_" -"4dbEV5u-W7uR8gF6EDKfowUCmTq3a5vEOJ3w\", \ - \"protected\": \ - \"eyJmb3JtYXRMZW5ndGgiOjUwNTgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS" - "0wOC0xMVQwMzo0Mjo1OVoifQ\" \ - } \ - ] \ - }"; - - const string manifestHttpResponse = - string("HTTP/1.1 200 OK\r\n") + - "Content-Length : " + - stringify(manifestResponse.length()) + "\r\n" + - "Docker-Content-Digest: " - "sha256:df9e13f36d2d5b30c16bfbf2a6110c45ebed0bfa1ea42d357651bc6c736d5322" - + "\r\n" + - "\r\n" + - manifestResponse; - - AWAIT_ASSERT_READY(Socket(socket.get()).send(manifestHttpResponse)); - - AWAIT_ASSERT_READY(manifestResponseFuture); -} - - -// Tests docker registry's getBlob API. -TEST_F(RegistryClientTest, SimpleGetBlob) -{ - Try<Socket> server = setup_server({ - {"SSL_ENABLED", "true"}, - {"SSL_KEY_FILE", key_path().value}, - {"SSL_CERT_FILE", certificate_path().value}}); - - ASSERT_SOME(server); - ASSERT_SOME(server.get().address()); - ASSERT_SOME(server.get().address().get().hostname()); - - Future<Socket> socket = server.get().accept(); - - const http::URL url( - "https", - server.get().address().get().hostname().get(), - server.get().address().get().port); - - Try<Owned<RegistryClient>> registryClient = - RegistryClient::create(url, url, None()); - - ASSERT_SOME(registryClient); - - const Path blobPath(RegistryClientTest::OUTPUT_DIR + "/blob"); - - Future<size_t> resultFuture = - registryClient.get()->getBlob( - "/blob", - "digest", - blobPath, - None(), - None()); - - const string unauthResponseHeaders = "WWW-Authenticate: Bearer" - " realm=\"https://auth.docker.io/token\"," - "service=" + stringify(server.get().address().get()) + "," - "scope=\"repository:library/busybox:pull\""; - - const string unauthHttpResponse = - string("HTTP/1.1 401 Unauthorized\r\n") + - unauthResponseHeaders + "\r\n" + - "\r\n"; - - AWAIT_ASSERT_READY(socket); - - // Send 401 Unauthorized response. - Future<string> blobHttpRequestFuture = Socket(socket.get()).recv(); - AWAIT_ASSERT_READY(blobHttpRequestFuture); - AWAIT_ASSERT_READY(Socket(socket.get()).send(unauthHttpResponse)); - - // Send token response. - socket = server.get().accept(); - AWAIT_ASSERT_READY(socket); - - Future<string> tokenRequestFuture = Socket(socket.get()).recv(); - AWAIT_ASSERT_READY(tokenRequestFuture); - - const string tokenResponse = - "{\"token\":\"" + getDefaultTokenString() + "\"}"; - - const string tokenHttpResponse = - string("HTTP/1.1 200 OK\r\n") + - "Content-Length : " + - stringify(tokenResponse.length()) + "\r\n" + - "\r\n" + - tokenResponse; - - AWAIT_ASSERT_READY(Socket(socket.get()).send(tokenHttpResponse)); - - // Send redirect. - socket = server.get().accept(); - AWAIT_ASSERT_READY(socket); - - blobHttpRequestFuture = Socket(socket.get()).recv(); - AWAIT_ASSERT_READY(blobHttpRequestFuture); - - const string redirectHttpResponse = - string("HTTP/1.1 307 Temporary Redirect\r\n") + - "Location: https://" + - stringify(server.get().address().get()) + "\r\n" + - "\r\n"; - - AWAIT_ASSERT_READY(Socket(socket.get()).send(redirectHttpResponse)); - - // Finally send blob response. - socket = server.get().accept(); - AWAIT_ASSERT_READY(socket); - - blobHttpRequestFuture = Socket(socket.get()).recv(); - AWAIT_ASSERT_READY(blobHttpRequestFuture); - - const string blobResponse = stringify(Clock::now()); - - const string blobHttpResponse = - string("HTTP/1.1 200 OK\r\n") + - "Content-Length : " + - stringify(blobResponse.length()) + "\r\n" + - "\r\n" + - blobResponse; - - AWAIT_ASSERT_READY(Socket(socket.get()).send(blobHttpResponse)); - - AWAIT_ASSERT_READY(resultFuture); - - Try<string> blob = os::read(blobPath); - ASSERT_SOME(blob); - ASSERT_EQ(blob.get(), blobResponse); -} - -#endif // USE_SSL_SOCKET - -} // namespace tests { -} // namespace internal { -} // namespace mesos {
