Remove /process/:id/report/:at endpoint, extend /process/:id/report endpoint

This commit is contained in:
Ingo Oppermann
2023-03-22 12:31:41 +01:00
parent d950c45eb0
commit b21aba5f9d
8 changed files with 192 additions and 200 deletions

View File

@@ -1794,6 +1794,18 @@ const docTemplate = `{
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "required": true
},
{
"type": "integer",
"description": "Select only the report with that created_at date. Unix timestamp, leave empty for any. In combination with exited_at it denotes a range or reports.",
"name": "created_at",
"in": "query"
},
{
"type": "integer",
"description": "Select only the report with that exited_at date. Unix timestamp, leave empty for any. In combination with created_at it denotes a range or reports.",
"name": "exited_at",
"in": "query"
} }
], ],
"responses": { "responses": {
@@ -1803,54 +1815,6 @@ const docTemplate = `{
"$ref": "#/definitions/api.ProcessReport" "$ref": "#/definitions/api.ProcessReport"
} }
}, },
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/api.Error"
}
}
}
}
},
"/api/v3/process/{id}/report/{at}": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Get the log history entry of a process that finished at a certain time.",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Get the log history entry of a process",
"operationId": "process-3-get-report-at",
"parameters": [
{
"type": "string",
"description": "Process ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Unix timestamp",
"name": "at",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ProcessReportHistoryEntry"
}
},
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
"schema": { "schema": {
@@ -3462,10 +3426,17 @@ const docTemplate = `{
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"exit_state": {
"type": "string"
},
"exited_at": {
"type": "integer",
"format": "int64"
},
"history": { "history": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/api.ProcessReportHistoryEntry" "$ref": "#/definitions/api.ProcessReportEntry"
} }
}, },
"log": { "log": {
@@ -3482,10 +3453,13 @@ const docTemplate = `{
"items": { "items": {
"type": "string" "type": "string"
} }
},
"progress": {
"$ref": "#/definitions/api.Progress"
} }
} }
}, },
"api.ProcessReportHistoryEntry": { "api.ProcessReportEntry": {
"type": "object", "type": "object",
"properties": { "properties": {
"created_at": { "created_at": {

View File

@@ -1787,6 +1787,18 @@
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "required": true
},
{
"type": "integer",
"description": "Select only the report with that created_at date. Unix timestamp, leave empty for any. In combination with exited_at it denotes a range or reports.",
"name": "created_at",
"in": "query"
},
{
"type": "integer",
"description": "Select only the report with that exited_at date. Unix timestamp, leave empty for any. In combination with created_at it denotes a range or reports.",
"name": "exited_at",
"in": "query"
} }
], ],
"responses": { "responses": {
@@ -1796,54 +1808,6 @@
"$ref": "#/definitions/api.ProcessReport" "$ref": "#/definitions/api.ProcessReport"
} }
}, },
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/api.Error"
}
}
}
}
},
"/api/v3/process/{id}/report/{at}": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Get the log history entry of a process that finished at a certain time.",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Get the log history entry of a process",
"operationId": "process-3-get-report-at",
"parameters": [
{
"type": "string",
"description": "Process ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Unix timestamp",
"name": "at",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.ProcessReportHistoryEntry"
}
},
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
"schema": { "schema": {
@@ -3455,10 +3419,17 @@
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"exit_state": {
"type": "string"
},
"exited_at": {
"type": "integer",
"format": "int64"
},
"history": { "history": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/api.ProcessReportHistoryEntry" "$ref": "#/definitions/api.ProcessReportEntry"
} }
}, },
"log": { "log": {
@@ -3475,10 +3446,13 @@
"items": { "items": {
"type": "string" "type": "string"
} }
},
"progress": {
"$ref": "#/definitions/api.Progress"
} }
} }
}, },
"api.ProcessReportHistoryEntry": { "api.ProcessReportEntry": {
"type": "object", "type": "object",
"properties": { "properties": {
"created_at": { "created_at": {

View File

@@ -811,9 +811,14 @@ definitions:
created_at: created_at:
format: int64 format: int64
type: integer type: integer
exit_state:
type: string
exited_at:
format: int64
type: integer
history: history:
items: items:
$ref: '#/definitions/api.ProcessReportHistoryEntry' $ref: '#/definitions/api.ProcessReportEntry'
type: array type: array
log: log:
items: items:
@@ -825,8 +830,10 @@ definitions:
items: items:
type: string type: string
type: array type: array
progress:
$ref: '#/definitions/api.Progress'
type: object type: object
api.ProcessReportHistoryEntry: api.ProcessReportEntry:
properties: properties:
created_at: created_at:
format: int64 format: int64
@@ -3138,37 +3145,17 @@ paths:
name: id name: id
required: true required: true
type: string type: string
produces: - description: Select only the report with that created_at date. Unix timestamp,
- application/json leave empty for any. In combination with exited_at it denotes a range or
responses: reports.
"200": in: query
description: OK name: created_at
schema: type: integer
$ref: '#/definitions/api.ProcessReport' - description: Select only the report with that exited_at date. Unix timestamp,
"404": leave empty for any. In combination with created_at it denotes a range or
description: Not Found reports.
schema: in: query
$ref: '#/definitions/api.Error' name: exited_at
security:
- ApiKeyAuth: []
summary: Get the logs of a process
tags:
- v16.7.2
/api/v3/process/{id}/report/{at}:
get:
description: Get the log history entry of a process that finished at a certain
time.
operationId: process-3-get-report-at
parameters:
- description: Process ID
in: path
name: id
required: true
type: string
- description: Unix timestamp
in: path
name: at
required: true
type: integer type: integer
produces: produces:
- application/json - application/json
@@ -3176,7 +3163,7 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/api.ProcessReportHistoryEntry' $ref: '#/definitions/api.ProcessReport'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@@ -3187,9 +3174,9 @@ paths:
$ref: '#/definitions/api.Error' $ref: '#/definitions/api.Error'
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
summary: Get the log history entry of a process summary: Get the logs of a process
tags: tags:
- v16.?.? - v16.7.2
/api/v3/process/{id}/state: /api/v3/process/{id}/state:
get: get:
description: Get the state and progress data of a process. description: Get the state and progress data of a process.

View File

@@ -170,7 +170,7 @@ func New(config Config) Parser {
p.collector = session.NewNullCollector() p.collector = session.NewNullCollector()
} }
p.logStart = time.Now() p.logStart = time.Time{}
p.lock.log.Unlock() p.lock.log.Unlock()
p.ResetStats() p.ResetStats()
@@ -721,7 +721,7 @@ func (p *parser) ResetLog() {
p.lock.log.Lock() p.lock.log.Lock()
p.log = ring.New(p.logLines) p.log = ring.New(p.logLines)
p.logStart = time.Now() p.logStart = time.Time{}
p.lock.log.Unlock() p.lock.log.Unlock()
} }
@@ -791,6 +791,8 @@ func (p *parser) storeReportHistory(state string) {
report := p.Report() report := p.Report()
p.ResetLog()
if len(report.Prelude) == 0 { if len(report.Prelude) == 0 {
return return
} }

View File

@@ -11,20 +11,19 @@ type ProcessReportEntry struct {
CreatedAt int64 `json:"created_at" format:"int64"` CreatedAt int64 `json:"created_at" format:"int64"`
Prelude []string `json:"prelude,omitempty"` Prelude []string `json:"prelude,omitempty"`
Log [][2]string `json:"log,omitempty"` Log [][2]string `json:"log,omitempty"`
ExitedAt int64 `json:"exited_at,omitempty" format:"int64"`
ExitState string `json:"exit_state,omitempty"`
Progress *Progress `json:"progress,omitempty"`
} }
type ProcessReportHistoryEntry struct { type ProcessReportHistoryEntry struct {
ProcessReportEntry ProcessReportEntry
ExitedAt int64 `json:"exited_at" format:"int64"`
ExitState string `json:"exit_state"`
Progress Progress `json:"progress"`
} }
// ProcessReport represents the current log and the logs of previous runs of a restream process // ProcessReport represents the current log and the logs of previous runs of a restream process
type ProcessReport struct { type ProcessReport struct {
ProcessReportEntry ProcessReportEntry
History []ProcessReportHistoryEntry `json:"history"` History []ProcessReportEntry `json:"history"`
} }
// Unmarshal converts a restream log to a report // Unmarshal converts a restream log to a report
@@ -41,24 +40,23 @@ func (report *ProcessReport) Unmarshal(l *app.Log) {
report.Log[i][1] = line.Data report.Log[i][1] = line.Data
} }
report.History = []ProcessReportHistoryEntry{} report.History = []ProcessReportEntry{}
for _, h := range l.History { for _, h := range l.History {
he := ProcessReportHistoryEntry{ he := ProcessReportEntry{
ProcessReportEntry: ProcessReportEntry{
CreatedAt: h.CreatedAt.Unix(), CreatedAt: h.CreatedAt.Unix(),
Prelude: h.Prelude, Prelude: h.Prelude,
Log: make([][2]string, len(h.Log)), Log: make([][2]string, len(h.Log)),
},
ExitedAt: h.ExitedAt.Unix(), ExitedAt: h.ExitedAt.Unix(),
ExitState: h.ExitState, ExitState: h.ExitState,
} }
he.Progress = &Progress{}
he.Progress.Unmarshal(&h.Progress) he.Progress.Unmarshal(&h.Progress)
for i, line := range h.Log { for i, line := range h.Log {
he.ProcessReportEntry.Log[i][0] = strconv.FormatInt(line.Timestamp.Unix(), 10) he.Log[i][0] = strconv.FormatInt(line.Timestamp.Unix(), 10)
he.ProcessReportEntry.Log[i][1] = line.Data he.Log[i][1] = line.Data
} }
report.History = append(report.History, he) report.History = append(report.History, he)

View File

@@ -2,6 +2,7 @@ package api
import ( import (
"net/http" "net/http"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -320,12 +321,36 @@ func (h *RestreamHandler) GetState(c echo.Context) error {
// @ID process-3-get-report // @ID process-3-get-report
// @Produce json // @Produce json
// @Param id path string true "Process ID" // @Param id path string true "Process ID"
// @Param created_at query int64 false "Select only the report with that created_at date. Unix timestamp, leave empty for any. In combination with exited_at it denotes a range or reports."
// @Param exited_at query int64 false "Select only the report with that exited_at date. Unix timestamp, leave empty for any. In combination with created_at it denotes a range or reports."
// @Success 200 {object} api.ProcessReport // @Success 200 {object} api.ProcessReport
// @Failure 400 {object} api.Error
// @Failure 404 {object} api.Error // @Failure 404 {object} api.Error
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /api/v3/process/{id}/report [get] // @Router /api/v3/process/{id}/report [get]
func (h *RestreamHandler) GetReport(c echo.Context) error { func (h *RestreamHandler) GetReport(c echo.Context) error {
id := util.PathParam(c, "id") id := util.PathParam(c, "id")
createdUnix := util.DefaultQuery(c, "created_at", "")
exitedUnix := util.DefaultQuery(c, "exited_at", "")
var createdAt *int64 = nil
var exitedAt *int64 = nil
if len(createdUnix) != 0 {
if x, err := strconv.ParseInt(createdUnix, 10, 64); err != nil {
return api.Err(http.StatusBadRequest, "Invalid created_at unix timestamp", "%s", err)
} else {
createdAt = &x
}
}
if len(exitedUnix) != 0 {
if x, err := strconv.ParseInt(exitedUnix, 10, 64); err != nil {
return api.Err(http.StatusBadRequest, "Invalid exited_at unix timestamp", "%s", err)
} else {
exitedAt = &x
}
}
l, err := h.restream.GetProcessLog(id) l, err := h.restream.GetProcessLog(id)
if err != nil { if err != nil {
@@ -335,44 +360,54 @@ func (h *RestreamHandler) GetReport(c echo.Context) error {
report := api.ProcessReport{} report := api.ProcessReport{}
report.Unmarshal(l) report.Unmarshal(l)
if createdAt == nil && exitedAt == nil {
return c.JSON(http.StatusOK, report) return c.JSON(http.StatusOK, report)
} }
// GetReportAt return the log history entry of a process filteredReport := api.ProcessReport{}
// @Summary Get the log history entry of a process
// @Description Get the log history entry of a process that finished at a certain time.
// @Tags v16.?.?
// @ID process-3-get-report-at
// @Produce json
// @Param id path string true "Process ID"
// @Param at path integer true "Unix timestamp"
// @Success 200 {object} api.ProcessReportHistoryEntry
// @Failure 404 {object} api.Error
// @Failure 400 {object} api.Error
// @Security ApiKeyAuth
// @Router /api/v3/process/{id}/report/{at} [get]
func (h *RestreamHandler) GetReportAt(c echo.Context) error {
id := util.PathParam(c, "id")
at, err := strconv.ParseInt(util.PathParam(c, "at"), 10, 64)
if err != nil {
return api.Err(http.StatusBadRequest, "Invalid process report date", "%s", err)
}
l, err := h.restream.GetProcessLog(id) report.History = append(report.History, api.ProcessReportEntry{
if err != nil { CreatedAt: report.CreatedAt,
return api.Err(http.StatusNotFound, "Unknown process ID", "%s", err) Prelude: report.Prelude,
} Log: report.Log,
})
report := api.ProcessReport{} entries := []api.ProcessReportEntry{}
report.Unmarshal(l)
for _, r := range report.History { for _, r := range report.History {
if r.ExitedAt == at { if createdAt != nil && exitedAt == nil {
return c.JSON(http.StatusOK, r) if r.CreatedAt == *createdAt {
entries = append(entries, r)
}
} else if createdAt == nil && exitedAt != nil {
if r.ExitedAt == *exitedAt {
entries = append(entries, r)
}
} else {
if r.CreatedAt >= *createdAt || r.ExitedAt <= *exitedAt {
entries = append(entries, r)
}
} }
} }
return api.Err(http.StatusNotFound, "Unknown process report date") if len(entries) == 0 {
return api.Err(http.StatusNotFound, "No matching reports found")
}
sort.SliceStable(entries, func(i, j int) bool {
return entries[i].CreatedAt < entries[j].CreatedAt
})
if entries[0].ExitState == "" {
// This is a running process
filteredReport.CreatedAt = entries[0].CreatedAt
filteredReport.Prelude = entries[0].Prelude
filteredReport.Log = entries[0].Log
}
filteredReport.History = entries[1:]
return c.JSON(http.StatusOK, filteredReport)
} }
// SearchReportHistory returns a list of matching report references // SearchReportHistory returns a list of matching report references

View File

@@ -46,7 +46,6 @@ func getDummyRestreamRouter() (*echo.Echo, error) {
router.GET("/:id/config", restream.GetConfig) router.GET("/:id/config", restream.GetConfig)
router.GET("/:id/report", restream.GetReport) router.GET("/:id/report", restream.GetReport)
router.GET("/:id/state", restream.GetState) router.GET("/:id/state", restream.GetState)
router.GET("/:id/report/:at", restream.GetReportAt)
router.PUT("/:id", restream.Update) router.PUT("/:id", restream.Update)
router.DELETE("/:id", restream.Delete) router.DELETE("/:id", restream.Delete)
router.PUT("/:id/command", restream.Command) router.PUT("/:id/command", restream.Command)
@@ -334,18 +333,42 @@ func TestProcessReportAt(t *testing.T) {
mock.Request(t, http.StatusOK, router, "PUT", "/test/command", command) mock.Request(t, http.StatusOK, router, "PUT", "/test/command", command)
mock.Request(t, http.StatusOK, router, "GET", "/test", nil) mock.Request(t, http.StatusOK, router, "GET", "/test", nil)
command = mock.Read(t, "./fixtures/commandStart.json")
mock.Request(t, http.StatusOK, router, "PUT", "/test/command", command)
mock.Request(t, http.StatusOK, router, "GET", "/test", nil)
time.Sleep(2 * time.Second)
command = mock.Read(t, "./fixtures/commandStop.json")
mock.Request(t, http.StatusOK, router, "PUT", "/test/command", command)
mock.Request(t, http.StatusOK, router, "GET", "/test", nil)
response := mock.Request(t, http.StatusOK, router, "GET", "/test/report", nil) response := mock.Request(t, http.StatusOK, router, "GET", "/test/report", nil)
x := api.ProcessReport{} x := api.ProcessReport{}
err = json.Unmarshal(response.Raw, &x) err = json.Unmarshal(response.Raw, &x)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(x.History)) require.Equal(t, 2, len(x.History))
at := x.History[0].ExitedAt created := x.History[0].CreatedAt
exited := x.History[0].ExitedAt
mock.Request(t, http.StatusOK, router, "GET", "/test/report/"+strconv.FormatInt(at, 10), nil) mock.Request(t, http.StatusOK, router, "GET", "/test/report?created_at="+strconv.FormatInt(created, 10), nil)
mock.Request(t, http.StatusNotFound, router, "GET", "/test/report/1234", nil) mock.Request(t, http.StatusNotFound, router, "GET", "/test/report?created_at=1234", nil)
mock.Request(t, http.StatusOK, router, "GET", "/test/report?exited_at="+strconv.FormatInt(exited, 10), nil)
mock.Request(t, http.StatusNotFound, router, "GET", "/test/report?exited_at=1234", nil)
exited = x.History[1].ExitedAt
response = mock.Request(t, http.StatusOK, router, "GET", "/test/report?created_at="+strconv.FormatInt(created, 10)+"&exited_at="+strconv.FormatInt(exited, 10), nil)
x = api.ProcessReport{}
err = json.Unmarshal(response.Raw, &x)
require.NoError(t, err)
require.Equal(t, 2, len(x.History))
} }
func TestSearchReportHistory(t *testing.T) { func TestSearchReportHistory(t *testing.T) {

View File

@@ -555,7 +555,6 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
v3.GET("/process/:id/config", s.v3handler.restream.GetConfig) v3.GET("/process/:id/config", s.v3handler.restream.GetConfig)
v3.GET("/process/:id/state", s.v3handler.restream.GetState) v3.GET("/process/:id/state", s.v3handler.restream.GetState)
v3.GET("/process/:id/report", s.v3handler.restream.GetReport) v3.GET("/process/:id/report", s.v3handler.restream.GetReport)
v3.GET("/process/:id/report/:at", s.v3handler.restream.GetReportAt)
v3.GET("/process/:id/probe", s.v3handler.restream.Probe) v3.GET("/process/:id/probe", s.v3handler.restream.Probe)
v3.GET("/process/:id/metadata", s.v3handler.restream.GetProcessMetadata) v3.GET("/process/:id/metadata", s.v3handler.restream.GetProcessMetadata)