Adding new endpoints cuts across many concepts in Nexus, from the HTTP interface, authorization, the database, and several tests. This guide attempts to act as a "check-list" when adding new resources.
Note
|
This guide is not intended to be exhaustive, or even particularly detailed. For that, refer to the documentation which exists in the codebase — this document should act as a jumping-off point. |
-
Add endpoints for either the internal or external API
-
The External API is customer-facing, and provides interfaces for both developers and operators
-
The Internal API is internal, and provides interfaces for services on the Oxide rack (such as the Sled Agent) to call
-
Register endpoints in the
register_endpoints
method (Example) -
These endpoints typically call into the Application layer, and do not access the database directly
-
-
Inputs and Outputs
-
Declare a new resource-to-be-looked-up via
lookup_resource!
in lookup.rs (Example)-
This defines a new struct named after your resource, with some auto-generated methods, including
lookup_for
(look up the authz object),fetch_for
(look up and return the object), and more
-
-
Add helper functions to
LookupPath
to make it possible to fetch the resource by either UUID or name (Example)-
These are often named
pub fn <my_resource>_name
, orpub fn <my_resource>_id
-
-
Use the
authz_resource!
macro to define a newauthz::…
structure, which is returned from the Lookup functions (Example)-
If you define
polar_snippet = InProject
(for developer resources) orpolar_snippet = FleetChild
(for operator resources), most of the polar policy is automatically defined for you -
If you define
polar_snippet = Custom
, you should edit the omicron.polar file to describe the authorization policy for your object (Example)
-
-
Either way, you should add reference the new resource when constructing the Oso structure
-
Add any "business logic" for the resource to the app directory
-
This layer bridges the gap between the database and external services.
-
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 saga to define the operations durably.
-
CREATE TABLE
for the resource in dbinit.sql (Example) -
Add an equivalent schema for the resource in schema.rs, which allows Diesel to translate raw SQL to rust queries (Example)
-
Add a Rust representation of the database object to the DB model (Example)
-
Methods to send queries to the database are defined as part of the datastore structure (Example)
-
Authorization
-
There exists a policy test which compares all Oso objects against an expected policy. New resources are usually added to resources.rs to get coverage.
-
-
openapi
-
Nexus generates a new openapi spec from the dropshot endpoints. If you modify endpoints, you’ll need to update openapi JSON files.
-
The following commands may be used to update APIs:
$ cargo run -p omicron-nexus --bin nexus -- -I nexus/examples/config.toml > openapi/nexus-internal.json $ cargo run -p omicron-nexus --bin nexus -- -O nexus/examples/config.toml > openapi/nexus.json $ cargo run -p omicron-sled-agent --bin sled-agent -- openapi > openapi/sled-agent.json
-
Alternative, you can run:
$ EXPECTORATE=overwrite cargo test_nexus_openapi test_nexus_openapi_internal test_sled_agent_openapi_sled
-
-
-
Integration Tests
-
Nexus' integration tests are used to cross the HTTP interface for testing. Typically, one file is used "per-resource".
-
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.
-
-
-
Saga Tests
-
Sagas are implictly tested through most integration tests, but they are also subject to more scrutiny, since they must fulfill certain properties (such as idempotency and an ability to unwind). These properties are typically tested in the same file as the saga definition (Example)
-