238 lines
7.2 KiB
Rust
238 lines
7.2 KiB
Rust
use std::{error, fmt};
|
|
use std::net::SocketAddr;
|
|
use std::time::Duration;
|
|
use std::sync::Arc;
|
|
|
|
use futures::{Async, Poll};
|
|
use http;
|
|
use rand;
|
|
use tower;
|
|
use tower_balance::{self, choose, load, Balance};
|
|
use tower_buffer::Buffer;
|
|
use tower_discover::{Change, Discover};
|
|
use tower_in_flight_limit::InFlightLimit;
|
|
use tower_h2;
|
|
use conduit_proxy_router::{Reuse, Recognize};
|
|
|
|
use bind::{self, Bind, Protocol};
|
|
use control::{self, discovery};
|
|
use control::discovery::Bind as BindTrait;
|
|
use ctx;
|
|
use fully_qualified_authority::{FullyQualifiedAuthority, NamedAddress};
|
|
use timeout::Timeout;
|
|
|
|
type BindProtocol<B> = bind::BindProtocol<Arc<ctx::Proxy>, B>;
|
|
|
|
pub struct Outbound<B> {
|
|
bind: Bind<Arc<ctx::Proxy>, B>,
|
|
discovery: control::Control,
|
|
default_namespace: Option<String>,
|
|
default_zone: Option<String>,
|
|
bind_timeout: Duration,
|
|
}
|
|
|
|
const MAX_IN_FLIGHT: usize = 10_000;
|
|
|
|
// ===== impl Outbound =====
|
|
|
|
impl<B> Outbound<B> {
|
|
pub fn new(bind: Bind<Arc<ctx::Proxy>, B>,
|
|
discovery: control::Control,
|
|
default_namespace: Option<String>,
|
|
default_zone: Option<String>,
|
|
bind_timeout: Duration,)
|
|
-> Outbound<B> {
|
|
Self {
|
|
bind,
|
|
discovery,
|
|
default_namespace,
|
|
default_zone,
|
|
bind_timeout,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub enum Destination {
|
|
LocalSvc(FullyQualifiedAuthority),
|
|
External(SocketAddr),
|
|
}
|
|
|
|
impl From<FullyQualifiedAuthority> for Destination {
|
|
#[inline]
|
|
fn from(authority: FullyQualifiedAuthority) -> Self {
|
|
Destination::LocalSvc(authority)
|
|
}
|
|
}
|
|
|
|
impl From<SocketAddr> for Destination {
|
|
#[inline]
|
|
fn from(addr: SocketAddr) -> Self {
|
|
Destination::External(addr)
|
|
}
|
|
}
|
|
|
|
|
|
impl<B> Recognize for Outbound<B>
|
|
where
|
|
B: tower_h2::Body + 'static,
|
|
{
|
|
type Request = http::Request<B>;
|
|
type Response = bind::HttpResponse;
|
|
type Error = <Self::Service as tower::Service>::Error;
|
|
type Key = (Destination, Protocol);
|
|
type RouteError = bind::BufferSpawnError;
|
|
type Service = InFlightLimit<Timeout<Buffer<Balance<
|
|
load::WithPendingRequests<Discovery<B>>,
|
|
choose::PowerOfTwoChoices<rand::ThreadRng>
|
|
>>>>;
|
|
|
|
fn recognize(&self, req: &Self::Request) -> Option<Reuse<Self::Key>> {
|
|
let proto = bind::Protocol::detect(req);
|
|
|
|
let local = req.uri().authority_part().map(|authority| {
|
|
FullyQualifiedAuthority::normalize(
|
|
authority,
|
|
self.default_namespace.as_ref().map(|s| s.as_ref()),
|
|
self.default_zone.as_ref().map(|s| s.as_ref()))
|
|
|
|
});
|
|
|
|
// If we can't fully qualify the authority as a local service,
|
|
// and there is no original dst, then we have nothing! In that
|
|
// case, we return `None`, which results an "unrecognized" error.
|
|
//
|
|
// In practice, this shouldn't ever happen, since we expect the proxy
|
|
// to be run on Linux servers, with iptables setup, so there should
|
|
// always be an original destination.
|
|
let dest = if let Some(NamedAddress {
|
|
name,
|
|
use_destination_service: true
|
|
}) = local {
|
|
Destination::LocalSvc(name)
|
|
} else {
|
|
let orig_dst = req.extensions()
|
|
.get::<Arc<ctx::transport::Server>>()
|
|
.and_then(|ctx| {
|
|
ctx.orig_dst_if_not_local()
|
|
});
|
|
Destination::External(orig_dst?)
|
|
};
|
|
|
|
|
|
Some(proto.into_key(dest))
|
|
}
|
|
|
|
/// Builds a dynamic, load balancing service.
|
|
///
|
|
/// Resolves the authority in service discovery and initializes a service that buffers
|
|
/// and load balances requests across.
|
|
///
|
|
/// # TODO
|
|
///
|
|
/// Buffering is currently unbounded and does not apply timeouts. This must be
|
|
/// changed.
|
|
fn bind_service(
|
|
&mut self,
|
|
key: &Self::Key,
|
|
) -> Result<Self::Service, Self::RouteError> {
|
|
let &(ref dest, ref protocol) = key;
|
|
debug!("building outbound {:?} client to {:?}", protocol, dest);
|
|
|
|
let resolve = match *dest {
|
|
Destination::LocalSvc(ref authority) => {
|
|
Discovery::LocalSvc(self.discovery.resolve(
|
|
authority,
|
|
self.bind.clone().with_protocol(protocol.clone()),
|
|
))
|
|
},
|
|
Destination::External(addr) => {
|
|
Discovery::External(Some((addr, self.bind.clone()
|
|
.with_protocol(protocol.clone()))))
|
|
}
|
|
};
|
|
|
|
let loaded = tower_balance::load::WithPendingRequests::new(resolve);
|
|
|
|
let balance = tower_balance::power_of_two_choices(loaded, rand::thread_rng());
|
|
|
|
// use the same executor as the underlying `Bind` for the `Buffer` and
|
|
// `Timeout`.
|
|
let handle = self.bind.executor();
|
|
|
|
let buffer = Buffer::new(balance, handle)
|
|
.map_err(|_| bind::BufferSpawnError::Outbound)?;
|
|
|
|
let timeout = Timeout::new(buffer, self.bind_timeout, handle);
|
|
|
|
Ok(InFlightLimit::new(timeout, MAX_IN_FLIGHT))
|
|
|
|
}
|
|
}
|
|
|
|
pub enum Discovery<B> {
|
|
LocalSvc(discovery::Watch<BindProtocol<B>>),
|
|
External(Option<(SocketAddr, BindProtocol<B>)>),
|
|
}
|
|
|
|
impl<B> Discover for Discovery<B>
|
|
where
|
|
B: tower_h2::Body + 'static,
|
|
{
|
|
type Key = SocketAddr;
|
|
type Request = http::Request<B>;
|
|
type Response = bind::HttpResponse;
|
|
type Error = <bind::Service<B> as tower::Service>::Error;
|
|
type Service = bind::Service<B>;
|
|
type DiscoverError = BindError;
|
|
|
|
fn poll(&mut self) -> Poll<Change<Self::Key, Self::Service>, Self::DiscoverError> {
|
|
match *self {
|
|
Discovery::LocalSvc(ref mut w) => w.poll()
|
|
.map_err(|_| BindError::Internal),
|
|
Discovery::External(ref mut opt) => {
|
|
// This "discovers" a single address for an external service
|
|
// that never has another change. This can mean it floats
|
|
// in the Balancer forever. However, when we finally add
|
|
// circuit-breaking, this should be able to take care of itself,
|
|
// closing down when the connection is no longer usable.
|
|
if let Some((addr, bind)) = opt.take() {
|
|
let svc = bind.bind(&addr)
|
|
.map_err(|_| BindError::External{ addr })?;
|
|
Ok(Async::Ready(Change::Insert(addr, svc)))
|
|
} else {
|
|
Ok(Async::NotReady)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum BindError {
|
|
External { addr: SocketAddr },
|
|
Internal,
|
|
}
|
|
|
|
impl fmt::Display for BindError {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match *self {
|
|
BindError::External { addr } =>
|
|
write!(f, "binding external service for {:?} failed", addr),
|
|
BindError::Internal =>
|
|
write!(f, "binding internal service failed"),
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
impl error::Error for BindError {
|
|
fn description(&self) -> &str {
|
|
match *self {
|
|
BindError::External { .. } => "binding external service failed",
|
|
BindError::Internal => "binding internal service failed",
|
|
}
|
|
}
|
|
|
|
fn cause(&self) -> Option<&error::Error> { None }
|
|
}
|