diff --git a/dev-tools/omdb/src/bin/omdb/main.rs b/dev-tools/omdb/src/bin/omdb/main.rs index 53e21fdd92..ecf70dc11d 100644 --- a/dev-tools/omdb/src/bin/omdb/main.rs +++ b/dev-tools/omdb/src/bin/omdb/main.rs @@ -90,7 +90,7 @@ struct Omdb { #[arg(env = "OMDB_DNS_SERVER", long)] dns_server: Option, - /// allow potentially-destructive subcommands + /// Allow potentially-destructive subcommands. #[arg(short = 'w', long = "destructive")] allow_destructive: bool, diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index 8813f910b0..11ee64a225 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -20,6 +20,7 @@ use futures::future::try_join; use futures::TryStreamExt; use nexus_client::types::ActivationReason; use nexus_client::types::BackgroundTask; +use nexus_client::types::BackgroundTasksActivateRequest; use nexus_client::types::CurrentStatus; use nexus_client::types::LastResult; use nexus_client::types::SledSelector; @@ -77,6 +78,15 @@ enum BackgroundTasksCommands { List, /// Print human-readable summary of the status of each background task Show, + /// Activate one or more background tasks + Activate(BackgroundTasksActivateArgs), +} + +#[derive(Debug, Args)] +struct BackgroundTasksActivateArgs { + /// Name of the background tasks to activate + #[clap(value_name = "TASK_NAME", required = true)] + tasks: Vec, } #[derive(Debug, Args)] @@ -294,6 +304,12 @@ impl NexusArgs { NexusCommands::BackgroundTasks(BackgroundTasksArgs { command: BackgroundTasksCommands::Show, }) => cmd_nexus_background_tasks_show(&client).await, + NexusCommands::BackgroundTasks(BackgroundTasksArgs { + command: BackgroundTasksCommands::Activate(args), + }) => { + let token = omdb.check_allow_destructive()?; + cmd_nexus_background_tasks_activate(&client, args, token).await + } NexusCommands::Blueprints(BlueprintsArgs { command: BlueprintsCommands::List, @@ -461,6 +477,26 @@ async fn cmd_nexus_background_tasks_show( Ok(()) } +/// Runs `omdb nexus background-tasks activate` +async fn cmd_nexus_background_tasks_activate( + client: &nexus_client::Client, + args: &BackgroundTasksActivateArgs, + // This isn't quite "destructive" in the sense that of it being potentially + // dangerous, but it does modify the system rather than being a read-only + // view on it. + _destruction_token: DestructiveOperationToken, +) -> Result<(), anyhow::Error> { + let body = + BackgroundTasksActivateRequest { bgtask_names: args.tasks.clone() }; + client + .bgtask_activate(&body) + .await + .context("error activating background tasks")?; + + eprintln!("activated background tasks: {}", args.tasks.join(", ")); + Ok(()) +} + fn print_task(bgtask: &BackgroundTask) { println!("task: {:?}", bgtask.name); println!( diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index c91ea77ee6..9dcf9ec61a 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -448,6 +448,15 @@ warning: unknown background task: "switch_port_config_manager" (don't know how t stderr: note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ ============================================= +EXECUTING COMMAND: omdb ["--destructive", "nexus", "background-tasks", "activate", "inventory_collection"] +termination: Exited(0) +--------------------------------------------- +stdout: +--------------------------------------------- +stderr: +note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ +activated background tasks: inventory_collection +============================================= EXECUTING COMMAND: omdb ["nexus", "blueprints", "list"] termination: Exited(0) --------------------------------------------- diff --git a/dev-tools/omdb/tests/test_all_output.rs b/dev-tools/omdb/tests/test_all_output.rs index b2dcadbc97..a480683f04 100644 --- a/dev-tools/omdb/tests/test_all_output.rs +++ b/dev-tools/omdb/tests/test_all_output.rs @@ -52,6 +52,8 @@ async fn test_omdb_usage_errors() { &["mgs"], &["nexus"], &["nexus", "background-tasks"], + &["nexus", "blueprints"], + &["nexus", "sleds"], &["sled-agent"], &["sled-agent", "zones"], &["sled-agent", "zpools"], @@ -95,6 +97,13 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { &["mgs", "inventory"], &["nexus", "background-tasks", "doc"], &["nexus", "background-tasks", "show"], + &[ + "--destructive", + "nexus", + "background-tasks", + "activate", + "inventory_collection", + ], &["nexus", "blueprints", "list"], &["nexus", "blueprints", "show", &initial_blueprint_id], &["nexus", "blueprints", "show", "current-target"], diff --git a/dev-tools/omdb/tests/usage_errors.out b/dev-tools/omdb/tests/usage_errors.out index d6a6f180fe..7b45d33700 100644 --- a/dev-tools/omdb/tests/usage_errors.out +++ b/dev-tools/omdb/tests/usage_errors.out @@ -20,7 +20,7 @@ Commands: Options: --log-level log level filter [env: LOG_LEVEL=] [default: warn] --dns-server [env: OMDB_DNS_SERVER=] - -w, --destructive allow potentially-destructive subcommands + -w, --destructive Allow potentially-destructive subcommands -h, --help Print help (see more with '--help') ============================================= EXECUTING COMMAND: omdb ["--help"] @@ -54,7 +54,7 @@ Options: [env: OMDB_DNS_SERVER=] -w, --destructive - allow potentially-destructive subcommands + Allow potentially-destructive subcommands -h, --help Print help (see a summary with '-h') @@ -328,10 +328,53 @@ print information about background tasks Usage: omdb nexus background-tasks Commands: - doc Show documentation about background tasks - list Print a summary of the status of all background tasks - show Print human-readable summary of the status of each background task - help Print this message or the help of the given subcommand(s) + doc Show documentation about background tasks + list Print a summary of the status of all background tasks + show Print human-readable summary of the status of each background task + activate Activate one or more background tasks + help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help +============================================= +EXECUTING COMMAND: omdb ["nexus", "blueprints"] +termination: Exited(2) +--------------------------------------------- +stdout: +--------------------------------------------- +stderr: +interact with blueprints + +Usage: omdb nexus blueprints + +Commands: + list List all blueprints + show Show a blueprint + diff Diff two blueprints + delete Delete a blueprint + target Interact with the current target blueprint + regenerate Generate a new blueprint + import Import a blueprint + help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help +============================================= +EXECUTING COMMAND: omdb ["nexus", "sleds"] +termination: Exited(2) +--------------------------------------------- +stdout: +--------------------------------------------- +stderr: +interact with sleds + +Usage: omdb nexus sleds + +Commands: + list-uninitialized List all uninitialized sleds + add Add an uninitialized sled + expunge Expunge a sled (DANGEROUS) + help Print this message or the help of the given subcommand(s) Options: -h, --help Print help diff --git a/nexus/src/app/background/status.rs b/nexus/src/app/background/status.rs index 120401c439..f4fb9e56e5 100644 --- a/nexus/src/app/background/status.rs +++ b/nexus/src/app/background/status.rs @@ -13,6 +13,7 @@ use omicron_common::api::external::LookupResult; use omicron_common::api::external::LookupType; use omicron_common::api::external::ResourceType; use std::collections::BTreeMap; +use std::collections::BTreeSet; impl Nexus { pub(crate) async fn bgtasks_list( @@ -53,4 +54,38 @@ impl Nexus { let period = driver.task_period(task); Ok(BackgroundTask::new(task.name(), description, period, status)) } + + pub(crate) async fn bgtask_activate( + &self, + opctx: &OpContext, + mut names: BTreeSet, + ) -> Result<(), Error> { + opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; + let driver = &self.background_tasks.driver; + + // Ensure all task names are valid by removing them from the set of + // names as we find them. + let tasks_to_activate: Vec<_> = + driver.tasks().filter(|t| names.remove(t.name())).collect(); + + // If any names weren't recognized, return an error. + if !names.is_empty() { + let mut names_str = "background tasks: ".to_owned(); + for (i, name) in names.iter().enumerate() { + names_str.push_str(name); + if i < names.len() - 1 { + names_str.push_str(", "); + } + } + + return Err(LookupType::ByOther(names_str) + .into_not_found(ResourceType::BackgroundTask)); + } + + for task in tasks_to_activate { + driver.activate(task); + } + + Ok(()) + } } diff --git a/nexus/src/internal_api/http_entrypoints.rs b/nexus/src/internal_api/http_entrypoints.rs index c2478da0bf..3770271ff5 100644 --- a/nexus/src/internal_api/http_entrypoints.rs +++ b/nexus/src/internal_api/http_entrypoints.rs @@ -58,6 +58,7 @@ use omicron_uuid_kinds::UpstairsRepairKind; use schemars::JsonSchema; use serde::Deserialize; use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::sync::Arc; use uuid::Uuid; @@ -93,6 +94,7 @@ pub(crate) fn internal_api() -> NexusApiDescription { api.register(bgtask_list)?; api.register(bgtask_view)?; + api.register(bgtask_activate)?; api.register(blueprint_list)?; api.register(blueprint_view)?; @@ -737,12 +739,18 @@ struct BackgroundTaskPathParam { bgtask_name: String, } +/// Query parameters for Background Task activation requests. +#[derive(Deserialize, JsonSchema)] +struct BackgroundTasksActivateRequest { + bgtask_names: BTreeSet, +} + /// Fetch status of one background task /// /// This is exposed for support and debugging. #[endpoint { method = GET, - path = "/bgtasks/{bgtask_name}", + path = "/bgtasks/view/{bgtask_name}", }] async fn bgtask_view( rqctx: RequestContext>, @@ -759,6 +767,27 @@ async fn bgtask_view( apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Activates one or more background tasks, causing them to be run immediately +/// if idle, or scheduled to run again as soon as possible if already running. +#[endpoint { + method = POST, + path = "/bgtasks/activate", +}] +async fn bgtask_activate( + rqctx: RequestContext>, + body: TypedBody, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let body = body.into_inner(); + nexus.bgtask_activate(&opctx, body.bgtask_names).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + // NAT RPW internal APIs /// Path parameters for NAT ChangeSet diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 4c5ed77d35..ac0426eb2b 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -90,7 +90,35 @@ } } }, - "/bgtasks/{bgtask_name}": { + "/bgtasks/activate": { + "post": { + "summary": "Activates one or more background tasks, causing them to be run immediately", + "description": "if idle, or scheduled to run again as soon as possible if already running.", + "operationId": "bgtask_activate", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BackgroundTasksActivateRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/bgtasks/view/{bgtask_name}": { "get": { "summary": "Fetch status of one background task", "description": "This is exposed for support and debugging.", @@ -1396,6 +1424,22 @@ "period" ] }, + "BackgroundTasksActivateRequest": { + "description": "Query parameters for Background Task activation requests.", + "type": "object", + "properties": { + "bgtask_names": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "required": [ + "bgtask_names" + ] + }, "Baseboard": { "description": "Properties that uniquely identify an Oxide hardware component", "type": "object",