Add ServiceAddress to utils

Summary: This diff adds `ServiceAddress` to utils. This is a class that encapsulates an address of a service that could be a traditional hostname & port pair as well as an SMC tier name. This eliminates the needs of the manually resolving network address for remote services.

Reviewed By: chadaustin

Differential Revision: D14845257

fbshipit-source-id: 9fe9847cca4bba0170be94b9c209247342708574
This commit is contained in:
Zeyi Fan 2019-04-12 11:17:47 -07:00 committed by Facebook Github Bot
parent 08b96e33b3
commit 13da025c6c
3 changed files with 257 additions and 0 deletions

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2019-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include "eden/fs/utils/ServiceAddress.h"
#include <folly/Random.h>
#include <folly/SocketAddress.h>
#include <folly/String.h>
#include <folly/logging/xlog.h>
#include <optional>
#include "eden/fs/eden-config.h"
#ifdef EDEN_HAVE_SERVICEROUTER
#include <servicerouter/client/cpp2/ServiceRouter.h>
#endif
namespace facebook {
namespace eden {
ServiceAddress::ServiceAddress(std::string name) : name_(std::move(name)){};
ServiceAddress::ServiceAddress(std::string hostname, uint16_t port)
: name_(std::make_pair(std::move(hostname), port)){};
std::optional<SocketAddressWithHostname>
ServiceAddress::getSocketAddressBlocking() {
if (std::holds_alternative<HostPortPair>(name_)) {
return addressFromHostname();
}
return addressFromSMCTier();
}
std::optional<SocketAddressWithHostname> ServiceAddress::addressFromHostname() {
auto hostPort = std::get<HostPortPair>(name_);
auto addr = folly::SocketAddress();
addr.setFromHostPort(hostPort.first, hostPort.second);
return std::make_pair(addr, hostPort.first);
}
std::optional<SocketAddressWithHostname> ServiceAddress::addressFromSMCTier(
std::shared_ptr<facebook::servicerouter::ServiceCacheIf> selector) {
#ifdef EDEN_HAVE_SERVICEROUTER
auto selection = selector->getSelection(std::get<std::string>(name_));
if (selection.hosts.empty()) {
return std::nullopt;
}
// TODO(zeyi, t42568801): better host selection algorithm
auto selected = folly::Random::rand32(selection.hosts.size());
const auto& host = selection.hosts[selected];
auto location = host->location();
return std::make_pair(
folly::SocketAddress(location.getIpAddress(), location.getPort()),
location.getHostname());
#else
return std::nullopt;
#endif
}
std::optional<SocketAddressWithHostname> ServiceAddress::addressFromSMCTier() {
#ifdef EDEN_HAVE_SERVICEROUTER
auto& factory = servicerouter::cpp2::getClientFactory();
auto selector = factory.getSelector();
return addressFromSMCTier(selector);
#else
return std::nullopt;
#endif
}
} // namespace eden
} // namespace facebook

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2019-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#pragma once
#include <chrono>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
namespace folly {
class SocketAddress;
} // namespace folly
namespace facebook {
namespace servicerouter {
class ServiceCacheIf;
}
namespace eden {
// `folly::SocketAddress` represents the IP and port of the server, and the
// `std::string` is the hostname the client should use.
using SocketAddressWithHostname = std::pair<folly::SocketAddress, std::string>;
using HostPortPair = std::pair<std::string, uint16_t>;
/// This class represents a remote service that can be identified with a
/// traditional hostname and port pair as well as a smc tier name. Users that
/// only need a socket address can use this class to avoid worrying about
/// underlying details.
class ServiceAddress {
public:
/// Constructs a `ServiceAddress` from SMC tier name
explicit ServiceAddress(std::string name);
/// Constructs a `ServiceAddress` from a pair of hostname and port;
ServiceAddress(std::string hostname, uint16_t port);
ServiceAddress(const ServiceAddress&) = default;
ServiceAddress& operator=(const ServiceAddress& other) = default;
ServiceAddress(ServiceAddress&&) = default;
ServiceAddress& operator=(ServiceAddress&& other) = default;
/// Synchronously gets the socket address and hostname of the service this
/// object represents.
///
/// When `ServiceAddress` is `Type::Hostname`:
///
/// Throws `std::invalid_argument` if the hostname string is invalid.
/// See `folly::SocketAddress::setFromHostPort` for details.
///
/// Throws `std::system_error` if the hostname is unabled to be resolved.
///
/// When `ServiceAddress` is `Type::SmcTier`:
///
/// Always returns `std::nullopt` when there is no ServiceRouter support.
///
/// Note: this function WILL block for performing DNS and SMC resolution.
std::optional<SocketAddressWithHostname> getSocketAddressBlocking();
/// Test method
std::optional<SocketAddressWithHostname> addressFromSMCTier(
std::shared_ptr<facebook::servicerouter::ServiceCacheIf> selector);
private:
std::optional<SocketAddressWithHostname> addressFromHostname();
std::optional<SocketAddressWithHostname> addressFromSMCTier();
std::variant<HostPortPair, std::string> name_;
};
} // namespace eden
} // namespace facebook

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include "eden/fs/utils/ServiceAddress.h"
#include <folly/SocketAddress.h>
#include <folly/logging/xlog.h>
#include <gtest/gtest.h>
#include "eden/fs/eden-config.h"
#ifdef EDEN_HAVE_SERVICEROUTER
#include <servicerouter/client/cpp2/ServiceRouter.h>
#endif
using namespace facebook::eden;
TEST(ServiceAddressTest, fromHostnameAndPort) {
auto hostname = "::1";
auto svc = ServiceAddress{hostname, 1234};
auto result = svc.getSocketAddressBlocking();
EXPECT_EQ(result->first.getAddressStr(), "::1");
EXPECT_EQ(result->first.getPort(), 1234);
EXPECT_EQ(result->second, "::1");
}
TEST(ServiceAddressTest, nonexistentHostname) {
auto hostname = "this-hostname-should-never-exist";
auto svc = ServiceAddress{hostname, 1234};
EXPECT_THROW(svc.getSocketAddressBlocking(), std::system_error);
}
#ifdef EDEN_HAVE_SERVICEROUTER
namespace {
using namespace facebook::servicerouter;
class MockServiceCacheIf : public ServiceCacheIf {
public:
virtual Selection getSelection(
const std::string& serviceName,
const ServiceOptions& /* options */,
const ConnConfigs& /* overrides */) override {
Selection selection;
if (serviceName == "mononoke-apiserver") {
auto location = std::make_shared<HostInfoLocation>("::1", 1234);
location->setHostname("some-hostname");
selection.hosts.push_back(std::make_shared<HostInfo>(
std::make_unique<HostInfoProperties>(), std::move(location)));
}
return selection;
}
virtual void getSelectionAsync(
const std::string& /* serviceName */,
DebugContext&& /* dbgCtx */,
SelectionCacheCallback&& /* callback */,
folly::EventBase* /* eventBase */,
ServiceOptions&& /* options */,
ConnConfigs&& /* overrides */) override {}
bool invalidateSelection(
const string& /* serviceName */,
const Config& /* cfg */) override {
return true;
}
};
} // namespace
TEST(ServiceAddressTest, fromSMCTier) {
auto tier = "mononoke-apiserver";
auto svc = ServiceAddress{tier};
auto result = svc.addressFromSMCTier(std::make_shared<MockServiceCacheIf>());
EXPECT_EQ(result->first.getAddressStr(), "::1");
EXPECT_EQ(result->first.getPort(), 1234);
EXPECT_EQ(result->second, "some-hostname");
}
TEST(ServiceAddressTest, failFromSMCTier) {
auto tier = "nonexistent-tier";
auto svc = ServiceAddress{tier};
auto result = svc.addressFromSMCTier(std::make_shared<MockServiceCacheIf>());
EXPECT_EQ(result, std::nullopt);
}
#endif