From b21aba5f9d5f08fe76fa2b0d7a03d35adf6ae8a8 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 22 Mar 2023 12:31:41 +0100 Subject: [PATCH] Remove /process/:id/report/:at endpoint, extend /process/:id/report endpoint --- docs/docs.go | 74 ++++++------------- docs/swagger.json | 74 ++++++------------- docs/swagger.yaml | 59 ++++++--------- ffmpeg/parse/parser.go | 6 +- http/api/report.go | 26 +++---- http/handler/api/restream.go | 119 +++++++++++++++++++----------- http/handler/api/restream_test.go | 33 +++++++-- http/server.go | 1 - 8 files changed, 192 insertions(+), 200 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index eb4abc86..3ab93859 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -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": { diff --git a/docs/swagger.json b/docs/swagger.json index 162aa986..4dfc2a69 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -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": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 58533d17..ec307640 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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. diff --git a/ffmpeg/parse/parser.go b/ffmpeg/parse/parser.go index 25ff4b3b..b335fe14 100644 --- a/ffmpeg/parse/parser.go +++ b/ffmpeg/parse/parser.go @@ -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 } diff --git a/http/api/report.go b/http/api/report.go index 6fbddac9..1350367f 100644 --- a/http/api/report.go +++ b/http/api/report.go @@ -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{ - CreatedAt: h.CreatedAt.Unix(), - Prelude: h.Prelude, - Log: make([][2]string, len(h.Log)), - }, + 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) diff --git a/http/handler/api/restream.go b/http/handler/api/restream.go index 12cb0161..415d48c3 100644 --- a/http/handler/api/restream.go +++ b/http/handler/api/restream.go @@ -2,6 +2,7 @@ package api import ( "net/http" + "sort" "strconv" "strings" "time" @@ -320,59 +321,93 @@ 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", "") - l, err := h.restream.GetProcessLog(id) - if err != nil { - return api.Err(http.StatusNotFound, "Unknown process ID", "%s", err) - } + var createdAt *int64 = nil + var exitedAt *int64 = nil - report := api.ProcessReport{} - report.Unmarshal(l) - - 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) - } - - report := api.ProcessReport{} - report.Unmarshal(l) - - for _, r := range report.History { - if r.ExitedAt == at { - return c.JSON(http.StatusOK, r) + 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 } } - return api.Err(http.StatusNotFound, "Unknown process report date") + 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 { + return api.Err(http.StatusNotFound, "Unknown process ID", "%s", err) + } + + report := api.ProcessReport{} + report.Unmarshal(l) + + if createdAt == nil && exitedAt == nil { + return c.JSON(http.StatusOK, report) + } + + filteredReport := api.ProcessReport{} + + 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 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) + } + } + } + + 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 diff --git a/http/handler/api/restream_test.go b/http/handler/api/restream_test.go index a2964365..87c20ffb 100644 --- a/http/handler/api/restream_test.go +++ b/http/handler/api/restream_test.go @@ -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) { diff --git a/http/server.go b/http/server.go index 033d42f9..007368de 100644 --- a/http/server.go +++ b/http/server.go @@ -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)