Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions common/src/sql/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,9 @@ CREATE TABLE omicron.public.project (
/* Indicates that the object has been deleted */
time_deleted TIMESTAMPTZ,

/* child resource generation number, per RFD 192 */
rcgen INT NOT NULL,

/* Which organization this project belongs to */
organization_id UUID NOT NULL /* foreign key into "Organization" table */
);
Expand Down
64 changes: 62 additions & 2 deletions nexus/db-model/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use super::Name;
use crate::schema::project;
use super::{
Disk, ExternalIp, Generation, Image, Instance, IpPool, Name, Snapshot, Vpc,
};
use crate::collection::DatastoreCollectionConfig;
use crate::schema::{
disk, external_ip, image, instance, ip_pool, project, snapshot, vpc,
};
use chrono::{DateTime, Utc};
use db_macros::Resource;
use nexus_types::external_api::params;
Expand All @@ -18,6 +23,8 @@ pub struct Project {
#[diesel(embed)]
identity: ProjectIdentity,

/// child resource generation number, per RFD 192
pub rcgen: Generation,
pub organization_id: Uuid,
}

Expand All @@ -26,6 +33,7 @@ impl Project {
pub fn new(organization_id: Uuid, params: params::ProjectCreate) -> Self {
Self {
identity: ProjectIdentity::new(Uuid::new_v4(), params.identity),
rcgen: Generation::new(),
organization_id,
}
}
Expand All @@ -40,6 +48,58 @@ impl From<Project> for views::Project {
}
}

impl DatastoreCollectionConfig<Instance> for Project {
type CollectionId = Uuid;
type GenerationNumberColumn = project::dsl::rcgen;
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
type CollectionIdColumn = instance::dsl::project_id;
}

impl DatastoreCollectionConfig<Disk> for Project {
type CollectionId = Uuid;
type GenerationNumberColumn = project::dsl::rcgen;
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
type CollectionIdColumn = disk::dsl::project_id;
}

impl DatastoreCollectionConfig<Image> for Project {
type CollectionId = Uuid;
type GenerationNumberColumn = project::dsl::rcgen;
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
type CollectionIdColumn = image::dsl::project_id;
}

impl DatastoreCollectionConfig<Snapshot> for Project {
type CollectionId = Uuid;
type GenerationNumberColumn = project::dsl::rcgen;
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
type CollectionIdColumn = snapshot::dsl::project_id;
}

impl DatastoreCollectionConfig<Vpc> for Project {
type CollectionId = Uuid;
type GenerationNumberColumn = project::dsl::rcgen;
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
type CollectionIdColumn = vpc::dsl::project_id;
}

// NOTE: "IpPoolRange" also contains a reference to "project_id", but
// ranges should only exist within IP Pools.
impl DatastoreCollectionConfig<IpPool> for Project {
type CollectionId = Uuid;
type GenerationNumberColumn = project::dsl::rcgen;
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
type CollectionIdColumn = ip_pool::dsl::project_id;
}

// TODO(https://github.com/oxidecomputer/omicron/issues/1482): Not yet utilized
impl DatastoreCollectionConfig<ExternalIp> for Project {
type CollectionId = Uuid;
type GenerationNumberColumn = project::dsl::rcgen;
type CollectionTimeDeletedColumn = project::dsl::time_deleted;
type CollectionIdColumn = external_ip::dsl::project_id;
}

/// Describes a set of updates for the [`Project`] model.
#[derive(AsChangeset)]
#[diesel(table_name = project)]
Expand Down
1 change: 1 addition & 0 deletions nexus/db-model/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ table! {
time_created -> Timestamptz,
time_modified -> Timestamptz,
time_deleted -> Nullable<Timestamptz>,
rcgen -> Int8,
organization_id -> Uuid,
}
}
Expand Down
4 changes: 4 additions & 0 deletions nexus/src/app/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ impl super::Nexus {
.project_name(project_name)
.lookup_for(authz::Action::CreateChild)
.await?;

// TODO(https://github.com/oxidecomputer/omicron/issues/1482): When
// we implement this, remember to insert the image within a Project
// using "insert_resource".
Err(self.unimplemented_todo(opctx, Unimpl::Public).await)
}

Expand Down
15 changes: 9 additions & 6 deletions nexus/src/app/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,15 @@ impl super::Nexus {
organization_name: &Name,
project_name: &Name,
) -> DeleteResult {
let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore)
.organization_name(organization_name)
.project_name(project_name)
.lookup_for(authz::Action::Delete)
.await?;
self.db_datastore.project_delete(opctx, &authz_project).await
let (.., authz_project, db_project) =
LookupPath::new(opctx, &self.db_datastore)
.organization_name(organization_name)
.project_name(project_name)
.fetch()
.await?;
self.db_datastore
.project_delete(opctx, &authz_project, &db_project)
.await
}

// Role assignments
Expand Down
31 changes: 22 additions & 9 deletions nexus/src/db/datastore/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use crate::db::collection_attach::AttachError;
use crate::db::collection_attach::DatastoreAttachTarget;
use crate::db::collection_detach::DatastoreDetachTarget;
use crate::db::collection_detach::DetachError;
use crate::db::collection_insert::AsyncInsertError;
use crate::db::collection_insert::DatastoreCollection;
use crate::db::error::public_error_from_diesel_pool;
use crate::db::error::ErrorHandler;
use crate::db::identity::Resource;
Expand All @@ -21,6 +23,7 @@ use crate::db::model::Disk;
use crate::db::model::DiskRuntimeState;
use crate::db::model::Instance;
use crate::db::model::Name;
use crate::db::model::Project;
use crate::db::pagination::paginated;
use crate::db::update_and_check::UpdateAndCheck;
use crate::db::update_and_check::UpdateStatus;
Expand Down Expand Up @@ -64,19 +67,29 @@ impl DataStore {

let gen = disk.runtime().gen;
let name = disk.name().clone();
let disk: Disk = diesel::insert_into(dsl::disk)
.values(disk)
.on_conflict(dsl::id)
.do_nothing()
.returning(Disk::as_returning())
.get_result_async(self.pool())
.await
.map_err(|e| {
let project_id = disk.project_id;

let disk: Disk = Project::insert_resource(
project_id,
diesel::insert_into(dsl::disk)
.values(disk)
.on_conflict(dsl::id)
.do_nothing(),
)
.insert_and_get_result_async(self.pool())
.await
.map_err(|e| match e {
AsyncInsertError::CollectionNotFound => Error::ObjectNotFound {
type_name: ResourceType::Project,
lookup_type: LookupType::ById(project_id),
},
AsyncInsertError::DatabaseError(e) => {
public_error_from_diesel_pool(
e,
ErrorHandler::Conflict(ResourceType::Disk, name.as_str()),
)
})?;
}
})?;

let runtime = disk.runtime();
bail_unless!(
Expand Down
31 changes: 22 additions & 9 deletions nexus/src/db/datastore/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ use crate::context::OpContext;
use crate::db;
use crate::db::collection_detach_many::DatastoreDetachManyTarget;
use crate::db::collection_detach_many::DetachManyError;
use crate::db::collection_insert::AsyncInsertError;
use crate::db::collection_insert::DatastoreCollection;
use crate::db::error::public_error_from_diesel_pool;
use crate::db::error::ErrorHandler;
use crate::db::identity::Resource;
use crate::db::lookup::LookupPath;
use crate::db::model::Instance;
use crate::db::model::InstanceRuntimeState;
use crate::db::model::Name;
use crate::db::model::Project;
use crate::db::pagination::paginated;
use crate::db::update_and_check::UpdateAndCheck;
use crate::db::update_and_check::UpdateStatus;
Expand Down Expand Up @@ -66,22 +69,32 @@ impl DataStore {

let gen = instance.runtime().gen;
let name = instance.name().clone();
let instance: Instance = diesel::insert_into(dsl::instance)
.values(instance)
.on_conflict(dsl::id)
.do_nothing()
.returning(Instance::as_returning())
.get_result_async(self.pool())
.await
.map_err(|e| {
let project_id = instance.project_id;

let instance: Instance = Project::insert_resource(
project_id,
diesel::insert_into(dsl::instance)
.values(instance)
.on_conflict(dsl::id)
.do_nothing(),
)
.insert_and_get_result_async(self.pool())
.await
.map_err(|e| match e {
AsyncInsertError::CollectionNotFound => Error::ObjectNotFound {
type_name: ResourceType::Project,
lookup_type: LookupType::ById(project_id),
},
AsyncInsertError::DatabaseError(e) => {
public_error_from_diesel_pool(
e,
ErrorHandler::Conflict(
ResourceType::Instance,
name.as_str(),
),
)
})?;
}
})?;

bail_unless!(
instance.runtime().state.state()
Expand Down
47 changes: 38 additions & 9 deletions nexus/src/db/datastore/ip_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::db::model::IpPool;
use crate::db::model::IpPoolRange;
use crate::db::model::IpPoolUpdate;
use crate::db::model::Name;
use crate::db::model::Project;
use crate::db::pagination::paginated;
use crate::db::queries::ip_pool::FilterOverlappingIpRanges;
use crate::external_api::params;
Expand Down Expand Up @@ -158,17 +159,45 @@ impl DataStore {
};
let pool = IpPool::new(&new_pool.identity, project_id, rack_id);
let pool_name = pool.name().as_str().to_string();
diesel::insert_into(dsl::ip_pool)
.values(pool)
.returning(IpPool::as_returning())
.get_result_async(self.pool_authorized(opctx).await?)

if let Some(project_id) = project_id {
Project::insert_resource(
project_id,
diesel::insert_into(dsl::ip_pool).values(pool),
)
.insert_and_get_result_async(self.pool_authorized(opctx).await?)
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::Conflict(ResourceType::IpPool, &pool_name),
)
.map_err(|e| match e {
AsyncInsertError::CollectionNotFound => Error::ObjectNotFound {
type_name: ResourceType::Project,
lookup_type: LookupType::ById(project_id),
},
AsyncInsertError::DatabaseError(e) => {
public_error_from_diesel_pool(
e,
ErrorHandler::Conflict(
ResourceType::IpPool,
&pool_name,
),
)
}
})
} else {
diesel::insert_into(dsl::ip_pool)
.values(pool)
.returning(IpPool::as_returning())
.get_result_async(self.pool_authorized(opctx).await?)
.await
.map_err(|e| {
public_error_from_diesel_pool(
e,
ErrorHandler::Conflict(
ResourceType::IpPool,
&pool_name,
),
)
})
}
}

pub async fn ip_pool_delete(
Expand Down
Loading