Skip to content

Commit cddd41e

Browse files
authored
[2/5] User data export: omdb sub commands (#8532)
Before adding anything else, start with new omdb commands. This will enable inspection and debugging in the future PRs of this set.
1 parent b6766b6 commit cddd41e

File tree

4 files changed

+328
-0
lines changed

4 files changed

+328
-0
lines changed

dev-tools/omdb/src/bin/omdb/db.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ use nexus_db_model::SwCaboose;
103103
use nexus_db_model::SwRotPage;
104104
use nexus_db_model::UpstairsRepairNotification;
105105
use nexus_db_model::UpstairsRepairProgress;
106+
use nexus_db_model::UserDataExportRecord;
107+
use nexus_db_model::UserDataExportResource;
106108
use nexus_db_model::Vmm;
107109
use nexus_db_model::Volume;
108110
use nexus_db_model::VolumeRepair;
@@ -189,6 +191,7 @@ use uuid::Uuid;
189191
mod alert;
190192
mod ereport;
191193
mod saga;
194+
mod user_data_export;
192195

193196
const NO_ACTIVE_PROPOLIS_MSG: &str = "<no active Propolis>";
194197
const NOT_ON_SLED_MSG: &str = "<not on any sled>";
@@ -408,6 +411,8 @@ enum DbCommands {
408411
Alert(AlertArgs),
409412
/// Commands for querying and interacting with pools
410413
Zpool(ZpoolArgs),
414+
/// Commands for querying and interacting with user data export objects
415+
UserDataExport(user_data_export::UserDataExportArgs),
411416
}
412417

413418
#[derive(Debug, Args, Clone)]
@@ -1508,6 +1513,9 @@ impl DbArgs {
15081513
DbCommands::Ereport(args) => {
15091514
cmd_db_ereport(&datastore, &fetch_opts, &args).await
15101515
}
1516+
DbCommands::UserDataExport(args) => {
1517+
args.exec(&omdb, &opctx, &datastore).await
1518+
}
15111519
}
15121520
}
15131521
}).await
@@ -3576,6 +3584,33 @@ async fn volume_used_by(
35763584
String::from("listing images used")
35773585
});
35783586

3587+
let export_used: Vec<UserDataExportRecord> = {
3588+
let volumes = volumes.to_vec();
3589+
datastore
3590+
.pool_connection_for_tests()
3591+
.await?
3592+
.transaction_async(async move |conn| {
3593+
use nexus_db_schema::schema::user_data_export::dsl;
3594+
3595+
conn.batch_execute_async(ALLOW_FULL_TABLE_SCAN_SQL).await?;
3596+
3597+
paginated(
3598+
dsl::user_data_export,
3599+
dsl::id,
3600+
&first_page::<dsl::id>(fetch_opts.fetch_limit),
3601+
)
3602+
.filter(dsl::volume_id.eq_any(volumes))
3603+
.select(UserDataExportRecord::as_select())
3604+
.load_async(&conn)
3605+
.await
3606+
})
3607+
.await?
3608+
};
3609+
3610+
check_limit(&export_used, fetch_opts.fetch_limit, || {
3611+
String::from("listing user data export used")
3612+
});
3613+
35793614
Ok(volumes
35803615
.iter()
35813616
.map(|volume_id| {
@@ -3594,6 +3629,9 @@ async fn volume_used_by(
35943629
let maybe_disk =
35953630
disks_used.iter().find(|x| x.volume_id() == volume_id);
35963631

3632+
let maybe_export =
3633+
export_used.iter().find(|x| x.volume_id() == Some(volume_id));
3634+
35973635
if let Some(image) = maybe_image {
35983636
VolumeUsedBy {
35993637
volume_id,
@@ -3626,6 +3664,23 @@ async fn volume_used_by(
36263664
usage_name: disk.name().to_string(),
36273665
deleted: disk.time_deleted().is_some(),
36283666
}
3667+
} else if let Some(export) = maybe_export {
3668+
match export.resource() {
3669+
UserDataExportResource::Snapshot { id } => VolumeUsedBy {
3670+
volume_id,
3671+
usage_type: String::from("export"),
3672+
usage_id: id.to_string(),
3673+
usage_name: String::from("snapshot"),
3674+
deleted: export.deleted(),
3675+
},
3676+
UserDataExportResource::Image { id } => VolumeUsedBy {
3677+
volume_id,
3678+
usage_type: String::from("export"),
3679+
usage_id: id.to_string(),
3680+
usage_name: String::from("image"),
3681+
deleted: export.deleted(),
3682+
},
3683+
}
36293684
} else {
36303685
VolumeUsedBy {
36313686
volume_id,
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! `omdb db user-data-export` subcommands
6+
7+
use crate::Omdb;
8+
use crate::check_allow_destructive::DestructiveOperationToken;
9+
use crate::helpers::display_debug;
10+
use crate::helpers::display_option_blank;
11+
use async_bb8_diesel::AsyncRunQueryDsl;
12+
use clap::Args;
13+
use clap::Subcommand;
14+
use diesel::prelude::*;
15+
use nexus_db_model::UserDataExportRecord;
16+
use nexus_db_model::UserDataExportResourceType;
17+
use nexus_db_model::UserDataExportState;
18+
use nexus_db_queries::context::OpContext;
19+
use nexus_db_queries::db::DataStore;
20+
use omicron_uuid_kinds::UserDataExportUuid;
21+
use omicron_uuid_kinds::VolumeUuid;
22+
use std::net::SocketAddrV6;
23+
use tabled::Tabled;
24+
use uuid::Uuid;
25+
26+
/// `omdb db user-data-export` subcommand
27+
#[derive(Debug, Args, Clone)]
28+
pub struct UserDataExportArgs {
29+
#[command(subcommand)]
30+
command: UserDataExportCommands,
31+
}
32+
33+
#[derive(Debug, Subcommand, Clone)]
34+
enum UserDataExportCommands {
35+
/// Check if a read-only resource has a user data export object
36+
Query(UserDataExportQueryArgs),
37+
38+
/// Manually request that a user data export object be deleted.
39+
Delete(UserDataExportDeleteArgs),
40+
41+
/// Show the user data export related changes required by Nexus
42+
ShowChangeset,
43+
}
44+
45+
#[derive(Clone, Debug, Args)]
46+
struct UserDataExportQueryArgs {
47+
#[clap(long)]
48+
resource_type: UserDataExportResourceType,
49+
50+
#[clap(long)]
51+
resource_id: Uuid,
52+
}
53+
54+
#[derive(Clone, Debug, Args)]
55+
struct UserDataExportDeleteArgs {
56+
#[clap(long)]
57+
user_data_export_id: UserDataExportUuid,
58+
}
59+
60+
impl UserDataExportArgs {
61+
pub async fn exec(
62+
&self,
63+
omdb: &Omdb,
64+
opctx: &OpContext,
65+
datastore: &DataStore,
66+
) -> Result<(), anyhow::Error> {
67+
match &self.command {
68+
UserDataExportCommands::Query(args) => {
69+
cmd_user_data_export_query(opctx, datastore, args).await
70+
}
71+
72+
UserDataExportCommands::Delete(args) => {
73+
let token = omdb.check_allow_destructive()?;
74+
75+
cmd_user_data_export_delete(opctx, datastore, args, token).await
76+
}
77+
78+
UserDataExportCommands::ShowChangeset => {
79+
cmd_user_data_export_show_changeset(opctx, datastore).await
80+
}
81+
}
82+
}
83+
}
84+
85+
async fn cmd_user_data_export_query(
86+
_opctx: &OpContext,
87+
datastore: &DataStore,
88+
args: &UserDataExportQueryArgs,
89+
) -> Result<(), anyhow::Error> {
90+
let conn = datastore.pool_connection_for_tests().await?;
91+
92+
let record = {
93+
use nexus_db_schema::schema::user_data_export::dsl;
94+
95+
dsl::user_data_export
96+
.filter(dsl::resource_type.eq(args.resource_type))
97+
.filter(dsl::resource_id.eq(args.resource_id))
98+
.select(UserDataExportRecord::as_select())
99+
.first_async(&*conn)
100+
.await?
101+
};
102+
103+
#[derive(Tabled)]
104+
struct Row {
105+
id: UserDataExportUuid,
106+
107+
#[tabled(display_with = "display_debug")]
108+
state: UserDataExportState,
109+
#[tabled(display_with = "display_option_blank")]
110+
operating_saga_id: Option<Uuid>,
111+
generation: i64,
112+
113+
resource_type: String,
114+
resource_id: Uuid,
115+
resource_deleted: bool,
116+
117+
#[tabled(display_with = "display_option_blank")]
118+
pantry_address: Option<SocketAddrV6>,
119+
#[tabled(display_with = "display_option_blank")]
120+
volume_id: Option<VolumeUuid>,
121+
}
122+
123+
let rows: Vec<_> = vec![Row {
124+
id: record.id(),
125+
126+
state: record.state(),
127+
operating_saga_id: record.operating_saga_id(),
128+
generation: record.generation(),
129+
130+
resource_type: args.resource_type.to_string(),
131+
resource_id: args.resource_id,
132+
resource_deleted: record.deleted(),
133+
134+
pantry_address: record.pantry_address(),
135+
volume_id: record.volume_id(),
136+
}];
137+
138+
let table = tabled::Table::new(rows)
139+
.with(tabled::settings::Style::psql())
140+
.to_string();
141+
142+
println!("{}", table);
143+
144+
Ok(())
145+
}
146+
147+
async fn cmd_user_data_export_delete(
148+
_opctx: &OpContext,
149+
datastore: &DataStore,
150+
args: &UserDataExportDeleteArgs,
151+
_destruction_token: DestructiveOperationToken,
152+
) -> Result<(), anyhow::Error> {
153+
datastore.user_data_export_mark_deleted(args.user_data_export_id).await?;
154+
155+
println!("marked record {} for deletion", args.user_data_export_id);
156+
157+
Ok(())
158+
}
159+
160+
async fn cmd_user_data_export_show_changeset(
161+
opctx: &OpContext,
162+
datastore: &DataStore,
163+
) -> Result<(), anyhow::Error> {
164+
let changeset = datastore.compute_user_data_export_changeset(opctx).await?;
165+
166+
#[derive(Tabled)]
167+
struct RequestRow {
168+
action: String,
169+
resource_type: String,
170+
resource_id: Uuid,
171+
}
172+
173+
#[derive(Tabled)]
174+
struct CreateOrDeleteRow {
175+
action: String,
176+
177+
id: UserDataExportUuid,
178+
179+
#[tabled(display_with = "display_debug")]
180+
state: UserDataExportState,
181+
#[tabled(display_with = "display_option_blank")]
182+
operating_saga_id: Option<Uuid>,
183+
generation: i64,
184+
185+
resource_type: String,
186+
resource_id: Uuid,
187+
resource_deleted: bool,
188+
189+
#[tabled(display_with = "display_option_blank")]
190+
pantry_address: Option<SocketAddrV6>,
191+
#[tabled(display_with = "display_option_blank")]
192+
volume_id: Option<VolumeUuid>,
193+
}
194+
195+
let request_rows: Vec<_> = changeset
196+
.request_required
197+
.iter()
198+
.map(|resource| RequestRow {
199+
action: String::from("request"),
200+
resource_type: resource.type_string(),
201+
resource_id: resource.id(),
202+
})
203+
.collect();
204+
205+
let create_rows: Vec<_> = changeset
206+
.create_required
207+
.iter()
208+
.map(|record| CreateOrDeleteRow {
209+
action: String::from("create"),
210+
211+
id: record.id(),
212+
213+
state: record.state(),
214+
operating_saga_id: record.operating_saga_id(),
215+
generation: record.generation(),
216+
217+
resource_type: record.resource().type_string(),
218+
resource_id: record.resource().id(),
219+
resource_deleted: record.deleted(),
220+
221+
pantry_address: record.pantry_address(),
222+
volume_id: record.volume_id(),
223+
})
224+
.collect();
225+
226+
let delete_rows: Vec<_> = changeset
227+
.delete_required
228+
.iter()
229+
.map(|record| CreateOrDeleteRow {
230+
action: String::from("delete"),
231+
232+
id: record.id(),
233+
234+
state: record.state(),
235+
operating_saga_id: record.operating_saga_id(),
236+
generation: record.generation(),
237+
238+
resource_type: record.resource().type_string(),
239+
resource_id: record.resource().id(),
240+
resource_deleted: record.deleted(),
241+
242+
pantry_address: record.pantry_address(),
243+
volume_id: record.volume_id(),
244+
})
245+
.collect();
246+
247+
let table = tabled::Table::new(request_rows)
248+
.with(tabled::settings::Style::psql())
249+
.to_string();
250+
251+
println!("{}", table);
252+
253+
let table = tabled::Table::new(create_rows)
254+
.with(tabled::settings::Style::psql())
255+
.to_string();
256+
257+
println!("{}", table);
258+
259+
let table = tabled::Table::new(delete_rows)
260+
.with(tabled::settings::Style::psql())
261+
.to_string();
262+
263+
println!("{}", table);
264+
265+
Ok(())
266+
}

dev-tools/omdb/src/bin/omdb/helpers.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ pub(crate) const fn const_max_len(strs: &[&str]) -> usize {
3838
max
3939
}
4040

41+
// Wrapper for deriving Tabled with something that derives Debug
42+
pub(crate) fn display_debug<T: std::fmt::Debug>(v: &T) -> String {
43+
format!("{:?}", v)
44+
}
45+
4146
// Display an empty cell for an Option<T> if it's None.
4247
pub(crate) fn display_option_blank<T: std::fmt::Display>(
4348
opt: &Option<T>,

dev-tools/omdb/tests/usage_errors.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ Commands:
144144
oximeter Print information about the oximeter collector
145145
alert Print information about alerts
146146
zpool Commands for querying and interacting with pools
147+
user-data-export Commands for querying and interacting with user data export objects
147148
help Print this message or the help of the given subcommand(s)
148149

149150
Options:
@@ -202,6 +203,7 @@ Commands:
202203
oximeter Print information about the oximeter collector
203204
alert Print information about alerts
204205
zpool Commands for querying and interacting with pools
206+
user-data-export Commands for querying and interacting with user data export objects
205207
help Print this message or the help of the given subcommand(s)
206208

207209
Options:

0 commit comments

Comments
 (0)