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",
"in": "path",
"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": {
@@ -1803,54 +1815,6 @@ const docTemplate = `{
"$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": {
"description": "Bad Request",
"schema": {
@@ -3462,10 +3426,17 @@ const docTemplate = `{
"type": "integer",
"format": "int64"
},
"exit_state": {
"type": "string"
},
"exited_at": {
"type": "integer",
"format": "int64"
},
"history": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ProcessReportHistoryEntry"
"$ref": "#/definitions/api.ProcessReportEntry"
}
},
"log": {
@@ -3482,10 +3453,13 @@ const docTemplate = `{
"items": {
"type": "string"
}
},
"progress": {
"$ref": "#/definitions/api.Progress"
}
}
},
"api.ProcessReportHistoryEntry": {
"api.ProcessReportEntry": {
"type": "object",
"properties": {
"created_at": {

View File

@@ -1787,6 +1787,18 @@
"name": "id",
"in": "path",
"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": {
@@ -1796,54 +1808,6 @@
"$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": {
"description": "Bad Request",
"schema": {
@@ -3455,10 +3419,17 @@
"type": "integer",
"format": "int64"
},
"exit_state": {
"type": "string"
},
"exited_at": {
"type": "integer",
"format": "int64"
},
"history": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ProcessReportHistoryEntry"
"$ref": "#/definitions/api.ProcessReportEntry"
}
},
"log": {
@@ -3475,10 +3446,13 @@
"items": {
"type": "string"
}
},
"progress": {
"$ref": "#/definitions/api.Progress"
}
}
},
"api.ProcessReportHistoryEntry": {
"api.ProcessReportEntry": {
"type": "object",
"properties": {
"created_at": {

View File

@@ -811,9 +811,14 @@ definitions:
created_at:
format: int64
type: integer
exit_state:
type: string
exited_at:
format: int64
type: integer
history:
items:
$ref: '#/definitions/api.ProcessReportHistoryEntry'
$ref: '#/definitions/api.ProcessReportEntry'
type: array
log:
items:
@@ -825,8 +830,10 @@ definitions:
items:
type: string
type: array
progress:
$ref: '#/definitions/api.Progress'
type: object
api.ProcessReportHistoryEntry:
api.ProcessReportEntry:
properties:
created_at:
format: int64
@@ -3138,37 +3145,17 @@ paths:
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.ProcessReport'
"404":
description: Not Found
schema:
$ref: '#/definitions/api.Error'
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
- 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.
in: query
name: created_at
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.
in: query
name: exited_at
type: integer
produces:
- application/json
@@ -3176,7 +3163,7 @@ paths:
"200":
description: OK
schema:
$ref: '#/definitions/api.ProcessReportHistoryEntry'
$ref: '#/definitions/api.ProcessReport'
"400":
description: Bad Request
schema:
@@ -3187,9 +3174,9 @@ paths:
$ref: '#/definitions/api.Error'
security:
- ApiKeyAuth: []
summary: Get the log history entry of a process
summary: Get the logs of a process
tags:
- v16.?.?
- v16.7.2
/api/v3/process/{id}/state:
get:
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.logStart = time.Now()
p.logStart = time.Time{}
p.lock.log.Unlock()
p.ResetStats()
@@ -721,7 +721,7 @@ func (p *parser) ResetLog() {
p.lock.log.Lock()
p.log = ring.New(p.logLines)
p.logStart = time.Now()
p.logStart = time.Time{}
p.lock.log.Unlock()
}
@@ -791,6 +791,8 @@ func (p *parser) storeReportHistory(state string) {
report := p.Report()
p.ResetLog()
if len(report.Prelude) == 0 {
return
}

View File

@@ -11,20 +11,19 @@ type ProcessReportEntry struct {
CreatedAt int64 `json:"created_at" format:"int64"`
Prelude []string `json:"prelude,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 {
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
type ProcessReport struct {
ProcessReportEntry
History []ProcessReportHistoryEntry `json:"history"`
History []ProcessReportEntry `json:"history"`
}
// 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.History = []ProcessReportHistoryEntry{}
report.History = []ProcessReportEntry{}
for _, h := range l.History {
he := ProcessReportHistoryEntry{
ProcessReportEntry: ProcessReportEntry{
he := ProcessReportEntry{
CreatedAt: h.CreatedAt.Unix(),
Prelude: h.Prelude,
Log: make([][2]string, len(h.Log)),
},
ExitedAt: h.ExitedAt.Unix(),
ExitState: h.ExitState,
}
he.Progress = &Progress{}
he.Progress.Unmarshal(&h.Progress)
for i, line := range h.Log {
he.ProcessReportEntry.Log[i][0] = strconv.FormatInt(line.Timestamp.Unix(), 10)
he.ProcessReportEntry.Log[i][1] = line.Data
he.Log[i][0] = strconv.FormatInt(line.Timestamp.Unix(), 10)
he.Log[i][1] = line.Data
}
report.History = append(report.History, he)

View File

@@ -2,6 +2,7 @@ package api
import (
"net/http"
"sort"
"strconv"
"strings"
"time"
@@ -320,12 +321,36 @@ func (h *RestreamHandler) GetState(c echo.Context) error {
// @ID process-3-get-report
// @Produce json
// @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
// @Failure 400 {object} api.Error
// @Failure 404 {object} api.Error
// @Security ApiKeyAuth
// @Router /api/v3/process/{id}/report [get]
func (h *RestreamHandler) GetReport(c echo.Context) error {
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)
if err != nil {
@@ -335,44 +360,54 @@ func (h *RestreamHandler) GetReport(c echo.Context) error {
report := api.ProcessReport{}
report.Unmarshal(l)
if createdAt == nil && exitedAt == nil {
return c.JSON(http.StatusOK, report)
}
// GetReportAt return the log history entry of a process
// @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)
if err != nil {
return api.Err(http.StatusNotFound, "Unknown process ID", "%s", err)
}
filteredReport := api.ProcessReport{}
report := api.ProcessReport{}
report.Unmarshal(l)
report.History = append(report.History, api.ProcessReportEntry{
CreatedAt: report.CreatedAt,
Prelude: report.Prelude,
Log: report.Log,
})
entries := []api.ProcessReportEntry{}
for _, r := range report.History {
if r.ExitedAt == at {
return c.JSON(http.StatusOK, r)
if createdAt != nil && exitedAt == nil {
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

View File

@@ -46,7 +46,6 @@ func getDummyRestreamRouter() (*echo.Echo, error) {
router.GET("/:id/config", restream.GetConfig)
router.GET("/:id/report", restream.GetReport)
router.GET("/:id/state", restream.GetState)
router.GET("/:id/report/:at", restream.GetReportAt)
router.PUT("/:id", restream.Update)
router.DELETE("/:id", restream.Delete)
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, "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)
x := api.ProcessReport{}
err = json.Unmarshal(response.Raw, &x)
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.StatusNotFound, router, "GET", "/test/report/1234", 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?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) {

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/state", s.v3handler.restream.GetState)
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/metadata", s.v3handler.restream.GetProcessMetadata)