Skip to content

Commit 9734e3b

Browse files
authored
commonize logging and error handling in blueprint_planner task (#9438)
1 parent 6d70269 commit 9734e3b

File tree

1 file changed

+80
-79
lines changed

1 file changed

+80
-79
lines changed

nexus/src/app/background/tasks/blueprint_planner.rs

Lines changed: 80 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,39 @@ use nexus_types::deployment::PlanningReport;
2020
use nexus_types::deployment::{Blueprint, BlueprintTarget};
2121
use nexus_types::internal_api::background::BlueprintPlannerStatus;
2222
use nexus_types::inventory::Collection;
23+
use omicron_common::api::external::Error;
2324
use omicron_common::api::external::LookupType;
25+
use omicron_uuid_kinds::BlueprintUuid;
2426
use omicron_uuid_kinds::GenericUuid as _;
2527
use serde_json::json;
2628
use slog_error_chain::InlineErrorChain;
2729
use std::sync::Arc;
2830
use tokio::sync::watch::{self, Receiver, Sender};
2931

32+
/// Error type for blueprint planning operations.
33+
#[derive(Debug, thiserror::Error)]
34+
enum PlanError {
35+
// Warning-level problems
36+
#[error("no target blueprint available")]
37+
NoTargetBlueprint,
38+
#[error("no inventory collection available")]
39+
NoInventoryCollection,
40+
41+
// Error-level problems
42+
#[error("failed to assemble planning input")]
43+
AssemblePlanningInput(#[source] Error),
44+
#[error("failed to make planner")]
45+
MakePlanner(#[source] anyhow::Error),
46+
#[error("can't plan")]
47+
Plan(#[source] nexus_reconfigurator_planning::blueprint_builder::Error),
48+
#[error("can't save blueprint {blueprint_id}")]
49+
SaveBlueprint {
50+
blueprint_id: BlueprintUuid,
51+
#[source]
52+
source: Error,
53+
},
54+
}
55+
3056
/// Background task that runs the update planner.
3157
pub struct BlueprintPlanner {
3258
datastore: Arc<DataStore>,
@@ -81,6 +107,39 @@ impl BlueprintPlanner {
81107
/// If it is different from the current target blueprint,
82108
/// save it and make it the current target.
83109
pub async fn plan(&mut self, opctx: &OpContext) -> BlueprintPlannerStatus {
110+
match self.plan_impl(opctx).await {
111+
Ok(status) => status,
112+
Err(plan_error) => {
113+
let error = InlineErrorChain::new(&plan_error);
114+
match &plan_error {
115+
PlanError::NoTargetBlueprint
116+
| PlanError::NoInventoryCollection => {
117+
warn!(
118+
&opctx.log,
119+
"blueprint planning skipped";
120+
&error,
121+
);
122+
}
123+
PlanError::AssemblePlanningInput(_)
124+
| PlanError::MakePlanner { .. }
125+
| PlanError::Plan(_)
126+
| PlanError::SaveBlueprint { .. } => {
127+
error!(
128+
&opctx.log,
129+
"blueprint planning failed";
130+
&error,
131+
);
132+
}
133+
}
134+
BlueprintPlannerStatus::Error(error.to_string())
135+
}
136+
}
137+
}
138+
139+
async fn plan_impl(
140+
&mut self,
141+
opctx: &OpContext,
142+
) -> Result<BlueprintPlannerStatus, PlanError> {
84143
// Refuse to run if we haven't had a chance to load our config from the
85144
// database yet. (There might not be a config, which is fine! But the
86145
// loading task needs to have a chance to check.)
@@ -90,26 +149,19 @@ impl BlueprintPlanner {
90149
opctx.log,
91150
"reconfigurator config not yet loaded; doing nothing"
92151
);
93-
return BlueprintPlannerStatus::Disabled;
152+
return Ok(BlueprintPlannerStatus::Disabled);
94153
}
95154
ReconfiguratorConfigLoaderState::Loaded(config) => config.clone(),
96155
};
97156
if !config.config.planner_enabled {
98157
debug!(&opctx.log, "blueprint planning disabled, doing nothing");
99-
return BlueprintPlannerStatus::Disabled;
158+
return Ok(BlueprintPlannerStatus::Disabled);
100159
}
101160

102161
// Get the current target blueprint to use as a parent.
103162
// Cloned so that we don't block the channel.
104163
let Some(loaded) = self.rx_blueprint.borrow_and_update().clone() else {
105-
warn!(
106-
&opctx.log,
107-
"blueprint planning skipped";
108-
"reason" => "no target blueprint loaded"
109-
);
110-
return BlueprintPlannerStatus::Error(String::from(
111-
"no target blueprint to use as parent for planning",
112-
));
164+
return Err(PlanError::NoTargetBlueprint);
113165
};
114166
let (target, parent) = &*loaded;
115167
let parent_blueprint_id = parent.id;
@@ -120,69 +172,30 @@ impl BlueprintPlanner {
120172
let Some(collection) =
121173
self.rx_inventory.borrow_and_update().as_ref().map(Arc::clone)
122174
else {
123-
warn!(
124-
&opctx.log,
125-
"blueprint planning skipped";
126-
"reason" => "no inventory collection available"
127-
);
128-
return BlueprintPlannerStatus::Error(String::from(
129-
"no inventory collection available",
130-
));
175+
return Err(PlanError::NoInventoryCollection);
131176
};
132177

133178
// Assemble the planning context.
134-
let input = match PlanningInputFromDb::assemble(
179+
let input = PlanningInputFromDb::assemble(
135180
opctx,
136181
&self.datastore,
137182
config.config.planner_config,
138183
)
139184
.await
140-
{
141-
Ok(input) => input,
142-
Err(error) => {
143-
error!(
144-
&opctx.log,
145-
"can't assemble planning input";
146-
"error" => %error,
147-
);
148-
return BlueprintPlannerStatus::Error(format!(
149-
"can't assemble planning input: {error}"
150-
));
151-
}
152-
};
185+
.map_err(PlanError::AssemblePlanningInput)?;
153186

154187
// Generate a new blueprint.
155-
let planner = match Planner::new_based_on(
188+
let planner = Planner::new_based_on(
156189
opctx.log.clone(),
157190
&parent,
158191
&input,
159192
"blueprint_planner",
160193
&collection,
161194
PlannerRng::from_entropy(),
162-
) {
163-
Ok(planner) => planner,
164-
Err(error) => {
165-
error!(
166-
&opctx.log,
167-
"can't make planner";
168-
"error" => %error,
169-
"parent_blueprint_id" => %parent_blueprint_id,
170-
);
171-
return BlueprintPlannerStatus::Error(format!(
172-
"can't make planner based on {}: {}",
173-
parent_blueprint_id, error
174-
));
175-
}
176-
};
177-
let blueprint = match planner.plan() {
178-
Ok(blueprint) => blueprint,
179-
Err(error) => {
180-
error!(&opctx.log, "can't plan: {error}");
181-
return BlueprintPlannerStatus::Error(format!(
182-
"can't plan: {error}"
183-
));
184-
}
185-
};
195+
)
196+
.map_err(PlanError::MakePlanner)?;
197+
198+
let blueprint = planner.plan().map_err(PlanError::Plan)?;
186199

187200
// We just ran the planner, so we should always get its report. This
188201
// output is for debugging only, though, so just make an empty one in
@@ -216,7 +229,7 @@ impl BlueprintPlanner {
216229
match self.check_blueprint_limit_reached(opctx, &report).await {
217230
Ok(count) => count,
218231
Err(status) => {
219-
return status;
232+
return Ok(status);
220233
}
221234
};
222235

@@ -230,12 +243,12 @@ impl BlueprintPlanner {
230243
"blueprint unchanged from current target";
231244
"parent_blueprint_id" => %parent_blueprint_id,
232245
);
233-
return BlueprintPlannerStatus::Unchanged {
246+
return Ok(BlueprintPlannerStatus::Unchanged {
234247
parent_blueprint_id,
235248
report,
236249
blueprint_count,
237250
limit: self.blueprint_limit,
238-
};
251+
});
239252
}
240253
}
241254

@@ -247,21 +260,9 @@ impl BlueprintPlanner {
247260
"parent_blueprint_id" => %parent_blueprint_id,
248261
"blueprint_id" => %blueprint_id,
249262
);
250-
match self.datastore.blueprint_insert(opctx, &blueprint).await {
251-
Ok(()) => (),
252-
Err(error) => {
253-
error!(
254-
&opctx.log,
255-
"can't save blueprint";
256-
"error" => %error,
257-
"blueprint_id" => %blueprint_id,
258-
);
259-
return BlueprintPlannerStatus::Error(format!(
260-
"can't save blueprint {}: {}",
261-
blueprint_id, error
262-
));
263-
}
264-
}
263+
self.datastore.blueprint_insert(opctx, &blueprint).await.map_err(
264+
|error| PlanError::SaveBlueprint { blueprint_id, source: error },
265+
)?;
265266

266267
// Try to make it the current target.
267268
let target = BlueprintTarget {
@@ -299,27 +300,27 @@ impl BlueprintPlanner {
299300
);
300301
}
301302
}
302-
return BlueprintPlannerStatus::Planned {
303+
return Ok(BlueprintPlannerStatus::Planned {
303304
parent_blueprint_id,
304305
error: format!("{error}"),
305306
report,
306307
blueprint_count,
307308
limit: self.blueprint_limit,
308-
};
309+
});
309310
}
310311
}
311312

312313
// We have a new target!
313314

314315
self.tx_blueprint.send_replace(Some(Arc::new((target, blueprint))));
315-
BlueprintPlannerStatus::Targeted {
316+
Ok(BlueprintPlannerStatus::Targeted {
316317
parent_blueprint_id,
317318
blueprint_id,
318319
report,
319320
// A new blueprint was added, so increment the count by 1.
320321
blueprint_count: blueprint_count + 1,
321322
limit: self.blueprint_limit,
322-
}
323+
})
323324
}
324325

325326
async fn check_blueprint_limit_reached(

0 commit comments

Comments
 (0)