Line data Source code
1 : //! Structs representing the JSON formats used in the compute_ctl's HTTP API.
2 :
3 : use chrono::{DateTime, Utc};
4 : use jsonwebtoken::jwk::JwkSet;
5 : use serde::{Deserialize, Serialize, Serializer};
6 : use std::fmt::Display;
7 :
8 : use crate::privilege::Privilege;
9 : use crate::spec::{ComputeSpec, Database, ExtVersion, PgIdent, Role};
10 :
11 0 : #[derive(Serialize, Debug, Deserialize)]
12 : pub struct GenericAPIError {
13 : pub error: String,
14 : }
15 :
16 : /// All configuration parameters necessary for a compute. When
17 : /// [`ComputeConfig::spec`] is provided, it means that the compute is attached
18 : /// to a tenant. [`ComputeConfig::compute_ctl_config`] will always be provided
19 : /// and contains parameters necessary for operating `compute_ctl` independently
20 : /// of whether a tenant is attached to the compute or not.
21 : ///
22 : /// This also happens to be the body of `compute_ctl`'s /configure request.
23 0 : #[derive(Debug, Deserialize, Serialize)]
24 : pub struct ComputeConfig {
25 : /// The compute spec
26 : pub spec: Option<ComputeSpec>,
27 :
28 : /// The compute_ctl configuration
29 : #[allow(dead_code)]
30 : pub compute_ctl_config: ComputeCtlConfig,
31 : }
32 :
33 : impl From<ControlPlaneConfigResponse> for ComputeConfig {
34 0 : fn from(value: ControlPlaneConfigResponse) -> Self {
35 0 : Self {
36 0 : spec: value.spec,
37 0 : compute_ctl_config: value.compute_ctl_config,
38 0 : }
39 0 : }
40 : }
41 :
42 : #[derive(Debug, Clone, Serialize)]
43 : pub struct ExtensionInstallResponse {
44 : pub extension: PgIdent,
45 : pub version: ExtVersion,
46 : }
47 :
48 : /// Status of the LFC prewarm process. The same state machine is reused for
49 : /// both autoprewarm (prewarm after compute/Postgres start using the previously
50 : /// stored LFC state) and explicit prewarming via API.
51 : #[derive(Serialize, Default, Debug, Clone)]
52 : #[serde(tag = "status", rename_all = "snake_case")]
53 : pub enum LfcPrewarmState {
54 : /// Default value when compute boots up.
55 : #[default]
56 : NotPrewarmed,
57 : /// Prewarming thread is active and loading pages into LFC.
58 : Prewarming,
59 : /// We found requested LFC state in the endpoint storage and
60 : /// completed prewarming successfully.
61 : Completed {
62 : total: i32,
63 : prewarmed: i32,
64 : skipped: i32,
65 : state_download_time_ms: u32,
66 : uncompress_time_ms: u32,
67 : prewarm_time_ms: u32,
68 : },
69 : /// Unexpected error happened during prewarming. Note, `Not Found 404`
70 : /// response from the endpoint storage is explicitly excluded here
71 : /// because it can normally happen on the first compute start,
72 : /// since LFC state is not available yet.
73 : Failed { error: String },
74 : /// We tried to fetch the corresponding LFC state from the endpoint storage,
75 : /// but received `Not Found 404`. This should normally happen only during the
76 : /// first endpoint start after creation with `autoprewarm: true`.
77 : /// This may also happen if LFC is turned off or not initialized
78 : ///
79 : /// During the orchestrated prewarm via API, when a caller explicitly
80 : /// provides the LFC state key to prewarm from, it's the caller responsibility
81 : /// to handle this status as an error state in this case.
82 : Skipped,
83 : /// LFC prewarm was cancelled. Some pages in LFC cache may be prewarmed if query
84 : /// has started working before cancellation
85 : Cancelled,
86 : }
87 :
88 : impl Display for LfcPrewarmState {
89 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 0 : match self {
91 0 : LfcPrewarmState::NotPrewarmed => f.write_str("NotPrewarmed"),
92 0 : LfcPrewarmState::Prewarming => f.write_str("Prewarming"),
93 0 : LfcPrewarmState::Completed { .. } => f.write_str("Completed"),
94 0 : LfcPrewarmState::Skipped => f.write_str("Skipped"),
95 0 : LfcPrewarmState::Failed { error } => write!(f, "Error({error})"),
96 0 : LfcPrewarmState::Cancelled => f.write_str("Cancelled"),
97 : }
98 0 : }
99 : }
100 :
101 : #[derive(Serialize, Default, Debug, Clone)]
102 : #[serde(tag = "status", rename_all = "snake_case")]
103 : pub enum LfcOffloadState {
104 : #[default]
105 : NotOffloaded,
106 : Offloading,
107 : Completed {
108 : state_query_time_ms: u32,
109 : compress_time_ms: u32,
110 : state_upload_time_ms: u32,
111 : },
112 : Failed {
113 : error: String,
114 : },
115 : /// LFC state was empty so it wasn't offloaded
116 : Skipped,
117 : }
118 :
119 : #[derive(Serialize, Debug, Clone)]
120 : #[serde(tag = "status", rename_all = "snake_case")]
121 : pub enum PromoteState {
122 : NotPromoted,
123 : Completed {
124 : lsn_wait_time_ms: u32,
125 : pg_promote_time_ms: u32,
126 : reconfigure_time_ms: u32,
127 : },
128 : Failed {
129 : error: String,
130 : },
131 : }
132 :
133 0 : #[derive(Deserialize, Default, Debug)]
134 : #[serde(rename_all = "snake_case")]
135 : pub struct PromoteConfig {
136 : pub spec: ComputeSpec,
137 : pub wal_flush_lsn: utils::lsn::Lsn,
138 : }
139 :
140 : /// Response of the /status API
141 0 : #[derive(Serialize, Debug, Deserialize)]
142 : #[serde(rename_all = "snake_case")]
143 : pub struct ComputeStatusResponse {
144 : pub start_time: DateTime<Utc>,
145 : pub tenant: Option<String>,
146 : pub timeline: Option<String>,
147 : pub status: ComputeStatus,
148 : #[serde(serialize_with = "rfc3339_serialize")]
149 : pub last_active: Option<DateTime<Utc>>,
150 : pub error: Option<String>,
151 : }
152 :
153 0 : #[derive(Serialize, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Default)]
154 : #[serde(rename_all = "snake_case")]
155 : pub enum TerminateMode {
156 : #[default]
157 : /// wait 30s till returning from /terminate to allow control plane to get the error
158 : Fast,
159 : /// return from /terminate immediately as soon as all components are terminated
160 : Immediate,
161 : }
162 :
163 : impl From<TerminateMode> for ComputeStatus {
164 0 : fn from(mode: TerminateMode) -> Self {
165 0 : match mode {
166 0 : TerminateMode::Fast => ComputeStatus::TerminationPendingFast,
167 0 : TerminateMode::Immediate => ComputeStatus::TerminationPendingImmediate,
168 : }
169 0 : }
170 : }
171 :
172 0 : #[derive(Serialize, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
173 : #[serde(rename_all = "snake_case")]
174 : pub enum ComputeStatus {
175 : // Spec wasn't provided at start, waiting for it to be
176 : // provided by control-plane.
177 : Empty,
178 : // Compute configuration was requested.
179 : ConfigurationPending,
180 : // Compute node has spec and initial startup and
181 : // configuration is in progress.
182 : Init,
183 : // Compute is configured and running.
184 : Running,
185 : // New spec is being applied.
186 : Configuration,
187 : // Either startup or configuration failed,
188 : // compute will exit soon or is waiting for
189 : // control-plane to terminate it.
190 : Failed,
191 : // Termination requested
192 : TerminationPendingFast,
193 : // Termination requested, without waiting 30s before returning from /terminate
194 : TerminationPendingImmediate,
195 : // Terminated Postgres
196 : Terminated,
197 : // A spec refresh is being requested
198 : RefreshConfigurationPending,
199 : // A spec refresh is being applied. We cannot refresh configuration again until the current
200 : // refresh is done, i.e., signal_refresh_configuration() will return 500 error.
201 : RefreshConfiguration,
202 : }
203 :
204 0 : #[derive(Deserialize, Serialize)]
205 : pub struct TerminateResponse {
206 : pub lsn: Option<utils::lsn::Lsn>,
207 : }
208 :
209 : impl Display for ComputeStatus {
210 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 0 : match self {
212 0 : ComputeStatus::Empty => f.write_str("empty"),
213 0 : ComputeStatus::ConfigurationPending => f.write_str("configuration-pending"),
214 0 : ComputeStatus::RefreshConfiguration => f.write_str("refresh-configuration"),
215 : ComputeStatus::RefreshConfigurationPending => {
216 0 : f.write_str("refresh-configuration-pending")
217 : }
218 0 : ComputeStatus::Init => f.write_str("init"),
219 0 : ComputeStatus::Running => f.write_str("running"),
220 0 : ComputeStatus::Configuration => f.write_str("configuration"),
221 0 : ComputeStatus::Failed => f.write_str("failed"),
222 0 : ComputeStatus::TerminationPendingFast => f.write_str("termination-pending-fast"),
223 : ComputeStatus::TerminationPendingImmediate => {
224 0 : f.write_str("termination-pending-immediate")
225 : }
226 0 : ComputeStatus::Terminated => f.write_str("terminated"),
227 : }
228 0 : }
229 : }
230 :
231 0 : pub fn rfc3339_serialize<S>(x: &Option<DateTime<Utc>>, s: S) -> Result<S::Ok, S::Error>
232 0 : where
233 0 : S: Serializer,
234 : {
235 0 : if let Some(x) = x {
236 0 : x.to_rfc3339().serialize(s)
237 : } else {
238 0 : s.serialize_none()
239 : }
240 0 : }
241 :
242 : /// Response of the /metrics.json API
243 : #[derive(Clone, Debug, Default, Serialize)]
244 : pub struct ComputeMetrics {
245 : /// Time spent waiting in pool
246 : pub wait_for_spec_ms: u64,
247 :
248 : /// Time spent checking if safekeepers are synced
249 : pub sync_sk_check_ms: u64,
250 :
251 : /// Time spent syncing safekeepers (walproposer.c).
252 : /// In most cases this should be zero.
253 : pub sync_safekeepers_ms: u64,
254 :
255 : /// Time it took to establish a pg connection to the pageserver.
256 : /// This is two roundtrips, so it's a good proxy for compute-pageserver
257 : /// latency. The latency is usually 0.2ms, but it's not safe to assume
258 : /// that.
259 : pub pageserver_connect_micros: u64,
260 :
261 : /// Time to get basebackup from pageserver and write it to disk.
262 : pub basebackup_ms: u64,
263 :
264 : /// Compressed size of basebackup received.
265 : pub basebackup_bytes: u64,
266 :
267 : /// Time spent starting potgres. This includes initialization of shared
268 : /// buffers, preloading extensions, and other pg operations.
269 : pub start_postgres_ms: u64,
270 :
271 : /// Time spent applying pg catalog updates that were made in the console
272 : /// UI. This should be 0 when startup time matters, since cplane tries
273 : /// to do these updates eagerly, and passes the skip_pg_catalog_updates
274 : /// when it's safe to skip this step.
275 : pub config_ms: u64,
276 :
277 : /// Total time, from when we receive the spec to when we're ready to take
278 : /// pg connections.
279 : pub total_startup_ms: u64,
280 : pub load_ext_ms: u64,
281 : pub num_ext_downloaded: u64,
282 : pub largest_ext_size: u64, // these are measured in bytes
283 : pub total_ext_download_size: u64,
284 : }
285 :
286 : #[derive(Clone, Debug, Default, Serialize)]
287 : pub struct CatalogObjects {
288 : pub roles: Vec<Role>,
289 : pub databases: Vec<Database>,
290 : }
291 :
292 0 : #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
293 : pub struct ComputeCtlConfig {
294 : /// Set of JSON web keys that the compute can use to authenticate
295 : /// communication from the control plane.
296 : pub jwks: JwkSet,
297 : pub tls: Option<TlsConfig>,
298 : }
299 :
300 : impl Default for ComputeCtlConfig {
301 0 : fn default() -> Self {
302 0 : Self {
303 0 : jwks: JwkSet {
304 0 : keys: Vec::default(),
305 0 : },
306 0 : tls: None,
307 0 : }
308 0 : }
309 : }
310 :
311 0 : #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
312 : pub struct TlsConfig {
313 : pub key_path: String,
314 : pub cert_path: String,
315 : }
316 :
317 : /// Response of the `/computes/{compute_id}/spec` control-plane API.
318 0 : #[derive(Deserialize, Debug)]
319 : pub struct ControlPlaneConfigResponse {
320 : pub spec: Option<ComputeSpec>,
321 : pub status: ControlPlaneComputeStatus,
322 : pub compute_ctl_config: ComputeCtlConfig,
323 : }
324 :
325 0 : #[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
326 : #[serde(rename_all = "snake_case")]
327 : pub enum ControlPlaneComputeStatus {
328 : // Compute is known to control-plane, but it's not
329 : // yet attached to any timeline / endpoint.
330 : Empty,
331 : // Compute is attached to some timeline / endpoint and
332 : // should be able to start with provided spec.
333 : Attached,
334 : }
335 :
336 : #[derive(Clone, Debug, Default, Serialize)]
337 : pub struct InstalledExtension {
338 : pub extname: String,
339 : pub version: String,
340 : pub n_databases: u32, // Number of databases using this extension
341 : pub owned_by_superuser: String,
342 : }
343 :
344 : #[derive(Clone, Debug, Default, Serialize)]
345 : pub struct InstalledExtensions {
346 : pub extensions: Vec<InstalledExtension>,
347 : }
348 :
349 : #[derive(Clone, Debug, Default, Serialize)]
350 : pub struct ExtensionInstallResult {
351 : pub extension: PgIdent,
352 : pub version: ExtVersion,
353 : }
354 : #[derive(Clone, Debug, Default, Serialize)]
355 : pub struct SetRoleGrantsResponse {
356 : pub database: PgIdent,
357 : pub schema: PgIdent,
358 : pub privileges: Vec<Privilege>,
359 : pub role: PgIdent,
360 : }
|