Coverage Report

Created: 2026-04-14 11:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-config/src/cas_server.rs
Line
Count
Source
1
// Copyright 2024 The NativeLink Authors. All rights reserved.
2
//
3
// Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    See LICENSE file for details
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use std::collections::HashMap;
16
17
use nativelink_error::{Error, ResultExt};
18
#[cfg(feature = "dev-schema")]
19
use schemars::JsonSchema;
20
use serde::{Deserialize, Serialize};
21
22
use crate::schedulers::SchedulerSpec;
23
use crate::serde_utils::{
24
    convert_data_size_with_shellexpand, convert_duration_with_shellexpand,
25
    convert_numeric_with_shellexpand, convert_optional_numeric_with_shellexpand,
26
    convert_optional_string_with_shellexpand, convert_string_with_shellexpand,
27
    convert_vec_string_with_shellexpand,
28
};
29
use crate::stores::{ClientTlsConfig, ConfigDigestHashFunction, StoreRefName, StoreSpec};
30
31
/// Name of the scheduler. This type will be used when referencing a
32
/// scheduler in the `CasConfig::schedulers`'s map key.
33
pub type SchedulerRefName = String;
34
35
/// Used when the config references `instance_name` in the protocol.
36
pub type InstanceName = String;
37
38
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
39
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
40
pub struct WithInstanceName<T> {
41
    #[serde(default)]
42
    pub instance_name: InstanceName,
43
    #[serde(flatten)]
44
    pub config: T,
45
}
46
47
impl<T> core::ops::Deref for WithInstanceName<T> {
48
    type Target = T;
49
50
94
    fn deref(&self) -> &Self::Target {
51
94
        &self.config
52
94
    }
53
}
54
55
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
56
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
57
pub struct NamedConfig<Spec> {
58
    pub name: String,
59
    #[serde(flatten)]
60
    pub spec: Spec,
61
}
62
63
#[derive(Deserialize, Serialize, Debug, Default, Clone, Copy)]
64
#[serde(rename_all = "snake_case")]
65
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
66
pub enum HttpCompressionAlgorithm {
67
    /// No compression.
68
    #[default]
69
    None,
70
71
    /// Zlib compression.
72
    Gzip,
73
}
74
75
/// Note: Compressing data in the cloud rarely has a benefit, since most
76
/// cloud providers have very high bandwidth backplanes. However, for
77
/// clients not inside the data center, it might be a good idea to
78
/// compress data to and from the cloud. This will however come at a high
79
/// CPU and performance cost. If you are making remote execution share the
80
/// same CAS/AC servers as client's remote cache, you can create multiple
81
/// services with different compression settings that are served on
82
/// different ports. Then configure the non-cloud clients to use one port
83
/// and cloud-clients to use another.
84
#[derive(Deserialize, Serialize, Debug, Default)]
85
#[serde(deny_unknown_fields)]
86
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
87
pub struct HttpCompressionConfig {
88
    /// The compression algorithm that the server will use when sending
89
    /// responses to clients. Enabling this will likely save a lot of
90
    /// data transfer, but will consume a lot of CPU and add a lot of
91
    /// latency.
92
    /// see: <https://github.com/tracemachina/nativelink/issues/109>
93
    ///
94
    /// Default: `HttpCompressionAlgorithm::None`
95
    pub send_compression_algorithm: Option<HttpCompressionAlgorithm>,
96
97
    /// The compression algorithm that the server will accept from clients.
98
    /// The server will broadcast the supported compression algorithms to
99
    /// clients and the client will choose which compression algorithm to
100
    /// use. Enabling this will likely save a lot of data transfer, but
101
    /// will consume a lot of CPU and add a lot of latency.
102
    /// see: <https://github.com/tracemachina/nativelink/issues/109>
103
    ///
104
    /// Default: {no supported compression}
105
    pub accepted_compression_algorithms: Vec<HttpCompressionAlgorithm>,
106
}
107
108
#[derive(Deserialize, Serialize, Debug)]
109
#[serde(deny_unknown_fields)]
110
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
111
pub struct AcStoreConfig {
112
    /// The store name referenced in the `stores` map in the main config.
113
    /// This store name referenced here may be reused multiple times.
114
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
115
    pub ac_store: StoreRefName,
116
117
    /// Whether the Action Cache store may be written to, this if set to false
118
    /// it is only possible to read from the Action Cache.
119
    #[serde(default)]
120
    pub read_only: bool,
121
}
122
123
#[derive(Deserialize, Serialize, Debug)]
124
#[serde(deny_unknown_fields)]
125
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
126
pub struct CasStoreConfig {
127
    /// The store name referenced in the `stores` map in the main config.
128
    /// This store name referenced here may be reused multiple times.
129
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
130
    pub cas_store: StoreRefName,
131
}
132
133
#[derive(Deserialize, Serialize, Debug, Default)]
134
#[serde(deny_unknown_fields)]
135
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
136
pub struct CapabilitiesRemoteExecutionConfig {
137
    /// Scheduler used to configure the capabilities of remote execution.
138
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
139
    pub scheduler: SchedulerRefName,
140
}
141
142
#[derive(Deserialize, Serialize, Debug, Default)]
143
#[serde(deny_unknown_fields)]
144
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
145
pub struct CapabilitiesConfig {
146
    /// Configuration for remote execution capabilities.
147
    /// If not set the capabilities service will inform the client that remote
148
    /// execution is not supported.
149
    pub remote_execution: Option<CapabilitiesRemoteExecutionConfig>,
150
}
151
152
#[derive(Deserialize, Serialize, Debug)]
153
#[serde(deny_unknown_fields)]
154
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
155
pub struct ExecutionConfig {
156
    /// The store name referenced in the `stores` map in the main config.
157
    /// This store name referenced here may be reused multiple times.
158
    /// This value must be a CAS store reference.
159
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
160
    pub cas_store: StoreRefName,
161
162
    /// The scheduler name referenced in the `schedulers` map in the main config.
163
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
164
    pub scheduler: SchedulerRefName,
165
}
166
167
#[derive(Deserialize, Serialize, Debug, Clone)]
168
#[serde(deny_unknown_fields)]
169
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
170
pub struct FetchConfig {
171
    /// The store name referenced in the `stores` map in the main config.
172
    /// This store name referenced here may be reused multiple times.
173
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
174
    pub fetch_store: StoreRefName,
175
}
176
177
#[derive(Deserialize, Serialize, Debug, Clone)]
178
#[serde(deny_unknown_fields)]
179
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
180
pub struct PushConfig {
181
    /// The store name referenced in the `stores` map in the main config.
182
    /// This store name referenced here may be reused multiple times.
183
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
184
    pub push_store: StoreRefName,
185
186
    /// Whether the Action Cache store may be written to, this if set to false
187
    /// it is only possible to read from the Action Cache.
188
    #[serde(default)]
189
    pub read_only: bool,
190
}
191
192
// From https://github.com/serde-rs/serde/issues/818#issuecomment-287438544
193
80
fn is_default<T: Default + PartialEq>(t: &T) -> bool {
194
80
    *t == Default::default()
195
80
}
196
197
#[derive(Deserialize, Serialize, Debug, Default, PartialEq, Eq)]
198
#[serde(deny_unknown_fields)]
199
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
200
pub struct ByteStreamConfig {
201
    /// Name of the store in the "stores" configuration.
202
    pub cas_store: StoreRefName,
203
204
    /// Max number of bytes to send on each grpc stream chunk.
205
    /// According to <https://github.com/grpc/grpc.github.io/issues/371>
206
    /// 16KiB - 64KiB is optimal.
207
    ///
208
    ///
209
    /// Default: 64KiB
210
    #[serde(
211
        default,
212
        deserialize_with = "convert_data_size_with_shellexpand",
213
        skip_serializing_if = "is_default"
214
    )]
215
    pub max_bytes_per_stream: usize,
216
217
    /// In the event a client disconnects while uploading a blob, we will hold
218
    /// the internal stream open for this many seconds before closing it.
219
    /// This allows clients that disconnect to reconnect and continue uploading
220
    /// the same blob.
221
    ///
222
    /// Default: 10 (seconds)
223
    #[serde(
224
        default,
225
        deserialize_with = "convert_duration_with_shellexpand",
226
        skip_serializing_if = "is_default"
227
    )]
228
    pub persist_stream_on_disconnect_timeout: usize,
229
}
230
231
// Older bytestream config. All fields are as per the newer docs, but this requires
232
// the hashed cas_stores v.s. the WithInstanceName approach. This should _not_ be updated
233
// with newer fields, and eventually dropped
234
#[derive(Deserialize, Serialize, Debug, Clone)]
235
#[serde(deny_unknown_fields)]
236
pub struct OldByteStreamConfig {
237
    pub cas_stores: HashMap<InstanceName, StoreRefName>,
238
    #[serde(
239
        default,
240
        deserialize_with = "convert_data_size_with_shellexpand",
241
        skip_serializing_if = "is_default"
242
    )]
243
    pub max_bytes_per_stream: usize,
244
    #[serde(
245
        default,
246
        deserialize_with = "convert_data_size_with_shellexpand",
247
        skip_serializing_if = "is_default"
248
    )]
249
    pub max_decoding_message_size: usize,
250
    #[serde(
251
        default,
252
        deserialize_with = "convert_duration_with_shellexpand",
253
        skip_serializing_if = "is_default"
254
    )]
255
    pub persist_stream_on_disconnect_timeout: usize,
256
}
257
258
#[derive(Deserialize, Serialize, Debug)]
259
#[serde(deny_unknown_fields)]
260
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
261
pub struct WorkerApiConfig {
262
    /// The scheduler name referenced in the `schedulers` map in the main config.
263
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
264
    pub scheduler: SchedulerRefName,
265
}
266
267
#[derive(Deserialize, Serialize, Debug, Default)]
268
#[serde(deny_unknown_fields)]
269
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
270
pub struct AdminConfig {
271
    /// Path to register the admin API. If path is "/admin", and your
272
    /// domain is "example.com", you can reach the endpoint with:
273
    /// <http://example.com/admin>.
274
    ///
275
    /// Default: "/admin"
276
    #[serde(default)]
277
    pub path: String,
278
}
279
280
#[derive(Deserialize, Serialize, Debug, Default)]
281
#[serde(deny_unknown_fields)]
282
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
283
pub struct HealthConfig {
284
    /// Path to register the health status check. If path is "/status", and your
285
    /// domain is "example.com", you can reach the endpoint with:
286
    /// <http://example.com/status>.
287
    ///
288
    /// Default: "/status"
289
    #[serde(default)]
290
    pub path: String,
291
292
    // Timeout on health checks. Defaults to 5s.
293
    #[serde(default)]
294
    pub timeout_seconds: u64,
295
}
296
297
#[derive(Deserialize, Serialize, Debug)]
298
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
299
pub struct BepConfig {
300
    /// The store to publish build events to.
301
    /// The store name referenced in the `stores` map in the main config.
302
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
303
    pub store: StoreRefName,
304
}
305
306
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
307
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
308
pub struct IdentityHeaderSpec {
309
    /// The name of the header to look for the identity in.
310
    /// Default: "x-identity"
311
    #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")]
312
    pub header_name: Option<String>,
313
314
    /// If the header is required to be set or fail the request.
315
    #[serde(default)]
316
    pub required: bool,
317
}
318
319
#[derive(Deserialize, Serialize, Clone, Debug)]
320
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
321
pub struct OriginEventsPublisherSpec {
322
    /// The store to publish nativelink events to.
323
    /// The store name referenced in the `stores` map in the main config.
324
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
325
    pub store: StoreRefName,
326
}
327
328
#[derive(Deserialize, Serialize, Clone, Debug)]
329
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
330
pub struct OriginEventsSpec {
331
    /// The publisher configuration for origin events.
332
    pub publisher: OriginEventsPublisherSpec,
333
334
    /// The maximum number of events to queue before applying back pressure.
335
    /// IMPORTANT: Backpressure causes all clients to slow down significantly.
336
    /// Zero is default.
337
    ///
338
    /// Default: 65536 (zero defaults to this)
339
    #[serde(default, deserialize_with = "convert_numeric_with_shellexpand")]
340
    pub max_event_queue_size: usize,
341
}
342
343
#[derive(Deserialize, Serialize, Debug)]
344
#[serde(deny_unknown_fields)]
345
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
346
pub struct ServicesConfig {
347
    /// The Content Addressable Storage (CAS) backend config.
348
    /// The key is the `instance_name` used in the protocol and the
349
    /// value is the underlying CAS store config.
350
    #[serde(
351
        default,
352
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
353
    )]
354
    pub cas: Option<Vec<WithInstanceName<CasStoreConfig>>>,
355
356
    /// The Action Cache (AC) backend config.
357
    /// The key is the `instance_name` used in the protocol and the
358
    /// value is the underlying AC store config.
359
    #[serde(
360
        default,
361
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
362
    )]
363
    pub ac: Option<Vec<WithInstanceName<AcStoreConfig>>>,
364
365
    /// Capabilities service is required in order to use most of the
366
    /// bazel protocol. This service is used to provide the supported
367
    /// features and versions of this bazel GRPC service.
368
    #[serde(
369
        default,
370
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
371
    )]
372
    pub capabilities: Option<Vec<WithInstanceName<CapabilitiesConfig>>>,
373
374
    /// The remote execution service configuration.
375
    /// NOTE: This service is under development and is currently just a
376
    /// place holder.
377
    #[serde(
378
        default,
379
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
380
    )]
381
    pub execution: Option<Vec<WithInstanceName<ExecutionConfig>>>,
382
383
    /// This is the service used to stream data to and from the CAS.
384
    /// Bazel's protocol strongly encourages users to use this streaming
385
    /// interface to interact with the CAS when the data is large.
386
    #[serde(default, deserialize_with = "super::backcompat::opt_bytestream")]
387
    pub bytestream: Option<Vec<WithInstanceName<ByteStreamConfig>>>,
388
389
    /// These two are collectively the Remote Asset protocol, but it's
390
    /// defined as two separate services
391
    #[serde(
392
        default,
393
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
394
    )]
395
    pub fetch: Option<Vec<WithInstanceName<FetchConfig>>>,
396
397
    #[serde(
398
        default,
399
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
400
    )]
401
    pub push: Option<Vec<WithInstanceName<PushConfig>>>,
402
403
    /// This is the service used for workers to connect and communicate
404
    /// through.
405
    /// NOTE: This service should be served on a different, non-public port.
406
    /// In other words, `worker_api` configuration should not have any other
407
    /// services that are served on the same port. Doing so is a security
408
    /// risk, as workers have a different permission set than a client
409
    /// that makes the remote execution/cache requests.
410
    pub worker_api: Option<WorkerApiConfig>,
411
412
    /// Experimental - Build Event Protocol (BEP) configuration. This is
413
    /// the service that will consume build events from the client and
414
    /// publish them to a store for processing by an external service.
415
    pub experimental_bep: Option<BepConfig>,
416
417
    /// This is the service for any administrative tasks.
418
    /// It provides a REST API endpoint for administrative purposes.
419
    pub admin: Option<AdminConfig>,
420
421
    /// This is the service for health status check.
422
    pub health: Option<HealthConfig>,
423
}
424
425
#[derive(Deserialize, Serialize, Debug)]
426
#[serde(deny_unknown_fields)]
427
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
428
pub struct TlsConfig {
429
    /// Path to the certificate file.
430
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
431
    pub cert_file: String,
432
433
    /// Path to the private key file.
434
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
435
    pub key_file: String,
436
437
    /// Path to the certificate authority for mTLS, if client authentication is
438
    /// required for this endpoint.
439
    #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")]
440
    pub client_ca_file: Option<String>,
441
442
    /// Path to the certificate revocation list for mTLS, if client
443
    /// authentication is required for this endpoint.
444
    #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")]
445
    pub client_crl_file: Option<String>,
446
}
447
448
/// Advanced Http configurations. These are generally should not be set.
449
/// For documentation on what each of these do, see the hyper documentation:
450
/// See: <https://docs.rs/hyper/latest/hyper/server/conn/struct.Http.html>
451
///
452
/// Note: All of these default to hyper's default values unless otherwise
453
/// specified.
454
#[derive(Deserialize, Serialize, Debug, Default, Clone, Copy)]
455
#[serde(deny_unknown_fields)]
456
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
457
pub struct HttpServerConfig {
458
    /// Interval to send keep-alive pings via HTTP2.
459
    /// Note: This is in seconds.
460
    #[serde(
461
        default,
462
        deserialize_with = "convert_optional_numeric_with_shellexpand"
463
    )]
464
    pub http2_keep_alive_interval: Option<u32>,
465
466
    #[serde(
467
        default,
468
        deserialize_with = "convert_optional_numeric_with_shellexpand"
469
    )]
470
    pub experimental_http2_max_pending_accept_reset_streams: Option<u32>,
471
472
    #[serde(
473
        default,
474
        deserialize_with = "convert_optional_numeric_with_shellexpand"
475
    )]
476
    pub experimental_http2_initial_stream_window_size: Option<u32>,
477
478
    #[serde(
479
        default,
480
        deserialize_with = "convert_optional_numeric_with_shellexpand"
481
    )]
482
    pub experimental_http2_initial_connection_window_size: Option<u32>,
483
484
    #[serde(default)]
485
    pub experimental_http2_adaptive_window: Option<bool>,
486
487
    #[serde(
488
        default,
489
        deserialize_with = "convert_optional_numeric_with_shellexpand"
490
    )]
491
    pub experimental_http2_max_frame_size: Option<u32>,
492
493
    #[serde(
494
        default,
495
        deserialize_with = "convert_optional_numeric_with_shellexpand"
496
    )]
497
    pub experimental_http2_max_concurrent_streams: Option<u32>,
498
499
    /// Note: This is in seconds.
500
    #[serde(
501
        default,
502
        deserialize_with = "convert_optional_numeric_with_shellexpand"
503
    )]
504
    pub experimental_http2_keep_alive_timeout: Option<u32>,
505
506
    #[serde(
507
        default,
508
        deserialize_with = "convert_optional_numeric_with_shellexpand"
509
    )]
510
    pub experimental_http2_max_send_buf_size: Option<u32>,
511
512
    #[serde(default)]
513
    pub experimental_http2_enable_connect_protocol: Option<bool>,
514
515
    #[serde(
516
        default,
517
        deserialize_with = "convert_optional_numeric_with_shellexpand"
518
    )]
519
    pub experimental_http2_max_header_list_size: Option<u32>,
520
}
521
522
#[derive(Deserialize, Serialize, Debug)]
523
#[serde(rename_all = "snake_case")]
524
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
525
pub enum ListenerConfig {
526
    /// Listener for HTTP/HTTPS/HTTP2 sockets.
527
    Http(HttpListener),
528
}
529
530
#[derive(Deserialize, Serialize, Debug, Default)]
531
#[serde(deny_unknown_fields)]
532
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
533
pub struct HttpListener {
534
    /// Address to listen on. Example: `127.0.0.1:8080` or `:8080` to listen
535
    /// to all IPs.
536
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
537
    pub socket_address: String,
538
539
    /// Data transport compression configuration to use for this service.
540
    #[serde(default)]
541
    pub compression: HttpCompressionConfig,
542
543
    /// Advanced Http server configuration.
544
    #[serde(default)]
545
    pub advanced_http: HttpServerConfig,
546
547
    /// Maximum number of bytes to decode on each grpc stream chunk.
548
    /// Default: 4 MiB
549
    #[serde(default, deserialize_with = "convert_data_size_with_shellexpand")]
550
    pub max_decoding_message_size: usize,
551
552
    /// Tls Configuration for this server.
553
    /// If not set, the server will not use TLS.
554
    ///
555
    /// Default: None
556
    #[serde(default)]
557
    pub tls: Option<TlsConfig>,
558
}
559
560
#[derive(Deserialize, Serialize, Debug)]
561
#[serde(deny_unknown_fields)]
562
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
563
pub struct ServerConfig {
564
    /// Name of the server. This is used to help identify the service
565
    /// for telemetry and logs.
566
    ///
567
    /// Default: {index of server in config}
568
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
569
    pub name: String,
570
571
    /// Configuration
572
    pub listener: ListenerConfig,
573
574
    /// Services to attach to server.
575
    pub services: Option<ServicesConfig>,
576
577
    /// The config related to identifying the client.
578
    /// Default: {see `IdentityHeaderSpec`}
579
    #[serde(default)]
580
    pub experimental_identity_header: IdentityHeaderSpec,
581
}
582
583
#[derive(Deserialize, Serialize, Debug)]
584
#[serde(rename_all = "snake_case")]
585
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
586
pub enum WorkerProperty {
587
    /// List of static values.
588
    /// Note: Generally there should only ever be 1 value, but if the platform
589
    /// property key is `PropertyType::Priority` it may have more than one value.
590
    #[serde(deserialize_with = "convert_vec_string_with_shellexpand")]
591
    Values(Vec<String>),
592
593
    /// A dynamic configuration. The string will be executed as a command
594
    /// (not sell) and will be split by "\n" (new line character).
595
    QueryCmd(String),
596
}
597
598
/// Generic config for an endpoint and associated configs.
599
#[derive(Deserialize, Serialize, Debug, Default)]
600
#[serde(deny_unknown_fields)]
601
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
602
pub struct EndpointConfig {
603
    /// URI of the endpoint.
604
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
605
    pub uri: String,
606
607
    /// Timeout in seconds that a request should take.
608
    /// Default: 5 (seconds)
609
    pub timeout: Option<f32>,
610
611
    /// The TLS configuration to use to connect to the endpoint.
612
    pub tls_config: Option<ClientTlsConfig>,
613
}
614
615
#[derive(Copy, Clone, Deserialize, Serialize, Debug, Default)]
616
#[serde(rename_all = "snake_case")]
617
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
618
pub enum UploadCacheResultsStrategy {
619
    /// Only upload action results with an exit code of 0.
620
    #[default]
621
    SuccessOnly,
622
623
    /// Don't upload any action results.
624
    Never,
625
626
    /// Upload all action results that complete.
627
    Everything,
628
629
    /// Only upload action results that fail.
630
    FailuresOnly,
631
}
632
633
#[derive(Clone, Deserialize, Serialize, Debug)]
634
#[serde(rename_all = "snake_case")]
635
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
636
pub enum EnvironmentSource {
637
    /// The name of the platform property in the action to get the value from.
638
    Property(String),
639
640
    /// The raw value to set.
641
    Value(#[serde(deserialize_with = "convert_string_with_shellexpand")] String),
642
643
    /// Take the value from the local environment corresponding to the name key
644
    FromEnvironment,
645
646
    /// The max amount of time in milliseconds the command is allowed to run
647
    /// (requested by the client).
648
    TimeoutMillis,
649
650
    /// A special file path will be provided that can be used to communicate
651
    /// with the parent process about out-of-band information. This file
652
    /// will be read after the command has finished executing. Based on the
653
    /// contents of the file, the behavior of the result may be modified.
654
    ///
655
    /// The format of the file contents should be json with the following
656
    /// schema:
657
    /// {
658
    ///   // If set the command will be considered a failure.
659
    ///   // May be one of the following static strings:
660
    ///   // "timeout": Will Consider this task to be a timeout.
661
    ///   "failure": "timeout",
662
    /// }
663
    ///
664
    /// All fields are optional, file does not need to be created and may be
665
    /// empty.
666
    SideChannelFile,
667
668
    /// A "root" directory for the action. This directory can be used to
669
    /// store temporary files that are not needed after the action has
670
    /// completed. This directory will be purged after the action has
671
    /// completed.
672
    ///
673
    /// For example:
674
    /// If an action writes temporary data to a path but nativelink should
675
    /// clean up this path after the job has executed, you may create any
676
    /// directory under the path provided in this variable. A common pattern
677
    /// would be to use `entrypoint` to set a shell script that reads this
678
    /// variable, `mkdir $ENV_VAR_NAME/tmp` and `export TMPDIR=$ENV_VAR_NAME/tmp`.
679
    /// Another example might be to bind-mount the `/tmp` path in a container to
680
    /// this path in `entrypoint`.
681
    ActionDirectory,
682
}
683
684
#[derive(Deserialize, Serialize, Debug, Default)]
685
#[serde(deny_unknown_fields)]
686
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
687
pub struct UploadActionResultConfig {
688
    /// Underlying AC store that the worker will use to publish execution results
689
    /// into. Objects placed in this store should be reachable from the
690
    /// scheduler/client-cas after they have finished updating.
691
    /// Default: {No uploading is done}
692
    pub ac_store: Option<StoreRefName>,
693
694
    /// In which situations should the results be published to the `ac_store`,
695
    /// if set to `SuccessOnly` then only results with an exit code of 0 will be
696
    /// uploaded, if set to Everything all completed results will be uploaded.
697
    ///
698
    /// Default: `UploadCacheResultsStrategy::SuccessOnly`
699
    #[serde(default)]
700
    pub upload_ac_results_strategy: UploadCacheResultsStrategy,
701
702
    /// Store to upload historical results to. This should be a CAS store if set.
703
    ///
704
    /// Default: {CAS store of parent}
705
    pub historical_results_store: Option<StoreRefName>,
706
707
    /// In which situations should the results be published to the historical CAS.
708
    /// The historical CAS is where failures are published. These messages conform
709
    /// to the CAS key-value lookup format and are always a `HistoricalExecuteResponse`
710
    /// serialized message.
711
    ///
712
    /// Default: `UploadCacheResultsStrategy::FailuresOnly`
713
    #[serde(default)]
714
    pub upload_historical_results_strategy: Option<UploadCacheResultsStrategy>,
715
716
    /// Template to use for the `ExecuteResponse.message` property. This message
717
    /// is attached to the response before it is sent to the client. The following
718
    /// special variables are supported:
719
    /// - `digest_function`: Digest function used to calculate the action digest.
720
    /// - `action_digest_hash`: Action digest hash.
721
    /// - `action_digest_size`: Action digest size.
722
    /// - `historical_results_hash`: `HistoricalExecuteResponse` digest hash.
723
    /// - `historical_results_size`: `HistoricalExecuteResponse` digest size.
724
    ///
725
    /// A common use case of this is to provide a link to the web page that
726
    /// contains more useful information for the user.
727
    ///
728
    /// An example that is fully compatible with `bb_browser` is:
729
    /// <https://example.com/my-instance-name-here/blobs/{digest_function}/action/{action_digest_hash}-{action_digest_size}/>
730
    ///
731
    /// Default: "" (no message)
732
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
733
    pub success_message_template: String,
734
735
    /// Same as `success_message_template` but for failure case.
736
    ///
737
    /// An example that is fully compatible with `bb_browser` is:
738
    /// <https://example.com/my-instance-name-here/blobs/{digest_function}/historical_execute_response/{historical_results_hash}-{historical_results_size}/>
739
    ///
740
    /// Default: "" (no message)
741
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
742
    pub failure_message_template: String,
743
}
744
745
#[derive(Deserialize, Serialize, Debug, Default)]
746
#[serde(deny_unknown_fields)]
747
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
748
pub struct LocalWorkerConfig {
749
    /// Name of the worker. This is give a more friendly name to a worker for logging
750
    /// and metric publishing. This is also the prefix of the worker id
751
    /// (ie: "{name}{uuidv6}").
752
    /// Default: {Index position in the workers list}
753
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
754
    pub name: String,
755
756
    /// Endpoint which the worker will connect to the scheduler's `WorkerApiService`.
757
    pub worker_api_endpoint: EndpointConfig,
758
759
    /// The maximum time an action is allowed to run. If a task requests for a timeout
760
    /// longer than this time limit, the task will be rejected. Value in seconds.
761
    ///
762
    /// Default: 1200 (seconds / 20 mins)
763
    #[serde(default, deserialize_with = "convert_duration_with_shellexpand")]
764
    pub max_action_timeout: usize,
765
766
    /// Maximum time allowed for uploading action results to CAS after execution
767
    /// completes. If upload takes longer than this, the action fails with
768
    /// `DeadlineExceeded` and may be retried by the scheduler. Value in seconds.
769
    ///
770
    /// Default: 600 (seconds / 10 mins)
771
    #[serde(default, deserialize_with = "convert_duration_with_shellexpand")]
772
    pub max_upload_timeout: usize,
773
774
    /// Maximum number of inflight tasks this worker can cope with.
775
    ///
776
    /// Default: 0 (infinite tasks)
777
    #[serde(default, deserialize_with = "convert_numeric_with_shellexpand")]
778
    pub max_inflight_tasks: u64,
779
780
    /// If timeout is handled in `entrypoint` or another wrapper script.
781
    /// If set to true `NativeLink` will not honor the timeout the action requested
782
    /// and instead will always force kill the action after `max_action_timeout`
783
    /// has been reached. If this is set to false, the smaller value of the action's
784
    /// timeout and `max_action_timeout` will be used to which `NativeLink` will kill
785
    /// the action.
786
    ///
787
    /// The real timeout can be received via an environment variable set in:
788
    /// `EnvironmentSource::TimeoutMillis`.
789
    ///
790
    /// Example on where this is useful: `entrypoint` launches the action inside
791
    /// a docker container, but the docker container may need to be downloaded. Thus
792
    /// the timer should not start until the docker container has started executing
793
    /// the action. In this case, action will likely be wrapped in another program,
794
    /// like `timeout` and propagate timeouts via `EnvironmentSource::SideChannelFile`.
795
    ///
796
    /// Default: false (`NativeLink` fully handles timeouts)
797
    #[serde(default)]
798
    pub timeout_handled_externally: bool,
799
800
    /// The command to execute on every execution request. This will be parsed as
801
    /// a command + arguments (not shell).
802
    /// Example: "run.sh" and a job with command: "sleep 5" will result in a
803
    /// command like: "run.sh sleep 5".
804
    /// Default: {Use the command from the job request}.
805
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
806
    pub entrypoint: String,
807
808
    /// An optional script to run before every action is processed on the worker.
809
    /// The value should be the full path to the script to execute and will pause
810
    /// all actions on the worker if it returns an exit code other than 0.
811
    /// If not set, then the worker will never pause and will continue to accept
812
    /// jobs according to the scheduler configuration.
813
    /// This is useful, for example, if the worker should not take any more
814
    /// actions until there is enough resource available on the machine to
815
    /// handle them.
816
    pub experimental_precondition_script: Option<String>,
817
818
    /// Underlying CAS store that the worker will use to download CAS artifacts.
819
    /// This store must be a `FastSlowStore`. The `fast` store must be a
820
    /// `FileSystemStore` because it will use hardlinks when building out the files
821
    /// instead of copying the files. The slow store must eventually resolve to the
822
    /// same store the scheduler/client uses to send job requests.
823
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
824
    pub cas_fast_slow_store: StoreRefName,
825
826
    /// Configuration for uploading action results.
827
    #[serde(default)]
828
    pub upload_action_result: UploadActionResultConfig,
829
830
    /// The directory work jobs will be executed from. This directory will be fully
831
    /// managed by the worker service and will be purged on startup.
832
    /// This directory and the directory referenced in `local_filesystem_store_ref`'s
833
    /// `stores::FilesystemStore::content_path` must be on the same filesystem.
834
    /// Hardlinks will be used when placing files that are accessible to the jobs
835
    /// that are sourced from `local_filesystem_store_ref`'s `content_path`.
836
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
837
    pub work_directory: String,
838
839
    /// Properties of this worker. This configuration will be sent to the scheduler
840
    /// and used to tell the scheduler to restrict what should be executed on this
841
    /// worker.
842
    pub platform_properties: HashMap<String, WorkerProperty>,
843
844
    /// An optional mapping of environment names to set for the execution
845
    /// as well as those specified in the action itself.  If set, will set each
846
    /// key as an environment variable before executing the job with the value
847
    /// of the environment variable being the value of the property of the
848
    /// action being executed of that name or the fixed value.
849
    pub additional_environment: Option<HashMap<String, EnvironmentSource>>,
850
851
    /// Optional directory cache configuration for improving performance by caching
852
    /// reconstructed input directories and using hardlinks instead of rebuilding
853
    /// them from CAS for every action.
854
    /// Default: None (directory cache disabled)
855
    pub directory_cache: Option<DirectoryCacheConfig>,
856
857
    /// Whether to use namespaces to isolate the execution.  This is only available
858
    /// on Linux.  It is highly recommended as it avoids a number of issues with
859
    /// zombie processes and also provides additional hermeticity.  If explicitly set
860
    /// to true and it is not supported the worker will exit with an error.
861
    /// Default: False.
862
    pub use_namespaces: Option<bool>,
863
864
    /// Whether to use a mount namespace to isolate the worker root.  This is only
865
    /// available on Linux and when `use_namespaces` is true.  It is highly recommended
866
    /// provides additional hermeticity.  If explicitly set to true and it is not
867
    /// supported or `use_namespaces` is not set to true the worker will exit with an
868
    /// error.
869
    /// Default: False.
870
    pub use_mount_namespace: Option<bool>,
871
}
872
873
#[derive(Deserialize, Serialize, Debug, Clone)]
874
#[serde(deny_unknown_fields)]
875
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
876
pub struct DirectoryCacheConfig {
877
    /// Maximum number of cached directories.
878
    /// Default: 1000
879
    #[serde(default = "default_directory_cache_max_entries")]
880
    pub max_entries: usize,
881
882
    /// Maximum total size in bytes for all cached directories (0 = unlimited).
883
    /// Default: 10737418240 (10 GB)
884
    #[serde(
885
        default = "default_directory_cache_max_size_bytes",
886
        deserialize_with = "convert_data_size_with_shellexpand"
887
    )]
888
    pub max_size_bytes: u64,
889
890
    /// Base directory for cache storage. This directory will be managed by
891
    /// the worker and should be on the same filesystem as `work_directory`.
892
    /// Default: `{work_directory}/../directory_cache`
893
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
894
    pub cache_root: String,
895
}
896
897
0
const fn default_directory_cache_max_entries() -> usize {
898
0
    1000
899
0
}
900
901
0
const fn default_directory_cache_max_size_bytes() -> u64 {
902
0
    10 * 1024 * 1024 * 1024 // 10 GB
903
0
}
904
905
#[derive(Deserialize, Serialize, Debug)]
906
#[serde(rename_all = "snake_case")]
907
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
908
pub enum WorkerConfig {
909
    /// A worker type that executes jobs locally on this machine.
910
    Local(LocalWorkerConfig),
911
}
912
913
#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
914
#[serde(deny_unknown_fields)]
915
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
916
pub struct GlobalConfig {
917
    /// Maximum number of open files that can be opened at one time.
918
    /// This value is not strictly enforced, it is a best effort. Some internal libraries
919
    /// open files or read metadata from a files which do not obey this limit, however
920
    /// the vast majority of cases will have this limit be honored.
921
    /// This value must be larger than `ulimit -n` to have any effect.
922
    /// Any network open file descriptors is not counted in this limit, but is counted
923
    /// in the kernel limit. It is a good idea to set a very large `ulimit -n`.
924
    /// Note: This value must be greater than 10.
925
    ///
926
    /// Default: 24576 (= 24 * 1024)
927
    #[serde(deserialize_with = "convert_numeric_with_shellexpand")]
928
    pub max_open_files: usize,
929
930
    /// Default hash function to use while uploading blobs to the CAS when not set
931
    /// by client.
932
    ///
933
    /// Default: `ConfigDigestHashFunction::sha256`
934
    pub default_digest_hash_function: Option<ConfigDigestHashFunction>,
935
936
    /// Default digest size to use for health check when running
937
    /// diagnostics checks. Health checks are expected to use this
938
    /// size for filling a buffer that is used for creation of
939
    /// digest.
940
    ///
941
    /// Default: 1024*1024 (1MiB)
942
    #[serde(default, deserialize_with = "convert_data_size_with_shellexpand")]
943
    pub default_digest_size_health_check: usize,
944
}
945
946
pub type StoreConfig = NamedConfig<StoreSpec>;
947
pub type SchedulerConfig = NamedConfig<SchedulerSpec>;
948
949
#[derive(Deserialize, Serialize, Debug)]
950
#[serde(deny_unknown_fields)]
951
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
952
pub struct CasConfig {
953
    /// List of stores available to use in this config.
954
    /// The keys can be used in other configs when needing to reference a store.
955
    pub stores: Vec<StoreConfig>,
956
957
    /// Worker configurations used to execute jobs.
958
    pub workers: Option<Vec<WorkerConfig>>,
959
960
    /// List of schedulers available to use in this config.
961
    /// The keys can be used in other configs when needing to reference a
962
    /// scheduler.
963
    pub schedulers: Option<Vec<SchedulerConfig>>,
964
965
    /// Servers to setup for this process.
966
    pub servers: Vec<ServerConfig>,
967
968
    /// Experimental - Origin events configuration. This is the service that will
969
    /// collect and publish nativelink events to a store for processing by an
970
    /// external service.
971
    pub experimental_origin_events: Option<OriginEventsSpec>,
972
973
    /// Any global configurations that apply to all modules live here.
974
    pub global: Option<GlobalConfig>,
975
}
976
977
impl CasConfig {
978
    /// # Errors
979
    ///
980
    /// Will return `Err` if we can't load the file.
981
13
    pub fn try_from_json5_file(config_file: &str) -> Result<Self, Error> {
982
13
        let json_contents = std::fs::read_to_string(config_file)
983
13
            .err_tip(|| 
format!0
("Could not open config file {config_file}"))
?0
;
984
13
        Ok(serde_json5::from_str(&json_contents)
?0
)
985
13
    }
986
}