You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.adoc
+6-31
Original file line number
Diff line number
Diff line change
@@ -208,12 +208,14 @@ We also use these OpenAPI documents as the source for the clients we generate
208
208
using https://github.com/oxidecomputer/progenitor[Progenitor]. Clients are
209
209
automatically updated when the coresponding OpenAPI document is modified.
210
210
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.
212
212
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`.
215
216
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.
Note that Omicron contains a nominally circular dependency:
219
221
@@ -225,33 +227,6 @@ Note that Omicron contains a nominally circular dependency:
225
227
We effectively "break" this circular dependency by virtue of the OpenAPI
226
228
documents being checked in.
227
229
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
-
255
230
=== Resolving merge conflicts in Cargo.lock
256
231
257
232
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:
Copy file name to clipboardExpand all lines: dev-tools/openapi-manager/README.adoc
+16-38
Original file line number
Diff line number
Diff line change
@@ -4,19 +4,15 @@ This tool manages the OpenAPI documents (JSON files) checked into Omicron's `ope
4
4
5
5
NOTE: For more information about API traits, see https://rfd.shared.oxide.computer/rfd/0479[RFD 479].
6
6
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
-
11
7
== Basic usage
12
8
13
9
The OpenAPI manager is meant to be invoked via `cargo xtask openapi`. Currently, three commands are provided:
14
10
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.
17
13
* `cargo xtask openapi generate`: Update and generate OpenAPI documents.
18
14
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.
20
16
21
17
=== API crates [[api_crates]]
22
18
@@ -49,51 +45,33 @@ In the implementation crate:
49
45
. Add a dependency on the API crate.
50
46
. 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.
51
47
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
-
77
48
Once the API crate is defined, inform the OpenAPI manager of its existence. Within this directory:
78
49
79
50
. In `Cargo.toml`, add a dependency on the API crate.
80
51
. In `src/spec.rs`, add the crate to the `all_apis` function. (Please keep the list sorted by filename.)
81
52
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.
86
55
87
56
==== Performing extra validation [[extra_validation]]
88
57
89
58
By default, the OpenAPI manager does basic validation on the generated document. Some documents require extra validation steps.
90
59
91
60
It's best to put extra validation next to the trait, within the API crate.
92
61
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.
95
64
. In `all_apis`, set the `extra_validation` field to this function.
96
65
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
+
97
75
== Design notes
98
76
99
77
The OpenAPI manager uses the new support for Dropshot API traits described in https://rfd.shared.oxide.computer/rfd/0479[RFD 479].
Copy file name to clipboardExpand all lines: docs/adding-an-endpoint.adoc
+18-24
Original file line number
Diff line number
Diff line change
@@ -12,17 +12,21 @@ NOTE: This guide is not intended to be exhaustive, or even particularly
12
12
detailed. For that, refer to the documentation which exists in the codebase --
13
13
this document should act as a jumping-off point.
14
14
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:
** These endpoints typically call into the *Application* layer, and do not access the database directly
21
24
* Inputs and Outputs
22
25
** 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])
23
26
** 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])
24
27
25
-
=== **Lookup & Authorization**
28
+
== **Lookup & Authorization**
29
+
26
30
* 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])
27
31
** 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
28
32
* 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.
32
36
** 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])
33
37
* 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]
34
38
35
-
=== **Application**
39
+
== **Application**
40
+
36
41
* Add any "business logic" for the resource to xref:../nexus/src/app[the app directory]
37
42
* This layer bridges the gap between the database and external services.
38
43
* 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.
39
44
40
-
=== **Database**
45
+
== **Database**
46
+
41
47
* `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])
42
48
* 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])
43
49
* 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.
48
54
49
55
* Authorization
50
56
** 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:
** 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.
67
61
* Integration Tests
68
62
** 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".
69
63
*** 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.
0 commit comments