Skip to content

Commit 90898fb

Browse files
authored
replace TUF artifact primary key with a UUID (#7340)
`(name, version, kind)` is a natural key for the `tuf_artifact` table: given a kind and name for an artifact, we should only ever have one copy of any particular version. However, this natural key does not distribute well, which [makes it a poor choice for a primary key in CockroachDB][1]. It also makes pagination more difficult. Instead of altering the schema we drop the tables and recreate them, as any potential contents would be useless to us; until #7129 lands we have no means to store the artifacts from a repository. [1]: https://www.cockroachlabs.com/blog/how-to-choose-a-primary-key/
1 parent d9d0c53 commit 90898fb

File tree

14 files changed

+79
-142
lines changed

14 files changed

+79
-142
lines changed

nexus/auth/src/authz/api_resources.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ use authz_macros::authz_resource;
3838
use futures::future::BoxFuture;
3939
use futures::FutureExt;
4040
use nexus_db_fixed_data::FLEET_ID;
41-
use nexus_db_model::{ArtifactId, SemverVersion};
4241
use nexus_types::external_api::shared::{FleetRole, ProjectRole, SiloRole};
4342
use omicron_common::api::external::{Error, LookupType, ResourceType};
4443
use once_cell::sync::Lazy;
@@ -1014,8 +1013,7 @@ authz_resource! {
10141013
authz_resource! {
10151014
name = "TufArtifact",
10161015
parent = "Fleet",
1017-
primary_key = (String, SemverVersion, String),
1018-
input_key = ArtifactId,
1016+
primary_key = { uuid_kind = TufArtifactKind },
10191017
roles_allowed = false,
10201018
polar_snippet = FleetChild,
10211019
}

nexus/db-model/src/schema.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,7 +1336,8 @@ table! {
13361336
}
13371337

13381338
table! {
1339-
tuf_artifact (name, version, kind) {
1339+
tuf_artifact (id) {
1340+
id -> Uuid,
13401341
name -> Text,
13411342
version -> Text,
13421343
kind -> Text,
@@ -1347,11 +1348,9 @@ table! {
13471348
}
13481349

13491350
table! {
1350-
tuf_repo_artifact (tuf_repo_id, tuf_artifact_name, tuf_artifact_version, tuf_artifact_kind) {
1351+
tuf_repo_artifact (tuf_repo_id, tuf_artifact_id) {
13511352
tuf_repo_id -> Uuid,
1352-
tuf_artifact_name -> Text,
1353-
tuf_artifact_version -> Text,
1354-
tuf_artifact_kind -> Text,
1353+
tuf_artifact_id -> Uuid,
13551354
}
13561355
}
13571356

@@ -1361,8 +1360,7 @@ allow_tables_to_appear_in_same_query!(
13611360
tuf_artifact
13621361
);
13631362
joinable!(tuf_repo_artifact -> tuf_repo (tuf_repo_id));
1364-
// Can't specify joinable for a composite primary key (tuf_repo_artifact ->
1365-
// tuf_artifact).
1363+
joinable!(tuf_repo_artifact -> tuf_artifact (tuf_artifact_id));
13661364

13671365
table! {
13681366
support_bundle {

nexus/db-model/src/schema_versions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::collections::BTreeMap;
1717
///
1818
/// This must be updated when you change the database schema. Refer to
1919
/// schema/crdb/README.adoc in the root of this repository for details.
20-
pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(118, 0, 0);
20+
pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(119, 0, 0);
2121

2222
/// List of all past database schema versions, in *reverse* order
2323
///
@@ -29,6 +29,7 @@ static KNOWN_VERSIONS: Lazy<Vec<KnownVersion>> = Lazy::new(|| {
2929
// | leaving the first copy as an example for the next person.
3030
// v
3131
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
32+
KnownVersion::new(119, "tuf-artifact-key-uuid"),
3233
KnownVersion::new(118, "support-bundles"),
3334
KnownVersion::new(117, "add-completing-and-new-region-volume"),
3435
KnownVersion::new(116, "bp-physical-disk-disposition"),

nexus/db-model/src/tuf_repo.rs

Lines changed: 26 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ use chrono::{DateTime, Utc};
1313
use diesel::{deserialize::FromSql, serialize::ToSql, sql_types::Text};
1414
use omicron_common::{
1515
api::external,
16-
update::{
17-
ArtifactHash as ExternalArtifactHash, ArtifactId as ExternalArtifactId,
18-
ArtifactKind,
19-
},
16+
update::{ArtifactHash as ExternalArtifactHash, ArtifactId, ArtifactKind},
2017
};
18+
use omicron_uuid_kinds::TufArtifactKind;
2119
use omicron_uuid_kinds::TufRepoKind;
2220
use omicron_uuid_kinds::TypedUuid;
2321
use serde::{Deserialize, Serialize};
@@ -148,8 +146,10 @@ impl TufRepo {
148146
#[derive(Queryable, Insertable, Clone, Debug, Selectable, AsChangeset)]
149147
#[diesel(table_name = tuf_artifact)]
150148
pub struct TufArtifact {
151-
#[diesel(embed)]
152-
pub id: ArtifactId,
149+
pub id: DbTypedUuid<TufArtifactKind>,
150+
pub name: String,
151+
pub version: SemverVersion,
152+
pub kind: String,
153153
pub time_created: DateTime<Utc>,
154154
pub sha256: ArtifactHash,
155155
artifact_size: i64,
@@ -158,12 +158,15 @@ pub struct TufArtifact {
158158
impl TufArtifact {
159159
/// Creates a new `TufArtifact` ready for insertion.
160160
pub fn new(
161-
id: ArtifactId,
161+
artifact_id: ArtifactId,
162162
sha256: ArtifactHash,
163163
artifact_size: u64,
164164
) -> Self {
165165
Self {
166-
id,
166+
id: TypedUuid::new_v4().into(),
167+
name: artifact_id.name,
168+
version: artifact_id.version.into(),
169+
kind: artifact_id.kind.as_str().to_owned(),
167170
time_created: Utc::now(),
168171
sha256,
169172
artifact_size: artifact_size as i64,
@@ -177,21 +180,31 @@ impl TufArtifact {
177180
/// as part of the process, which `From` doesn't necessarily communicate
178181
/// and can be surprising.
179182
pub fn from_external(artifact: external::TufArtifactMeta) -> Self {
180-
Self::new(artifact.id.into(), artifact.hash.into(), artifact.size)
183+
Self::new(artifact.id, artifact.hash.into(), artifact.size)
181184
}
182185

183186
/// Converts self into [`external::TufArtifactMeta`].
184187
pub fn into_external(self) -> external::TufArtifactMeta {
185188
external::TufArtifactMeta {
186-
id: self.id.into(),
189+
id: ArtifactId {
190+
name: self.name,
191+
version: self.version.into(),
192+
kind: ArtifactKind::new(self.kind),
193+
},
187194
hash: self.sha256.into(),
188195
size: self.artifact_size as u64,
189196
}
190197
}
191198

192199
/// Returns the artifact's ID.
193-
pub fn id(&self) -> (String, SemverVersion, String) {
194-
(self.id.name.clone(), self.id.version.clone(), self.id.kind.clone())
200+
pub fn id(&self) -> TypedUuid<TufArtifactKind> {
201+
self.id.into()
202+
}
203+
204+
/// Returns the artifact's name, version, and kind, which is unique across
205+
/// all artifacts.
206+
pub fn nvk(&self) -> (&str, &SemverVersion, &str) {
207+
(&self.name, &self.version, &self.kind)
195208
}
196209

197210
/// Returns the artifact length in bytes.
@@ -200,70 +213,12 @@ impl TufArtifact {
200213
}
201214
}
202215

203-
/// The ID (primary key) of a [`TufArtifact`].
204-
///
205-
/// This is the internal variant of a [`ExternalArtifactId`].
206-
#[derive(
207-
Queryable,
208-
Insertable,
209-
Clone,
210-
Debug,
211-
Selectable,
212-
PartialEq,
213-
Eq,
214-
Hash,
215-
Deserialize,
216-
Serialize,
217-
)]
218-
#[diesel(table_name = tuf_artifact)]
219-
pub struct ArtifactId {
220-
pub name: String,
221-
pub version: SemverVersion,
222-
pub kind: String,
223-
}
224-
225-
impl From<ExternalArtifactId> for ArtifactId {
226-
fn from(id: ExternalArtifactId) -> Self {
227-
Self {
228-
name: id.name,
229-
version: id.version.into(),
230-
kind: id.kind.as_str().to_owned(),
231-
}
232-
}
233-
}
234-
235-
impl From<ArtifactId> for ExternalArtifactId {
236-
fn from(id: ArtifactId) -> Self {
237-
Self {
238-
name: id.name,
239-
version: id.version.into(),
240-
kind: ArtifactKind::new(id.kind),
241-
}
242-
}
243-
}
244-
245-
impl fmt::Display for ArtifactId {
246-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247-
// This is the same as ExternalArtifactId's Display impl.
248-
write!(f, "{} v{} ({})", self.name, self.version, self.kind)
249-
}
250-
}
251-
252-
/// Required by the authz_resource macro.
253-
impl From<ArtifactId> for (String, SemverVersion, String) {
254-
fn from(id: ArtifactId) -> Self {
255-
(id.name, id.version, id.kind)
256-
}
257-
}
258-
259216
/// A many-to-many relationship between [`TufRepo`] and [`TufArtifact`].
260217
#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
261218
#[diesel(table_name = tuf_repo_artifact)]
262219
pub struct TufRepoArtifact {
263220
pub tuf_repo_id: Uuid,
264-
pub tuf_artifact_name: String,
265-
pub tuf_artifact_version: SemverVersion,
266-
pub tuf_artifact_kind: String,
221+
pub tuf_artifact_id: Uuid,
267222
}
268223

269224
/// A wrapper around omicron-common's [`ArtifactHash`](ExternalArtifactHash),

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

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,8 @@ async fn artifacts_for_repo(
5050
use db::schema::tuf_artifact::dsl as tuf_artifact_dsl;
5151
use db::schema::tuf_repo_artifact::dsl as tuf_repo_artifact_dsl;
5252

53-
let join_on_dsl = tuf_artifact_dsl::name
54-
.eq(tuf_repo_artifact_dsl::tuf_artifact_name)
55-
.and(
56-
tuf_artifact_dsl::version
57-
.eq(tuf_repo_artifact_dsl::tuf_artifact_version),
58-
)
59-
.and(
60-
tuf_artifact_dsl::kind.eq(tuf_repo_artifact_dsl::tuf_artifact_kind),
61-
);
53+
let join_on_dsl =
54+
tuf_artifact_dsl::id.eq(tuf_repo_artifact_dsl::tuf_artifact_id);
6255
// Don't bother paginating because each repo should only have a few (under
6356
// 20) artifacts.
6457
tuf_repo_artifact_dsl::tuf_repo_artifact
@@ -215,9 +208,9 @@ async fn insert_impl(
215208
for artifact in desc.artifacts.clone() {
216209
filter_dsl = filter_dsl.or_filter(
217210
dsl::name
218-
.eq(artifact.id.name)
219-
.and(dsl::version.eq(artifact.id.version))
220-
.and(dsl::kind.eq(artifact.id.kind)),
211+
.eq(artifact.name)
212+
.and(dsl::version.eq(artifact.version))
213+
.and(dsl::kind.eq(artifact.kind)),
221214
);
222215
}
223216

@@ -233,7 +226,7 @@ async fn insert_impl(
233226

234227
let results_by_id = results
235228
.iter()
236-
.map(|artifact| (&artifact.id, artifact))
229+
.map(|artifact| (artifact.nvk(), artifact))
237230
.collect::<HashMap<_, _>>();
238231

239232
// uploaded_and_existing contains non-matching artifacts in pairs of
@@ -244,7 +237,7 @@ async fn insert_impl(
244237

245238
for uploaded_artifact in desc.artifacts.clone() {
246239
let Some(&existing_artifact) =
247-
results_by_id.get(&uploaded_artifact.id)
240+
results_by_id.get(&uploaded_artifact.nvk())
248241
else {
249242
// This is a new artifact.
250243
new_artifacts.push(uploaded_artifact.clone());
@@ -301,9 +294,7 @@ async fn insert_impl(
301294
);
302295
values.push((
303296
dsl::tuf_repo_id.eq(desc.repo.id),
304-
dsl::tuf_artifact_name.eq(artifact.id.name),
305-
dsl::tuf_artifact_version.eq(artifact.id.version),
306-
dsl::tuf_artifact_kind.eq(artifact.id.kind),
297+
dsl::tuf_artifact_id.eq(artifact.id),
307298
));
308299
}
309300

nexus/db-queries/src/db/lookup.rs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use omicron_common::api::external::Error;
2222
use omicron_common::api::external::InternalContext;
2323
use omicron_common::api::external::{LookupResult, LookupType, ResourceType};
2424
use omicron_uuid_kinds::PhysicalDiskUuid;
25+
use omicron_uuid_kinds::TufArtifactKind;
2526
use omicron_uuid_kinds::TufRepoKind;
2627
use omicron_uuid_kinds::TypedUuid;
2728
use uuid::Uuid;
@@ -446,18 +447,11 @@ impl<'a> LookupPath<'a> {
446447

447448
/// Select a resource of type UpdateArtifact, identified by its
448449
/// `(name, version, kind)` tuple
449-
pub fn tuf_artifact_tuple(
450+
pub fn tuf_artifact_id(
450451
self,
451-
name: impl Into<String>,
452-
version: db::model::SemverVersion,
453-
kind: impl Into<String>,
452+
id: TypedUuid<TufArtifactKind>,
454453
) -> TufArtifact<'a> {
455-
TufArtifact::PrimaryKey(
456-
Root { lookup_root: self },
457-
name.into(),
458-
version,
459-
kind.into(),
460-
)
454+
TufArtifact::PrimaryKey(Root { lookup_root: self }, id)
461455
}
462456

463457
/// Select a resource of type UserBuiltin, identified by its `name`
@@ -895,11 +889,7 @@ lookup_resource! {
895889
children = [],
896890
lookup_by_name = false,
897891
soft_deletes = false,
898-
primary_key_columns = [
899-
{ column_name = "name", rust_type = String },
900-
{ column_name = "version", rust_type = db::model::SemverVersion },
901-
{ column_name = "kind", rust_type = String },
902-
]
892+
primary_key_columns = [ { column_name = "id", uuid_kind = TufArtifactKind } ]
903893
}
904894

905895
lookup_resource! {

nexus/db-queries/src/policy_test/resources.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
77
use super::resource_builder::ResourceBuilder;
88
use super::resource_builder::ResourceSet;
9-
use crate::db::model::ArtifactId;
109
use nexus_auth::authz;
11-
use nexus_db_model::SemverVersion;
1210
use omicron_common::api::external::LookupType;
1311
use omicron_uuid_kinds::GenericUuid;
1412
use omicron_uuid_kinds::PhysicalDiskUuid;
@@ -139,16 +137,12 @@ pub async fn make_resources(
139137
LookupType::ById(tuf_repo_id.into_untyped_uuid()),
140138
));
141139

142-
let artifact_id = ArtifactId {
143-
name: "a".to_owned(),
144-
version: SemverVersion("1.0.0".parse().unwrap()),
145-
kind: "b".to_owned(),
146-
};
147-
let artifact_id_desc = artifact_id.to_string();
140+
let tuf_artifact_id =
141+
"6827813e-bfaa-4205-9b9f-9f7901e4aab1".parse().unwrap();
148142
builder.new_resource(authz::TufArtifact::new(
149143
authz::FLEET,
150-
artifact_id,
151-
LookupType::ByCompositeId(artifact_id_desc),
144+
tuf_artifact_id,
145+
LookupType::ById(tuf_artifact_id.into_untyped_uuid()),
152146
));
153147

154148
let address_lot_id =

nexus/db-queries/tests/output/authz-roles.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@ resource: TufRepo id "3c52d72f-cbf7-4951-a62f-a4154e74da87"
10901090
silo1-proj1-viewer ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘
10911091
unauthenticated ! ! ! ! ! ! ! !
10921092

1093-
resource: TufArtifact id "a v1.0.0 (b)"
1093+
resource: TufArtifact id "6827813e-bfaa-4205-9b9f-9f7901e4aab1"
10941094

10951095
USER Q R LC RP M MP CC D
10961096
fleet-admin ✘ ✔ ✔ ✔ ✔ ✔ ✔ ✔

0 commit comments

Comments
 (0)