mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-07 13:32:57 +00:00
Add OpenAPI spec for safekeeper HTTP API (neondatabase/cloud#1264, #2061)
This spec is used in the `cloud` repo to generate HTTP client.
This commit is contained in:
5
.github/ansible/scripts/init_safekeeper.sh
vendored
5
.github/ansible/scripts/init_safekeeper.sh
vendored
@@ -12,10 +12,9 @@ cat <<EOF | tee /tmp/payload
|
||||
"version": 1,
|
||||
"host": "${HOST}",
|
||||
"port": 6500,
|
||||
"http_port": 7676,
|
||||
"region_id": {{ console_region_id }},
|
||||
"instance_id": "${INSTANCE_ID}",
|
||||
"http_host": "${HOST}",
|
||||
"http_port": 7676
|
||||
"instance_id": "${INSTANCE_ID}"
|
||||
}
|
||||
EOF
|
||||
|
||||
|
||||
@@ -304,10 +304,9 @@ impl SafekeeperNode {
|
||||
Ok(self
|
||||
.http_request(
|
||||
Method::POST,
|
||||
format!("{}/{}", self.http_base_url, "timeline"),
|
||||
format!("{}/tenant/{}/timeline", self.http_base_url, tenant_id),
|
||||
)
|
||||
.json(&TimelineCreateRequest {
|
||||
tenant_id,
|
||||
timeline_id,
|
||||
peer_ids,
|
||||
})
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utils::zid::{NodeId, ZTenantId, ZTimelineId};
|
||||
use utils::zid::{NodeId, ZTimelineId};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TimelineCreateRequest {
|
||||
pub tenant_id: ZTenantId,
|
||||
pub timeline_id: ZTimelineId,
|
||||
pub peer_ids: Vec<NodeId>,
|
||||
}
|
||||
|
||||
365
safekeeper/src/http/openapi_spec.yaml
Normal file
365
safekeeper/src/http/openapi_spec.yaml
Normal file
@@ -0,0 +1,365 @@
|
||||
openapi: "3.0.2"
|
||||
info:
|
||||
title: Safekeeper control API
|
||||
version: "1.0"
|
||||
|
||||
|
||||
servers:
|
||||
- url: "http://localhost:7676"
|
||||
|
||||
|
||||
paths:
|
||||
/v1/status:
|
||||
get:
|
||||
tags:
|
||||
- "Info"
|
||||
summary: Get safekeeper status
|
||||
description: ""
|
||||
operationId: v1GetSafekeeperStatus
|
||||
responses:
|
||||
"200":
|
||||
description: Safekeeper status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SafekeeperStatus"
|
||||
"403":
|
||||
$ref: "#/components/responses/ForbiddenError"
|
||||
default:
|
||||
$ref: "#/components/responses/GenericError"
|
||||
|
||||
|
||||
/v1/tenant/{tenant_id}:
|
||||
parameters:
|
||||
- name: tenant_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: hex
|
||||
|
||||
delete:
|
||||
tags:
|
||||
- "Tenant"
|
||||
summary: Delete tenant and all its timelines
|
||||
description: "Deletes tenant and returns a map of timelines that were deleted along with their statuses"
|
||||
operationId: v1DeleteTenant
|
||||
responses:
|
||||
"200":
|
||||
description: Tenant deleted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TenantDeleteResult"
|
||||
"403":
|
||||
$ref: "#/components/responses/ForbiddenError"
|
||||
default:
|
||||
$ref: "#/components/responses/GenericError"
|
||||
|
||||
|
||||
/v1/tenant/{tenant_id}/timeline:
|
||||
parameters:
|
||||
- name: tenant_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: hex
|
||||
|
||||
post:
|
||||
tags:
|
||||
- "Timeline"
|
||||
summary: Register new timeline
|
||||
description: ""
|
||||
operationId: v1CreateTenantTimeline
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TimelineCreateRequest"
|
||||
responses:
|
||||
"201":
|
||||
description: Timeline created
|
||||
# TODO: return timeline info?
|
||||
"403":
|
||||
$ref: "#/components/responses/ForbiddenError"
|
||||
default:
|
||||
$ref: "#/components/responses/GenericError"
|
||||
|
||||
|
||||
/v1/tenant/{tenant_id}/timeline/{timeline_id}:
|
||||
parameters:
|
||||
- name: tenant_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: hex
|
||||
- name: timeline_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: hex
|
||||
|
||||
get:
|
||||
tags:
|
||||
- "Timeline"
|
||||
summary: Get timeline information and status
|
||||
description: ""
|
||||
operationId: v1GetTenantTimeline
|
||||
responses:
|
||||
"200":
|
||||
description: Timeline status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TimelineStatus"
|
||||
"403":
|
||||
$ref: "#/components/responses/ForbiddenError"
|
||||
default:
|
||||
$ref: "#/components/responses/GenericError"
|
||||
|
||||
delete:
|
||||
tags:
|
||||
- "Timeline"
|
||||
summary: Delete timeline
|
||||
description: ""
|
||||
operationId: v1DeleteTenantTimeline
|
||||
responses:
|
||||
"200":
|
||||
description: Timeline deleted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TimelineDeleteResult"
|
||||
"403":
|
||||
$ref: "#/components/responses/ForbiddenError"
|
||||
default:
|
||||
$ref: "#/components/responses/GenericError"
|
||||
|
||||
|
||||
/v1/record_safekeeper_info/{tenant_id}/{timeline_id}:
|
||||
parameters:
|
||||
- name: tenant_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: hex
|
||||
- name: timeline_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: hex
|
||||
|
||||
post:
|
||||
tags:
|
||||
- "Tests"
|
||||
summary: Used only in tests to hand craft required data
|
||||
description: ""
|
||||
operationId: v1RecordSafekeeperInfo
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SkTimelineInfo"
|
||||
responses:
|
||||
"200":
|
||||
description: Timeline info posted
|
||||
# TODO: return timeline info?
|
||||
"403":
|
||||
$ref: "#/components/responses/ForbiddenError"
|
||||
default:
|
||||
$ref: "#/components/responses/GenericError"
|
||||
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
JWT:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
|
||||
schemas:
|
||||
|
||||
#
|
||||
# Requests
|
||||
#
|
||||
|
||||
TimelineCreateRequest:
|
||||
type: object
|
||||
required:
|
||||
- timeline_id
|
||||
- peer_ids
|
||||
properties:
|
||||
timeline_id:
|
||||
type: string
|
||||
format: hex
|
||||
peer_ids:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
minimum: 0
|
||||
|
||||
SkTimelineInfo:
|
||||
type: object
|
||||
required:
|
||||
- last_log_term
|
||||
- flush_lsn
|
||||
- commit_lsn
|
||||
- backup_lsn
|
||||
- remote_consistent_lsn
|
||||
- peer_horizon_lsn
|
||||
- safekeeper_connstr
|
||||
properties:
|
||||
last_log_term:
|
||||
type: integer
|
||||
minimum: 0
|
||||
flush_lsn:
|
||||
type: string
|
||||
commit_lsn:
|
||||
type: string
|
||||
backup_lsn:
|
||||
type: string
|
||||
remote_consistent_lsn:
|
||||
type: string
|
||||
peer_horizon_lsn:
|
||||
type: string
|
||||
safekeeper_connstr:
|
||||
type: string
|
||||
|
||||
#
|
||||
# Responses
|
||||
#
|
||||
|
||||
SafekeeperStatus:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
minimum: 0 # kind of unsigned integer
|
||||
|
||||
TimelineStatus:
|
||||
type: object
|
||||
required:
|
||||
- timeline_id
|
||||
- tenant_id
|
||||
properties:
|
||||
timeline_id:
|
||||
type: string
|
||||
format: hex
|
||||
tenant_id:
|
||||
type: string
|
||||
format: hex
|
||||
acceptor_state:
|
||||
$ref: '#/components/schemas/AcceptorStateStatus'
|
||||
flush_lsn:
|
||||
type: string
|
||||
timeline_start_lsn:
|
||||
type: string
|
||||
local_start_lsn:
|
||||
type: string
|
||||
commit_lsn:
|
||||
type: string
|
||||
backup_lsn:
|
||||
type: string
|
||||
peer_horizon_lsn:
|
||||
type: string
|
||||
remote_consistent_lsn:
|
||||
type: string
|
||||
|
||||
AcceptorStateStatus:
|
||||
type: object
|
||||
required:
|
||||
- term
|
||||
- epoch
|
||||
properties:
|
||||
term:
|
||||
type: integer
|
||||
minimum: 0 # kind of unsigned integer
|
||||
epoch:
|
||||
type: integer
|
||||
minimum: 0 # kind of unsigned integer
|
||||
term_history:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/TermSwitchEntry'
|
||||
|
||||
TermSwitchEntry:
|
||||
type: object
|
||||
required:
|
||||
- term
|
||||
- lsn
|
||||
properties:
|
||||
term:
|
||||
type: integer
|
||||
minimum: 0 # kind of unsigned integer
|
||||
lsn:
|
||||
type: string
|
||||
|
||||
TimelineDeleteResult:
|
||||
type: object
|
||||
required:
|
||||
- dir_existed
|
||||
- was_active
|
||||
properties:
|
||||
dir_existed:
|
||||
type: boolean
|
||||
was_active:
|
||||
type: boolean
|
||||
|
||||
TenantDeleteResult:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/TimelineDeleteResult"
|
||||
example:
|
||||
57fd1b39f23704a63423de0a8435d85c:
|
||||
dir_existed: true
|
||||
was_active: false
|
||||
67fd1b39f23704a63423gb8435d85c33:
|
||||
dir_existed: false
|
||||
was_active: false
|
||||
|
||||
#
|
||||
# Errors
|
||||
#
|
||||
|
||||
GenericErrorContent:
|
||||
type: object
|
||||
properties:
|
||||
msg:
|
||||
type: string
|
||||
|
||||
responses:
|
||||
|
||||
#
|
||||
# Errors
|
||||
#
|
||||
|
||||
GenericError:
|
||||
description: Generic error response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/GenericErrorContent"
|
||||
|
||||
ForbiddenError:
|
||||
description: Forbidden error response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- msg
|
||||
properties:
|
||||
msg:
|
||||
type: string
|
||||
|
||||
|
||||
security:
|
||||
- JWT: []
|
||||
@@ -126,7 +126,7 @@ async fn timeline_create_handler(mut request: Request<Body>) -> Result<Response<
|
||||
let request_data: TimelineCreateRequest = json_request(&mut request).await?;
|
||||
|
||||
let zttid = ZTenantTimelineId {
|
||||
tenant_id: request_data.tenant_id,
|
||||
tenant_id: parse_request_param(&request, "tenant_id")?,
|
||||
timeline_id: request_data.timeline_id,
|
||||
};
|
||||
check_permission(&request, Some(zttid.tenant_id))?;
|
||||
@@ -214,16 +214,19 @@ pub fn make_router(
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// NB: on any changes do not forget to update the OpenAPI spec
|
||||
// located nearby (/safekeeper/src/http/openapi_spec.yaml).
|
||||
router
|
||||
.data(Arc::new(conf))
|
||||
.data(auth)
|
||||
.get("/v1/status", status_handler)
|
||||
// Will be used in the future instead of implicit timeline creation
|
||||
.post("/v1/tenant/:tenant_id/timeline", timeline_create_handler)
|
||||
.get(
|
||||
"/v1/timeline/:tenant_id/:timeline_id",
|
||||
"/v1/tenant/:tenant_id/timeline/:timeline_id",
|
||||
timeline_status_handler,
|
||||
)
|
||||
// Will be used in the future instead of implicit timeline creation
|
||||
.post("/v1/timeline", timeline_create_handler)
|
||||
.delete(
|
||||
"/v1/tenant/:tenant_id/timeline/:timeline_id",
|
||||
timeline_delete_force_handler,
|
||||
|
||||
@@ -1929,7 +1929,7 @@ class SafekeeperHttpClient(requests.Session):
|
||||
self.get(f"http://localhost:{self.port}/v1/status").raise_for_status()
|
||||
|
||||
def timeline_status(self, tenant_id: str, timeline_id: str) -> SafekeeperTimelineStatus:
|
||||
res = self.get(f"http://localhost:{self.port}/v1/timeline/{tenant_id}/{timeline_id}")
|
||||
res = self.get(f"http://localhost:{self.port}/v1/tenant/{tenant_id}/timeline/{timeline_id}")
|
||||
res.raise_for_status()
|
||||
resj = res.json()
|
||||
return SafekeeperTimelineStatus(acceptor_epoch=resj['acceptor_state']['epoch'],
|
||||
|
||||
Reference in New Issue
Block a user