Skip to content

Commit

Permalink
WIP: flesh out those internal APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
davepacheco committed Jan 8, 2024
1 parent a46520d commit e9bbb5c
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 50 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions nexus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ sled-agent-client.workspace = true
slog.workspace = true
slog-async.workspace = true
slog-dtrace.workspace = true
slog-error-chain.workspace = true
slog-term.workspace = true
steno.workspace = true
tempfile.workspace = true
Expand All @@ -76,6 +77,7 @@ uuid.workspace = true
nexus-defaults.workspace = true
nexus-db-model.workspace = true
nexus-db-queries.workspace = true
nexus-deployment.workspace = true
nexus-inventory.workspace = true
nexus-types.workspace = true
omicron-common.workspace = true
Expand Down
8 changes: 3 additions & 5 deletions nexus/deployment/src/blueprint_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ impl<'a> BlueprintBuilder<'a> {
pub fn build_initial_from_collection(
collection: &'a Collection,
sleds: &'a BTreeMap<Uuid, SledInfo>,
zones_in_service: BTreeSet<Uuid>,
creator: &str,
reason: &str,
) -> Result<Blueprint, Error> {
Expand Down Expand Up @@ -110,7 +109,7 @@ impl<'a> BlueprintBuilder<'a> {
sleds: sleds.keys().copied().collect(),
omicron_zones: omicron_zones,
zones_in_service,
parent_blueprint: None,
parent_blueprint_id: None,
time_created: chrono::Utc::now(),
creator: creator.to_owned(),
reason: reason.to_owned(),
Expand All @@ -122,7 +121,6 @@ impl<'a> BlueprintBuilder<'a> {
pub fn new_based_on(
parent_blueprint: &'a Blueprint,
sleds: &'a BTreeMap<Uuid, SledInfo>,
zones_in_service: BTreeSet<Uuid>,
creator: &str,
reason: &str,
) -> BlueprintBuilder<'a> {
Expand All @@ -131,7 +129,7 @@ impl<'a> BlueprintBuilder<'a> {
sleds,
sled_ip_allocators: BTreeMap::new(),
omicron_zones: BTreeMap::new(),
zones_in_service,
zones_in_service: parent_blueprint.zones_in_service.clone(),
creator: creator.to_owned(),
reason: reason.to_owned(),
}
Expand Down Expand Up @@ -171,7 +169,7 @@ impl<'a> BlueprintBuilder<'a> {
sleds: self.sleds.keys().copied().collect(),
omicron_zones: omicron_zones,
zones_in_service: self.zones_in_service,
parent_blueprint: Some(self.parent_blueprint.id),
parent_blueprint_id: Some(self.parent_blueprint.id),
time_created: chrono::Utc::now(),
creator: self.creator,
reason: self.reason,
Expand Down
3 changes: 0 additions & 3 deletions nexus/deployment/src/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use nexus_types::deployment::OmicronZoneType;
use nexus_types::inventory::Collection;
use slog::{info, Logger};
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use uuid::Uuid;

pub struct Planner<'a> {
Expand All @@ -31,14 +30,12 @@ impl<'a> Planner<'a> {
parent_blueprint: &'a Blueprint,
collection: &'a Collection,
sleds: &'a BTreeMap<Uuid, SledInfo>,
zones_in_service: BTreeSet<Uuid>,
creator: &str,
reason: &str,
) -> Planner<'a> {
let blueprint = BlueprintBuilder::new_based_on(
parent_blueprint,
sleds,
zones_in_service,
creator,
reason,
);
Expand Down
243 changes: 243 additions & 0 deletions nexus/src/app/deployment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
// 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/2.0/.

//! Configuration of the deployment system
use nexus_db_queries::context::OpContext;
use nexus_deployment::blueprint_builder::BlueprintBuilder;
use nexus_deployment::blueprint_builder::SledInfo;
use nexus_deployment::planner::Planner;
use nexus_types::deployment::params;
use nexus_types::deployment::Blueprint;
use nexus_types::deployment::BlueprintTarget;
use nexus_types::inventory::Collection;
use omicron_common::api::external::CreateResult;
use omicron_common::api::external::DataPageParams;
use omicron_common::api::external::DeleteResult;
use omicron_common::api::external::Error;
use omicron_common::api::external::ListResultVec;
use omicron_common::api::external::LookupResult;
use omicron_common::api::external::ResourceType;
use slog_error_chain::InlineErrorChain;
use std::collections::BTreeMap;
use uuid::Uuid;

// XXX-dap temporary in-memory store of blueprints, for testing. This will move
// to the database.
pub struct Blueprints {
all_blueprints: BTreeMap<Uuid, Blueprint>,
target: BlueprintTarget,
}

impl Blueprints {
pub fn new() -> Blueprints {
Blueprints {
all_blueprints: BTreeMap::new(),
target: BlueprintTarget {
target_id: None,
enabled: false,
time_set: chrono::Utc::now(),
},
}
}
}

impl super::Nexus {
pub async fn blueprint_list(
&self,
_opctx: &OpContext,
_pagparams: &DataPageParams<'_, Uuid>,
) -> ListResultVec<Blueprint> {
// XXX-dap authz check
// Since this is just temporary until we have a database impl, ignore
// pagination.
Ok(self
.blueprints
.lock()
.unwrap()
.all_blueprints
.values()
.cloned()
.collect())
}

pub async fn blueprint_view(
&self,
_opctx: &OpContext,
blueprint_id: Uuid,
) -> LookupResult<Blueprint> {
// XXX-dap replace with an authz resource + lookup resource etc.
self.blueprints
.lock()
.unwrap()
.all_blueprints
.get(&blueprint_id)
.cloned()
.ok_or_else(|| {
Error::not_found_by_id(ResourceType::Blueprint, &blueprint_id)
})
}

pub async fn blueprint_delete(
&self,
_opctx: &OpContext,
blueprint_id: Uuid,
) -> DeleteResult {
// XXX-dap authz check
let mut blueprints = self.blueprints.lock().unwrap();
if let Some(target_id) = blueprints.target.target_id {
if target_id == blueprint_id {
// XXX-dap
return Err(Error::conflict(format!(
"blueprint {} is the current target and cannot be deleted",
blueprint_id
)));
}
}

if blueprints.all_blueprints.remove(&blueprint_id).is_none() {
return Err(Error::not_found_by_id(
ResourceType::Blueprint,
&blueprint_id,
));
}

Ok(())
}

pub async fn blueprint_target_view(
&self,
_opctx: &OpContext,
) -> Result<BlueprintTarget, Error> {
// XXX-dap authz check
let blueprints = self.blueprints.lock().unwrap();
Ok(blueprints.target.clone())
}

pub async fn blueprint_target_set(
&self,
_opctx: &OpContext,
params: params::BlueprintTarget,
) -> Result<BlueprintTarget, Error> {
// XXX-dap authz check
let new_target_id = params.target_id;
let enabled = params.enabled;
let mut blueprints = self.blueprints.lock().unwrap();
if let Some(blueprint) = blueprints.all_blueprints.get(&new_target_id) {
if blueprint.parent_blueprint_id != blueprints.target.target_id {
return Err(Error::conflict(&format!(
"blueprint {:?}: parent is {:?}, which is not the current \
target {:?}",
new_target_id,
blueprint
.parent_blueprint_id
.map(|p| p.to_string())
.unwrap_or_else(|| String::from("<none>")),
blueprints
.target
.target_id
.map(|p| p.to_string())
.unwrap_or_else(|| String::from("<none>")),
)));
}
blueprints.target = BlueprintTarget {
target_id: Some(new_target_id),
enabled,
time_set: chrono::Utc::now(),
};
// XXX-dap this is the point where we'd poke a background task
Ok(blueprints.target.clone())
} else {
Err(Error::not_found_by_id(ResourceType::Blueprint, &new_target_id))
}
}

async fn blueprint_planning_context(
&self,
opctx: &OpContext,
) -> Result<PlanningContext, Error> {
todo!(); // XXX-dap this is where we fetch collection, sleds, etc.
}

async fn blueprint_add(
&self,
opctx: &OpContext,
blueprint: Blueprint,
) -> Result<(), Error> {
// XXX-dap authz check
let mut blueprints = self.blueprints.lock().unwrap();
assert!(blueprints
.all_blueprints
.insert(blueprint.id, blueprint)
.is_none());
Ok(())
}

pub async fn blueprint_create_current(
&self,
opctx: &OpContext,
) -> CreateResult<Blueprint> {
let planning_context = self.blueprint_planning_context(opctx).await?;
let blueprint = BlueprintBuilder::build_initial_from_collection(
&planning_context.collection,
&planning_context.sleds,
&planning_context.creator,
"initial blueprint",
)
.map_err(|error| {
Error::internal_error(&format!(
"error generating initial blueprint from current state: {}",
InlineErrorChain::new(&error)
))
})?;

self.blueprint_add(&opctx, blueprint.clone()).await?;
Ok(blueprint)
}

pub async fn blueprint_create_regenerate(
&self,
opctx: &OpContext,
) -> CreateResult<Blueprint> {
let parent_blueprint = {
let blueprints = self.blueprints.lock().unwrap();
let Some(target_id) = blueprints.target.target_id else {
return Err(Error::conflict(&format!(
"cannot add sled before initial blueprint is created"
)));
};
blueprints
.all_blueprints
.get(&target_id)
.cloned()
.expect("expected target_id to correspond to a blueprint")
};
let planning_context = self.blueprint_planning_context(opctx).await?;
let planner = Planner::new_based_on(
opctx.log.clone(),
&parent_blueprint,
&planning_context.collection,
&planning_context.sleds,
&planning_context.creator,
// XXX-dap this "reason" was intended for the case where we know why
// we're doing this. Right now such a case doesn't exist.
"on-demand regenerate",
);
let blueprint = planner.plan().map_err(|error| {
Error::internal_error(&format!(
"error generating blueprint: {}",
InlineErrorChain::new(&error)
))
})?;

self.blueprint_add(&opctx, blueprint.clone()).await?;
Ok(blueprint)
}
}

struct PlanningContext {
collection: Collection,
sleds: BTreeMap<Uuid, SledInfo>,
creator: String,
}
Loading

0 comments on commit e9bbb5c

Please sign in to comment.