function_sdk_rust/
server.rs1use std::future::Future;
4use std::net::SocketAddr;
5use std::path::{Path, PathBuf};
6use std::pin::Pin;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9
10use prometheus_client::registry::Registry;
11use tonic::transport::{Certificate, Identity, Server as GrpcServer, ServerTlsConfig};
12use tonic::{Request, Response, Status};
13use tonic_health::ServingStatus;
14use tonic_health::server::HealthReporter;
15
16use crate::Error;
17use crate::proto::v1::function_runner_service_server::{
18 FunctionRunnerService, FunctionRunnerServiceServer, SERVICE_NAME,
19};
20use crate::proto::v1::{RunFunctionRequest, RunFunctionResponse};
21
22#[derive(clap::Parser, Debug)]
24#[command(version, about = "A Crossplane composition function")]
25pub struct Args {
26 #[arg(short, long, env = "DEBUG", default_value_t = false)]
28 pub debug: bool,
29
30 #[arg(long, default_value = "0.0.0.0:9443")]
32 pub address: String,
33
34 #[arg(long, env = "TLS_SERVER_CERTS_DIR")]
36 pub tls_certs_dir: Option<PathBuf>,
37
38 #[arg(long, default_value_t = false)]
40 pub insecure: bool,
41
42 #[arg(long)]
45 pub max_recv_message_size: Option<usize>,
46
47 #[arg(long, env = "METRICS_ADDRESS", default_value = ":8080")]
51 pub metrics_address: String,
52}
53
54pub async fn serve<F: FunctionRunnerService>(function: F, args: &Args) -> Result<(), Error> {
70 Server::new(function, args).serve().await
71}
72
73type BoxError = Box<dyn std::error::Error + Send + Sync>;
74type ReadyFuture = Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send>>;
75
76pub struct Server<'a, F> {
91 function: F,
92 args: &'a Args,
93 ready: Option<ReadyFuture>,
94 metrics_registry: Option<Registry>,
95}
96
97impl<'a, F: FunctionRunnerService> Server<'a, F> {
98 pub fn new(function: F, args: &'a Args) -> Self {
100 Self {
101 function,
102 args,
103 ready: None,
104 metrics_registry: None,
105 }
106 }
107
108 pub fn ready<R, E>(mut self, ready: R) -> Self
115 where
116 R: Future<Output = Result<(), E>> + Send + 'static,
117 E: Into<BoxError>,
118 {
119 self.ready = Some(Box::pin(async move { ready.await.map_err(Into::into) }));
120 self
121 }
122
123 pub fn metrics_registry(mut self, registry: Registry) -> Self {
134 self.metrics_registry = Some(registry);
135 self
136 }
137
138 pub async fn serve(self) -> Result<(), Error> {
141 let Server {
142 function,
143 args,
144 ready,
145 metrics_registry,
146 } = self;
147 let address: SocketAddr = args.address.parse()?;
148
149 let mut builder = GrpcServer::builder();
150 if !args.insecure {
151 let dir = args
152 .tls_certs_dir
153 .as_deref()
154 .ok_or(Error::MissingTlsCertsDir)?;
155 builder = builder.tls_config(tls_config(dir)?)?;
156 }
157
158 let is_ready = Arc::new(AtomicBool::new(false));
159 let mut function_service = FunctionRunnerServiceServer::new(Gate {
160 function,
161 ready: is_ready.clone(),
162 });
163 if let Some(size) = args.max_recv_message_size {
164 function_service = function_service.max_decoding_message_size(size);
165 }
166
167 let reflection_v1 = tonic_reflection::server::Builder::configure()
168 .register_encoded_file_descriptor_set(crate::proto::FILE_DESCRIPTOR_SET)
169 .build_v1()?;
170 let reflection_v1alpha = tonic_reflection::server::Builder::configure()
171 .register_encoded_file_descriptor_set(crate::proto::FILE_DESCRIPTOR_SET)
172 .build_v1alpha()?;
173
174 let (health_reporter, health_service) = tonic_health::server::health_reporter();
177 let (not_ready_tx, not_ready_rx) = tokio::sync::oneshot::channel::<BoxError>();
178 match ready {
179 None => {
180 is_ready.store(true, Ordering::Release);
181 set_health(&health_reporter, ServingStatus::Serving).await;
182 }
183 Some(ready) => {
184 set_health(&health_reporter, ServingStatus::NotServing).await;
185 let loading = tokio::spawn(ready);
188 tokio::spawn(async move {
189 match loading.await.unwrap_or_else(|e| Err(e.into())) {
190 Ok(()) => {
191 is_ready.store(true, Ordering::Release);
192 set_health(&health_reporter, ServingStatus::Serving).await;
193 tracing::info!("function is ready");
194 }
195 Err(e) => {
196 let _ = not_ready_tx.send(e);
197 }
198 }
199 });
200 }
201 }
202
203 if !args.metrics_address.is_empty() {
209 crate::metrics::initialize();
210 let metrics_address = args.metrics_address.clone();
211 let registry = crate::metrics::served_registry(metrics_registry);
212 tokio::spawn(async move {
213 if let Err(e) = crate::metrics::serve(&metrics_address, registry).await {
214 tracing::error!(error = %e, "cannot serve metrics");
215 }
216 });
217 }
218
219 tracing::info!(%address, insecure = args.insecure, "serving FunctionRunnerService");
220
221 let mut not_ready = None;
225 builder
226 .layer(crate::metrics::MetricsLayer)
227 .add_service(function_service)
228 .add_service(health_service)
229 .add_service(reflection_v1)
230 .add_service(reflection_v1alpha)
231 .serve_with_shutdown(address, async {
232 tokio::select! {
233 () = shutdown_signal() => {}
234 Ok(e) = not_ready_rx => not_ready = Some(e),
235 }
236 })
237 .await?;
238
239 match not_ready {
240 Some(e) => Err(Error::NotReady(e)),
241 None => Ok(()),
242 }
243 }
244}
245
246async fn set_health(reporter: &HealthReporter, status: ServingStatus) {
248 for service in ["", SERVICE_NAME] {
249 reporter.set_service_status(service, status).await;
250 }
251}
252
253struct Gate<F> {
255 function: F,
256 ready: Arc<AtomicBool>,
257}
258
259#[tonic::async_trait]
260impl<F: FunctionRunnerService> FunctionRunnerService for Gate<F> {
261 async fn run_function(
262 &self,
263 request: Request<RunFunctionRequest>,
264 ) -> Result<Response<RunFunctionResponse>, Status> {
265 if !self.ready.load(Ordering::Acquire) {
266 return Err(Status::unavailable("the function is not ready"));
267 }
268 self.function.run_function(request).await
269 }
270}
271
272fn tls_config(dir: &Path) -> Result<ServerTlsConfig, Error> {
273 let read = |name: &str| {
274 let path = dir.join(name);
275 std::fs::read(&path).map_err(|source| Error::ReadCertificate { path, source })
276 };
277 let cert = read("tls.crt")?;
278 let key = read("tls.key")?;
279 let ca = read("ca.crt")?;
280
281 Ok(ServerTlsConfig::new()
282 .identity(Identity::from_pem(cert, key))
283 .client_ca_root(Certificate::from_pem(ca))
284 .client_auth_optional(false))
285}
286
287#[cfg(unix)]
288async fn shutdown_signal() {
289 let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
290 .expect("cannot install SIGTERM handler");
291 tokio::select! {
292 _ = sigterm.recv() => {}
293 _ = tokio::signal::ctrl_c() => {}
294 }
295 tracing::info!("shutting down");
296}
297
298#[cfg(not(unix))]
299async fn shutdown_signal() {
300 let _ = tokio::signal::ctrl_c().await;
301 tracing::info!("shutting down");
302}