diff --git a/cluster/cluster.go b/cluster/cluster.go index c836533f..8c52797c 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -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 +} diff --git a/cluster/docs/ClusterAPI_docs.go b/cluster/docs/ClusterAPI_docs.go index b8444b26..b63ac085 100644 --- a/cluster/docs/ClusterAPI_docs.go +++ b/cluster/docs/ClusterAPI_docs.go @@ -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" diff --git a/cluster/docs/ClusterAPI_swagger.json b/cluster/docs/ClusterAPI_swagger.json index 4711ee7e..6fa6a20b 100644 --- a/cluster/docs/ClusterAPI_swagger.json +++ b/cluster/docs/ClusterAPI_swagger.json @@ -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" diff --git a/cluster/docs/ClusterAPI_swagger.yaml b/cluster/docs/ClusterAPI_swagger.yaml index c8638b8c..45e391bf 100644 --- a/cluster/docs/ClusterAPI_swagger.yaml +++ b/cluster/docs/ClusterAPI_swagger.yaml @@ -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 diff --git a/docs/docs.go b/docs/docs.go index 99fd0b48..05250d65 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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": { diff --git a/docs/swagger.json b/docs/swagger.json index 5a3e932c..bbfa67d7 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 3da5e864..5551de0c 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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: diff --git a/http/api/cluster.go b/http/api/cluster.go index b287ebfe..b0f7361f 100644 --- a/http/api/cluster.go +++ b/http/api/cluster.go @@ -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"` +} diff --git a/http/handler/api/cluster.go b/http/handler/api/cluster.go index 4fbbd801..8a34fdc5 100644 --- a/http/handler/api/cluster.go +++ b/http/handler/api/cluster.go @@ -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), + }, + }) +} diff --git a/http/server.go b/http/server.go index 5a6cf62e..603e5b41 100644 --- a/http/server.go +++ b/http/server.go @@ -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)