Coverage Report

Created: 2026-11-13 23:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-service/src/ac_server.rs
Line
Count
Source
1
// Copyright 2024 Trace Machina, Inc. 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 core::convert::Into;
16
use core::fmt::Debug;
17
use std::collections::HashMap;
18
19
use bytes::BytesMut;
20
use nativelink_config::cas_server::{AcStoreConfig, WithInstanceName};
21
use nativelink_error::{Code, Error, ResultExt, make_err, make_input_err};
22
use nativelink_proto::build::bazel::remote::execution::v2::action_cache_server::{
23
    ActionCache, ActionCacheServer as Server,
24
};
25
use nativelink_proto::build::bazel::remote::execution::v2::{
26
    ActionResult, GetActionResultRequest, UpdateActionResultRequest,
27
};
28
use nativelink_store::ac_utils::{ESTIMATED_DIGEST_SIZE, get_and_decode_digest};
29
use nativelink_store::grpc_store::GrpcStore;
30
use nativelink_store::store_manager::StoreManager;
31
use nativelink_util::common::DigestInfo;
32
use nativelink_util::digest_hasher::make_ctx_for_hash_func;
33
use nativelink_util::store_trait::{Store, StoreLike};
34
use opentelemetry::context::FutureExt;
35
use prost::Message;
36
use tonic::{Request, Response, Status};
37
use tracing::{Instrument, Level, error, error_span, instrument};
38
39
#[derive(Debug, Clone)]
40
pub struct AcStoreInfo {
41
    store: Store,
42
    read_only: bool,
43
}
44
45
pub struct AcServer {
46
    stores: HashMap<String, AcStoreInfo>,
47
}
48
49
impl Debug for AcServer {
50
0
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51
0
        f.debug_struct("AcServer").finish()
52
0
    }
53
}
54
55
impl AcServer {
56
5
    pub fn new(
57
5
        configs: &[WithInstanceName<AcStoreConfig>],
58
5
        store_manager: &StoreManager,
59
5
    ) -> Result<Self, Error> {
60
5
        let mut stores = HashMap::with_capacity(configs.len());
61
5
        for config in configs {
62
5
            let store = store_manager.get_store(&config.ac_store).ok_or_else(|| 
{0
63
0
                make_input_err!("'ac_store': '{}' does not exist", config.ac_store)
64
0
            })?;
65
5
            stores.insert(
66
5
                config.instance_name.clone(),
67
5
                AcStoreInfo {
68
5
                    store,
69
5
                    read_only: config.read_only,
70
5
                },
71
            );
72
        }
73
5
        Ok(Self {
74
5
            stores: stores.clone(),
75
5
        })
76
5
    }
77
78
0
    pub fn into_service(self) -> Server<Self> {
79
0
        Server::new(self)
80
0
    }
81
82
5
    async fn inner_get_action_result(
83
5
        &self,
84
5
        request: GetActionResultRequest,
85
5
    ) -> Result<Response<ActionResult>, Error> {
86
5
        let instance_name = &request.instance_name;
87
5
        let 
store_info4
= self.stores.get(instance_name).ok_or_else(||
{1
88
1
            make_input_err!("'instance_name' not configured for '{instance_name}'")
89
1
        })?;
90
91
        // TODO(palfrey) We should write a test for these errors.
92
4
        let 
digest3
:
DigestInfo3
= request
93
4
            .action_digest
94
4
            .clone()
95
4
            .ok_or_else(|| 
make_input_err!1
("Action digest was not set in message"))
?1
96
3
            .try_into()
?0
;
97
98
        // If we are a GrpcStore we shortcut here, as this is a special store.
99
3
        if let Some(
grpc_store0
) = store_info
100
3
            .store
101
3
            .downcast_ref::<GrpcStore>(Some(digest.into()))
102
        {
103
0
            return grpc_store.get_action_result(Request::new(request)).await;
104
3
        }
105
106
3
        let res = get_and_decode_digest::<ActionResult>(&store_info.store, digest.into()).await;
107
3
        match res {
108
1
            Ok(action_result) => Ok(Response::new(action_result)),
109
2
            Err(mut e) => {
110
2
                if e.code == Code::NotFound {
111
2
                    // `get_action_result` is frequent to get NotFound errors, so remove all
112
2
                    // messages to save space.
113
2
                    e.messages.clear();
114
2
                
}0
115
2
                Err(e)
116
            }
117
        }
118
5
    }
119
120
2
    async fn inner_update_action_result(
121
2
        &self,
122
2
        request: UpdateActionResultRequest,
123
2
    ) -> Result<Response<ActionResult>, Error> {
124
2
        let instance_name = &request.instance_name;
125
2
        let store_info = self.stores.get(instance_name).ok_or_else(|| 
{0
126
0
            make_input_err!("'instance_name' not configured for '{instance_name}'")
127
0
        })?;
128
129
2
        if store_info.read_only {
130
0
            return Err(make_err!(
131
0
                Code::PermissionDenied,
132
0
                "The store '{instance_name}' is read only on this endpoint",
133
0
            ));
134
2
        }
135
136
2
        let digest: DigestInfo = request
137
2
            .action_digest
138
2
            .clone()
139
2
            .ok_or_else(|| 
make_input_err!0
("Action digest was not set in message"))
?0
140
2
            .try_into()
?0
;
141
142
        // If we are a GrpcStore we shortcut here, as this is a special store.
143
2
        if let Some(
grpc_store0
) = store_info
144
2
            .store
145
2
            .downcast_ref::<GrpcStore>(Some(digest.into()))
146
        {
147
0
            return grpc_store.update_action_result(Request::new(request)).await;
148
2
        }
149
150
2
        let 
action_result1
= request
151
2
            .action_result
152
2
            .ok_or_else(|| 
make_input_err!1
("Action result was not set in message"))
?1
;
153
154
1
        let mut store_data = BytesMut::with_capacity(ESTIMATED_DIGEST_SIZE);
155
1
        action_result
156
1
            .encode(&mut store_data)
157
1
            .err_tip(|| "Provided ActionResult could not be serialized")
?0
;
158
159
1
        store_info
160
1
            .store
161
1
            .update_oneshot(digest, store_data.freeze())
162
1
            .await
163
1
            .err_tip(|| "Failed to update in action cache")
?0
;
164
1
        Ok(Response::new(action_result))
165
2
    }
166
}
167
168
#[tonic::async_trait]
169
impl ActionCache for AcServer {
170
    #[instrument(
171
        ret(level = Level::DEBUG),
172
        level = Level::ERROR,
173
        skip_all,
174
        fields(request = ?grpc_request.get_ref())
175
    )]
176
    async fn get_action_result(
177
        &self,
178
        grpc_request: Request<GetActionResultRequest>,
179
    ) -> Result<Response<ActionResult>, Status> {
180
        let request = grpc_request.into_inner();
181
        let digest_function = request.digest_function;
182
        let result = self
183
            .inner_get_action_result(request)
184
            .instrument(error_span!("ac_server_get_action_result"))
185
            .with_context(
186
                make_ctx_for_hash_func(digest_function)
187
                    .err_tip(|| "In AcServer::get_action_result")?,
188
            )
189
            .await;
190
191
        if let Err(ref err) = result
192
            && err.code != Code::NotFound
193
        {
194
            error!(error = ?err, "Error in get_action_result");
195
        }
196
197
        result.map_err(Into::into)
198
    }
199
200
    #[instrument(
201
        err,
202
        ret(level = Level::TRACE),
203
        level = Level::ERROR,
204
        skip_all,
205
        fields(request = ?grpc_request.get_ref())
206
    )]
207
    async fn update_action_result(
208
        &self,
209
        grpc_request: Request<UpdateActionResultRequest>,
210
    ) -> Result<Response<ActionResult>, Status> {
211
        let request = grpc_request.into_inner();
212
        let digest_function = request.digest_function;
213
        self.inner_update_action_result(request)
214
            .instrument(error_span!("ac_server_update_action_result"))
215
            .with_context(
216
                make_ctx_for_hash_func(digest_function)
217
                    .err_tip(|| "In AcServer::update_action_result")?,
218
            )
219
            .await
220
            .map_err(Into::into)
221
    }
222
}