From a5e5cbea34191525f97511726d78bc6231fc5a44 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 21 Aug 2023 13:41:26 -0500 Subject: [PATCH] add /v1/system/health endpoint that always returns ok if it can --- nexus/src/external_api/http_entrypoints.rs | 18 ++++++ nexus/src/external_api/tag-config.json | 6 ++ nexus/tests/integration_tests/basic.rs | 13 ++++- nexus/tests/integration_tests/endpoints.rs | 1 - nexus/tests/output/nexus_tags.txt | 4 ++ .../output/uncovered-authz-endpoints.txt | 1 + nexus/types/src/external_api/views.rs | 15 +++++ openapi/nexus.json | 57 +++++++++++++++++++ 8 files changed, 113 insertions(+), 2 deletions(-) diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 6e614d5644..b6d47c9baa 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -98,6 +98,8 @@ type NexusApiDescription = ApiDescription>; /// Returns a description of the external nexus API pub(crate) fn external_api() -> NexusApiDescription { fn register_endpoints(api: &mut NexusApiDescription) -> Result<(), String> { + api.register(system_health)?; + api.register(system_policy_view)?; api.register(system_policy_update)?; @@ -364,6 +366,22 @@ pub(crate) fn external_api() -> NexusApiDescription { // clients. Client generators use operationId to name API methods, so changing // a function name is a breaking change from a client perspective. +/// Fetch system status +/// +/// Always responds with Ok if it responds at all. +#[endpoint { + method = GET, + path = "/v1/system/health", + tags = ["system"], +}] +async fn system_health( + _rqctx: RequestContext>, +) -> Result, HttpError> { + Ok(HttpResponseOk(views::SystemHealth { + api: views::SystemHealthStatus::Ok, + })) +} + /// Fetch the top-level IAM policy #[endpoint { method = GET, diff --git a/nexus/src/external_api/tag-config.json b/nexus/src/external_api/tag-config.json index e985ea7db4..a742b7abb7 100644 --- a/nexus/src/external_api/tag-config.json +++ b/nexus/src/external_api/tag-config.json @@ -80,6 +80,12 @@ "url": "http://docs.oxide.computer/api/vpcs" } }, + "system": { + "description": "General system endpoints", + "external_docs": { + "url": "http://docs.oxide.computer/api/system" + } + }, "system/hardware": { "description": "These operations pertain to hardware inventory and management. Racks are the unit of expansion of an Oxide deployment. Racks are in turn composed of sleds, switches, power supplies, and a cabled backplane.", "external_docs": { diff --git a/nexus/tests/integration_tests/basic.rs b/nexus/tests/integration_tests/basic.rs index ab54c97197..107c9e6193 100644 --- a/nexus/tests/integration_tests/basic.rs +++ b/nexus/tests/integration_tests/basic.rs @@ -10,7 +10,8 @@ use dropshot::HttpErrorResponseBody; use http::method::Method; use http::StatusCode; -use nexus_types::external_api::{params, views::Project}; +use nexus_types::external_api::params; +use nexus_types::external_api::views::{self, Project}; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::Name; @@ -546,3 +547,13 @@ async fn test_projects_list(cptestctx: &ControlPlaneTestContext) { .collect::>() ); } + +#[nexus_test] +async fn test_system_health(cptestctx: &ControlPlaneTestContext) { + let client = &cptestctx.external_client; + + let health = NexusRequest::object_get(client, "/v1/system/health") + .execute_and_parse_unwrap::() + .await; + assert_eq!(health.api, views::SystemHealthStatus::Ok); +} diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index e04d26cc45..e9ae11c21f 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -1876,6 +1876,5 @@ lazy_static! { AllowedMethod::GetNonexistent ], }, - ]; } diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index ca2f737cb0..eaffdfb09c 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -108,6 +108,10 @@ snapshot_delete DELETE /v1/snapshots/{snapshot} snapshot_list GET /v1/snapshots snapshot_view GET /v1/snapshots/{snapshot} +API operations found with tag "system" +OPERATION ID METHOD URL PATH +system_health GET /v1/system/health + API operations found with tag "system/hardware" OPERATION ID METHOD URL PATH networking_switch_port_apply_settings POST /v1/system/hardware/switch-port/{port}/settings diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index 0e53222a8a..376f611c37 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -1,4 +1,5 @@ API endpoints with no coverage in authz tests: +system_health (get "/v1/system/health") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") diff --git a/nexus/types/src/external_api/views.rs b/nexus/types/src/external_api/views.rs index 4b30b0be1c..99a6d4fbe6 100644 --- a/nexus/types/src/external_api/views.rs +++ b/nexus/types/src/external_api/views.rs @@ -522,3 +522,18 @@ pub struct UpdateDeployment { pub version: SemverVersion, pub status: UpdateStatus, } + +// SYSTEM HEALTH + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SystemHealthStatus { + Ok, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct SystemHealth { + /// Whether the external API is reachable. Will always be Ok if the endpoint + /// returns anything at all. + pub api: SystemHealthStatus, +} diff --git a/openapi/nexus.json b/openapi/nexus.json index 779b1f556c..7230553698 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -4036,6 +4036,34 @@ } } }, + "/v1/system/health": { + "get": { + "tags": [ + "system" + ], + "summary": "Fetch system status", + "description": "Always responds with Ok if it responds at all.", + "operationId": "system_health", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemHealth" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/system/identity-providers": { "get": { "tags": [ @@ -14031,6 +14059,28 @@ "vlan_id" ] }, + "SystemHealth": { + "type": "object", + "properties": { + "api": { + "description": "Whether the external API is reachable. Will always be Ok if the endpoint returns anything at all.", + "allOf": [ + { + "$ref": "#/components/schemas/SystemHealthStatus" + } + ] + } + }, + "required": [ + "api" + ] + }, + "SystemHealthStatus": { + "type": "string", + "enum": [ + "ok" + ] + }, "User": { "description": "View of a User", "type": "object", @@ -15247,6 +15297,13 @@ "url": "http://docs.oxide.computer/api/snapshots" } }, + { + "name": "system", + "description": "General system endpoints", + "externalDocs": { + "url": "http://docs.oxide.computer/api/system" + } + }, { "name": "system/hardware", "description": "These operations pertain to hardware inventory and management. Racks are the unit of expansion of an Oxide deployment. Racks are in turn composed of sleds, switches, power supplies, and a cabled backplane.",