1+ use std:: {
2+ sync:: {
3+ Arc ,
4+ atomic:: { AtomicUsize , Ordering } ,
5+ } ,
6+ task:: { Context , Poll } ,
7+ } ;
8+
9+ use futures:: { FutureExt , TryFutureExt , future:: BoxFuture } ;
110use http:: Uri ;
211use hyper:: client:: HttpConnector ;
312use hyper_openssl:: HttpsConnector ;
413use hyper_proxy:: ProxyConnector ;
514use tonic:: body:: BoxBody ;
6- use tower:: ServiceBuilder ;
15+ use tower:: { Service , ServiceBuilder } ;
716use vector_lib:: configurable:: configurable_component;
817
918use super :: {
@@ -22,8 +31,9 @@ use crate::{
2231 sinks:: {
2332 Healthcheck , VectorSink as VectorSinkType ,
2433 util:: {
25- BatchConfig , RealtimeEventBasedDefaultBatchSettings , ServiceBuilderExt ,
26- TowerRequestConfig , retries:: RetryLogic ,
34+ BatchConfig , RealtimeEventBasedDefaultBatchSettings , TowerRequestConfig ,
35+ retries:: RetryLogic ,
36+ service:: { HealthConfig , HealthLogic , ServiceBuilderExt } ,
2737 } ,
2838 } ,
2939 tls:: { MaybeTlsSettings , TlsEnableableConfig } ,
@@ -45,10 +55,28 @@ pub struct VectorConfig {
4555 /// Both IP address and hostname are accepted formats.
4656 ///
4757 /// The address _must_ include a port.
58+ ///
59+ /// This option is mutually exclusive with `addresses`. Set exactly one of
60+ /// `address` or `addresses`.
61+ #[ configurable( validation( format = "uri" ) ) ]
62+ #[ configurable( metadata( docs:: examples = "92.12.333.224:6000" ) ) ]
63+ #[ configurable( metadata( docs:: examples = "https://somehost:6000" ) ) ]
64+ #[ serde( default ) ]
65+ address : Option < String > ,
66+
67+ /// The downstream Vector addresses to which to connect.
68+ ///
69+ /// Both IP addresses and hostnames are accepted formats.
70+ ///
71+ /// Each address _must_ include a port.
72+ ///
73+ /// This option is mutually exclusive with `address`. Set exactly one of
74+ /// `address` or `addresses`.
4875 #[ configurable( validation( format = "uri" ) ) ]
4976 #[ configurable( metadata( docs:: examples = "92.12.333.224:6000" ) ) ]
5077 #[ configurable( metadata( docs:: examples = "https://somehost:6000" ) ) ]
51- address : String ,
78+ #[ serde( default ) ]
79+ addresses : Vec < String > ,
5280
5381 /// Compression algorithm for requests.
5482 ///
@@ -72,6 +100,17 @@ pub struct VectorConfig {
72100 #[ serde( default ) ]
73101 pub request : TowerRequestConfig ,
74102
103+ /// Options for determining the health of Vector endpoints.
104+ #[ serde( default ) ]
105+ #[ configurable( derived) ]
106+ pub endpoint_health : Option < HealthConfig > ,
107+
108+ /// Strategy for routing requests across multiple configured addresses.
109+ ///
110+ /// This option is only used when `addresses` is configured.
111+ #[ serde( default ) ]
112+ pub endpoint_strategy : EndpointStrategy ,
113+
75114 #[ configurable( derived) ]
76115 #[ serde( default ) ]
77116 tls : Option < TlsEnableableConfig > ,
@@ -102,10 +141,13 @@ impl GenerateConfig for VectorConfig {
102141fn default_config ( address : & str ) -> VectorConfig {
103142 VectorConfig {
104143 version : None ,
105- address : address. to_owned ( ) ,
144+ address : Some ( address. to_owned ( ) ) ,
145+ addresses : Vec :: new ( ) ,
106146 compression : VectorCompression :: None ,
107147 batch : BatchConfig :: default ( ) ,
108148 request : TowerRequestConfig :: default ( ) ,
149+ endpoint_health : None ,
150+ endpoint_strategy : EndpointStrategy :: default ( ) ,
109151 tls : None ,
110152 acknowledgements : Default :: default ( ) ,
111153 }
@@ -116,36 +158,63 @@ fn default_config(address: &str) -> VectorConfig {
116158impl SinkConfig for VectorConfig {
117159 async fn build ( & self , cx : SinkContext ) -> crate :: Result < ( VectorSinkType , Healthcheck ) > {
118160 let tls = MaybeTlsSettings :: from_config ( self . tls . as_ref ( ) , false ) ?;
119- let uri = with_default_scheme ( & self . address , tls. is_tls ( ) ) ?;
161+ let uris = self . uris ( tls. is_tls ( ) ) ?;
120162
121163 let client = new_client ( & tls, cx. proxy ( ) ) ?;
122164
123- let healthcheck_uri = cx
124- . healthcheck
125- . uri
126- . clone ( )
127- . map ( |uri| uri. uri )
128- . unwrap_or_else ( || uri. clone ( ) ) ;
129- let healthcheck_client =
130- VectorService :: new ( client. clone ( ) , healthcheck_uri, VectorCompression :: None ) ;
131- let healthcheck = healthcheck ( healthcheck_client, cx. healthcheck ) ;
132- let service = VectorService :: new ( client, uri, self . compression ) ;
165+ let healthcheck = healthchecks ( client. clone ( ) , & uris, cx. healthcheck ) ;
133166 let request_settings = self . request . into_settings ( ) ;
134167 let batch_settings = self . batch . into_batcher_settings ( ) ?;
135168
136- let service = ServiceBuilder :: new ( )
137- . settings ( request_settings, VectorGrpcRetryLogic )
138- . service ( service) ;
169+ let services = uris
170+ . into_iter ( )
171+ . map ( |uri| {
172+ let endpoint = uri. to_string ( ) ;
173+ let service = VectorService :: new ( client. clone ( ) , uri, self . compression ) ;
174+ ( endpoint, service)
175+ } )
176+ . collect :: < Vec < _ > > ( ) ;
177+
178+ let sink = match self . endpoint_strategy {
179+ EndpointStrategy :: LoadBalance => {
180+ let service = request_settings. distributed_service (
181+ VectorGrpcRetryLogic ,
182+ services,
183+ self . endpoint_health . clone ( ) . unwrap_or_default ( ) ,
184+ VectorGrpcHealthLogic ,
185+ 1 ,
186+ ) ;
139187
140- let sink = VectorSink {
141- batch_settings,
142- service,
188+ VectorSinkType :: from_event_streamsink ( VectorSink {
189+ batch_settings,
190+ service,
191+ } )
192+ }
193+ EndpointStrategy :: Failover => {
194+ let endpoint_timeout = request_settings. timeout ;
195+ let mut failover_request_settings = request_settings;
196+ failover_request_settings. timeout = endpoint_timeout
197+ . checked_mul ( services. len ( ) as u32 )
198+ . unwrap_or ( endpoint_timeout) ;
199+
200+ let service = ServiceBuilder :: new ( )
201+ . settings ( failover_request_settings, VectorGrpcRetryLogic )
202+ . service ( FailoverVectorService :: new (
203+ services
204+ . into_iter ( )
205+ . map ( |( _endpoint, service) | service)
206+ . collect ( ) ,
207+ endpoint_timeout,
208+ ) ) ;
209+
210+ VectorSinkType :: from_event_streamsink ( VectorSink {
211+ batch_settings,
212+ service,
213+ } )
214+ }
143215 } ;
144216
145- Ok ( (
146- VectorSinkType :: from_event_streamsink ( sink) ,
147- Box :: pin ( healthcheck) ,
148- ) )
217+ Ok ( ( sink, Box :: pin ( healthcheck) ) )
149218 }
150219
151220 fn input ( & self ) -> Input {
@@ -157,6 +226,100 @@ impl SinkConfig for VectorConfig {
157226 }
158227}
159228
229+ /// Strategy for routing requests across multiple Vector endpoints.
230+ #[ configurable_component]
231+ #[ derive( Clone , Copy , Debug , Default , Eq , PartialEq ) ]
232+ #[ serde( rename_all = "snake_case" ) ]
233+ pub enum EndpointStrategy {
234+ /// Distribute requests across healthy endpoints.
235+ #[ default]
236+ LoadBalance ,
237+ /// Use endpoints in configured order, moving to the next endpoint only when
238+ /// the current endpoint fails.
239+ Failover ,
240+ }
241+
242+ #[ derive( Clone ) ]
243+ struct FailoverVectorService {
244+ services : Vec < VectorService > ,
245+ active : Arc < AtomicUsize > ,
246+ endpoint_timeout : std:: time:: Duration ,
247+ }
248+
249+ impl FailoverVectorService {
250+ fn new ( services : Vec < VectorService > , endpoint_timeout : std:: time:: Duration ) -> Self {
251+ Self {
252+ services,
253+ active : Arc :: new ( AtomicUsize :: new ( 0 ) ) ,
254+ endpoint_timeout,
255+ }
256+ }
257+ }
258+
259+ impl Service < VectorRequest > for FailoverVectorService {
260+ type Response = VectorResponse ;
261+ type Error = crate :: Error ;
262+ type Future = BoxFuture < ' static , Result < Self :: Response , Self :: Error > > ;
263+
264+ fn poll_ready ( & mut self , _cx : & mut Context < ' _ > ) -> Poll < Result < ( ) , Self :: Error > > {
265+ Poll :: Ready ( Ok ( ( ) ) )
266+ }
267+
268+ fn call ( & mut self , request : VectorRequest ) -> Self :: Future {
269+ let services = self . services . clone ( ) ;
270+ let active = Arc :: clone ( & self . active ) ;
271+ let endpoint_timeout = self . endpoint_timeout ;
272+
273+ Box :: pin ( async move {
274+ let start = active. load ( Ordering :: Acquire ) ;
275+ let mut last_error = None ;
276+
277+ for offset in 0 ..services. len ( ) {
278+ let index = ( start + offset) % services. len ( ) ;
279+ let mut service = services[ index] . clone ( ) ;
280+
281+ match tokio:: time:: timeout ( endpoint_timeout, service. call ( request. clone ( ) ) ) . await {
282+ Ok ( Ok ( response) ) => {
283+ active. store ( index, Ordering :: Release ) ;
284+ return Ok ( response) ;
285+ }
286+ Ok ( Err ( error) ) => {
287+ active. store ( ( index + 1 ) % services. len ( ) , Ordering :: Release ) ;
288+ last_error = Some ( error) ;
289+ }
290+ Err ( _elapsed) => {
291+ active. store ( ( index + 1 ) % services. len ( ) , Ordering :: Release ) ;
292+ last_error = Some ( Box :: new ( VectorSinkError :: Request {
293+ source : tonic:: Status :: deadline_exceeded (
294+ "vector endpoint request timed out" ,
295+ ) ,
296+ } ) as crate :: Error ) ;
297+ }
298+ }
299+ }
300+
301+ Err ( last_error. expect ( "failover service should have at least one endpoint" ) )
302+ } )
303+ }
304+ }
305+
306+ impl VectorConfig {
307+ fn uris ( & self , tls : bool ) -> crate :: Result < Vec < Uri > > {
308+ match ( self . address . as_ref ( ) , self . addresses . as_slice ( ) ) {
309+ ( Some ( _) , [ _first, ..] ) => Err (
310+ "`address` and `addresses` options are mutually exclusive. Please use `addresses` for multiple Vector endpoints."
311+ . into ( ) ,
312+ ) ,
313+ ( None , [ ] ) => Err ( "No Vector endpoint configured. Please set `address` or `addresses`." . into ( ) ) ,
314+ ( Some ( address) , [ ] ) => Ok ( vec ! [ with_default_scheme( address, tls) ?] ) ,
315+ ( None , addresses) => addresses
316+ . iter ( )
317+ . map ( |address| with_default_scheme ( address, tls) )
318+ . collect ( ) ,
319+ }
320+ }
321+ }
322+
160323/// Check to see if the remote service accepts new events.
161324async fn healthcheck (
162325 mut service : VectorService ,
@@ -183,6 +346,39 @@ async fn healthcheck(
183346 }
184347}
185348
349+ fn healthchecks (
350+ client : hyper:: Client < ProxyConnector < HttpsConnector < HttpConnector > > , BoxBody > ,
351+ uris : & [ Uri ] ,
352+ options : SinkHealthcheckOptions ,
353+ ) -> Healthcheck {
354+ if !options. enabled {
355+ return Box :: pin ( futures:: future:: ok ( ( ) ) ) ;
356+ }
357+
358+ let healthcheck_uris = options
359+ . uri
360+ . clone ( )
361+ . map ( |uri| vec ! [ uri. uri] )
362+ . unwrap_or_else ( || uris. to_vec ( ) ) ;
363+
364+ Box :: pin (
365+ futures:: future:: select_ok ( healthcheck_uris. into_iter ( ) . map ( move |uri| {
366+ let service = VectorService :: new ( client. clone ( ) , uri, VectorCompression :: None ) ;
367+ let timeout = options. timeout ;
368+ healthcheck (
369+ service,
370+ SinkHealthcheckOptions {
371+ enabled : true ,
372+ uri : None ,
373+ timeout,
374+ } ,
375+ )
376+ . boxed ( )
377+ } ) )
378+ . map_ok ( |( ( ) , _) | ( ) ) ,
379+ )
380+ }
381+
186382/// grpc doesn't like an address without a scheme, so we default to http or https if one isn't
187383/// specified in the address.
188384pub fn with_default_scheme ( address : & str , tls : bool ) -> crate :: Result < Uri > {
@@ -256,3 +452,15 @@ impl RetryLogic for VectorGrpcRetryLogic {
256452 }
257453 }
258454}
455+
456+ #[ derive( Debug , Clone ) ]
457+ struct VectorGrpcHealthLogic ;
458+
459+ impl HealthLogic for VectorGrpcHealthLogic {
460+ type Error = crate :: Error ;
461+ type Response = VectorResponse ;
462+
463+ fn is_healthy ( & self , response : & Result < Self :: Response , Self :: Error > ) -> Option < bool > {
464+ Some ( response. is_ok ( ) )
465+ }
466+ }
0 commit comments