Skip to content

Commit 7463797

Browse files
authored
[crucible-agent] migrate to API trait (#1766)
Part of oxidecomputer/omicron#8922.
1 parent 32390aa commit 7463797

File tree

15 files changed

+563
-422
lines changed

15 files changed

+563
-422
lines changed

Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
[workspace]
22
members = [
33
"agent",
4+
"agent-api",
45
"agent-client",
6+
"agent-types",
57
"crucible-client-types",
68
"common",
79
"control-client",
@@ -131,8 +133,10 @@ oximeter-producer = { git = "https://github.com/oxidecomputer/omicron", branch =
131133

132134
# local path
133135
crucible = { path = "./upstairs" }
134-
crucible-client-types = { path = "./crucible-client-types" }
136+
crucible-agent-api = { path = "./agent-api" }
135137
crucible-agent-client = { path = "./agent-client" }
138+
crucible-agent-types = { path = "./agent-types" }
139+
crucible-client-types = { path = "./crucible-client-types" }
136140
crucible-common = { path = "./common" }
137141
crucible-control-client = { path = "./control-client" }
138142
# importantly, don't use features = ["zfs_snapshot"] here, this will cause

agent-api/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "crucible-agent-api"
3+
version = "0.1.0"
4+
license = "MPL-2.0"
5+
edition = "2024"
6+
7+
[dependencies]
8+
crucible-agent-types.workspace = true
9+
crucible-workspace-hack.workspace = true
10+
dropshot.workspace = true
11+
schemars.workspace = true
12+
serde.workspace = true

agent-api/src/lib.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright 2025 Oxide Computer Company
2+
3+
use std::collections::BTreeMap;
4+
5+
use crucible_agent_types::{
6+
region::{CreateRegion, Region, RegionId},
7+
snapshot::{RunningSnapshot, Snapshot},
8+
};
9+
use dropshot::{
10+
HttpError, HttpResponseDeleted, HttpResponseOk, Path, RequestContext,
11+
TypedBody,
12+
};
13+
use schemars::JsonSchema;
14+
use serde::{Deserialize, Serialize};
15+
16+
#[dropshot::api_description]
17+
pub trait CrucibleAgentApi {
18+
type Context;
19+
20+
#[endpoint {
21+
method = GET,
22+
path = "/crucible/0/regions",
23+
}]
24+
async fn region_list(
25+
rqctx: RequestContext<Self::Context>,
26+
) -> Result<HttpResponseOk<Vec<Region>>, HttpError>;
27+
28+
#[endpoint {
29+
method = POST,
30+
path = "/crucible/0/regions",
31+
}]
32+
async fn region_create(
33+
rqctx: RequestContext<Self::Context>,
34+
body: TypedBody<CreateRegion>,
35+
) -> Result<HttpResponseOk<Region>, HttpError>;
36+
37+
#[endpoint {
38+
method = GET,
39+
path = "/crucible/0/regions/{id}",
40+
}]
41+
async fn region_get(
42+
rqctx: RequestContext<Self::Context>,
43+
path: Path<RegionPath>,
44+
) -> Result<HttpResponseOk<Region>, HttpError>;
45+
46+
#[endpoint {
47+
method = DELETE,
48+
path = "/crucible/0/regions/{id}",
49+
}]
50+
async fn region_delete(
51+
rqctx: RequestContext<Self::Context>,
52+
path: Path<RegionPath>,
53+
) -> Result<HttpResponseDeleted, HttpError>;
54+
55+
#[endpoint {
56+
method = GET,
57+
path = "/crucible/0/regions/{id}/snapshots",
58+
}]
59+
async fn region_get_snapshots(
60+
rqctx: RequestContext<Self::Context>,
61+
path: Path<RegionPath>,
62+
) -> Result<HttpResponseOk<GetSnapshotResponse>, HttpError>;
63+
64+
#[endpoint {
65+
method = GET,
66+
path = "/crucible/0/regions/{id}/snapshots/{name}",
67+
}]
68+
async fn region_get_snapshot(
69+
rqctx: RequestContext<Self::Context>,
70+
path: Path<GetSnapshotPath>,
71+
) -> Result<HttpResponseOk<Snapshot>, HttpError>;
72+
73+
#[endpoint {
74+
method = DELETE,
75+
path = "/crucible/0/regions/{id}/snapshots/{name}",
76+
}]
77+
async fn region_delete_snapshot(
78+
rqctx: RequestContext<Self::Context>,
79+
path: Path<DeleteSnapshotPath>,
80+
) -> Result<HttpResponseDeleted, HttpError>;
81+
82+
#[endpoint {
83+
method = POST,
84+
path = "/crucible/0/regions/{id}/snapshots/{name}/run",
85+
}]
86+
async fn region_run_snapshot(
87+
rqctx: RequestContext<Self::Context>,
88+
path: Path<RunSnapshotPath>,
89+
) -> Result<HttpResponseOk<RunningSnapshot>, HttpError>;
90+
91+
#[endpoint {
92+
method = DELETE,
93+
path = "/crucible/0/regions/{id}/snapshots/{name}/run",
94+
}]
95+
async fn region_delete_running_snapshot(
96+
rc: RequestContext<Self::Context>,
97+
path: Path<RunSnapshotPath>,
98+
) -> Result<HttpResponseDeleted, HttpError>;
99+
}
100+
101+
#[derive(Deserialize, JsonSchema)]
102+
pub struct RegionPath {
103+
pub id: RegionId,
104+
}
105+
106+
#[derive(Serialize, JsonSchema)]
107+
pub struct GetSnapshotResponse {
108+
pub snapshots: Vec<Snapshot>,
109+
pub running_snapshots: BTreeMap<String, RunningSnapshot>,
110+
}
111+
112+
#[derive(Deserialize, JsonSchema)]
113+
pub struct GetSnapshotPath {
114+
pub id: RegionId,
115+
pub name: String,
116+
}
117+
118+
#[derive(Deserialize, JsonSchema)]
119+
pub struct DeleteSnapshotPath {
120+
pub id: RegionId,
121+
pub name: String,
122+
}
123+
124+
#[derive(Deserialize, JsonSchema)]
125+
pub struct RunSnapshotPath {
126+
pub id: RegionId,
127+
pub name: String,
128+
}

agent-types/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "crucible-agent-types"
3+
version = "0.1.0"
4+
license = "MPL-2.0"
5+
edition = "2024"
6+
7+
[dependencies]
8+
chrono.workspace = true
9+
crucible-smf.workspace = true
10+
crucible-workspace-hack.workspace = true
11+
serde.workspace = true
12+
schemars.workspace = true
13+
14+
[dev-dependencies]
15+
serde_json.workspace = true

agent-types/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright 2025 Oxide Computer Company
2+
3+
pub mod region;
4+
pub mod smf;
5+
pub mod snapshot;

agent/src/model.rs renamed to agent-types/src/region.rs

Lines changed: 4 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
// Copyright 2021 Oxide Computer Company
1+
// Copyright 2025 Oxide Computer Company
22

33
use std::net::SocketAddr;
44
use std::path::Path;
55

6-
use chrono::prelude::*;
7-
use crucible_smf::scf_type_t::{self, *};
6+
use crucible_smf::scf_type_t::*;
87
use schemars::JsonSchema;
98
use serde::{Deserialize, Serialize};
109

10+
use crate::smf::SmfProperty;
11+
1112
#[allow(clippy::derive_partial_eq_without_eq)]
1213
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)]
1314
#[serde(rename_all = "lowercase")]
@@ -58,12 +59,6 @@ pub struct Region {
5859
pub read_only: bool,
5960
}
6061

61-
pub struct SmfProperty<'a> {
62-
pub name: &'a str,
63-
pub typ: scf_type_t,
64-
pub val: String,
65-
}
66-
6762
impl Region {
6863
/**
6964
* Given a root directory, return a list of SMF properties to ensure for
@@ -205,124 +200,6 @@ impl CreateRegion {
205200
)]
206201
pub struct RegionId(pub String);
207202

208-
#[allow(clippy::derive_partial_eq_without_eq)]
209-
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)]
210-
pub struct Snapshot {
211-
pub name: String,
212-
pub created: DateTime<Utc>,
213-
}
214-
215-
#[allow(clippy::derive_partial_eq_without_eq)]
216-
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)]
217-
pub struct RunningSnapshot {
218-
pub id: RegionId,
219-
pub name: String,
220-
pub port_number: u16,
221-
pub state: State,
222-
}
223-
224-
impl RunningSnapshot {
225-
/**
226-
* Given a root directory, return a list of SMF properties to ensure for
227-
* the corresponding running instance.
228-
*/
229-
pub fn get_smf_properties(&self, dir: &Path) -> Vec<SmfProperty> {
230-
let mut results = vec![
231-
SmfProperty {
232-
name: "directory",
233-
typ: SCF_TYPE_ASTRING,
234-
val: dir.to_str().unwrap().to_string(),
235-
},
236-
SmfProperty {
237-
name: "port",
238-
typ: SCF_TYPE_COUNT,
239-
val: self.port_number.to_string(),
240-
},
241-
SmfProperty {
242-
name: "mode",
243-
typ: SCF_TYPE_ASTRING,
244-
val: "ro".to_string(),
245-
},
246-
];
247-
248-
// Test for X509 files in snapshot - note this means that running
249-
// snapshots will use the X509 information in the snapshot, not a new
250-
// set.
251-
{
252-
let mut path = dir.to_path_buf();
253-
path.push("cert.pem");
254-
let path = path.into_os_string().into_string().unwrap();
255-
256-
if Path::new(&path).exists() {
257-
results.push(SmfProperty {
258-
name: "cert_pem_path",
259-
typ: SCF_TYPE_ASTRING,
260-
val: path,
261-
});
262-
}
263-
}
264-
265-
{
266-
let mut path = dir.to_path_buf();
267-
path.push("key.pem");
268-
let path = path.into_os_string().into_string().unwrap();
269-
270-
if Path::new(&path).exists() {
271-
results.push(SmfProperty {
272-
name: "key_pem_path",
273-
typ: SCF_TYPE_ASTRING,
274-
val: path,
275-
});
276-
}
277-
}
278-
279-
{
280-
let mut path = dir.to_path_buf();
281-
path.push("root.pem");
282-
let path = path.into_os_string().into_string().unwrap();
283-
284-
if Path::new(&path).exists() {
285-
results.push(SmfProperty {
286-
name: "root_pem_path",
287-
typ: SCF_TYPE_ASTRING,
288-
val: path,
289-
});
290-
}
291-
}
292-
293-
results
294-
}
295-
}
296-
297-
pub struct CreateRunningSnapshotRequest {
298-
pub id: RegionId,
299-
pub name: String,
300-
pub cert_pem: Option<String>,
301-
pub key_pem: Option<String>,
302-
pub root_pem: Option<String>,
303-
}
304-
305-
#[allow(clippy::derive_partial_eq_without_eq)]
306-
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)]
307-
pub struct DeleteRunningSnapshotRequest {
308-
pub id: RegionId,
309-
pub name: String,
310-
}
311-
312-
#[allow(clippy::derive_partial_eq_without_eq)]
313-
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)]
314-
pub struct DeleteSnapshotRequest {
315-
pub id: RegionId,
316-
pub name: String,
317-
}
318-
319-
// The different types of resources the worker thread monitors for changes. This
320-
// wraps the object that has been added, or changed somehow.
321-
pub enum Resource {
322-
Region(Region),
323-
RunningSnapshot(RegionId, String, RunningSnapshot),
324-
}
325-
326203
#[cfg(test)]
327204
mod test {
328205
use super::*;

agent-types/src/smf.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2025 Oxide Computer Company
2+
3+
use crucible_smf::scf_type_t;
4+
5+
pub struct SmfProperty<'a> {
6+
pub name: &'a str,
7+
pub typ: scf_type_t,
8+
pub val: String,
9+
}

0 commit comments

Comments
 (0)