Integrated CRAM-MD5 Authenticator module. Enables selecting a module based authenticator via the new --authenticators flag for mesos master.
Additionally, all "> >" have been fixed towards ">>" in master.hpp and master.cpp. Review: https://reviews.apache.org/r/26859 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/9ca757fc Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/9ca757fc Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/9ca757fc Branch: refs/heads/master Commit: 9ca757fc5abf24d9eaa371abcdab2a4ad5449cc3 Parents: 880ea7d Author: Till Toenshoff <[email protected]> Authored: Mon Nov 3 14:55:01 2014 -0800 Committer: Adam B <[email protected]> Committed: Mon Nov 3 14:55:28 2014 -0800 ---------------------------------------------------------------------- src/master/constants.cpp | 2 + src/master/constants.hpp | 3 + src/master/flags.hpp | 8 ++ src/master/master.cpp | 90 ++++++++++++++------ src/master/master.hpp | 47 ++++++----- src/tests/cram_md5_authentication_tests.cpp | 100 +++++++++++++---------- 6 files changed, 155 insertions(+), 95 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/9ca757fc/src/master/constants.cpp ---------------------------------------------------------------------- diff --git a/src/master/constants.cpp b/src/master/constants.cpp index 3ebd246..9ee17e9 100644 --- a/src/master/constants.cpp +++ b/src/master/constants.cpp @@ -42,6 +42,8 @@ const Duration WHITELIST_WATCH_INTERVAL = Seconds(5); const uint32_t TASK_LIMIT = 100; const std::string MASTER_INFO_LABEL = "info"; const Duration ZOOKEEPER_SESSION_TIMEOUT = Seconds(10); +const std::string DEFAULT_AUTHENTICATOR = "crammd5"; + } // namespace master { } // namespace internal { } // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/9ca757fc/src/master/constants.hpp ---------------------------------------------------------------------- diff --git a/src/master/constants.hpp b/src/master/constants.hpp index a8298bc..c386eab 100644 --- a/src/master/constants.hpp +++ b/src/master/constants.hpp @@ -105,6 +105,9 @@ extern const std::string MASTER_INFO_LABEL; // TODO(vinod): Master detector/contender should use this timeout. extern const Duration ZOOKEEPER_SESSION_TIMEOUT; +// Name of the default, CRAM-MD5 authenticator. +extern const std::string DEFAULT_AUTHENTICATOR; + } // namespace master { } // namespace internal { } // namespace mesos { http://git-wip-us.apache.org/repos/asf/mesos/blob/9ca757fc/src/master/flags.hpp ---------------------------------------------------------------------- diff --git a/src/master/flags.hpp b/src/master/flags.hpp index b60306f..a144db0 100644 --- a/src/master/flags.hpp +++ b/src/master/flags.hpp @@ -347,6 +347,13 @@ public: " }\n" " ]\n" "}"); + + add(&Flags::authenticators, + "authenticators", + "Authenticator implementation to use when authenticating frameworks\n" + "and/or slaves. Use the default '" + DEFAULT_AUTHENTICATOR + "', or\n" + "load an alternate authenticator module using --modules.", + DEFAULT_AUTHENTICATOR); } bool version; @@ -377,6 +384,7 @@ public: Option<RateLimits> rate_limits; Option<Duration> offer_timeout; Option<Modules> modules; + std::string authenticators; #ifdef WITH_NETWORK_ISOLATOR Option<size_t> max_executors_per_slave; http://git-wip-us.apache.org/repos/asf/mesos/blob/9ca757fc/src/master/master.cpp ---------------------------------------------------------------------- diff --git a/src/master/master.cpp b/src/master/master.cpp index 39e27c5..d914786 100644 --- a/src/master/master.cpp +++ b/src/master/master.cpp @@ -25,6 +25,8 @@ #include <list> #include <sstream> +#include <mesos/module.hpp> + #include <process/check.hpp> #include <process/collect.hpp> #include <process/defer.hpp> @@ -51,6 +53,7 @@ #include <stout/utils.hpp> #include <stout/uuid.hpp> +#include "authentication/authenticator.hpp" #include "authentication/cram_md5/authenticator.hpp" #include "authorizer/authorizer.hpp" @@ -69,6 +72,9 @@ #include "master/flags.hpp" #include "master/master.hpp" +#include "module/authenticator.hpp" +#include "module/manager.hpp" + using std::list; using std::string; using std::vector; @@ -115,7 +121,7 @@ protected: void watch() { // Get the list of white listed slaves. - Option<hashset<string> > whitelist; + Option<hashset<string>> whitelist; if (path == "*") { // Accept all slaves. VLOG(1) << "No whitelist given. Advertising offers for all slaves"; } else { @@ -156,7 +162,7 @@ protected: private: const string path; Allocator* allocator; - Option<hashset<string> > lastWhitelist; + Option<hashset<string>> lastWhitelist; }; @@ -365,6 +371,23 @@ void Master::initialize() LOG(INFO) << "Master allowing unauthenticated slaves to register"; } + // Extract authenticator names and validate them. + authenticatorNames = strings::split(flags.authenticators, ","); + if (authenticatorNames.empty()) { + EXIT(1) << "No authenticator specified"; + } + if (authenticatorNames.size() > 1) { + EXIT(1) << "Multiple authenticators not supported"; + } + if (authenticatorNames[0] != DEFAULT_AUTHENTICATOR && + !modules::ModuleManager::contains<Authenticator>( + authenticatorNames[0])) { + EXIT(1) << "Authenticator '" << authenticatorNames[0] << "' not found. " + << "Check the spelling (compare to '" << DEFAULT_AUTHENTICATOR + << "'') or verify that the authenticator was loaded successfully " + << "(see --modules)"; + } + // Load credentials. if (flags.credentials.isSome()) { const string& path = @@ -379,13 +402,6 @@ void Master::initialize() } // Store credentials in master to use them in routes. credentials = _credentials.get(); - - // Load "registration" credentials into CRAM-MD5 Authenticator. - cram_md5::secrets::load(_credentials.get()); - - } else if (flags.authenticate_frameworks || flags.authenticate_slaves) { - EXIT(1) << "Authentication requires a credentials file" - << " (see --credentials flag)"; } if (authorizer.isSome()) { @@ -1195,7 +1211,7 @@ void Master::submitScheduler(const string& name) } -void Master::contended(const Future<Future<Nothing> >& candidacy) +void Master::contended(const Future<Future<Nothing>>& candidacy) { CHECK(!candidacy.isDiscarded()); @@ -1227,7 +1243,7 @@ void Master::lostCandidacy(const Future<Nothing>& lost) } -void Master::detected(const Future<Option<MasterInfo> >& _leader) +void Master::detected(const Future<Option<MasterInfo>>& _leader) { CHECK(!_leader.isDiscarded()); @@ -1272,7 +1288,7 @@ void Master::detected(const Future<Option<MasterInfo> >& _leader) // Helper to convert authorization result to Future<Option<Error> >. -static Future<Option<Error> > _authorize(const string& message, bool authorized) +static Future<Option<Error>> _authorize(const string& message, bool authorized) { if (authorized) { return None(); @@ -1282,7 +1298,7 @@ static Future<Option<Error> > _authorize(const string& message, bool authorized) } -Future<Option<Error> > Master::validate( +Future<Option<Error>> Master::validate( const FrameworkInfo& frameworkInfo, const UPID& from) { @@ -1374,7 +1390,7 @@ void Master::registerFramework( void Master::_registerFramework( const UPID& from, const FrameworkInfo& frameworkInfo, - const Future<Option<Error> >& validationError) + const Future<Option<Error>>& validationError) { CHECK_READY(validationError); if (validationError.get().isSome()) { @@ -1514,7 +1530,7 @@ void Master::_reregisterFramework( const UPID& from, const FrameworkInfo& frameworkInfo, bool failover, - const Future<Option<Error> >& validationError) + const Future<Option<Error>>& validationError) { CHECK_READY(validationError); if (validationError.get().isSome()) { @@ -2259,7 +2275,7 @@ void Master::launchTasks( if (offerIds.empty()) { error = Error("No offers specified"); } else { - list<Owned<OfferVisitor> > offerVisitors; + list<Owned<OfferVisitor>> offerVisitors; offerVisitors.push_back(Owned<OfferVisitor>(new ValidOfferChecker())); offerVisitors.push_back(Owned<OfferVisitor>(new FrameworkChecker())); offerVisitors.push_back(Owned<OfferVisitor>(new SlaveChecker())); @@ -2368,7 +2384,7 @@ Option<Error> Master::validateTask( // Create task visitors. // TODO(vinod): Create the visitors on the stack and make the visit // operation const. - list<Owned<TaskInfoVisitor> > taskVisitors; + list<Owned<TaskInfoVisitor>> taskVisitors; taskVisitors.push_back(Owned<TaskInfoVisitor>(new TaskIDChecker())); taskVisitors.push_back(Owned<TaskInfoVisitor>(new SlaveIDChecker())); taskVisitors.push_back(Owned<TaskInfoVisitor>(new UniqueTaskIDChecker())); @@ -3854,15 +3870,35 @@ void Master::authenticate(const UPID& from, const UPID& pid) // Create a promise to capture the entire "authenticating" // procedure. We'll set this _after_ we finish _authenticate. - Owned<Promise<Nothing> > promise(new Promise<Nothing>()); + Owned<Promise<Nothing>> promise(new Promise<Nothing>()); + + // Create and initialize the authenticator. + Authenticator* authenticator; + // TODO(tillt): Allow multiple authenticators to be loaded and enable + // the authenticatee to select the appropriate one. See MESOS-1939. + if (authenticatorNames[0] == DEFAULT_AUTHENTICATOR) { + LOG(INFO) << "Using default CRAM-MD5 authenticator"; + authenticator = new cram_md5::CRAMMD5Authenticator(); + } else { + Try<Authenticator*> module = + modules::ModuleManager::create<Authenticator>(authenticatorNames[0]); + if (module.isError()) { + EXIT(1) << "Could not create authenticator module '" + << authenticatorNames[0] << "': " << module.error(); + } + LOG(INFO) << "Using '" << authenticatorNames[0] << "' authenticator"; + authenticator = module.get(); + } + Owned<Authenticator> authenticator_ = Owned<Authenticator>(authenticator); - // Create the authenticator. - Owned<cram_md5::Authenticator> authenticator( - new cram_md5::Authenticator(from)); + Try<Nothing> initialize = authenticator_->initialize(from, credentials); + if (initialize.isError()) { + EXIT(1) << "Failed to initialize authenticator: " << initialize.error(); + } // Start authentication. - const Future<Option<string> >& future = authenticator->authenticate() - .onAny(defer(self(), &Self::_authenticate, pid, promise, lambda::_1)); + const Future<Option<string>>& future = authenticator_->authenticate() + .onAny(defer(self(), &Self::_authenticate, pid, promise, lambda::_1)); // Don't wait for authentication to happen for ever. delay(Seconds(5), @@ -3872,14 +3908,14 @@ void Master::authenticate(const UPID& from, const UPID& pid) // Save our state. authenticating[pid] = promise->future(); - authenticators.put(pid, authenticator); + authenticators.put(pid, authenticator_); } void Master::_authenticate( const UPID& pid, - const Owned<Promise<Nothing> >& promise, - const Future<Option<string> >& future) + const Owned<Promise<Nothing>>& promise, + const Future<Option<string>>& future) { if (!future.isReady() || future.get().isNone()) { const string& error = future.isReady() @@ -3903,7 +3939,7 @@ void Master::_authenticate( } -void Master::authenticationTimeout(Future<Option<string> > future) +void Master::authenticationTimeout(Future<Option<string>> future) { // Note that a 'discard' here is safe even if another // authenticator is in progress because this copy of the future http://git-wip-us.apache.org/repos/asf/mesos/blob/9ca757fc/src/master/master.hpp ---------------------------------------------------------------------- diff --git a/src/master/master.hpp b/src/master/master.hpp index 468b6e1..687f178 100644 --- a/src/master/master.hpp +++ b/src/master/master.hpp @@ -72,10 +72,7 @@ namespace registry { class Slaves; } -namespace cram_md5 { class Authenticator; -} - class Authorizer; namespace master { @@ -208,7 +205,7 @@ public: // Invoked when there is a newly elected leading master. // Made public for testing purposes. - void detected(const process::Future<Option<MasterInfo> >& pid); + void detected(const process::Future<Option<MasterInfo>>& pid); // Invoked when the contender has lost the candidacy. // Made public for testing purposes. @@ -281,16 +278,16 @@ protected: // 'future' is the future returned by the authenticator. void _authenticate( const process::UPID& pid, - const process::Owned<process::Promise<Nothing> >& promise, - const process::Future<Option<std::string> >& future); + const process::Owned<process::Promise<Nothing>>& promise, + const process::Future<Option<std::string>>& future); - void authenticationTimeout(process::Future<Option<std::string> > future); + void authenticationTimeout(process::Future<Option<std::string>> future); void fileAttached(const process::Future<Nothing>& result, const std::string& path); // Invoked when the contender has entered the contest. - void contended(const process::Future<process::Future<Nothing> >& candidacy); + void contended(const process::Future<process::Future<Nothing>>& candidacy); // Handles a known re-registering slave by reconciling the master's // view of the slave's tasks and executors. @@ -303,14 +300,14 @@ protected: void _registerFramework( const process::UPID& from, const FrameworkInfo& frameworkInfo, - const process::Future<Option<Error> >& validationError); + const process::Future<Option<Error>>& validationError); // 'reregisterFramework()' continuation. void _reregisterFramework( const process::UPID& from, const FrameworkInfo& frameworkInfo, bool failover, - const process::Future<Option<Error> >& validationError); + const process::Future<Option<Error>>& validationError); // Add a framework. void addFramework(Framework* framework); @@ -501,7 +498,7 @@ private: // Indicates when recovery is complete. Recovery begins once the // master is elected as a leader. - Option<process::Future<Nothing> > recovered; + Option<process::Future<Nothing>> recovered; struct Slaves { @@ -560,7 +557,7 @@ private: Frameworks() : completed(MAX_COMPLETED_FRAMEWORKS) {} hashmap<FrameworkID, Framework*> registered; - boost::circular_buffer<memory::shared_ptr<Framework> > completed; + boost::circular_buffer<memory::shared_ptr<Framework>> completed; // Principals of frameworks keyed by PID. // NOTE: Multiple PIDs can map to the same principal. The @@ -571,7 +568,7 @@ private: // 2) This map includes unauthenticated frameworks (when Master // allows them) if they have principals specified in // FrameworkInfo. - hashmap<process::UPID, Option<std::string> > principals; + hashmap<process::UPID, Option<std::string>> principals; } frameworks; hashmap<OfferID, Offer*> offers; @@ -579,13 +576,15 @@ private: hashmap<std::string, Role*> roles; + // Authenticator names as supplied via flags. + std::vector<std::string> authenticatorNames; + // Frameworks/slaves that are currently in the process of authentication. // 'authenticating' future for an authenticatee is ready when it is // authenticated. - hashmap<process::UPID, process::Future<Nothing> > authenticating; + hashmap<process::UPID, process::Future<Nothing>> authenticating; - hashmap<process::UPID, process::Owned<cram_md5::Authenticator> > - authenticators; + hashmap<process::UPID, process::Owned<Authenticator>> authenticators; // Principals of authenticated frameworks/slaves keyed by PID. hashmap<process::UPID, std::string> authenticated; @@ -678,7 +677,7 @@ private: // Per-framework-principal metrics keyed by the framework // principal. - hashmap<std::string, process::Owned<Frameworks> > frameworks; + hashmap<std::string, process::Owned<Frameworks>> frameworks; // Messages from schedulers. process::metrics::Counter messages_register_framework; @@ -789,7 +788,7 @@ private: // Returns None if the framework is valid. // Returns Error if the framework is invalid. // Returns Failure if authorization returns 'Failure'. - process::Future<Option<Error> > validate( + process::Future<Option<Error>> validate( const FrameworkInfo& frameworkInfo, const process::UPID& from); @@ -812,11 +811,11 @@ private: // BoundedRateLimiters keyed by the framework principal. // Like Metrics::Frameworks, all frameworks of the same principal // are throttled together at a common rate limit. - hashmap<std::string, Option<process::Owned<BoundedRateLimiter> > > limiters; + hashmap<std::string, Option<process::Owned<BoundedRateLimiter>>> limiters; // The default limiter is for frameworks not specified in // 'flags.rate_limits'. - Option<process::Owned<BoundedRateLimiter> > defaultLimiter; + Option<process::Owned<BoundedRateLimiter>> defaultLimiter; }; @@ -995,13 +994,13 @@ struct Slave bool active; // Executors running on this slave. - hashmap<FrameworkID, hashmap<ExecutorID, ExecutorInfo> > executors; + hashmap<FrameworkID, hashmap<ExecutorID, ExecutorInfo>> executors; // Tasks present on this slave. // TODO(bmahler): The task pointer ownership complexity arises from the fact // that we own the pointer here, but it's shared with the Framework struct. // We should find a way to eliminate this. - hashmap<FrameworkID, hashmap<TaskID, Task*> > tasks; + hashmap<FrameworkID, hashmap<TaskID, Task*>> tasks; // Tasks that were asked to kill by frameworks. // This is used for reconciliation when the slave re-registers. @@ -1181,11 +1180,11 @@ struct Framework // NOTE: We use a shared pointer for Task because clang doesn't like // Boost's implementation of circular_buffer with Task (Boost // attempts to do some memset's which are unsafe). - boost::circular_buffer<memory::shared_ptr<Task> > completedTasks; + boost::circular_buffer<memory::shared_ptr<Task>> completedTasks; hashset<Offer*> offers; // Active offers for framework. - hashmap<SlaveID, hashmap<ExecutorID, ExecutorInfo> > executors; + hashmap<SlaveID, hashmap<ExecutorID, ExecutorInfo>> executors; // TODO(bmahler): Summing set and ranges resources across slaves // does not yield meaningful totals. http://git-wip-us.apache.org/repos/asf/mesos/blob/9ca757fc/src/tests/cram_md5_authentication_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/cram_md5_authentication_tests.cpp b/src/tests/cram_md5_authentication_tests.cpp index d27c905..74ea2ad 100644 --- a/src/tests/cram_md5_authentication_tests.cpp +++ b/src/tests/cram_md5_authentication_tests.cpp @@ -47,19 +47,14 @@ namespace cram_md5 { TEST(CRAMMD5Authentication, success) { - // Set up secrets. - map<string, string> secrets; - secrets["benh"] = "secret"; - cram_md5::secrets::load(secrets); - // Launch a dummy process (somebody to send the AuthenticateMessage). UPID pid = spawn(new ProcessBase(), true); - Credential credential; - credential.set_principal("benh"); - credential.set_secret("secret"); + Credential credential1; + credential1.set_principal("benh"); + credential1.set_secret("secret"); - Authenticatee authenticatee(credential, UPID()); + Authenticatee authenticatee(credential1, UPID()); Future<Message> message = FUTURE_MESSAGE(Eq(AuthenticateMessage().GetTypeName()), _, _); @@ -68,9 +63,15 @@ TEST(CRAMMD5Authentication, success) AWAIT_READY(message); - Authenticator authenticator(message.get().from); + Credentials credentials; + Credential* credential2 = credentials.add_credentials(); + credential2->set_principal(credential1.principal()); + credential2->set_secret(credential1.secret()); + + CRAMMD5Authenticator authenticator; + EXPECT_SOME(authenticator.initialize(message.get().from, credentials)); - Future<Option<string> > principal = authenticator.authenticate(); + Future<Option<string>> principal = authenticator.authenticate(); AWAIT_EQ(true, client); AWAIT_READY(principal); @@ -83,19 +84,14 @@ TEST(CRAMMD5Authentication, success) // Bad password should return an authentication failure. TEST(CRAMMD5Authentication, failed1) { - // Set up secrets. - map<string, string> secrets; - secrets["benh"] = "secret1"; - cram_md5::secrets::load(secrets); - // Launch a dummy process (somebody to send the AuthenticateMessage). UPID pid = spawn(new ProcessBase(), true); - Credential credential; - credential.set_principal("benh"); - credential.set_secret("secret"); + Credential credential1; + credential1.set_principal("benh"); + credential1.set_secret("secret"); - Authenticatee authenticatee(credential, UPID()); + Authenticatee authenticatee(credential1, UPID()); Future<Message> message = FUTURE_MESSAGE(Eq(AuthenticateMessage().GetTypeName()), _, _); @@ -104,9 +100,15 @@ TEST(CRAMMD5Authentication, failed1) AWAIT_READY(message); - Authenticator authenticator(message.get().from); + Credentials credentials; + Credential* credential2 = credentials.add_credentials(); + credential2->set_principal(credential1.principal()); + credential2->set_secret("secret2"); - Future<Option<string> > server = authenticator.authenticate(); + CRAMMD5Authenticator authenticator; + EXPECT_SOME(authenticator.initialize(message.get().from, credentials)); + + Future<Option<string>> server = authenticator.authenticate(); AWAIT_EQ(false, client); AWAIT_READY(server); @@ -119,19 +121,14 @@ TEST(CRAMMD5Authentication, failed1) // No user should return an authentication failure. TEST(CRAMMD5Authentication, failed2) { - // Set up secrets. - map<string, string> secrets; - secrets["vinod"] = "secret"; - cram_md5::secrets::load(secrets); - // Launch a dummy process (somebody to send the AuthenticateMessage). UPID pid = spawn(new ProcessBase(), true); - Credential credential; - credential.set_principal("benh"); - credential.set_secret("secret"); + Credential credential1; + credential1.set_principal("benh"); + credential1.set_secret("secret"); - Authenticatee authenticatee(credential, UPID()); + Authenticatee authenticatee(credential1, UPID()); Future<Message> message = FUTURE_MESSAGE(Eq(AuthenticateMessage().GetTypeName()), _, _); @@ -140,9 +137,15 @@ TEST(CRAMMD5Authentication, failed2) AWAIT_READY(message); - Authenticator authenticator(message.get().from); + Credentials credentials; + Credential* credential2 = credentials.add_credentials(); + credential2->set_principal("vinod"); + credential2->set_secret(credential1.secret()); + + CRAMMD5Authenticator authenticator; + EXPECT_SOME(authenticator.initialize(message.get().from, credentials)); - Future<Option<string> > server = authenticator.authenticate(); + Future<Option<string>> server = authenticator.authenticate(); AWAIT_EQ(false, client); AWAIT_READY(server); @@ -157,19 +160,14 @@ TEST(CRAMMD5Authentication, failed2) // destructed in the middle of authentication. TEST(CRAMMD5Authentication, AuthenticatorDestructionRace) { - // Set up secrets. - map<string, string> secrets; - secrets["benh"] = "secret"; - cram_md5::secrets::load(secrets); - // Launch a dummy process (somebody to send the AuthenticateMessage). UPID pid = spawn(new ProcessBase(), true); - Credential credential; - credential.set_principal("benh"); - credential.set_secret("secret"); + Credential credential1; + credential1.set_principal("benh"); + credential1.set_secret("secret"); - Authenticatee authenticatee(credential, UPID()); + Authenticatee authenticatee(credential1, UPID()); Future<Message> message = FUTURE_MESSAGE(Eq(AuthenticateMessage().GetTypeName()), _, _); @@ -178,14 +176,20 @@ TEST(CRAMMD5Authentication, AuthenticatorDestructionRace) AWAIT_READY(message); - Authenticator* authenticator = new Authenticator(message.get().from); + Credentials credentials; + Credential* credential2 = credentials.add_credentials(); + credential2->set_principal(credential1.principal()); + credential2->set_secret(credential1.secret()); + + CRAMMD5Authenticator* authenticator = new CRAMMD5Authenticator(); + EXPECT_SOME(authenticator->initialize(message.get().from, credentials)); // Drop the AuthenticationStepMessage from authenticator to keep // the authentication from getting completed. Future<AuthenticationStepMessage> authenticationStepMessage = DROP_PROTOBUF(AuthenticationStepMessage(), _, _); - Future<Option<string> > principal = authenticator->authenticate(); + Future<Option<string>> principal = authenticator->authenticate(); AWAIT_READY(authenticationStepMessage); @@ -204,6 +208,14 @@ TEST(CRAMMD5Authentication, AuthenticatorDestructionRace) terminate(pid); } + +// Missing credentials should fail the initializing. +TEST(CRAMMD5Authentication, AuthenticatorCredentialsMissing) +{ + CRAMMD5Authenticator authenticator; + EXPECT_ERROR(authenticator.initialize(UPID(), None())); +} + } // namespace cram_md5 { } // namespace internal { } // namespace mesos {
