Skip to content

Commit 27191f0

Browse files
authored
[meta] update docs for API traits (#6486)
All of Omicron's OpenAPI documents have been converted over to API traits. Update our documentation to reflect that.
1 parent daf192a commit 27191f0

File tree

5 files changed

+47
-116
lines changed

5 files changed

+47
-116
lines changed

README.adoc

+6-31
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,14 @@ We also use these OpenAPI documents as the source for the clients we generate
208208
using https://github.com/oxidecomputer/progenitor[Progenitor]. Clients are
209209
automatically updated when the coresponding OpenAPI document is modified.
210210

211-
There are currently two kinds of services based on how their corresponding documents are generated: *managed* and *unmanaged*. Eventually, all services within Omicron will transition to being managed.
211+
OpenAPI documents are tracked by the `cargo xtask openapi` command.
212212

213-
* A *managed* service is tracked by the `cargo xtask openapi` command, using Dropshot's relatively new API trait functionality.
214-
* An *unmanaged* service is defined the traditional way, by gluing together a set of implementation functions, and is tracked by an independent test.
213+
* To regenerate all OpenAPI documents, run `cargo xtask openapi generate`.
214+
* To check whether all OpenAPI documents are up-to-date, run `cargo xtask
215+
openapi check`.
215216

216-
To check whether your document is managed, run `cargo xtask openapi list`: it will list out all managed OpenAPI documents. If your document is not on the list, it is unmanaged.
217+
For more information, see the documentation in
218+
link:./dev-tools/openapi-manager[`dev-tools/openapi-manager`].
217219

218220
Note that Omicron contains a nominally circular dependency:
219221

@@ -225,33 +227,6 @@ Note that Omicron contains a nominally circular dependency:
225227
We effectively "break" this circular dependency by virtue of the OpenAPI
226228
documents being checked in.
227229

228-
==== Updating or Creating New Managed Services
229-
230-
See the documentation in link:./dev-tools/openapi-manager[`dev-tools/openapi-manager`] for more information.
231-
232-
==== Updating Unmanaged Services
233-
234-
In general, changes to unmanaged service APs **require the following set of build steps**:
235-
236-
. Make changes to the service API.
237-
. Update the OpenAPI document by running the relevant test with overwrite set:
238-
`EXPECTORATE=overwrite cargo nextest run -p <package> -- test_nexus_openapi_internal`
239-
(changing the package name and test name as necessary). It's important to do
240-
this _before_ the next step.
241-
. This will cause the generated client to be updated which may break the build
242-
for dependent consumers.
243-
. Modify any dependent services to fix calls to the generated client.
244-
245-
Note that if you make changes to both Nexus and Sled Agent simultaneously, you
246-
may end up in a spot where neither can build and therefore neither OpenAPI
247-
document can be generated. In this case, revert or comment out changes in one
248-
so that the OpenAPI document can be generated.
249-
250-
This is a particular problem if you find yourself resolving merge conflicts in the generated files. You have basically two options for this:
251-
252-
* Resolve the merge conflicts by hand. This is usually not too bad in practice.
253-
* Take the upstream copy of the file, back out your client side changes (`git stash` and its `-p` option can be helpful for this), follow the steps above to regenerate the file using the automated test, and finally re-apply your changes to the client side. This is essentially getting yourself back to step 1 above and then following the procedure above.
254-
255230
=== Resolving merge conflicts in Cargo.lock
256231

257232
When pulling in new changes from upstream "main", you may find conflicts in Cargo.lock. The easiest way to deal with these is usually to take the upstream changes as-is, then trigger any Cargo operation that updates the lockfile. `cargo metadata` is a quick one. Here's an example:

common/src/api/internal/nexus.rs

+6-22
Original file line numberDiff line numberDiff line change
@@ -275,31 +275,15 @@ pub struct UpdateArtifactId {
275275
// Adding a new KnownArtifactKind
276276
// ===============================
277277
//
278-
// Adding a new update artifact kind is a tricky process. To do so:
278+
// To add a new kind of update artifact:
279279
//
280280
// 1. Add it here.
281+
// 2. Regenerate OpenAPI documents with `cargo xtask openapi generate` -- this
282+
// should work without any compile errors.
283+
// 3. Run `cargo check --all-targets` to resolve compile errors.
281284
//
282-
// 2. Add the new kind to <repo root>/clients/src/lib.rs.
283-
// The mapping from `UpdateArtifactKind::*` to `types::UpdateArtifactKind::*`
284-
// must be left as a `todo!()` for now; `types::UpdateArtifactKind` will not
285-
// be updated with the new variant until step 5 below.
286-
//
287-
// 4. Add the new kind and the mapping to its `update_artifact_kind` to
288-
// <repo root>/nexus/db-model/src/update_artifact.rs
289-
//
290-
// 5. Regenerate the OpenAPI specs for nexus and sled-agent:
291-
//
292-
// ```
293-
// EXPECTORATE=overwrite cargo nextest run -p omicron-nexus -p omicron-sled-agent openapi
294-
// ```
295-
//
296-
// 6. Return to <repo root>/{nexus-client,sled-agent-client}/lib.rs from step 2
297-
// and replace the `todo!()`s with the new `types::UpdateArtifactKind::*`
298-
// variant.
299-
//
300-
// See https://github.com/oxidecomputer/omicron/pull/2300 as an example.
301-
//
302-
// NOTE: KnownArtifactKind has to be in snake_case due to openapi-lint requirements.
285+
// NOTE: KnownArtifactKind has to be in snake_case due to openapi-lint
286+
// requirements.
303287

304288
/// Kinds of update artifacts, as used by Nexus to determine what updates are available and by
305289
/// sled-agent to determine how to apply an update when asked.

dev-tools/openapi-manager/README.adoc

+16-38
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@ This tool manages the OpenAPI documents (JSON files) checked into Omicron's `ope
44

55
NOTE: For more information about API traits, see https://rfd.shared.oxide.computer/rfd/0479[RFD 479].
66

7-
Currently, a subset of OpenAPI documents is managed by this tool. Eventually, all of the OpenAPI documents in Omicron will be managed by this tool; work to make that happen is ongoing.
8-
9-
To check whether your document is managed, run `cargo xtask openapi list`: it will list out all managed OpenAPI documents. If your document is not on the list, it is unmanaged.
10-
117
== Basic usage
128

139
The OpenAPI manager is meant to be invoked via `cargo xtask openapi`. Currently, three commands are provided:
1410

15-
* `cargo xtask openapi list`: List information about currently-managed documents.
16-
* `cargo xtask openapi check`: Check that all of the managed documents are up-to-date.
11+
* `cargo xtask openapi list`: List information about OpenAPI documents.
12+
* `cargo xtask openapi check`: Check that all of the documents are up-to-date.
1713
* `cargo xtask openapi generate`: Update and generate OpenAPI documents.
1814

19-
There is also a test which makes sure that all managed documents are up-to-date, and tells you to run `cargo xtask openapi generate` if they aren't.
15+
There is also a test which makes sure that all documents are up-to-date, and tells you to run `cargo xtask openapi generate` if they aren't.
2016

2117
=== API crates [[api_crates]]
2218

@@ -49,51 +45,33 @@ In the implementation crate:
4945
. Add a dependency on the API crate.
5046
. Following the example in https://rfd.shared.oxide.computer/rfd/0479#guide_api_implementation[RFD 479's _API implementation_], provide an implementation of the trait.
5147

52-
Once the API crate is defined, perform the steps in <<add_to_manager>> below.
53-
54-
=== Converting existing documents
55-
56-
Existing, unmanaged documents are generated via *function-based servers*: a set of functions that some code combines into a Dropshot `ApiDescription`. (There is also likely an expectorate test which ensures that the document is up-to-date.)
57-
58-
The first step is to convert the function-based server into an API trait. To do so, create an API crate (see <<api_crates>> above).
59-
60-
. Add the API crate to the workspace's `Cargo.toml`: `members` and `default-members`, and a reference in `[workspace.dependencies]`.
61-
. Follow the instructions in https://rfd.shared.oxide.computer/rfd/0479#guide_converting_functions_to_traits[RFD 479's _Converting functions to API traits_] for the API crate.
62-
63-
In the implementation crate:
64-
65-
. Continue following the instructions in https://rfd.shared.oxide.computer/rfd/0479#guide_converting_functions_to_traits[RFD 479's _Converting functions to API traits_] for where the endpoint functions are currently defined.
66-
. Find the test which currently manages the document (try searching the repo for `openapi_lint::validate`). If it performs any checks on the document beyond `openapi_lint::validate` or `openapi_lint::validate_external`, see <<extra_validation>>.
67-
68-
Next, perform the steps in <<add_to_manager>> below.
69-
70-
Finally, remove:
71-
72-
. The test which used to manage the document. The OpenAPI manager includes a test that will automatically run in CI.
73-
. The binary subcommand (typically called `openapi`) that generated the OpenAPI document. The test was the only practical use of this subcommand.
74-
75-
=== Adding the API crate to the manager [[add_to_manager]]
76-
7748
Once the API crate is defined, inform the OpenAPI manager of its existence. Within this directory:
7849

7950
. In `Cargo.toml`, add a dependency on the API crate.
8051
. In `src/spec.rs`, add the crate to the `all_apis` function. (Please keep the list sorted by filename.)
8152

82-
To ensure everything works well, run `cargo xtask openapi generate`.
83-
84-
* Your OpenAPI document should be generated on disk and listed in the output.
85-
* If you're converting an existing API, the only changes should be the ones you might have introduced as part of the refactor. If there are significant changes, something's gone wrong--maybe you missed an endpoint?
53+
To ensure everything works well, run `cargo xtask openapi generate`. Your
54+
OpenAPI document should be generated on disk and listed in the output.
8655

8756
==== Performing extra validation [[extra_validation]]
8857

8958
By default, the OpenAPI manager does basic validation on the generated document. Some documents require extra validation steps.
9059

9160
It's best to put extra validation next to the trait, within the API crate.
9261

93-
. In the API crate, add dependencies on `anyhow` and `openapiv3`.
94-
. Define a function with signature `fn extra_validation(openapi: &openapiv3::OpenAPI) -> anyhow::Result<()>` which performs the extra validation steps.
62+
. In the API crate, add dependencies on `openapiv3` and `openapi-manager-types`.
63+
. Define a function with signature `fn validate_api(spec: &openapiv3::OpenAPI, mut cx: openapi_manager_types::ValidationContext<'_>) which performs the extra validation steps.
9564
. In `all_apis`, set the `extra_validation` field to this function.
9665

66+
Currently, the validator can do two things:
67+
68+
. Via the `ValidationContext::report_error` function, report validation errors.
69+
. Via the `ValidationContext::record_file_contents` function, assert the contents of other generated files.
70+
71+
(This can be made richer as needed.)
72+
73+
For an example, see `validate_api` in the `nexus-external-api` crate.
74+
9775
== Design notes
9876

9977
The OpenAPI manager uses the new support for Dropshot API traits described in https://rfd.shared.oxide.computer/rfd/0479[RFD 479].

docs/adding-an-endpoint.adoc

+18-24
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,21 @@ NOTE: This guide is not intended to be exhaustive, or even particularly
1212
detailed. For that, refer to the documentation which exists in the codebase --
1313
this document should act as a jumping-off point.
1414

15-
=== **HTTP**
16-
* Add endpoints for either the internal or external API
17-
** xref:../nexus/src/external_api/http_entrypoints.rs[The External API] is customer-facing, and provides interfaces for both developers and operators
18-
** xref:../nexus/src/internal_api/http_entrypoints.rs[The Internal API] is internal, and provides interfaces for services on the Oxide rack (such as the Sled Agent) to call
19-
** Register endpoints in the `register_endpoints` method (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/external_api/http_entrypoints.rs#L84[Example])
15+
== **HTTP**
16+
17+
* Add endpoint _definitions_ for either the internal or external API
18+
** xref:../nexus/external-api/src/lib.rs[The External API] is customer-facing, and provides interfaces for both developers and operators
19+
** xref:../nexus/internal-api/src/lib.rs[The Internal API] is internal, and provides interfaces for services on the Oxide rack (such as the Sled Agent) to call
20+
* Add the corresponding _implementations_ to the respective `http_entrypoints.rs` files:
21+
** xref:../nexus/src/external_api/http_entrypoints.rs[The External API's `http_entrypoints.rs`]
22+
** xref:../nexus/src/internal_api/http_entrypoints.rs[The Internal API's `http_entrypoints.rs`]
2023
** These endpoints typically call into the *Application* layer, and do not access the database directly
2124
* Inputs and Outputs
2225
** Input parameters are defined in https://github.com/oxidecomputer/omicron/blob/main/nexus/types/src/external_api/params.rs[params.rs] (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/types/src/external_api/params.rs#L587-L601[Example])
2326
** Output views are defined in https://github.com/oxidecomputer/omicron/blob/main/nexus/types/src/external_api/views.rs[views.rs] (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/types/src/external_api/views.rs#L270-L274[Example])
2427

25-
=== **Lookup & Authorization**
28+
== **Lookup & Authorization**
29+
2630
* Declare a new resource-to-be-looked-up via `lookup_resource!` in xref:../nexus/src/db/lookup.rs[lookup.rs] (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/db/lookup.rs#L557-L564[Example])
2731
** This defines a new struct named after your resource, with some https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/db-macros/src/lookup.rs#L521-L628[auto-generated methods], including `lookup_for` (look up the authz object), `fetch_for` (look up and return the object), and more
2832
* Add helper functions to `LookupPath` to make it possible to fetch the resource by either UUID or name (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/db/lookup.rs#L225-L237[Example])
@@ -32,12 +36,14 @@ this document should act as a jumping-off point.
3236
** If you define `polar_snippet = Custom`, you should edit the omicron.polar file to describe the authorization policy for your object (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/authz/omicron.polar#L376-L393[Example])
3337
* Either way, you should add reference the new resource when https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/authz/oso_generic.rs#L119-L148[constructing the Oso structure]
3438

35-
=== **Application**
39+
== **Application**
40+
3641
* Add any "business logic" for the resource to xref:../nexus/src/app[the app directory]
3742
* This layer bridges the gap between the database and external services.
3843
* If your application logic involes any multi-step operations which would be interrupted by Nexus stopping mid-execution (due to reboot, crash, failure, etc), it is recommended to use a https://github.com/oxidecomputer/omicron/tree/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/src/app/sagas[saga] to define the operations durably.
3944

40-
=== **Database**
45+
== **Database**
46+
4147
* `CREATE TABLE` for the resource in xref:../schema/crdb/dbinit.sql[dbinit.sql] (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/common/src/sql/dbinit.sql#L1103-L1129[Example])
4248
* Add an equivalent schema for the resource in xref:../nexus/db-model/src/schema.rs[schema.rs], which allows https://docs.diesel.rs/master/diesel/index.html[Diesel] to translate raw SQL to rust queries (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/db-model/src/schema.rs#L144-L155[Example])
4349
* Add a Rust representation of the database object to xref:../nexus/db-model/src[the DB model] (https://github.com/oxidecomputer/omicron/blob/1dfe47c1b3122bc4f32a9c517cb31b1600581ea2/nexus/db-model/src/ip_pool.rs#L24-L40[Example])
@@ -48,22 +54,10 @@ this document should act as a jumping-off point.
4854

4955
* Authorization
5056
** There exists a https://github.com/oxidecomputer/omicron/blob/main/nexus/src/authz/policy_test[policy test] which compares all Oso objects against an expected policy. New resources are usually added to https://github.com/oxidecomputer/omicron/blob/main/nexus/src/authz/policy_test/resources.rs[resources.rs] to get coverage.
51-
* openapi
52-
** Nexus generates a new openapi spec from the dropshot endpoints. If you modify endpoints, you'll need to update openapi JSON files.
53-
*** The following commands may be used to update APIs:
54-
+
55-
[source, rust]
56-
----
57-
$ cargo run -p omicron-nexus --bin nexus -- -I nexus/examples/config.toml > openapi/nexus-internal.json
58-
$ cargo run -p omicron-nexus --bin nexus -- -O nexus/examples/config.toml > openapi/nexus.json
59-
$ cargo run -p omicron-sled-agent --bin sled-agent -- openapi > openapi/sled-agent.json
60-
----
61-
*** Alternative, you can run:
62-
+
63-
[source, rust]
64-
----
65-
$ EXPECTORATE=overwrite cargo test_nexus_openapi test_nexus_openapi_internal test_sled_agent_openapi_sled
66-
----
57+
* OpenAPI
58+
** Once you've added or changed endpoint definitions in `nexus-external-api` or `nexus-internal-api`, you'll need to update the corresponding OpenAPI documents (the JSON files in `openapi/`).
59+
** To update all OpenAPI documents, run `cargo xtask openapi generate`.
60+
** This does not require you to provide an implementation, or to get either omicron-nexus or omicron-sled-agent to compile: just the definition in the API crate is sufficient.
6761
* Integration Tests
6862
** Nexus' https://github.com/oxidecomputer/omicron/tree/main/nexus/tests/integration_tests[integration tests] are used to cross the HTTP interface for testing. Typically, one file is used "per-resource".
6963
*** These tests use a simulated Sled Agent, and keep the "Nexus" object in-process, so it can still be accessed and modified for invasive testing.

docs/crdb-upgrades.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ a tick, but they must occur in that order.)
6464
of CockroachDB versions:
6565
+
6666
....
67-
EXPECTORATE=overwrite cargo nextest run -p omicron-nexus -- integration_tests::commands::test_nexus_openapi_internal
67+
cargo xtask openapi generate
6868
....
6969
. Run the full test suite, which should catch any unexpected SQL
7070
compatibility issues between releases and help validate that your

0 commit comments

Comments
 (0)