Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(5/5) [nexus] Implement Affinity/Anti-Affinity Groups in external API #7447

Open
wants to merge 82 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
c9fb7a6
[nexus] Add Affinity/Anti-Affinity Groups to API (unimplemented)
smklein Jan 30, 2025
4020517
[nexus] Add Affinity/Anti-Affinity groups to database
smklein Jan 30, 2025
8f1d37c
[nexus] Add CRUD implementations for Affinity/Anti-Affinity Groups
smklein Jan 30, 2025
772e64f
[nexus] Consider Affinity/Anti-Affinity Groups during instance placement
smklein Jan 30, 2025
d8cff32
[nexus] Implement Affinity/Anti-Affinity Groups in external API
smklein Jan 30, 2025
161f9d6
fix policy tests
smklein Jan 30, 2025
df119b6
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Jan 30, 2025
83a26a4
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Jan 30, 2025
8dc0825
fmt
smklein Jan 30, 2025
e3113ff
Merge branch 'affinity-api' into affinity-db-model
smklein Jan 30, 2025
789bc97
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Jan 30, 2025
5e21f34
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Jan 30, 2025
fa9461b
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Jan 30, 2025
4e9cebc
tags
smklein Jan 30, 2025
6cfca2d
doc comments
smklein Jan 31, 2025
a1c97d4
Merge branch 'affinity-api' into affinity-db-model
smklein Jan 31, 2025
050b4c5
Merge branch 'main' into affinity-api
smklein Jan 31, 2025
4b08032
typed UUID
smklein Jan 31, 2025
8bc8f0c
Merge branch 'affinity-api' into affinity-db-model
smklein Jan 31, 2025
900f09c
Typed UUID
smklein Jan 31, 2025
195e167
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Jan 31, 2025
f2ebe31
Typed UUID
smklein Jan 31, 2025
62c38ec
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Jan 31, 2025
85985c1
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Jan 31, 2025
1326116
UUID typing
smklein Jan 31, 2025
aba9596
comments
smklein Jan 31, 2025
a271f1d
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Jan 31, 2025
1ad0101
review feedback
smklein Feb 1, 2025
4d26262
comment
smklein Feb 1, 2025
6ae1910
clippy
smklein Feb 1, 2025
58ebd65
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Feb 1, 2025
ab817bc
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 1, 2025
e21125f
Merge branch 'main' into affinity-api
smklein Feb 4, 2025
bd95b03
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 4, 2025
76b0af9
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Feb 4, 2025
1179919
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Feb 4, 2025
a07c46e
review feedback
smklein Feb 4, 2025
fdccd6b
review feedback (grammatical)
smklein Feb 5, 2025
db49c67
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 5, 2025
6e44392
anti-affinity group description
smklein Feb 5, 2025
d5b951f
Update comment
smklein Feb 5, 2025
97b9b2e
Eliza's feedback
smklein Feb 6, 2025
d949890
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 6, 2025
45b993a
Merge branch 'main' into affinity-api
smklein Feb 18, 2025
05c8b60
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 18, 2025
eee1d6f
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Feb 18, 2025
10aaed9
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Feb 18, 2025
f8b9b5d
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 18, 2025
393a925
Merge branch 'main' into affinity-api
smklein Feb 19, 2025
7a99a96
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 19, 2025
ee657cc
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Feb 19, 2025
931801d
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Feb 19, 2025
5f75a2b
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 19, 2025
c18449c
Merge branch 'main' into affinity-api
smklein Feb 19, 2025
4bf0e48
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 19, 2025
1a7e7e0
Merge branch 'main' into affinity-api
smklein Feb 19, 2025
01b8f03
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 19, 2025
a35a33c
Merge branch 'main' into affinity-api
smklein Feb 20, 2025
eb9682f
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 20, 2025
0e6eaf2
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Feb 20, 2025
45a97a3
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Feb 20, 2025
bd1f2cc
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 20, 2025
4a00ddd
Merge branch 'main' into affinity-api
smklein Feb 21, 2025
7a8da16
Merge branch 'affinity-api' into affinity-db-model (schema)
smklein Feb 21, 2025
081e8b4
Merge branch 'main' into affinity-api
smklein Feb 21, 2025
7733054
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 21, 2025
e9cc19c
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Feb 21, 2025
01914c3
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Feb 21, 2025
e6634dc
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 21, 2025
565ffc8
Merge branch 'main' into affinity-api
smklein Feb 21, 2025
e15460c
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 21, 2025
9190d80
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Feb 21, 2025
8d90161
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Feb 21, 2025
9f6ad7b
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 21, 2025
16b3df3
Merge branch 'main' into affinity-api
smklein Feb 24, 2025
f5bbe28
Merge branch 'affinity-api' into affinity-db-model
smklein Feb 24, 2025
beaedfc
Merge branch 'affinity-db-model' into affinity-db-crud
smklein Feb 24, 2025
afbc4ad
Merge branch 'affinity-db-crud' into affinity-instance-integration
smklein Feb 24, 2025
25a6e5e
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 24, 2025
0419d37
review feedback
smklein Feb 24, 2025
bba32a6
Merge branch 'main' into affinity-instance-integration
smklein Feb 25, 2025
e893250
Merge branch 'affinity-instance-integration' into affinity-integration
smklein Feb 25, 2025
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
13 changes: 13 additions & 0 deletions common/src/api/external/http_pagination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,19 @@ pub type PaginatedByNameOrId<Selector = ()> = PaginationParams<
pub type PageSelectorByNameOrId<Selector = ()> =
PageSelector<ScanByNameOrId<Selector>, NameOrId>;

pub fn id_pagination<'a, Selector>(
pag_params: &'a DataPageParams<Uuid>,
scan_params: &'a ScanById<Selector>,
) -> Result<PaginatedBy<'a>, HttpError>
where
Selector:
Clone + Debug + DeserializeOwned + JsonSchema + PartialEq + Serialize,
{
match scan_params.sort_by {
IdSortMode::IdAscending => Ok(PaginatedBy::Id(pag_params.clone())),
}
}

pub fn name_or_id_pagination<'a, Selector>(
pag_params: &'a DataPageParams<NameOrId>,
scan_params: &'a ScanByNameOrId<Selector>,
Expand Down
6 changes: 6 additions & 0 deletions common/src/api/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@ impl<'a> From<&'a Name> for &'a str {
}
}

impl From<Name> for String {
fn from(name: Name) -> Self {
name.0
}
}

/// `Name` instances are comparable like Strings, primarily so that they can
/// be used as keys in trees.
impl<S> PartialEq<S> for Name
Expand Down
1 change: 1 addition & 0 deletions dev-tools/omdb/src/bin/omdb/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6454,6 +6454,7 @@ async fn cmd_db_vmm_info(
rss_ram: ByteCount(rss),
reservoir_ram: ByteCount(reservoir),
},
instance_id: _,
} = resource;
const SLED_ID: &'static str = "sled ID";
const THREADS: &'static str = "hardware threads";
Expand Down
16 changes: 16 additions & 0 deletions nexus/auth/src/authz/api_resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,22 @@ authz_resource! {
polar_snippet = InProject,
}

authz_resource! {
name = "AffinityGroup",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
}

authz_resource! {
name = "AntiAffinityGroup",
parent = "Project",
primary_key = Uuid,
roles_allowed = false,
polar_snippet = InProject,
}

authz_resource! {
name = "InstanceNetworkInterface",
parent = "Instance",
Expand Down
2 changes: 2 additions & 0 deletions nexus/auth/src/authz/oso_generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ pub fn make_omicron_oso(log: &slog::Logger) -> Result<OsoInit, anyhow::Error> {
Disk::init(),
Snapshot::init(),
ProjectImage::init(),
AffinityGroup::init(),
AntiAffinityGroup::init(),
Instance::init(),
IpPool::init(),
InstanceNetworkInterface::init(),
Expand Down
259 changes: 259 additions & 0 deletions nexus/db-model/src/affinity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// 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/5.0/.

// Copyright 2025 Oxide Computer Company

//! Database representation of affinity and anti-affinity groups

use super::impl_enum_type;
use super::Name;
use crate::schema::affinity_group;
use crate::schema::affinity_group_instance_membership;
use crate::schema::anti_affinity_group;
use crate::schema::anti_affinity_group_instance_membership;
use crate::typed_uuid::DbTypedUuid;
use chrono::{DateTime, Utc};
use db_macros::Resource;
use nexus_types::external_api::params;
use nexus_types::external_api::views;
use omicron_common::api::external;
use omicron_common::api::external::IdentityMetadata;
use omicron_uuid_kinds::AffinityGroupKind;
use omicron_uuid_kinds::AffinityGroupUuid;
use omicron_uuid_kinds::AntiAffinityGroupKind;
use omicron_uuid_kinds::AntiAffinityGroupUuid;
use omicron_uuid_kinds::InstanceKind;
use omicron_uuid_kinds::InstanceUuid;
use uuid::Uuid;

impl_enum_type!(
#[derive(SqlType, Debug, QueryId)]
#[diesel(postgres_type(name = "affinity_policy", schema = "public"))]
pub struct AffinityPolicyEnum;

#[derive(Clone, Copy, Debug, AsExpression, FromSqlRow, PartialEq, Eq, Ord, PartialOrd)]
#[diesel(sql_type = AffinityPolicyEnum)]
pub enum AffinityPolicy;

// Enum values
Fail => b"fail"
Allow => b"allow"
);

impl From<AffinityPolicy> for external::AffinityPolicy {
fn from(policy: AffinityPolicy) -> Self {
match policy {
AffinityPolicy::Fail => Self::Fail,
AffinityPolicy::Allow => Self::Allow,
}
}
}

impl From<external::AffinityPolicy> for AffinityPolicy {
fn from(policy: external::AffinityPolicy) -> Self {
match policy {
external::AffinityPolicy::Fail => Self::Fail,
external::AffinityPolicy::Allow => Self::Allow,
}
}
}

impl_enum_type!(
#[derive(SqlType, Debug)]
#[diesel(postgres_type(name = "failure_domain", schema = "public"))]
pub struct FailureDomainEnum;

#[derive(Clone, Copy, Debug, AsExpression, FromSqlRow, PartialEq)]
#[diesel(sql_type = FailureDomainEnum)]
pub enum FailureDomain;

// Enum values
Sled => b"sled"
);

impl From<FailureDomain> for external::FailureDomain {
fn from(domain: FailureDomain) -> Self {
match domain {
FailureDomain::Sled => Self::Sled,
}
}
}

impl From<external::FailureDomain> for FailureDomain {
fn from(domain: external::FailureDomain) -> Self {
match domain {
external::FailureDomain::Sled => Self::Sled,
}
}
}

#[derive(
Queryable, Insertable, Clone, Debug, Resource, Selectable, PartialEq,
)]
#[diesel(table_name = affinity_group)]
pub struct AffinityGroup {
#[diesel(embed)]
pub identity: AffinityGroupIdentity,
pub project_id: Uuid,
pub policy: AffinityPolicy,
pub failure_domain: FailureDomain,
}

impl AffinityGroup {
pub fn new(project_id: Uuid, params: params::AffinityGroupCreate) -> Self {
Self {
identity: AffinityGroupIdentity::new(
Uuid::new_v4(),
params.identity,
),
project_id,
policy: params.policy.into(),
failure_domain: params.failure_domain.into(),
}
}
}

impl From<AffinityGroup> for views::AffinityGroup {
fn from(group: AffinityGroup) -> Self {
let identity = IdentityMetadata {
id: group.identity.id,
name: group.identity.name.into(),
description: group.identity.description,
time_created: group.identity.time_created,
time_modified: group.identity.time_modified,
};
Self {
identity,
policy: group.policy.into(),
failure_domain: group.failure_domain.into(),
}
}
}

/// Describes a set of updates for the [`AffinityGroup`] model.
#[derive(AsChangeset)]
#[diesel(table_name = affinity_group)]
pub struct AffinityGroupUpdate {
pub name: Option<Name>,
pub description: Option<String>,
pub time_modified: DateTime<Utc>,
}

impl From<params::AffinityGroupUpdate> for AffinityGroupUpdate {
fn from(params: params::AffinityGroupUpdate) -> Self {
Self {
name: params.identity.name.map(Name),
description: params.identity.description,
time_modified: Utc::now(),
}
}
}

#[derive(
Queryable, Insertable, Clone, Debug, Resource, Selectable, PartialEq,
)]
#[diesel(table_name = anti_affinity_group)]
pub struct AntiAffinityGroup {
#[diesel(embed)]
identity: AntiAffinityGroupIdentity,
pub project_id: Uuid,
pub policy: AffinityPolicy,
pub failure_domain: FailureDomain,
}

impl AntiAffinityGroup {
pub fn new(
project_id: Uuid,
params: params::AntiAffinityGroupCreate,
) -> Self {
Self {
identity: AntiAffinityGroupIdentity::new(
Uuid::new_v4(),
params.identity,
),
project_id,
policy: params.policy.into(),
failure_domain: params.failure_domain.into(),
}
}
}

impl From<AntiAffinityGroup> for views::AntiAffinityGroup {
fn from(group: AntiAffinityGroup) -> Self {
let identity = IdentityMetadata {
id: group.identity.id,
name: group.identity.name.into(),
description: group.identity.description,
time_created: group.identity.time_created,
time_modified: group.identity.time_modified,
};
Self {
identity,
policy: group.policy.into(),
failure_domain: group.failure_domain.into(),
}
}
}

/// Describes a set of updates for the [`AntiAffinityGroup`] model.
#[derive(AsChangeset)]
#[diesel(table_name = anti_affinity_group)]
pub struct AntiAffinityGroupUpdate {
pub name: Option<Name>,
pub description: Option<String>,
pub time_modified: DateTime<Utc>,
}

impl From<params::AntiAffinityGroupUpdate> for AntiAffinityGroupUpdate {
fn from(params: params::AntiAffinityGroupUpdate) -> Self {
Self {
name: params.identity.name.map(Name),
description: params.identity.description,
time_modified: Utc::now(),
}
}
}

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = affinity_group_instance_membership)]
pub struct AffinityGroupInstanceMembership {
pub group_id: DbTypedUuid<AffinityGroupKind>,
pub instance_id: DbTypedUuid<InstanceKind>,
}

impl AffinityGroupInstanceMembership {
pub fn new(group_id: AffinityGroupUuid, instance_id: InstanceUuid) -> Self {
Self { group_id: group_id.into(), instance_id: instance_id.into() }
}
}

impl From<AffinityGroupInstanceMembership> for external::AffinityGroupMember {
fn from(member: AffinityGroupInstanceMembership) -> Self {
Self::Instance(member.instance_id.into())
}
}

#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = anti_affinity_group_instance_membership)]
pub struct AntiAffinityGroupInstanceMembership {
pub group_id: DbTypedUuid<AntiAffinityGroupKind>,
pub instance_id: DbTypedUuid<InstanceKind>,
}

impl AntiAffinityGroupInstanceMembership {
pub fn new(
group_id: AntiAffinityGroupUuid,
instance_id: InstanceUuid,
) -> Self {
Self { group_id: group_id.into(), instance_id: instance_id.into() }
}
}

impl From<AntiAffinityGroupInstanceMembership>
for external::AntiAffinityGroupMember
{
fn from(member: AntiAffinityGroupInstanceMembership) -> Self {
Self::Instance(member.instance_id.into())
}
}
2 changes: 2 additions & 0 deletions nexus/db-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extern crate diesel;
extern crate newtype_derive;

mod address_lot;
mod affinity;
mod allow_list;
mod bfd;
mod bgp;
Expand Down Expand Up @@ -130,6 +131,7 @@ mod db {
pub use self::macaddr::*;
pub use self::unsigned::*;
pub use address_lot::*;
pub use affinity::*;
pub use allow_list::*;
pub use bfd::*;
pub use bgp::*;
Expand Down
24 changes: 22 additions & 2 deletions nexus/db-model/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
// 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::{Disk, Generation, Instance, Name, Snapshot, Vpc};
use super::{
AffinityGroup, AntiAffinityGroup, Disk, Generation, Instance, Name,
Snapshot, Vpc,
};
use crate::collection::DatastoreCollectionConfig;
use crate::schema::{disk, image, instance, project, snapshot, vpc};
use crate::schema::{
affinity_group, anti_affinity_group, disk, image, instance, project,
snapshot, vpc,
};
use crate::Image;
use chrono::{DateTime, Utc};
use db_macros::Resource;
Expand Down Expand Up @@ -69,6 +75,20 @@ impl DatastoreCollectionConfig<Instance> for Project {
type CollectionIdColumn = instance::dsl::project_id;
}

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

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

impl DatastoreCollectionConfig<Disk> for Project {
type CollectionId = Uuid;
type GenerationNumberColumn = project::dsl::rcgen;
Expand Down
Loading
Loading