/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 | | } |