Add /v3/cluster/deployments endpoint

This commit is contained in:
Ingo Oppermann
2025-09-16 12:03:51 +02:00
parent 852b836f7e
commit 8c7ca4898a
10 changed files with 653 additions and 77 deletions

View File

@@ -87,6 +87,7 @@ type Cluster interface {
Store() store.Store
Resources() (resources.Info, error)
Deployments() (Deployments, error)
}
type Peer struct {
@@ -1147,3 +1148,156 @@ func (c *cluster) Store() store.Store {
return c.store
}
type Deployments struct {
Process struct {
Delete []DeploymentsProcess
Update []DeploymentsProcess
Order []DeploymentsProcess
Add []DeploymentsProcess
Relocate []DeploymentsProcess
}
}
type DeploymentsProcess struct {
ID string
Domain string
NodeID string
Order string
Error string
UpdatedAt time.Time
}
func (c *cluster) Deployments() (Deployments, error) {
processDelete := []DeploymentsProcess{}
processUpdate := []DeploymentsProcess{}
processOrder := []DeploymentsProcess{}
processAdd := []DeploymentsProcess{}
processRelocate := []DeploymentsProcess{}
want := c.store.ProcessList()
have, err := c.manager.ClusterProcessList()
if err != nil {
return Deployments{}, err
}
// A map from the process ID to the process config of the processes
// we want to be running on the nodes.
wantMap := map[string]store.Process{}
for _, wantP := range want {
pid := wantP.Config.ProcessID().String()
wantMap[pid] = wantP
}
// Now we iterate through the processes we actually have running on the nodes
// and remove them from the wantMap.
for _, haveP := range have {
pid := haveP.Config.ProcessID().String()
wantP, ok := wantMap[pid]
if !ok {
processDelete = append(processDelete, DeploymentsProcess{
ID: haveP.Config.ID,
Domain: haveP.Config.Domain,
NodeID: haveP.NodeID,
Order: haveP.Order,
Error: "",
})
continue
}
hasConfigChanges := !wantP.Config.Equal(haveP.Config)
if !hasConfigChanges && wantP.Force {
hasConfigChanges = wantP.UpdatedAt.After(haveP.UpdatedAt)
}
hasMetadataChanges, _ := isMetadataUpdateRequired(wantP.Metadata, haveP.Metadata)
if hasConfigChanges || hasMetadataChanges {
processUpdate = append(processUpdate, DeploymentsProcess{
ID: wantP.Config.ID,
Domain: wantP.Config.Domain,
NodeID: haveP.NodeID,
Order: wantP.Order,
Error: wantP.Error,
UpdatedAt: wantP.UpdatedAt,
})
}
delete(wantMap, pid)
if haveP.Order != wantP.Order {
processOrder = append(processOrder, DeploymentsProcess{
ID: wantP.Config.ID,
Domain: wantP.Config.Domain,
NodeID: haveP.NodeID,
Order: wantP.Order,
Error: wantP.Error,
UpdatedAt: wantP.UpdatedAt,
})
}
}
// The wantMap now contains only those processes that need to be installed on a node.
for _, wantP := range wantMap {
processAdd = append(processAdd, DeploymentsProcess{
ID: wantP.Config.ID,
Domain: wantP.Config.Domain,
NodeID: "",
Order: wantP.Order,
Error: wantP.Error,
UpdatedAt: wantP.UpdatedAt,
})
}
// Rebuild want map
wantMap = map[string]store.Process{}
for _, wantP := range want {
pid := wantP.Config.ProcessID().String()
wantMap[pid] = wantP
}
relocateMap := c.store.ProcessGetRelocateMap()
for pid, targetNodeid := range relocateMap {
wantP, ok := wantMap[pid]
if !ok {
continue
}
haveP := clusternode.Process{}
found := false
for _, p := range have {
if pid == p.Config.ProcessID().String() {
haveP = p
found = true
break
}
}
if !found {
continue
}
sourceNodeid := haveP.NodeID
if sourceNodeid == targetNodeid {
continue
}
processRelocate = append(processRelocate, DeploymentsProcess{
ID: wantP.Config.ID,
Domain: wantP.Config.Domain,
NodeID: targetNodeid,
Order: wantP.Order,
Error: wantP.Error,
})
}
deployments := Deployments{}
deployments.Process.Delete = processDelete
deployments.Process.Update = processUpdate
deployments.Process.Order = processOrder
deployments.Process.Add = processAdd
deployments.Process.Relocate = processRelocate
return deployments, nil
}

View File

@@ -1655,6 +1655,9 @@ const docTemplateClusterAPI = `{
"properties": {
"config": {
"$ref": "#/definitions/app.Config"
},
"force": {
"type": "boolean"
}
}
},
@@ -2355,9 +2358,12 @@ const docTemplateClusterAPI = `{
"error": {
"type": "string"
},
"force": {
"type": "boolean"
},
"metadata": {
"type": "object",
"additionalProperties": true
"additionalProperties": {}
},
"order": {
"type": "string"

View File

@@ -1648,6 +1648,9 @@
"properties": {
"config": {
"$ref": "#/definitions/app.Config"
},
"force": {
"type": "boolean"
}
}
},
@@ -2348,9 +2351,12 @@
"error": {
"type": "string"
},
"force": {
"type": "boolean"
},
"metadata": {
"type": "object",
"additionalProperties": true
"additionalProperties": {}
},
"order": {
"type": "string"

View File

@@ -252,6 +252,8 @@ definitions:
properties:
config:
$ref: '#/definitions/app.Config'
force:
type: boolean
type: object
cluster.Error:
properties:
@@ -722,8 +724,10 @@ definitions:
type: string
error:
type: string
force:
type: boolean
metadata:
additionalProperties: true
additionalProperties: {}
type: object
order:
type: string

View File

@@ -208,6 +208,58 @@ const docTemplate = `{
}
}
},
"/api/v3/cluster/db/map/process": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve a map of which process is running on which node",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve a map of which process is running on which node",
"operationId": "cluster-3-db-process-node-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ClusterProcessMap"
}
}
}
}
},
"/api/v3/cluster/db/map/reallocate": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve a map of which processes should be relocated",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve a map of which processes should be relocated",
"operationId": "cluster-3-db-process-relocate-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ClusterProcessRelocateMap"
}
}
}
}
},
"/api/v3/cluster/db/node": {
"get": {
"security": [
@@ -403,6 +455,32 @@ const docTemplate = `{
}
}
},
"/api/v3/cluster/deployments": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve snapshot of pending deployments",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve snapshot of pending deployments",
"operationId": "cluster-3-deployments",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ClusterDeployments"
}
}
}
}
},
"/api/v3/cluster/events": {
"post": {
"security": [
@@ -991,32 +1069,6 @@ const docTemplate = `{
}
}
},
"/api/v3/cluster/map/process": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve a map of which process is running on which node",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve a map of which process is running on which node",
"operationId": "cluster-3-db-process-node-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ClusterProcessMap"
}
}
}
}
},
"/api/v3/cluster/node": {
"get": {
"security": [
@@ -2245,14 +2297,14 @@ const docTemplate = `{
"ApiKeyAuth": []
}
],
"description": "Retrieve snapshot of the cluster DB",
"description": "Issue reallocation requests of processes",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve snapshot of the cluster DB",
"summary": "Issue reallocation requests of processes",
"operationId": "cluster-3-reallocation",
"parameters": [
{
@@ -3889,6 +3941,12 @@ const docTemplate = `{
"description": "Process domain",
"name": "domain",
"in": "query"
},
{
"type": "string",
"description": "Whether to purge files",
"name": "purge",
"in": "query"
}
],
"responses": {
@@ -5563,6 +5621,72 @@ const docTemplate = `{
}
}
},
"api.ClusterDeployments": {
"type": "object",
"properties": {
"process": {
"$ref": "#/definitions/api.ClusterDeploymentsProcesses"
}
}
},
"api.ClusterDeploymentsProcess": {
"type": "object",
"properties": {
"domain": {
"type": "string"
},
"error": {
"type": "string"
},
"id": {
"type": "string"
},
"node_id": {
"type": "string"
},
"order": {
"type": "string"
},
"updated_at": {
"type": "integer"
}
}
},
"api.ClusterDeploymentsProcesses": {
"type": "object",
"properties": {
"add": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
},
"delete": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
},
"order": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
},
"relocate": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
},
"update": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
}
}
},
"api.ClusterKVS": {
"type": "object",
"additionalProperties": {
@@ -5808,6 +5932,12 @@ const docTemplate = `{
}
}
},
"api.ClusterProcessRelocateMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"api.ClusterRaft": {
"type": "object",
"properties": {

View File

@@ -201,6 +201,58 @@
}
}
},
"/api/v3/cluster/db/map/process": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve a map of which process is running on which node",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve a map of which process is running on which node",
"operationId": "cluster-3-db-process-node-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ClusterProcessMap"
}
}
}
}
},
"/api/v3/cluster/db/map/reallocate": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve a map of which processes should be relocated",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve a map of which processes should be relocated",
"operationId": "cluster-3-db-process-relocate-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ClusterProcessRelocateMap"
}
}
}
}
},
"/api/v3/cluster/db/node": {
"get": {
"security": [
@@ -396,6 +448,32 @@
}
}
},
"/api/v3/cluster/deployments": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve snapshot of pending deployments",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve snapshot of pending deployments",
"operationId": "cluster-3-deployments",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ClusterDeployments"
}
}
}
}
},
"/api/v3/cluster/events": {
"post": {
"security": [
@@ -984,32 +1062,6 @@
}
}
},
"/api/v3/cluster/map/process": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Retrieve a map of which process is running on which node",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve a map of which process is running on which node",
"operationId": "cluster-3-db-process-node-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ClusterProcessMap"
}
}
}
}
},
"/api/v3/cluster/node": {
"get": {
"security": [
@@ -2238,14 +2290,14 @@
"ApiKeyAuth": []
}
],
"description": "Retrieve snapshot of the cluster DB",
"description": "Issue reallocation requests of processes",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Retrieve snapshot of the cluster DB",
"summary": "Issue reallocation requests of processes",
"operationId": "cluster-3-reallocation",
"parameters": [
{
@@ -3882,6 +3934,12 @@
"description": "Process domain",
"name": "domain",
"in": "query"
},
{
"type": "string",
"description": "Whether to purge files",
"name": "purge",
"in": "query"
}
],
"responses": {
@@ -5556,6 +5614,72 @@
}
}
},
"api.ClusterDeployments": {
"type": "object",
"properties": {
"process": {
"$ref": "#/definitions/api.ClusterDeploymentsProcesses"
}
}
},
"api.ClusterDeploymentsProcess": {
"type": "object",
"properties": {
"domain": {
"type": "string"
},
"error": {
"type": "string"
},
"id": {
"type": "string"
},
"node_id": {
"type": "string"
},
"order": {
"type": "string"
},
"updated_at": {
"type": "integer"
}
}
},
"api.ClusterDeploymentsProcesses": {
"type": "object",
"properties": {
"add": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
},
"delete": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
},
"order": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
},
"relocate": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
},
"update": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ClusterDeploymentsProcess"
}
}
}
},
"api.ClusterKVS": {
"type": "object",
"additionalProperties": {
@@ -5801,6 +5925,12 @@
}
}
},
"api.ClusterProcessRelocateMap": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"api.ClusterRaft": {
"type": "object",
"properties": {

View File

@@ -204,6 +204,49 @@ definitions:
id:
type: string
type: object
api.ClusterDeployments:
properties:
process:
$ref: '#/definitions/api.ClusterDeploymentsProcesses'
type: object
api.ClusterDeploymentsProcess:
properties:
domain:
type: string
error:
type: string
id:
type: string
node_id:
type: string
order:
type: string
updated_at:
type: integer
type: object
api.ClusterDeploymentsProcesses:
properties:
add:
items:
$ref: '#/definitions/api.ClusterDeploymentsProcess'
type: array
delete:
items:
$ref: '#/definitions/api.ClusterDeploymentsProcess'
type: array
order:
items:
$ref: '#/definitions/api.ClusterDeploymentsProcess'
type: array
relocate:
items:
$ref: '#/definitions/api.ClusterDeploymentsProcess'
type: array
update:
items:
$ref: '#/definitions/api.ClusterDeploymentsProcess'
type: array
type: object
api.ClusterKVS:
additionalProperties:
$ref: '#/definitions/api.ClusterKVSValue'
@@ -371,6 +414,10 @@ definitions:
target_node_id:
type: string
type: object
api.ClusterProcessRelocateMap:
additionalProperties:
type: string
type: object
api.ClusterRaft:
properties:
address:
@@ -2999,6 +3046,38 @@ paths:
summary: List locks in the cluster DB
tags:
- v16.?.?
/api/v3/cluster/db/map/process:
get:
description: Retrieve a map of which process is running on which node
operationId: cluster-3-db-process-node-map
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.ClusterProcessMap'
security:
- ApiKeyAuth: []
summary: Retrieve a map of which process is running on which node
tags:
- v16.?.?
/api/v3/cluster/db/map/reallocate:
get:
description: Retrieve a map of which processes should be relocated
operationId: cluster-3-db-process-relocate-map
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.ClusterProcessRelocateMap'
security:
- ApiKeyAuth: []
summary: Retrieve a map of which processes should be relocated
tags:
- v16.?.?
/api/v3/cluster/db/node:
get:
description: List of nodes in the cluster DB
@@ -3121,6 +3200,22 @@ paths:
summary: List of identities in the cluster
tags:
- v16.?.?
/api/v3/cluster/deployments:
get:
description: Retrieve snapshot of pending deployments
operationId: cluster-3-deployments
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.ClusterDeployments'
security:
- ApiKeyAuth: []
summary: Retrieve snapshot of pending deployments
tags:
- v16.?.?
/api/v3/cluster/events:
post:
consumes:
@@ -3497,22 +3592,6 @@ paths:
summary: Leave the cluster gracefully
tags:
- v16.?.?
/api/v3/cluster/map/process:
get:
description: Retrieve a map of which process is running on which node
operationId: cluster-3-db-process-node-map
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.ClusterProcessMap'
security:
- ApiKeyAuth: []
summary: Retrieve a map of which process is running on which node
tags:
- v16.?.?
/api/v3/cluster/node:
get:
description: List of proxy nodes in the cluster
@@ -4332,7 +4411,7 @@ paths:
- v16.?.?
/api/v3/cluster/reallocation:
put:
description: Retrieve snapshot of the cluster DB
description: Issue reallocation requests of processes
operationId: cluster-3-reallocation
parameters:
- description: Process reallocations
@@ -4354,7 +4433,7 @@ paths:
$ref: '#/definitions/api.Error'
security:
- ApiKeyAuth: []
summary: Retrieve snapshot of the cluster DB
summary: Issue reallocation requests of processes
tags:
- v16.?.?
/api/v3/cluster/snapshot:
@@ -5242,6 +5321,10 @@ paths:
in: query
name: domain
type: string
- description: Whether to purge files
in: query
name: purge
type: string
produces:
- application/json
responses:

View File

@@ -120,3 +120,24 @@ type ClusterStoreNode struct {
State string `json:"state"`
UpdatedAt time.Time `json:"updated_at"`
}
type ClusterDeployments struct {
Process ClusterDeploymentsProcesses `json:"process"`
}
type ClusterDeploymentsProcesses struct {
Delete []ClusterDeploymentsProcess `json:"delete"`
Update []ClusterDeploymentsProcess `json:"update"`
Order []ClusterDeploymentsProcess `json:"order"`
Add []ClusterDeploymentsProcess `json:"add"`
Relocate []ClusterDeploymentsProcess `json:"relocate"`
}
type ClusterDeploymentsProcess struct {
ID string `json:"id"`
Domain string `json:"domain"`
NodeID string `json:"node_id"`
Order string `json:"order"`
Error string `json:"error"`
UpdateAt int64 `json:"updated_at"`
}

View File

@@ -289,3 +289,44 @@ func (h *ClusterHandler) Reallocation(c echo.Context) error {
return c.JSON(http.StatusOK, "OK")
}
// Deployments returns a current snapshot of pending deployments
// @Summary Retrieve snapshot of pending deployments
// @Description Retrieve snapshot of pending deployments
// @Tags v16.?.?
// @ID cluster-3-deployments
// @Produce application/json
// @Success 200 {object} api.ClusterDeployments
// @Security ApiKeyAuth
// @Router /api/v3/cluster/deployments [get]
func (h *ClusterHandler) Deployments(c echo.Context) error {
deployments, err := h.cluster.Deployments()
if err != nil {
return api.Err(http.StatusInternalServerError, "", "%s", err.Error())
}
marshal := func(processes []cluster.DeploymentsProcess) []api.ClusterDeploymentsProcess {
apiProcesses := []api.ClusterDeploymentsProcess{}
for _, p := range processes {
apiProcesses = append(apiProcesses, api.ClusterDeploymentsProcess{
ID: p.ID,
Domain: p.Domain,
NodeID: p.NodeID,
Order: p.Order,
Error: p.Error,
UpdateAt: p.UpdatedAt.Unix(),
})
}
return apiProcesses
}
return c.JSON(http.StatusOK, api.ClusterDeployments{
Process: api.ClusterDeploymentsProcesses{
Delete: marshal(deployments.Process.Delete),
Update: marshal(deployments.Process.Update),
Order: marshal(deployments.Process.Order),
Add: marshal(deployments.Process.Add),
Relocate: marshal(deployments.Process.Relocate),
},
})
}

View File

@@ -733,6 +733,7 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
v3.GET("/cluster/healthy", s.v3handler.cluster.Healthy)
v3.GET("/cluster/snapshot", s.v3handler.cluster.GetSnapshot)
v3.GET("/cluster/deployments", s.v3handler.cluster.Deployments)
v3.GET("/cluster/db/process", s.v3handler.cluster.StoreListProcesses)
v3.GET("/cluster/db/process/:id", s.v3handler.cluster.StoreGetProcess)