Coverage Report

Created: 2026-07-21 15:28

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::{Code, Error, ResultExt, make_err};
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_boolean_with_shellexpand, convert_data_size_with_shellexpand,
25
    convert_duration_with_shellexpand, convert_numeric_with_shellexpand,
26
    convert_optional_numeric_with_shellexpand, convert_optional_string_with_shellexpand,
27
    convert_string_with_shellexpand, 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
    /// Used when the config references `instance_name` in the protocol.
42
    #[serde(default)]
43
    pub instance_name: InstanceName,
44
    #[serde(flatten)]
45
    pub config: T,
46
}
47
48
impl<T> core::ops::Deref for WithInstanceName<T> {
49
    type Target = T;
50
51
261
    fn deref(&self) -> &Self::Target {
52
261
        &self.config
53
261
    }
54
}
55
56
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
57
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
58
pub struct NamedConfig<Spec> {
59
    pub name: String,
60
    #[serde(flatten)]
61
    pub spec: Spec,
62
}
63
64
#[derive(Deserialize, Serialize, Debug, Default, Clone, Copy)]
65
#[serde(rename_all = "snake_case")]
66
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
67
pub enum HttpCompressionAlgorithm {
68
    /// No compression.
69
    #[default]
70
    None,
71
72
    /// Zlib compression.
73
    Gzip,
74
}
75
76
/// Note: Compressing data in the cloud rarely has a benefit, since most
77
/// cloud providers have very high bandwidth backplanes. However, for
78
/// clients not inside the data center, it might be a good idea to
79
/// compress data to and from the cloud. This will however come at a high
80
/// CPU and performance cost. If you are making remote execution share the
81
/// same CAS/AC servers as client's remote cache, you can create multiple
82
/// services with different compression settings that are served on
83
/// different ports. Then configure the non-cloud clients to use one port
84
/// and cloud-clients to use another.
85
#[derive(Deserialize, Serialize, Debug, Default)]
86
#[serde(deny_unknown_fields)]
87
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
88
pub struct HttpCompressionConfig {
89
    /// The compression algorithm that the server will use when sending
90
    /// responses to clients. Enabling this will likely save a lot of
91
    /// data transfer, but will consume a lot of CPU and add a lot of
92
    /// latency.
93
    /// see: <https://github.com/tracemachina/nativelink/issues/109>
94
    ///
95
    /// Default: `HttpCompressionAlgorithm::None`
96
    pub send_compression_algorithm: Option<HttpCompressionAlgorithm>,
97
98
    /// The compression algorithm that the server will accept from clients.
99
    /// The server will broadcast the supported compression algorithms to
100
    /// clients and the client will choose which compression algorithm to
101
    /// use. Enabling this will likely save a lot of data transfer, but
102
    /// will consume a lot of CPU and add a lot of latency.
103
    /// see: <https://github.com/tracemachina/nativelink/issues/109>
104
    ///
105
    /// Default: {no supported compression}
106
    pub accepted_compression_algorithms: Vec<HttpCompressionAlgorithm>,
107
}
108
109
#[derive(Deserialize, Serialize, Debug)]
110
#[serde(deny_unknown_fields)]
111
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
112
pub struct AcStoreConfig {
113
    /// The store name referenced in the `stores` map in the main config.
114
    /// This store name referenced here may be reused multiple times.
115
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
116
    pub ac_store: StoreRefName,
117
118
    /// Whether the Action Cache store may be written to, this if set to false
119
    /// it is only possible to read from the Action Cache.
120
    #[serde(default)]
121
    pub read_only: bool,
122
}
123
124
#[derive(Deserialize, Serialize, Debug, Clone)]
125
#[serde(deny_unknown_fields)]
126
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
127
pub struct CasStoreConfig {
128
    /// The store name referenced in the `stores` map in the main config.
129
    /// This store name referenced here may be reused multiple times.
130
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
131
    pub cas_store: StoreRefName,
132
133
    /// Optional and experimental: enables the REAPI `SplitBlob`/`SpliceBlob`
134
    /// RPCs used by content-defined chunking clients (e.g. Bazel's
135
    /// `--experimental_remote_cache_chunking`). When set, the capabilities
136
    /// service advertises blob split/splice support and `FastCDC` 2020
137
    /// parameters for this instance. When `cas_store` is a grpc store the
138
    /// RPCs are forwarded to the backend (which must support chunking with
139
    /// matching parameters); otherwise they are served locally.
140
    ///
141
    /// See `nativelink-config/examples/chunking_cas.json5` for a complete
142
    /// configuration example.
143
    ///
144
    /// Default: not set — chunking RPCs are rejected, nothing is advertised,
145
    /// and behavior is identical to when this option did not exist.
146
    #[serde(default)]
147
    pub experimental_chunking: Option<CasChunkingConfig>,
148
}
149
150
#[derive(Deserialize, Serialize, Debug, Clone)]
151
#[serde(deny_unknown_fields)]
152
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
153
pub struct CasChunkingConfig {
154
    /// The store name referenced in the `stores` map in the main config used
155
    /// to persist blob-to-chunks layouts. Keys are the digests of the
156
    /// original blobs and values are serialized chunk layouts (which do not
157
    /// hash to those digests), so this store MUST NOT perform content digest
158
    /// verification and MUST NOT be the same store as `cas_store` — writing
159
    /// layouts into the CAS would overwrite blob content. Using the same
160
    /// store name as `cas_store` is rejected at startup.
161
    ///
162
    /// Required unless `cas_store` is a grpc store: for proxied instances
163
    /// the `SplitBlob`/`SpliceBlob` RPCs are forwarded to the backend, which
164
    /// owns the chunk layouts, and setting an `index_store` is rejected at
165
    /// startup.
166
    #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")]
167
    pub index_store: Option<StoreRefName>,
168
169
    /// The average chunk size in bytes advertised to clients through the
170
    /// `FastCDC` 2020 capability parameters and used for server-side
171
    /// chunking in `SplitBlob`. Clients derive the minimum and maximum
172
    /// chunk sizes from this value (avg / 4 and avg * 4). The value must
173
    /// be between 1 KiB and 1 MiB.
174
    ///
175
    /// Default: 524288 (512 KiB)
176
    #[serde(default)]
177
    pub avg_chunk_size_bytes: u64,
178
179
    /// Maximum number of chunks accepted in a `SpliceBlob` request or
180
    /// produced by on-demand chunking in `SplitBlob`. Blobs that would
181
    /// produce more chunks are served without chunking (`SplitBlob` returns
182
    /// `NOT_FOUND` and clients fall back to a regular download). This bounds
183
    /// the size of stored chunk layouts and of `SplitBlobResponse` messages
184
    /// (roughly 80-140 bytes per chunk). At the default average chunk size
185
    /// the default cap supports blobs up to ~25 GiB; note that values above
186
    /// ~50000 may produce responses that exceed default gRPC message size
187
    /// limits on clients.
188
    ///
189
    /// Default: 50000
190
    #[serde(default)]
191
    pub max_chunk_count: u64,
192
}
193
194
impl CasChunkingConfig {
195
    /// Default for `avg_chunk_size_bytes`, the value recommended by the
196
    /// REAPI spec for `FastCdc2020Params`.
197
    pub const DEFAULT_AVG_CHUNK_SIZE_BYTES: u64 = 512 * 1024;
198
    /// Bounds for `avg_chunk_size_bytes` mandated by the REAPI spec for
199
    /// `FastCdc2020Params`.
200
    pub const MIN_AVG_CHUNK_SIZE_BYTES: u64 = 1024;
201
    pub const MAX_AVG_CHUNK_SIZE_BYTES: u64 = 1024 * 1024;
202
    /// Default for `max_chunk_count`.
203
    pub const DEFAULT_MAX_CHUNK_COUNT: u64 = 50_000;
204
205
    /// Returns `avg_chunk_size_bytes` with the default applied.
206
    #[must_use]
207
13
    pub const fn resolved_avg_chunk_size_bytes(&self) -> u64 {
208
13
        if self.avg_chunk_size_bytes == 0 {
209
11
            Self::DEFAULT_AVG_CHUNK_SIZE_BYTES
210
        } else {
211
2
            self.avg_chunk_size_bytes
212
        }
213
13
    }
214
215
    /// Returns `max_chunk_count` with the default applied.
216
    #[must_use]
217
0
    pub const fn resolved_max_chunk_count(&self) -> u64 {
218
0
        if self.max_chunk_count == 0 {
219
0
            Self::DEFAULT_MAX_CHUNK_COUNT
220
        } else {
221
0
            self.max_chunk_count
222
        }
223
0
    }
224
225
    /// Returns `avg_chunk_size_bytes` with the default applied, or an error
226
    /// when the configured value is outside the REAPI-mandated bounds.
227
13
    pub fn validated_avg_chunk_size_bytes(&self) -> Result<u64, Error> {
228
13
        let avg_chunk_size_bytes = self.resolved_avg_chunk_size_bytes();
229
13
        if !(Self::MIN_AVG_CHUNK_SIZE_BYTES..=Self::MAX_AVG_CHUNK_SIZE_BYTES)
230
13
            .contains(&avg_chunk_size_bytes)
231
        {
232
0
            return Err(make_err!(
233
0
                Code::InvalidArgument,
234
0
                "'experimental_chunking.avg_chunk_size_bytes' is {avg_chunk_size_bytes}, must be between {} and {}",
235
0
                Self::MIN_AVG_CHUNK_SIZE_BYTES,
236
0
                Self::MAX_AVG_CHUNK_SIZE_BYTES
237
0
            ));
238
13
        }
239
13
        Ok(avg_chunk_size_bytes)
240
13
    }
241
}
242
243
#[derive(Deserialize, Serialize, Debug, Default)]
244
#[serde(deny_unknown_fields)]
245
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
246
pub struct CapabilitiesRemoteExecutionConfig {
247
    /// Scheduler used to configure the capabilities of remote execution.
248
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
249
    pub scheduler: SchedulerRefName,
250
}
251
252
#[derive(Deserialize, Serialize, Debug, Default)]
253
#[serde(deny_unknown_fields)]
254
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
255
pub struct CapabilitiesConfig {
256
    /// Configuration for remote execution capabilities.
257
    /// If not set the capabilities service will inform the client that remote
258
    /// execution is not supported.
259
    pub remote_execution: Option<CapabilitiesRemoteExecutionConfig>,
260
261
    /// Whether this instance supports Bazel remote cache compression.
262
    /// When enabled, the capabilities service advertises zstd wire compression
263
    /// and the ByteStream/CAS services accept REAPI compressed-blobs/zstd data.
264
    ///
265
    /// Bazel clients enable this with `--remote_cache_compression`.
266
    #[serde(
267
        default,
268
        skip_serializing_if = "is_default",
269
        deserialize_with = "convert_boolean_with_shellexpand"
270
    )]
271
    pub remote_cache_compression: bool,
272
}
273
274
#[derive(Deserialize, Serialize, Debug)]
275
#[serde(deny_unknown_fields)]
276
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
277
pub struct ExecutionConfig {
278
    /// The store name referenced in the `stores` map in the main config.
279
    /// This store name referenced here may be reused multiple times.
280
    /// This value must be a CAS store reference.
281
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
282
    pub cas_store: StoreRefName,
283
284
    /// The scheduler name referenced in the `schedulers` map in the main config.
285
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
286
    pub scheduler: SchedulerRefName,
287
}
288
289
#[derive(Deserialize, Serialize, Debug, Clone)]
290
#[serde(deny_unknown_fields)]
291
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
292
pub struct FetchConfig {
293
    /// The store name referenced in the `stores` map in the main config.
294
    /// This store name referenced here may be reused multiple times.
295
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
296
    pub fetch_store: StoreRefName,
297
}
298
299
#[derive(Deserialize, Serialize, Debug, Clone)]
300
#[serde(deny_unknown_fields)]
301
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
302
pub struct PushConfig {
303
    /// The store name referenced in the `stores` map in the main config.
304
    /// This store name referenced here may be reused multiple times.
305
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
306
    pub push_store: StoreRefName,
307
308
    /// Whether the Action Cache store may be written to, this if set to false
309
    /// it is only possible to read from the Action Cache.
310
    #[serde(default)]
311
    pub read_only: bool,
312
}
313
314
// From https://github.com/serde-rs/serde/issues/818#issuecomment-287438544
315
94
fn is_default<T: Default + PartialEq>(t: &T) -> bool {
316
94
    *t == Default::default()
317
94
}
318
319
#[derive(Deserialize, Serialize, Debug, Default, PartialEq, Eq)]
320
#[serde(deny_unknown_fields)]
321
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
322
pub struct ByteStreamConfig {
323
    /// Name of the store in the "stores" configuration.
324
    pub cas_store: StoreRefName,
325
326
    /// Max number of bytes to send on each grpc stream chunk.
327
    /// According to <https://github.com/grpc/grpc.github.io/issues/371>
328
    /// 16KiB - 64KiB is optimal.
329
    ///
330
    ///
331
    /// Default: 64KiB
332
    #[serde(
333
        default,
334
        deserialize_with = "convert_data_size_with_shellexpand",
335
        skip_serializing_if = "is_default"
336
    )]
337
    pub max_bytes_per_stream: usize,
338
339
    /// In the event a client disconnects while uploading a blob, we will hold
340
    /// the internal stream open for this many seconds before closing it.
341
    /// This allows clients that disconnect to reconnect and continue uploading
342
    /// the same blob.
343
    ///
344
    /// Default: 10 seconds
345
    #[serde(
346
        default,
347
        deserialize_with = "convert_duration_with_shellexpand",
348
        skip_serializing_if = "is_default",
349
        alias = "persist_stream_on_disconnect_timeout"
350
    )]
351
    pub persist_stream_on_disconnect_timeout_s: usize,
352
}
353
354
// Older bytestream config. All fields are as per the newer docs, but this requires
355
// the hashed `cas_stores` v.s. the WithInstanceName approach. This should _not_ be updated
356
// with newer fields, and eventually dropped
357
#[derive(Deserialize, Serialize, Debug, Clone)]
358
#[serde(deny_unknown_fields)]
359
pub struct OldByteStreamConfig {
360
    pub cas_stores: HashMap<InstanceName, StoreRefName>,
361
    #[serde(
362
        default,
363
        deserialize_with = "convert_data_size_with_shellexpand",
364
        skip_serializing_if = "is_default"
365
    )]
366
    pub max_bytes_per_stream: usize,
367
    #[serde(
368
        default,
369
        deserialize_with = "convert_data_size_with_shellexpand",
370
        skip_serializing_if = "is_default"
371
    )]
372
    pub max_decoding_message_size: usize,
373
    #[serde(
374
        default,
375
        deserialize_with = "convert_duration_with_shellexpand",
376
        skip_serializing_if = "is_default",
377
        alias = "persist_stream_on_disconnect_timeout"
378
    )]
379
    pub persist_stream_on_disconnect_timeout_s: usize,
380
}
381
382
#[derive(Deserialize, Serialize, Debug)]
383
#[serde(deny_unknown_fields)]
384
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
385
pub struct WorkerApiConfig {
386
    /// The scheduler name referenced in the `schedulers` map in the main config.
387
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
388
    pub scheduler: SchedulerRefName,
389
}
390
391
#[derive(Deserialize, Serialize, Debug, Default)]
392
#[serde(deny_unknown_fields)]
393
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
394
pub struct AdminConfig {
395
    /// Path to register the admin API. If path is "/admin", and your
396
    /// domain is "example.com", you can reach the endpoint with:
397
    /// <http://example.com/admin>.
398
    ///
399
    /// Default: "/admin"
400
    #[serde(default)]
401
    pub path: String,
402
}
403
404
#[derive(Deserialize, Serialize, Debug, Default)]
405
#[serde(deny_unknown_fields)]
406
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
407
pub struct HealthConfig {
408
    /// Path to register the health status check. If path is "/status", and your
409
    /// domain is "example.com", you can reach the endpoint with:
410
    /// <http://example.com/status>.
411
    ///
412
    /// Default: "/status"
413
    #[serde(default)]
414
    pub path: String,
415
416
    /// Timeout on health checks. Default: 5s.
417
    #[serde(default)]
418
    pub timeout_seconds: u64,
419
}
420
421
#[derive(Deserialize, Serialize, Debug)]
422
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
423
pub struct BepConfig {
424
    /// The store to publish build events to.
425
    /// The store name referenced in the `stores` map in the main config.
426
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
427
    pub store: StoreRefName,
428
}
429
430
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
431
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
432
pub struct IdentityHeaderSpec {
433
    /// The name of the header to look for the identity in.
434
    /// Default: "x-identity"
435
    #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")]
436
    pub header_name: Option<String>,
437
438
    /// If the header is required to be set or fail the request.
439
    #[serde(default)]
440
    pub required: bool,
441
}
442
443
#[derive(Deserialize, Serialize, Clone, Debug)]
444
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
445
pub struct OriginEventsPublisherSpec {
446
    /// The store to publish nativelink events to.
447
    /// The store name referenced in the `stores` map in the main config.
448
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
449
    pub store: StoreRefName,
450
}
451
452
#[derive(Deserialize, Serialize, Clone, Debug)]
453
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
454
pub struct OriginEventsSpec {
455
    /// The publisher configuration for origin events.
456
    pub publisher: OriginEventsPublisherSpec,
457
458
    /// The maximum number of events to queue before applying back pressure.
459
    /// IMPORTANT: Backpressure causes all clients to slow down significantly.
460
    /// Zero is default.
461
    ///
462
    /// Default: 65536 (zero defaults to this)
463
    #[serde(default, deserialize_with = "convert_numeric_with_shellexpand")]
464
    pub max_event_queue_size: usize,
465
}
466
467
#[derive(Deserialize, Serialize, Debug)]
468
#[serde(deny_unknown_fields)]
469
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
470
pub struct ServicesConfig {
471
    /// The Content Addressable Storage (CAS) backend config.
472
    /// The key is the `instance_name` used in the protocol and the
473
    /// value is the underlying CAS store config.
474
    #[serde(
475
        default,
476
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
477
    )]
478
    pub cas: Option<Vec<WithInstanceName<CasStoreConfig>>>,
479
480
    /// The Action Cache (AC) backend config.
481
    /// The key is the `instance_name` used in the protocol and the
482
    /// value is the underlying AC store config.
483
    #[serde(
484
        default,
485
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
486
    )]
487
    pub ac: Option<Vec<WithInstanceName<AcStoreConfig>>>,
488
489
    /// Capabilities service is required in order to use most of the
490
    /// bazel protocol. This service is used to provide the supported
491
    /// features and versions of this bazel gRPC service.
492
    #[serde(
493
        default,
494
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
495
    )]
496
    pub capabilities: Option<Vec<WithInstanceName<CapabilitiesConfig>>>,
497
498
    /// The remote execution service configuration.
499
    /// NOTE: This service is under development and is currently just a
500
    /// place holder.
501
    #[serde(
502
        default,
503
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
504
    )]
505
    pub execution: Option<Vec<WithInstanceName<ExecutionConfig>>>,
506
507
    /// This is the service used to stream data to and from the CAS.
508
    /// Bazel's protocol strongly encourages users to use this streaming
509
    /// interface to interact with the CAS when the data is large.
510
    #[serde(default, deserialize_with = "super::backcompat::opt_bytestream")]
511
    pub bytestream: Option<Vec<WithInstanceName<ByteStreamConfig>>>,
512
513
    /// These two are collectively the Remote Asset protocol, but it's
514
    /// defined as two separate services
515
    #[serde(
516
        default,
517
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
518
    )]
519
    pub fetch: Option<Vec<WithInstanceName<FetchConfig>>>,
520
521
    #[serde(
522
        default,
523
        deserialize_with = "super::backcompat::opt_vec_with_instance_name"
524
    )]
525
    pub push: Option<Vec<WithInstanceName<PushConfig>>>,
526
527
    /// This is the service used for workers to connect and communicate
528
    /// through.
529
    /// NOTE: This service should be served on a different, non-public port.
530
    /// In other words, `worker_api` configuration should not have any other
531
    /// services that are served on the same port. Doing so is a security
532
    /// risk, as workers have a different permission set than a client
533
    /// that makes the remote execution/cache requests.
534
    pub worker_api: Option<WorkerApiConfig>,
535
536
    /// Experimental - Build Event Protocol (BEP) configuration. This is
537
    /// the service that will consume build events from the client and
538
    /// publish them to a store for processing by an external service.
539
    pub experimental_bep: Option<BepConfig>,
540
541
    /// This is the service for any administrative tasks.
542
    /// It provides a REST API endpoint for administrative purposes.
543
    pub admin: Option<AdminConfig>,
544
545
    /// This is the service for health status check.
546
    pub health: Option<HealthConfig>,
547
}
548
549
#[derive(Deserialize, Serialize, Debug)]
550
#[serde(deny_unknown_fields)]
551
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
552
pub struct TlsConfig {
553
    /// Path to the certificate file.
554
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
555
    pub cert_file: String,
556
557
    /// Path to the private key file.
558
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
559
    pub key_file: String,
560
561
    /// Path to the certificate authority for mTLS, if client authentication is
562
    /// required for this endpoint.
563
    #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")]
564
    pub client_ca_file: Option<String>,
565
566
    /// Path to the certificate revocation list for mTLS, if client
567
    /// authentication is required for this endpoint.
568
    #[serde(default, deserialize_with = "convert_optional_string_with_shellexpand")]
569
    pub client_crl_file: Option<String>,
570
}
571
572
/// Advanced Http configurations. These are generally should not be set.
573
/// For documentation on what each of these do, see the hyper documentation:
574
/// See: <https://docs.rs/hyper/latest/hyper/server/conn/struct.Http.html>
575
///
576
/// Note: All of these default to hyper's default values unless otherwise
577
/// specified.
578
#[derive(Deserialize, Serialize, Debug, Default, Clone, Copy)]
579
#[serde(deny_unknown_fields)]
580
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
581
pub struct HttpServerConfig {
582
    /// Interval to send keep-alive pings via HTTP2.
583
    /// Note: This is in seconds.
584
    #[serde(
585
        default,
586
        deserialize_with = "convert_optional_numeric_with_shellexpand"
587
    )]
588
    pub http2_keep_alive_interval: Option<u32>,
589
590
    #[serde(
591
        default,
592
        deserialize_with = "convert_optional_numeric_with_shellexpand"
593
    )]
594
    pub experimental_http2_max_pending_accept_reset_streams: Option<u32>,
595
596
    #[serde(
597
        default,
598
        deserialize_with = "convert_optional_numeric_with_shellexpand"
599
    )]
600
    pub experimental_http2_initial_stream_window_size: Option<u32>,
601
602
    #[serde(
603
        default,
604
        deserialize_with = "convert_optional_numeric_with_shellexpand"
605
    )]
606
    pub experimental_http2_initial_connection_window_size: Option<u32>,
607
608
    #[serde(default)]
609
    pub experimental_http2_adaptive_window: Option<bool>,
610
611
    #[serde(
612
        default,
613
        deserialize_with = "convert_optional_numeric_with_shellexpand"
614
    )]
615
    pub experimental_http2_max_frame_size: Option<u32>,
616
617
    #[serde(
618
        default,
619
        deserialize_with = "convert_optional_numeric_with_shellexpand"
620
    )]
621
    pub experimental_http2_max_concurrent_streams: Option<u32>,
622
623
    #[serde(
624
        default,
625
        deserialize_with = "convert_optional_numeric_with_shellexpand",
626
        alias = "experimental_http2_keep_alive_timeout"
627
    )]
628
    pub experimental_http2_keep_alive_timeout_s: Option<u32>,
629
630
    #[serde(
631
        default,
632
        deserialize_with = "convert_optional_numeric_with_shellexpand"
633
    )]
634
    pub experimental_http2_max_send_buf_size: Option<u32>,
635
636
    #[serde(default)]
637
    pub experimental_http2_enable_connect_protocol: Option<bool>,
638
639
    #[serde(
640
        default,
641
        deserialize_with = "convert_optional_numeric_with_shellexpand"
642
    )]
643
    pub experimental_http2_max_header_list_size: Option<u32>,
644
}
645
646
#[derive(Deserialize, Serialize, Debug)]
647
#[serde(rename_all = "snake_case")]
648
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
649
pub enum ListenerConfig {
650
    /// Listener for HTTP/HTTPS/HTTP2 sockets.
651
    Http(HttpListener),
652
}
653
654
#[derive(Deserialize, Serialize, Debug, Default)]
655
#[serde(deny_unknown_fields)]
656
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
657
pub struct HttpListener {
658
    /// Address to listen on. Example: `127.0.0.1:8080` or `:8080` to listen
659
    /// to all IPs.
660
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
661
    pub socket_address: String,
662
663
    /// Allow binding `socket_address` before it is assigned locally.
664
    ///
665
    /// Default: false
666
    #[serde(default)]
667
    pub freebind: bool,
668
669
    /// Data transport compression configuration to use for this service.
670
    #[serde(default)]
671
    pub compression: HttpCompressionConfig,
672
673
    /// Advanced Http server configuration.
674
    #[serde(default)]
675
    pub advanced_http: HttpServerConfig,
676
677
    /// Maximum number of bytes to decode on each grpc stream chunk.
678
    /// Default: 4 MiB
679
    #[serde(default, deserialize_with = "convert_data_size_with_shellexpand")]
680
    pub max_decoding_message_size: usize,
681
682
    /// Tls Configuration for this server.
683
    /// If not set, the server will not use TLS.
684
    ///
685
    /// Default: None
686
    #[serde(default)]
687
    pub tls: Option<TlsConfig>,
688
}
689
690
#[derive(Deserialize, Serialize, Debug)]
691
#[serde(deny_unknown_fields)]
692
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
693
pub struct ServerConfig {
694
    /// Name of the server. This is used to help identify the service
695
    /// for telemetry and logs.
696
    ///
697
    /// Default: {index of server in config}
698
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
699
    pub name: String,
700
701
    /// Configuration
702
    pub listener: ListenerConfig,
703
704
    /// Services to attach to server.
705
    pub services: Option<ServicesConfig>,
706
707
    /// The config related to identifying the client.
708
    /// Default: {see `IdentityHeaderSpec`}
709
    #[serde(default)]
710
    pub experimental_identity_header: IdentityHeaderSpec,
711
}
712
713
#[derive(Deserialize, Serialize, Debug)]
714
#[serde(rename_all = "snake_case")]
715
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
716
pub enum WorkerProperty {
717
    /// List of static values.
718
    /// Note: Generally there should only ever be 1 value, but if the platform
719
    /// property key is `PropertyType::Priority` it may have more than one value.
720
    #[serde(deserialize_with = "convert_vec_string_with_shellexpand")]
721
    Values(Vec<String>),
722
723
    /// A dynamic configuration. The string will be executed as a command
724
    /// (not shell) and will be split by "\n" (new line character).
725
    QueryCmd(String),
726
}
727
728
/// Generic config for an endpoint and associated configs.
729
#[derive(Deserialize, Serialize, Debug, Default)]
730
#[serde(deny_unknown_fields)]
731
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
732
pub struct EndpointConfig {
733
    /// URI of the endpoint.
734
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
735
    pub uri: String,
736
737
    /// Timeout in seconds that a request should take.
738
    /// Default: 5 seconds
739
    pub timeout: Option<f32>,
740
741
    /// The TLS configuration to use to connect to the endpoint.
742
    pub tls_config: Option<ClientTlsConfig>,
743
}
744
745
#[derive(Copy, Clone, Deserialize, Serialize, Debug, Default)]
746
#[serde(rename_all = "snake_case")]
747
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
748
pub enum UploadCacheResultsStrategy {
749
    /// Only upload action results with an exit code of 0.
750
    #[default]
751
    SuccessOnly,
752
753
    /// Don't upload any action results.
754
    Never,
755
756
    /// Upload all action results that complete.
757
    Everything,
758
759
    /// Only upload action results that fail.
760
    FailuresOnly,
761
}
762
763
#[derive(Clone, Deserialize, Serialize, Debug)]
764
#[serde(rename_all = "snake_case")]
765
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
766
pub enum EnvironmentSource {
767
    /// The name of the platform property in the action to get the value from.
768
    Property(String),
769
770
    /// The raw value to set.
771
    Value(#[serde(deserialize_with = "convert_string_with_shellexpand")] String),
772
773
    /// Take the value from the local environment corresponding to the name key
774
    FromEnvironment,
775
776
    /// The max amount of time in milliseconds the command is allowed to run
777
    /// (requested by the client).
778
    TimeoutMillis,
779
780
    /// A special file path will be provided that can be used to communicate
781
    /// with the parent process about out-of-band information. This file
782
    /// will be read after the command has finished executing. Based on the
783
    /// contents of the file, the behavior of the result may be modified.
784
    ///
785
    /// The format of the file contents should be json with the following
786
    /// schema:
787
    /// {
788
    ///   // If set the command will be considered a failure.
789
    ///   // May be one of the following static strings:
790
    ///   // "timeout": Will Consider this task to be a timeout.
791
    ///   "failure": "timeout",
792
    /// }
793
    ///
794
    /// All fields are optional, file does not need to be created and may be
795
    /// empty.
796
    SideChannelFile,
797
798
    /// A "root" directory for the action. This directory can be used to
799
    /// store temporary files that are not needed after the action has
800
    /// completed. This directory will be purged after the action has
801
    /// completed.
802
    ///
803
    /// For example:
804
    /// If an action writes temporary data to a path but nativelink should
805
    /// clean up this path after the job has executed, you may create any
806
    /// directory under the path provided in this variable. A common pattern
807
    /// would be to use `entrypoint` to set a shell script that reads this
808
    /// variable, `mkdir $ENV_VAR_NAME/tmp` and `export TMPDIR=$ENV_VAR_NAME/tmp`.
809
    /// Another example might be to bind-mount the `/tmp` path in a container to
810
    /// this path in `entrypoint`.
811
    ActionDirectory,
812
}
813
814
#[derive(Deserialize, Serialize, Debug, Default)]
815
#[serde(deny_unknown_fields)]
816
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
817
pub struct UploadActionResultConfig {
818
    /// Underlying AC store that the worker will use to publish execution results
819
    /// into. Objects placed in this store should be reachable from the
820
    /// scheduler/client-cas after they have finished updating.
821
    /// Default: {No uploading is done}
822
    pub ac_store: Option<StoreRefName>,
823
824
    /// In which situations should the results be published to the `ac_store`,
825
    /// if set to `SuccessOnly` then only results with an exit code of 0 will be
826
    /// uploaded, if set to Everything all completed results will be uploaded.
827
    ///
828
    /// Default: `SuccessOnly`
829
    #[serde(default)]
830
    pub upload_ac_results_strategy: UploadCacheResultsStrategy,
831
832
    /// Store to upload historical results to. This should be a CAS store if set.
833
    ///
834
    /// Default: {CAS store of parent}
835
    pub historical_results_store: Option<StoreRefName>,
836
837
    /// In which situations should the results be published to the historical CAS.
838
    /// The historical CAS is where failures are published. These messages conform
839
    /// to the CAS key-value lookup format and are always a `HistoricalExecuteResponse`
840
    /// serialized message.
841
    ///
842
    /// Default: `FailuresOnly`
843
    #[serde(default)]
844
    pub upload_historical_results_strategy: Option<UploadCacheResultsStrategy>,
845
846
    /// Template to use for the `ExecuteResponse.message` property. This message
847
    /// is attached to the response before it is sent to the client. The following
848
    /// special variables are supported:
849
    /// - `digest_function`: Digest function used to calculate the action digest.
850
    /// - `action_digest_hash`: Action digest hash.
851
    /// - `action_digest_size`: Action digest size.
852
    /// - `historical_results_hash`: `HistoricalExecuteResponse` digest hash.
853
    /// - `historical_results_size`: `HistoricalExecuteResponse` digest size.
854
    ///
855
    /// A common use case of this is to provide a link to the web page that
856
    /// contains more useful information for the user.
857
    ///
858
    /// An example that is fully compatible with `bb_browser` is:
859
    /// <https://example.com/my-instance-name-here/blobs/{digest_function}/action/{action_digest_hash}-{action_digest_size}/>
860
    ///
861
    /// Default: "" (no message)
862
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
863
    pub success_message_template: String,
864
865
    /// Same as `success_message_template` but for failure case.
866
    ///
867
    /// An example that is fully compatible with `bb_browser` is:
868
    /// <https://example.com/my-instance-name-here/blobs/{digest_function}/historical_execute_response/{historical_results_hash}-{historical_results_size}/>
869
    ///
870
    /// Default: "" (no message)
871
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
872
    pub failure_message_template: String,
873
}
874
875
#[derive(Deserialize, Serialize, Debug, Default)]
876
#[serde(deny_unknown_fields)]
877
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
878
pub struct LocalWorkerConfig {
879
    /// Name of the worker. This is give a more friendly name to a worker for logging
880
    /// and metric publishing. This is also the prefix of the worker id
881
    /// (i.e. "{name}{uuidv6}").
882
    /// Default: {Index position in the workers list}
883
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
884
    pub name: String,
885
886
    /// Endpoint which the worker will connect to the scheduler's `WorkerApiService`.
887
    pub worker_api_endpoint: EndpointConfig,
888
889
    /// The maximum time an action is allowed to run. If a task requests for a timeout
890
    /// longer than this time limit, the task will be rejected. Value in seconds.
891
    ///
892
    /// Default: 20 minutes
893
    #[serde(
894
        default,
895
        deserialize_with = "convert_duration_with_shellexpand",
896
        alias = "max_action_timeout"
897
    )]
898
    pub max_action_timeout_s: usize,
899
900
    /// Maximum time allowed for uploading action results to CAS after execution
901
    /// completes. If upload takes longer than this, the action fails with
902
    /// `DeadlineExceeded` and may be retried by the scheduler. Value in seconds.
903
    ///
904
    /// Default: 10 minutes
905
    #[serde(
906
        default,
907
        deserialize_with = "convert_duration_with_shellexpand",
908
        alias = "max_upload_timeout"
909
    )]
910
    pub max_upload_timeout_s: usize,
911
912
    /// Maximum time to wait for action directory cleanup before timing out.
913
    /// Value in seconds.
914
    ///
915
    /// Default: 30 seconds
916
    #[serde(default, deserialize_with = "convert_duration_with_shellexpand")]
917
    pub max_cleanup_wait_s: usize,
918
919
    /// Maximum backoff duration for exponential backoff when waiting for cleanup.
920
    /// Value in milliseconds.
921
    ///
922
    /// Default: 500 milliseconds
923
    #[serde(default, deserialize_with = "convert_duration_with_shellexpand")]
924
    pub max_cleanup_backoff_ms: usize,
925
926
    /// Maximum number of inflight tasks this worker can cope with.
927
    ///
928
    /// Default: 0 (infinite tasks)
929
    #[serde(default, deserialize_with = "convert_numeric_with_shellexpand")]
930
    pub max_inflight_tasks: u64,
931
932
    /// If timeout is handled in `entrypoint` or another wrapper script.
933
    /// If set to true `NativeLink` will not honor the timeout the action requested
934
    /// and instead will always force kill the action after `max_action_timeout`
935
    /// has been reached. If this is set to false, the smaller value of the action's
936
    /// timeout and `max_action_timeout` will be used to which `NativeLink` will kill
937
    /// the action.
938
    ///
939
    /// The real timeout can be received via an environment variable set in:
940
    /// `EnvironmentSource::TimeoutMillis`.
941
    ///
942
    /// Example on where this is useful: `entrypoint` launches the action inside
943
    /// a docker container, but the docker container may need to be downloaded. Thus
944
    /// the timer should not start until the docker container has started executing
945
    /// the action. In this case, action will likely be wrapped in another program,
946
    /// like `timeout` and propagate timeouts via `EnvironmentSource::SideChannelFile`.
947
    ///
948
    /// Default: false (`NativeLink` fully handles timeouts)
949
    #[serde(default)]
950
    pub timeout_handled_externally: bool,
951
952
    /// The command to execute on every execution request. This will be parsed as
953
    /// a command + arguments (not shell).
954
    /// Example: "run.sh" and a job with command: "sleep 5" will result in a
955
    /// command like: "run.sh sleep 5".
956
    /// Default: {Use the command from the job request}.
957
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
958
    pub entrypoint: String,
959
960
    /// An optional script to run before every action is processed on the worker.
961
    /// The value should be the full path to the script to execute and will pause
962
    /// all actions on the worker if it returns an exit code other than 0.
963
    /// If not set, then the worker will never pause and will continue to accept
964
    /// jobs according to the scheduler configuration.
965
    /// This is useful, for example, if the worker should not take any more
966
    /// actions until there is enough resource available on the machine to
967
    /// handle them.
968
    pub experimental_precondition_script: Option<String>,
969
970
    /// Underlying CAS store that the worker will use to download CAS artifacts.
971
    /// This store must be a `FastSlowStore`. The `fast` store must be a
972
    /// `FileSystemStore` because it will use hardlinks when building out the files
973
    /// instead of copying the files. The slow store must eventually resolve to the
974
    /// same store the scheduler/client uses to send job requests.
975
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
976
    pub cas_fast_slow_store: StoreRefName,
977
978
    /// Configuration for uploading action results.
979
    #[serde(default)]
980
    pub upload_action_result: UploadActionResultConfig,
981
982
    /// The directory work jobs will be executed from. This directory will be fully
983
    /// managed by the worker service and will be purged on startup.
984
    /// This directory and the directory referenced in `local_filesystem_store_ref`'s
985
    /// `stores::FilesystemStore::content_path` must be on the same filesystem.
986
    /// Hardlinks will be used when placing files that are accessible to the jobs
987
    /// that are sourced from `local_filesystem_store_ref`'s `content_path`.
988
    #[serde(deserialize_with = "convert_string_with_shellexpand")]
989
    pub work_directory: String,
990
991
    /// Properties of this worker. This configuration will be sent to the scheduler
992
    /// and used to tell the scheduler to restrict what should be executed on this
993
    /// worker.
994
    pub platform_properties: HashMap<String, WorkerProperty>,
995
996
    /// An optional mapping of environment names to set for the execution
997
    /// as well as those specified in the action itself. If set, will set each
998
    /// key as an environment variable before executing the job with the value
999
    /// of the environment variable being the value of the property of the
1000
    /// action being executed of that name or the fixed value.
1001
    pub additional_environment: Option<HashMap<String, EnvironmentSource>>,
1002
1003
    /// Optional directory cache configuration for improving performance by caching
1004
    /// reconstructed input directories and using hardlinks instead of rebuilding
1005
    /// them from CAS for every action.
1006
    /// Default: None (directory cache disabled)
1007
    pub directory_cache: Option<DirectoryCacheConfig>,
1008
1009
    /// Whether to use namespaces to isolate the execution. This is only available
1010
    /// on Linux. It is highly recommended as it avoids a number of issues with
1011
    /// zombie processes and also provides additional hermeticity. If explicitly set
1012
    /// to true and it is not supported the worker will exit with an error.
1013
    ///
1014
    /// Note: this will fail for non-privileged Dockerised workers, as workers in
1015
    /// Docker don't have permissions to make a new user namespace. Privileged
1016
    /// containers can do this.
1017
    ///
1018
    /// Default: False.
1019
    pub use_namespaces: Option<bool>,
1020
1021
    /// Whether to use a mount namespace to isolate the worker root. This is only
1022
    /// available on Linux and when `use_namespaces` is true. It is highly recommended
1023
    /// provides additional hermeticity. If explicitly set to true and it is not
1024
    /// supported or `use_namespaces` is not set to true the worker will exit with an
1025
    /// error.
1026
    /// Default: False.
1027
    pub use_mount_namespace: Option<bool>,
1028
}
1029
1030
#[derive(Deserialize, Serialize, Debug, Clone)]
1031
#[serde(deny_unknown_fields)]
1032
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
1033
pub struct DirectoryCacheConfig {
1034
    /// Maximum number of cached directories.
1035
    /// Default: 1000
1036
    #[serde(default = "default_directory_cache_max_entries")]
1037
    pub max_entries: usize,
1038
1039
    /// Maximum total size in bytes for all cached directories (0 = unlimited).
1040
    /// Default: 10737418240 (10 GB)
1041
    #[serde(
1042
        default = "default_directory_cache_max_size_bytes",
1043
        deserialize_with = "convert_data_size_with_shellexpand"
1044
    )]
1045
    pub max_size_bytes: u64,
1046
1047
    /// Base directory for cache storage. This directory will be managed by
1048
    /// the worker and should be on the same filesystem as `work_directory`.
1049
    /// Default: `{work_directory}/../directory_cache`
1050
    #[serde(default, deserialize_with = "convert_string_with_shellexpand")]
1051
    pub cache_root: String,
1052
1053
    /// Optional and experimental: additionally cache every subdirectory by
1054
    /// its own `Directory` digest, not just the root directory. REAPI Merkle
1055
    /// nodes are content-addressed, so a subtree that is byte-identical
1056
    /// between two different roots has the same digest and can be
1057
    /// materialized with a single hardlink pass instead of being rebuilt
1058
    /// from the CAS. This makes the common "one input file changed out of
1059
    /// thousands" case reuse every unchanged subtree.
1060
    ///
1061
    /// Note: subtree caching multiplies the entry COUNT in the cache (every
1062
    /// distinct subdirectory becomes its own entry, subject to the normal
1063
    /// eviction budgets), so operators enabling this should raise
1064
    /// `max_entries` accordingly (for example 10x). To avoid multiplying the
1065
    /// SIZE accounting too, an entry's recorded size covers only bytes not
1066
    /// owned by a descendant entry, so the cache's size total approximates
1067
    /// unique materialized bytes rather than counting each file once per
1068
    /// ancestor level.
1069
    ///
1070
    /// Trade-off: cold constructions pay roughly one extra hardlink pass per
1071
    /// tree level (measured ~10% cold overhead at depth 3, within run
1072
    /// variance) in exchange for the churn-path reuse above.
1073
    ///
1074
    /// Default: false (only root directories are cached; existing behavior)
1075
    #[serde(default)]
1076
    pub experimental_subtree_caching: bool,
1077
1078
    /// Maximum number of concurrent slow-store fetches across ALL directory
1079
    /// constructions of this cache. This bound protects backing stores from
1080
    /// RPC storms: per-level construction concurrency compounds
1081
    /// multiplicatively across tree levels and concurrent actions.
1082
    ///
1083
    /// Interaction with read coalescing: this semaphore fragments coalesced
1084
    /// batches to at most this many items and serializes fetch waves for
1085
    /// trees with many tiny files. Deployments using
1086
    /// `experimental_read_batching` on the underlying grpc store can raise
1087
    /// this substantially (for example 512), since batching collapses the
1088
    /// RPC count. Must be greater than 0.
1089
    ///
1090
    /// Default: 64
1091
    #[serde(default = "default_directory_cache_max_concurrent_fetches")]
1092
    pub max_concurrent_fetches: usize,
1093
}
1094
1095
0
const fn default_directory_cache_max_entries() -> usize {
1096
0
    1000
1097
0
}
1098
1099
0
const fn default_directory_cache_max_concurrent_fetches() -> usize {
1100
0
    64
1101
0
}
1102
1103
0
const fn default_directory_cache_max_size_bytes() -> u64 {
1104
0
    10 * 1024 * 1024 * 1024 // 10 GB
1105
0
}
1106
1107
#[derive(Deserialize, Serialize, Debug)]
1108
#[serde(rename_all = "snake_case")]
1109
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
1110
pub enum WorkerConfig {
1111
    /// A worker type that executes jobs locally on this machine.
1112
    Local(LocalWorkerConfig),
1113
}
1114
1115
#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
1116
#[serde(deny_unknown_fields)]
1117
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
1118
pub struct GlobalConfig {
1119
    /// Maximum number of open files that can be opened at one time.
1120
    /// This value is not strictly enforced, it is a best effort. Some internal libraries
1121
    /// open files or read metadata from a files which do not obey this limit, however
1122
    /// the vast majority of cases will have this limit be honored.
1123
    /// This value must be larger than `ulimit -n` to have any effect.
1124
    /// Any network open file descriptors is not counted in this limit, but is counted
1125
    /// in the kernel limit. It is a good idea to set a very large `ulimit -n`.
1126
    /// Note: This value must be greater than 10.
1127
    ///
1128
    /// Default: 24576 (= 24 * 1024)
1129
    #[serde(deserialize_with = "convert_numeric_with_shellexpand")]
1130
    pub max_open_files: usize,
1131
1132
    /// Default hash function to use while uploading blobs to the CAS when not set
1133
    /// by client.
1134
    ///
1135
    /// Default: `ConfigDigestHashFunction::sha256`
1136
    pub default_digest_hash_function: Option<ConfigDigestHashFunction>,
1137
1138
    /// Default digest size to use for health check when running
1139
    /// diagnostics checks. Health checks are expected to use this
1140
    /// size for filling a buffer that is used for creation of
1141
    /// digest.
1142
    ///
1143
    /// Default: 1024*1024 (1MiB)
1144
    #[serde(default, deserialize_with = "convert_data_size_with_shellexpand")]
1145
    pub default_digest_size_health_check: usize,
1146
}
1147
1148
pub type StoreConfig = NamedConfig<StoreSpec>;
1149
pub type SchedulerConfig = NamedConfig<SchedulerSpec>;
1150
1151
#[derive(Deserialize, Serialize, Debug)]
1152
#[serde(deny_unknown_fields)]
1153
#[cfg_attr(feature = "dev-schema", derive(JsonSchema))]
1154
pub struct CasConfig {
1155
    /// List of stores available to use in this config.
1156
    /// The keys can be used in other configs when needing to reference a store.
1157
    pub stores: Vec<StoreConfig>,
1158
1159
    /// Worker configurations used to execute jobs.
1160
    pub workers: Option<Vec<WorkerConfig>>,
1161
1162
    /// List of schedulers available to use in this config.
1163
    /// The keys can be used in other configs when needing to reference a
1164
    /// scheduler.
1165
    pub schedulers: Option<Vec<SchedulerConfig>>,
1166
1167
    /// Servers to setup for this process.
1168
    pub servers: Vec<ServerConfig>,
1169
1170
    /// Experimental - Origin events configuration. This is the service that will
1171
    /// collect and publish nativelink events to a store for processing by an
1172
    /// external service.
1173
    pub experimental_origin_events: Option<OriginEventsSpec>,
1174
1175
    /// Any global configurations that apply to all modules live here.
1176
    pub global: Option<GlobalConfig>,
1177
}
1178
1179
impl CasConfig {
1180
    /// # Errors
1181
    ///
1182
    /// Will return `Err` if we can't load the file.
1183
19
    pub fn try_from_json5_file(config_file: &str) -> Result<Self, Error> {
1184
19
        let json_contents = std::fs::read_to_string(config_file)
1185
19
            .err_tip(|| 
format!0
("Could not open config file {config_file}"))
?0
;
1186
19
        let config: Self = serde_json5::from_str(&json_contents)
?0
;
1187
23
        for server in 
&config.servers19
{
1188
23
            if let Some(services) = &server.services {
1189
23
                Self::check_store_conflict(services)
?1
;
1190
0
            }
1191
        }
1192
18
        Ok(config)
1193
19
    }
1194
1195
23
    fn check_store_conflict(services: &ServicesConfig) -> Result<(), Error> {
1196
23
        if let Some(
cas_config17
) = &services.cas
1197
17
            && let Some(ac_config) = &services.ac
1198
        {
1199
            // Create a hashmap from the CAS configuration for quick lookup
1200
17
            let cas_store_map: HashMap<_, _> = cas_config
1201
17
                .iter()
1202
19
                .
map17
(|with_instance_name| {
1203
19
                    (
1204
19
                        &with_instance_name.instance_name,
1205
19
                        &with_instance_name.cas_store,
1206
19
                    )
1207
19
                })
1208
17
                .collect();
1209
1210
19
            for with_instance_name in 
ac_config17
{
1211
19
                if let Some(cas_store) = cas_store_map.get(&with_instance_name.instance_name)
1212
19
                    && cas_store == &&with_instance_name.ac_store
1213
                {
1214
1
                    return Err(make_err!(
1215
1
                        Code::InvalidArgument,
1216
1
                        "CAS and AC use the same store '{}' in the config",
1217
1
                        cas_store
1218
1
                    ));
1219
18
                }
1220
            }
1221
6
        }
1222
22
        Ok(())
1223
23
    }
1224
}
1225
1226
#[cfg(test)]
1227
mod tests {
1228
    use super::*;
1229
1230
    #[test]
1231
1
    fn capabilities_config_remote_cache_compression_deserializes_true() {
1232
1
        let config: CapabilitiesConfig =
1233
1
            serde_json5::from_str(r#"{"remote_cache_compression": true}"#).unwrap();
1234
1235
1
        assert!(config.remote_cache_compression);
1236
1
    }
1237
1238
    #[test]
1239
1
    fn capabilities_config_remote_cache_compression_defaults_false() {
1240
1
        let config: CapabilitiesConfig = serde_json5::from_str("{}").unwrap();
1241
1242
1
        assert!(!config.remote_cache_compression);
1243
1
    }
1244
}