Skip to content

Commit

Permalink
[wicket] command-line interface to mupdate (#4062)
Browse files Browse the repository at this point in the history
## Overview

Implement a command-line interface to mupdates, with an eye to using
this in CI.

To achieve this, add two commands to wicket:

### `rack-update start`

This command starts mupdates on one or more components. The help looks
like:

```
Start one or more running updates

Usage: wicket rack-update start [OPTIONS]

Options:
  -h, --help  Print help (see more with '--help')

Component selectors:
      --sled <SLED>      The sleds to operate on
      --switch <SWITCH>  The switches to operate on
      --psc <PSC>        The PSCs to operate on

Update options:
      --force-update-rot  Force update the RoT even if the version is the same
      --force-update-sp   Force update the SP even if the version is the same
  -d, --detach            Detach after starting the update

Global options:
      --color <COLOR>  Color output [default: auto] [possible values: auto, always, never]
```

### `rack-update attach`

This command attaches to any existing mupdates on one or more
components. The help looks like:

```
Attach to one or more running updates

Usage: wicket rack-update attach [OPTIONS]

Options:
  -h, --help  Print help (see more with '--help')

Component selectors:
      --sled <SLED>      The sleds to operate on
      --switch <SWITCH>  The switches to operate on
      --psc <PSC>        The PSCs to operate on

Global options:
      --color <COLOR>  Color output [default: auto] [possible values: auto, always, never]
```

### What does this look like?

Here's a screenshot:


![image](https://github.com/oxidecomputer/omicron/assets/180618/11cad6ec-cc29-48d8-ba74-c0b3e63adc9d)

Buildomat doesn't yet support ANSI escape codes
(oxidecomputer/buildomat#40), but if we add
support for them then we'd get full color support. Otherwise, it'll look
like this without colors.

## Implementation

This is a somewhat large PR but almost all added code. The bulk of added
code is in the update-engine, which has two new displayers:

### 1. Line display

This displayer shows events line by line. Since this is meant to be used
in CI, we have a hard constraint that we can't go back and change old
lines.

### 2. Group display

This displayer shows events across several possible executions. This is
just a slightly more involved version of the line display, and shares
most of its code (via `line_display_shared.rs`).

The rest of this PR:

* Hooks up a group displayer to wicket, which is pretty straightforward.
* Adds some more to the `update-engine-basic` example.

## Testing this

The wicket side can be tested with:

```
SSH_ORIGINAL_COMMAND='rack-update start --sled 1' cargo run -p wicket
```

For a more comprehensive example, run `cargo run --example
update-engine-basic -- --display-style group`.

Depends on #4429 (and a number of other PRs that have already landed.)

## Future work

One bit of work that hasn't been done yet is doing an integration test
with wicketd, wicket, MGS and sp-sim. One issue is that currently, we
don't generate machine-readable output from wicket (maybe we should!),
and given that that's not the case we only know how to exit with either
the exit code 0 (success) or 1 (failure).
  • Loading branch information
sunshowers authored Nov 8, 2023
1 parent 3db5ae8 commit 88b5a1d
Show file tree
Hide file tree
Showing 26 changed files with 2,819 additions and 260 deletions.
33 changes: 33 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ static_assertions = "1.1.0"
steno = "0.4.0"
strum = { version = "0.25", features = [ "derive" ] }
subprocess = "0.2.9"
supports-color = "2.1.0"
swrite = "0.1.0"
libsw = { version = "3.3.0", features = ["tokio"] }
syn = { version = "2.0" }
Expand Down
6 changes: 6 additions & 0 deletions update-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ derive-where.workspace = true
either.workspace = true
futures.workspace = true
indexmap.workspace = true
libsw.workspace = true
linear-map.workspace = true
owo-colors.workspace = true
petgraph.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_with.workspace = true
schemars = { workspace = true, features = ["uuid1"] }
slog.workspace = true
swrite.workspace = true
tokio = { workspace = true, features = ["macros", "sync", "time", "rt-multi-thread"] }
unicode-width.workspace = true
uuid.workspace = true
omicron-workspace-hack.workspace = true

Expand All @@ -28,8 +32,10 @@ buf-list.workspace = true
bytes.workspace = true
camino.workspace = true
camino-tempfile.workspace = true
clap.workspace = true
indicatif.workspace = true
omicron-test-utils.workspace = true
owo-colors.workspace = true
supports-color.workspace = true
tokio = { workspace = true, features = ["io-util"] }
tokio-stream.workspace = true
123 changes: 115 additions & 8 deletions update-engine/examples/update-engine-basic/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,135 @@ use indexmap::{map::Entry, IndexMap};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use owo_colors::OwoColorize;
use tokio::{sync::mpsc, task::JoinHandle};
use update_engine::events::ProgressCounter;
use update_engine::{
display::{GroupDisplay, LineDisplay, LineDisplayStyles},
events::ProgressCounter,
};

use crate::spec::{
Event, ExampleComponent, ExampleStepId, ExampleStepMetadata, ProgressEvent,
ProgressEventKind, StepEventKind, StepInfoWithMetadata, StepOutcome,
use crate::{
spec::{
Event, EventBuffer, ExampleComponent, ExampleStepId,
ExampleStepMetadata, ProgressEvent, ProgressEventKind, StepEventKind,
StepInfoWithMetadata, StepOutcome,
},
DisplayStyle,
};

/// An example that displays an event stream on the command line.
pub(crate) fn make_displayer(
log: &slog::Logger,
display_style: DisplayStyle,
prefix: Option<String>,
) -> (JoinHandle<Result<()>>, mpsc::Sender<Event>) {
let (sender, receiver) = mpsc::channel(512);
let log = log.clone();
let join_handle =
tokio::task::spawn(
async move { display_messages(&log, receiver).await },
);
match display_style {
DisplayStyle::ProgressBar => tokio::task::spawn(async move {
display_progress_bar(&log, receiver).await
}),
DisplayStyle::Line => tokio::task::spawn(async move {
display_line(&log, receiver, prefix).await
}),
DisplayStyle::Group => tokio::task::spawn(async move {
display_group(&log, receiver).await
}),
};

(join_handle, sender)
}

async fn display_messages(
async fn display_line(
log: &slog::Logger,
mut receiver: mpsc::Receiver<Event>,
prefix: Option<String>,
) -> Result<()> {
slog::info!(log, "setting up display");
let mut buffer = EventBuffer::new(8);
let mut display = LineDisplay::new(std::io::stdout());
// For now, always colorize. TODO: figure out whether colorization should be
// done based on always/auto/never etc.
if supports_color::on(supports_color::Stream::Stdout).is_some() {
display.set_styles(LineDisplayStyles::colorized());
}
if let Some(prefix) = prefix {
display.set_prefix(prefix);
}
display.set_progress_interval(Duration::from_millis(50));
while let Some(event) = receiver.recv().await {
buffer.add_event(event);
display.write_event_buffer(&buffer)?;
}

Ok(())
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
enum GroupDisplayKey {
Example,
Other,
}

async fn display_group(
log: &slog::Logger,
mut receiver: mpsc::Receiver<Event>,
) -> Result<()> {
slog::info!(log, "setting up display");

let mut display = GroupDisplay::new(
[
(GroupDisplayKey::Example, "example"),
(GroupDisplayKey::Other, "other"),
],
std::io::stdout(),
);
// For now, always colorize. TODO: figure out whether colorization should be
// done based on always/auto/never etc.
if supports_color::on(supports_color::Stream::Stdout).is_some() {
display.set_styles(LineDisplayStyles::colorized());
}

display.set_progress_interval(Duration::from_millis(50));

let mut example_buffer = EventBuffer::default();
let mut example_buffer_last_seen = None;
let mut other_buffer = EventBuffer::default();
let mut other_buffer_last_seen = None;

let mut interval = tokio::time::interval(Duration::from_secs(2));
interval.tick().await;

loop {
tokio::select! {
_ = interval.tick() => {
// Print out status lines every 2 seconds.
display.write_stats("Status")?;
}
event = receiver.recv() => {
let Some(event) = event else { break };
example_buffer.add_event(event.clone());
other_buffer.add_event(event);

display.add_event_report(
&GroupDisplayKey::Example,
example_buffer.generate_report_since(&mut example_buffer_last_seen),
)?;
display.add_event_report(
&GroupDisplayKey::Other,
other_buffer.generate_report_since(&mut other_buffer_last_seen),
)?;
display.write_events()?;
}
}
}

// Print status at the end.
display.write_stats("Summary")?;

Ok(())
}

async fn display_progress_bar(
log: &slog::Logger,
mut receiver: mpsc::Receiver<Event>,
) -> Result<()> {
Expand Down
Loading

0 comments on commit 88b5a1d

Please sign in to comment.