Skip to content

Commit e036174

Browse files
committed
Merge branch 'main' into range-in-inventory
2 parents 0637cbe + 17ca05f commit e036174

File tree

19 files changed

+2394
-3
lines changed

19 files changed

+2394
-3
lines changed

nexus/db-model/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ mod typed_uuid;
118118
mod unsigned;
119119
mod upstairs_repair;
120120
mod user_builtin;
121+
mod user_data_export;
121122
mod utilization;
122123
mod virtual_provisioning_collection;
123124
mod virtual_provisioning_resource;
@@ -241,6 +242,7 @@ pub use typed_uuid::DbTypedUuid;
241242
pub use typed_uuid::to_db_typed_uuid;
242243
pub use upstairs_repair::*;
243244
pub use user_builtin::*;
245+
pub use user_data_export::*;
244246
pub use utilization::*;
245247
pub use v2p_mapping::*;
246248
pub use virtual_provisioning_collection::*;

nexus/db-model/src/schema_versions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
2828
// | leaving the first copy as an example for the next person.
2929
// v
3030
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
31-
KnownVersion::new(157, "inv_cockroachdb_status"),
31+
KnownVersion::new(158, "inv_cockroachdb_status"),
32+
KnownVersion::new(157, "user-data-export"),
3233
KnownVersion::new(156, "boot-partitions-inventory"),
3334
KnownVersion::new(155, "vpc-firewall-icmp"),
3435
KnownVersion::new(154, "add-pending-mgs-updates"),
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
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+
use super::impl_enum_type;
6+
use crate::SqlU16;
7+
use crate::ipv6;
8+
use crate::typed_uuid::DbTypedUuid;
9+
use nexus_db_schema::schema::user_data_export;
10+
use omicron_uuid_kinds::UserDataExportKind;
11+
use omicron_uuid_kinds::UserDataExportUuid;
12+
use omicron_uuid_kinds::VolumeKind;
13+
use omicron_uuid_kinds::VolumeUuid;
14+
use serde::Deserialize;
15+
use serde::Serialize;
16+
use std::net::SocketAddrV6;
17+
use uuid::Uuid;
18+
19+
impl_enum_type!(
20+
UserDataExportResourceTypeEnum:
21+
22+
#[derive(Copy, Clone, Debug, AsExpression, FromSqlRow, Serialize, Deserialize, PartialEq)]
23+
pub enum UserDataExportResourceType;
24+
25+
// Enum values
26+
Snapshot => b"snapshot"
27+
Image => b"image"
28+
);
29+
30+
// FromStr impl required for use with clap (aka omdb)
31+
impl std::str::FromStr for UserDataExportResourceType {
32+
type Err = String;
33+
34+
fn from_str(s: &str) -> Result<Self, Self::Err> {
35+
match s {
36+
"snapshot" => Ok(UserDataExportResourceType::Snapshot),
37+
"image" => Ok(UserDataExportResourceType::Image),
38+
_ => Err(format!("unrecognized value {} for enum", s)),
39+
}
40+
}
41+
}
42+
43+
impl UserDataExportResourceType {
44+
pub fn to_string(&self) -> String {
45+
String::from(match self {
46+
UserDataExportResourceType::Snapshot => "snapshot",
47+
UserDataExportResourceType::Image => "image",
48+
})
49+
}
50+
}
51+
52+
impl_enum_type!(
53+
UserDataExportStateEnum:
54+
55+
#[derive(Copy, Clone, Debug, AsExpression, FromSqlRow, Serialize, Deserialize, PartialEq)]
56+
pub enum UserDataExportState;
57+
58+
// Enum values
59+
Requested => b"requested"
60+
Assigning => b"assigning"
61+
Live => b"live"
62+
Deleting => b"deleting"
63+
Deleted => b"deleted"
64+
);
65+
66+
/// Instead of working directly with the UserDataExportRecord, callers can use
67+
/// this enum instead, where the call site only cares of the record is live or
68+
/// not.
69+
pub enum UserDataExport {
70+
NotLive,
71+
72+
Live { pantry_address: SocketAddrV6, volume_id: VolumeUuid },
73+
}
74+
75+
/// A "user data export" object represents an attachment of a read-only volume
76+
/// to a Pantry for the purpose of exporting data. As of this writing only
77+
/// snapshots and images are able to be exported this way. Management of these
78+
/// objects is done automatically by a background task.
79+
///
80+
/// Note that read-only volumes should never directly be constructed (read: be
81+
/// passed to Volume::construct). Copies should be created so that the
82+
/// appropriate reference counting for the read-only volume targets can be
83+
/// maintained. The user data export object stores that copied Volume, among
84+
/// other things.
85+
///
86+
/// The record transitions through the following states:
87+
///
88+
/// ```text
89+
/// Requested <-- ---
90+
/// | |
91+
/// | | |
92+
/// v | | responsibility of user
93+
/// | | export create saga
94+
/// Assigning -- |
95+
/// |
96+
/// | |
97+
/// v ---
98+
/// ---
99+
/// Live <-- |
100+
/// | |
101+
/// | | |
102+
/// v | | responsibility of user
103+
/// | | export delete saga
104+
/// Deleting -- |
105+
/// |
106+
/// | |
107+
/// v |
108+
/// ---
109+
/// Deleted
110+
/// ```
111+
///
112+
/// which are captured in the UserDataExportState enum. Annotated on the right
113+
/// are which sagas are responsible for which state transitions. The state
114+
/// transitions themselves are performed by these sagas and all involve a query
115+
/// that:
116+
///
117+
/// - checks that the starting state (and other values as required) make sense
118+
/// - updates the state while setting a unique operating_saga_id id (and any
119+
/// other fields as appropriate)
120+
///
121+
/// As multiple background tasks will be waking up, checking to see what sagas
122+
/// need to be triggered, and requesting that these sagas run, this is meant to
123+
/// block multiple sagas from running at the same time in an effort to cut down
124+
/// on interference - most will unwind at the first step of performing this
125+
/// state transition instead of somewhere in the middle. This is not required
126+
/// for correctness as each saga node can deal with this type of interference.
127+
#[derive(Queryable, Insertable, Selectable, Clone, Debug)]
128+
#[diesel(table_name = user_data_export)]
129+
pub struct UserDataExportRecord {
130+
id: DbTypedUuid<UserDataExportKind>,
131+
132+
state: UserDataExportState,
133+
operating_saga_id: Option<Uuid>,
134+
generation: i64,
135+
136+
resource_id: Uuid,
137+
resource_type: UserDataExportResourceType,
138+
resource_deleted: bool,
139+
140+
pantry_ip: Option<ipv6::Ipv6Addr>,
141+
pantry_port: Option<SqlU16>,
142+
volume_id: Option<DbTypedUuid<VolumeKind>>,
143+
}
144+
145+
impl UserDataExportRecord {
146+
pub fn new(
147+
id: UserDataExportUuid,
148+
resource: UserDataExportResource,
149+
) -> Self {
150+
let (resource_type, resource_id) = match resource {
151+
UserDataExportResource::Snapshot { id } => {
152+
(UserDataExportResourceType::Snapshot, id)
153+
}
154+
155+
UserDataExportResource::Image { id } => {
156+
(UserDataExportResourceType::Image, id)
157+
}
158+
};
159+
160+
Self {
161+
id: id.into(),
162+
163+
state: UserDataExportState::Requested,
164+
operating_saga_id: None,
165+
generation: 0,
166+
167+
resource_type,
168+
resource_id,
169+
resource_deleted: false,
170+
171+
pantry_ip: None,
172+
pantry_port: None,
173+
volume_id: None,
174+
}
175+
}
176+
177+
pub fn id(&self) -> UserDataExportUuid {
178+
self.id.into()
179+
}
180+
181+
pub fn state(&self) -> UserDataExportState {
182+
self.state
183+
}
184+
185+
pub fn operating_saga_id(&self) -> Option<Uuid> {
186+
self.operating_saga_id
187+
}
188+
189+
pub fn generation(&self) -> i64 {
190+
self.generation
191+
}
192+
193+
pub fn resource(&self) -> UserDataExportResource {
194+
match self.resource_type {
195+
UserDataExportResourceType::Snapshot => {
196+
UserDataExportResource::Snapshot { id: self.resource_id }
197+
}
198+
199+
UserDataExportResourceType::Image => {
200+
UserDataExportResource::Image { id: self.resource_id }
201+
}
202+
}
203+
}
204+
205+
pub fn deleted(&self) -> bool {
206+
self.resource_deleted
207+
}
208+
209+
pub fn pantry_address(&self) -> Option<SocketAddrV6> {
210+
match (&self.pantry_ip, &self.pantry_port) {
211+
(Some(pantry_ip), Some(pantry_port)) => Some(SocketAddrV6::new(
212+
(*pantry_ip).into(),
213+
(*pantry_port).into(),
214+
0,
215+
0,
216+
)),
217+
218+
(_, _) => None,
219+
}
220+
}
221+
222+
pub fn volume_id(&self) -> Option<VolumeUuid> {
223+
self.volume_id.map(|i| i.into())
224+
}
225+
226+
pub fn is_live(&self) -> Result<UserDataExport, &'static str> {
227+
match self.state {
228+
UserDataExportState::Requested
229+
| UserDataExportState::Assigning
230+
| UserDataExportState::Deleting
231+
| UserDataExportState::Deleted => Ok(UserDataExport::NotLive),
232+
233+
UserDataExportState::Live => {
234+
let Some(pantry_ip) = self.pantry_ip else {
235+
return Err("pantry_ip is None!");
236+
};
237+
238+
let Some(pantry_port) = self.pantry_port else {
239+
return Err("pantry_port is None!");
240+
};
241+
242+
let Some(volume_id) = self.volume_id else {
243+
return Err("volume_id is None!");
244+
};
245+
246+
Ok(UserDataExport::Live {
247+
pantry_address: SocketAddrV6::new(
248+
pantry_ip.into(),
249+
*pantry_port,
250+
0,
251+
0,
252+
),
253+
254+
volume_id: volume_id.into(),
255+
})
256+
}
257+
}
258+
}
259+
}
260+
261+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
262+
pub enum UserDataExportResource {
263+
Snapshot { id: Uuid },
264+
265+
Image { id: Uuid },
266+
}
267+
268+
impl UserDataExportResource {
269+
pub fn type_string(&self) -> String {
270+
String::from(match self {
271+
UserDataExportResource::Snapshot { .. } => "snapshot",
272+
UserDataExportResource::Image { .. } => "image",
273+
})
274+
}
275+
276+
pub fn id(&self) -> Uuid {
277+
match self {
278+
UserDataExportResource::Snapshot { id } => *id,
279+
UserDataExportResource::Image { id } => *id,
280+
}
281+
}
282+
}

nexus/db-queries/src/db/datastore/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ mod target_release;
107107
#[cfg(test)]
108108
pub(crate) mod test_utils;
109109
mod update;
110+
mod user_data_export;
110111
mod utilization;
111112
mod v2p_mapping;
112113
mod virtual_provisioning_collection;
@@ -137,6 +138,7 @@ pub use sled::SledTransition;
137138
pub use sled::TransitionError;
138139
pub use support_bundle::SupportBundleExpungementReport;
139140
pub use switch_port::SwitchPortSettingsCombinedResult;
141+
pub use user_data_export::*;
140142
pub use virtual_provisioning_collection::StorageType;
141143
pub use vmm::VmmStateUpdateResult;
142144
pub use volume::*;

0 commit comments

Comments
 (0)