draft
This commit is contained in:
87
internal/modules/jobs/handler.go
Normal file
87
internal/modules/jobs/handler.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
appv1 "stream.api/internal/gen/proto/app/v1"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
module *Module
|
||||
}
|
||||
|
||||
func NewHandler(module *Module) *Handler { return &Handler{module: module} }
|
||||
|
||||
func (h *Handler) ListAdminJobs(ctx context.Context, req *appv1.ListAdminJobsRequest) (*appv1.ListAdminJobsResponse, error) {
|
||||
useCursorPagination := req.Cursor != nil || int(req.GetPageSize()) > 0
|
||||
result, err := h.module.ListAdminJobs(ctx, ListAdminJobsQuery{AgentID: strings.TrimSpace(req.GetAgentId()), Offset: int(req.GetOffset()), Limit: int(req.GetLimit()), Cursor: req.Cursor, PageSize: int(req.GetPageSize()), UseCursorPagination: useCursorPagination})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentListAdminJobsResponse(result), nil
|
||||
}
|
||||
|
||||
func (h *Handler) GetAdminJob(ctx context.Context, req *appv1.GetAdminJobRequest) (*appv1.GetAdminJobResponse, error) {
|
||||
job, err := h.module.GetAdminJob(ctx, GetAdminJobQuery{ID: strings.TrimSpace(req.GetId())})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentGetAdminJobResponse(job), nil
|
||||
}
|
||||
|
||||
func (h *Handler) GetAdminJobLogs(ctx context.Context, req *appv1.GetAdminJobLogsRequest) (*appv1.GetAdminJobLogsResponse, error) {
|
||||
response, err := h.GetAdminJob(ctx, &appv1.GetAdminJobRequest{Id: req.GetId()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &appv1.GetAdminJobLogsResponse{Logs: response.GetJob().GetLogs()}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) CreateAdminJob(ctx context.Context, req *appv1.CreateAdminJobRequest) (*appv1.CreateAdminJobResponse, error) {
|
||||
job, err := h.module.CreateAdminJob(ctx, CreateAdminJobCommand{Command: strings.TrimSpace(req.GetCommand()), Image: strings.TrimSpace(req.GetImage()), Name: strings.TrimSpace(req.GetName()), UserID: strings.TrimSpace(req.GetUserId()), VideoID: req.VideoId, Env: req.GetEnv(), Priority: int(req.GetPriority()), TimeLimit: req.GetTimeLimit()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentCreateAdminJobResponse(job), nil
|
||||
}
|
||||
|
||||
func (h *Handler) CancelAdminJob(ctx context.Context, req *appv1.CancelAdminJobRequest) (*appv1.CancelAdminJobResponse, error) {
|
||||
result, err := h.module.CancelAdminJob(ctx, CancelAdminJobCommand{ID: strings.TrimSpace(req.GetId())})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentCancelAdminJobResponse(result), nil
|
||||
}
|
||||
|
||||
func (h *Handler) RetryAdminJob(ctx context.Context, req *appv1.RetryAdminJobRequest) (*appv1.RetryAdminJobResponse, error) {
|
||||
job, err := h.module.RetryAdminJob(ctx, RetryAdminJobCommand{ID: strings.TrimSpace(req.GetId())})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentRetryAdminJobResponse(job), nil
|
||||
}
|
||||
|
||||
func (h *Handler) ListAdminAgents(ctx context.Context, _ *appv1.ListAdminAgentsRequest) (*appv1.ListAdminAgentsResponse, error) {
|
||||
items, err := h.module.ListAdminAgents(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentListAdminAgentsResponse(items), nil
|
||||
}
|
||||
|
||||
func (h *Handler) RestartAdminAgent(ctx context.Context, req *appv1.RestartAdminAgentRequest) (*appv1.AdminAgentCommandResponse, error) {
|
||||
statusValue, err := h.module.RestartAdminAgent(ctx, AgentCommand{ID: strings.TrimSpace(req.GetId()), Command: "restart", Success: "restart command sent"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentAgentCommandResponse(statusValue), nil
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateAdminAgent(ctx context.Context, req *appv1.UpdateAdminAgentRequest) (*appv1.AdminAgentCommandResponse, error) {
|
||||
statusValue, err := h.module.UpdateAdminAgent(ctx, AgentCommand{ID: strings.TrimSpace(req.GetId()), Command: "update", Success: "update command sent"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentAgentCommandResponse(statusValue), nil
|
||||
}
|
||||
184
internal/modules/jobs/module.go
Normal file
184
internal/modules/jobs/module.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"gorm.io/gorm"
|
||||
"stream.api/internal/modules/common"
|
||||
videodomain "stream.api/internal/video"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
runtime *common.Runtime
|
||||
}
|
||||
|
||||
func New(runtime *common.Runtime) *Module {
|
||||
return &Module{runtime: runtime}
|
||||
}
|
||||
|
||||
func (m *Module) ListAdminJobs(ctx context.Context, queryValue ListAdminJobsQuery) (*ListAdminJobsResult, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoService := m.runtime.VideoService()
|
||||
if videoService == nil {
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
}
|
||||
var (
|
||||
result *videodomain.PaginatedJobs
|
||||
err error
|
||||
)
|
||||
cursor := ""
|
||||
if queryValue.Cursor != nil {
|
||||
cursor = *queryValue.Cursor
|
||||
}
|
||||
if queryValue.UseCursorPagination {
|
||||
result, err = videoService.ListJobsByCursor(ctx, queryValue.AgentID, cursor, queryValue.PageSize)
|
||||
} else if queryValue.AgentID != "" {
|
||||
result, err = videoService.ListJobsByAgent(ctx, queryValue.AgentID, queryValue.Offset, queryValue.Limit)
|
||||
} else {
|
||||
result, err = videoService.ListJobs(ctx, queryValue.Offset, queryValue.Limit)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, videodomain.ErrInvalidJobCursor) {
|
||||
return nil, status.Error(codes.InvalidArgument, "Invalid job cursor")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "Failed to list jobs")
|
||||
}
|
||||
var nextCursor *string
|
||||
if strings.TrimSpace(result.NextCursor) != "" {
|
||||
value := result.NextCursor
|
||||
nextCursor = &value
|
||||
}
|
||||
return &ListAdminJobsResult{Jobs: result.Jobs, Total: result.Total, Offset: result.Offset, Limit: result.Limit, HasMore: result.HasMore, PageSize: result.PageSize, NextCursor: nextCursor}, nil
|
||||
}
|
||||
|
||||
func (m *Module) GetAdminJob(ctx context.Context, queryValue GetAdminJobQuery) (*videodomain.Job, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoService := m.runtime.VideoService()
|
||||
if videoService == nil {
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
}
|
||||
if queryValue.ID == "" {
|
||||
return nil, status.Error(codes.NotFound, "Job not found")
|
||||
}
|
||||
job, err := videoService.GetJob(ctx, queryValue.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Error(codes.NotFound, "Job not found")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "Failed to load job")
|
||||
}
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func (m *Module) CreateAdminJob(ctx context.Context, cmd CreateAdminJobCommand) (*videodomain.Job, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoService := m.runtime.VideoService()
|
||||
if videoService == nil {
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
}
|
||||
if cmd.Command == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "Command is required")
|
||||
}
|
||||
image := strings.TrimSpace(cmd.Image)
|
||||
if image == "" {
|
||||
image = "alpine"
|
||||
}
|
||||
name := strings.TrimSpace(cmd.Name)
|
||||
if name == "" {
|
||||
name = cmd.Command
|
||||
}
|
||||
payload, err := json.Marshal(map[string]any{"image": image, "commands": []string{cmd.Command}, "environment": cmd.Env})
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to create job payload")
|
||||
}
|
||||
videoID := ""
|
||||
if cmd.VideoID != nil {
|
||||
videoID = strings.TrimSpace(*cmd.VideoID)
|
||||
}
|
||||
job, err := videoService.CreateJob(ctx, strings.TrimSpace(cmd.UserID), videoID, name, payload, cmd.Priority, cmd.TimeLimit)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to create job")
|
||||
}
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func (m *Module) CancelAdminJob(ctx context.Context, cmd CancelAdminJobCommand) (*CancelAdminJobResult, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoService := m.runtime.VideoService()
|
||||
if videoService == nil {
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
}
|
||||
if cmd.ID == "" {
|
||||
return nil, status.Error(codes.NotFound, "Job not found")
|
||||
}
|
||||
if err := videoService.CancelJob(ctx, cmd.ID); err != nil {
|
||||
if strings.Contains(strings.ToLower(err.Error()), "not found") {
|
||||
return nil, status.Error(codes.NotFound, "Job not found")
|
||||
}
|
||||
return nil, status.Error(codes.FailedPrecondition, err.Error())
|
||||
}
|
||||
return &CancelAdminJobResult{Status: "cancelled", JobID: cmd.ID}, nil
|
||||
}
|
||||
|
||||
func (m *Module) RetryAdminJob(ctx context.Context, cmd RetryAdminJobCommand) (*videodomain.Job, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoService := m.runtime.VideoService()
|
||||
if videoService == nil {
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
}
|
||||
if cmd.ID == "" {
|
||||
return nil, status.Error(codes.NotFound, "Job not found")
|
||||
}
|
||||
job, err := videoService.RetryJob(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
if strings.Contains(strings.ToLower(err.Error()), "not found") {
|
||||
return nil, status.Error(codes.NotFound, "Job not found")
|
||||
}
|
||||
return nil, status.Error(codes.FailedPrecondition, err.Error())
|
||||
}
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func (m *Module) ListAdminAgents(ctx context.Context) ([]*videodomain.AgentWithStats, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agentRuntime := m.runtime.AgentRuntime()
|
||||
if agentRuntime == nil {
|
||||
return nil, status.Error(codes.Unavailable, "Agent runtime is unavailable")
|
||||
}
|
||||
return agentRuntime.ListAgentsWithStats(), nil
|
||||
}
|
||||
|
||||
func (m *Module) RestartAdminAgent(ctx context.Context, cmd AgentCommand) (string, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
agentRuntime := m.runtime.AgentRuntime()
|
||||
if agentRuntime == nil {
|
||||
return "", status.Error(codes.Unavailable, "Agent runtime is unavailable")
|
||||
}
|
||||
if !agentRuntime.SendCommand(strings.TrimSpace(cmd.ID), cmd.Command) {
|
||||
return "", status.Error(codes.Unavailable, "Agent not active or command channel full")
|
||||
}
|
||||
return cmd.Success, nil
|
||||
}
|
||||
|
||||
func (m *Module) UpdateAdminAgent(ctx context.Context, cmd AgentCommand) (string, error) {
|
||||
return m.RestartAdminAgent(ctx, cmd)
|
||||
}
|
||||
54
internal/modules/jobs/presenter.go
Normal file
54
internal/modules/jobs/presenter.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
appv1 "stream.api/internal/gen/proto/app/v1"
|
||||
"stream.api/internal/modules/common"
|
||||
videodomain "stream.api/internal/video"
|
||||
)
|
||||
|
||||
func presentListAdminJobsResponse(result *ListAdminJobsResult) *appv1.ListAdminJobsResponse {
|
||||
jobs := make([]*appv1.AdminJob, 0, len(result.Jobs))
|
||||
for _, job := range result.Jobs {
|
||||
jobs = append(jobs, common.BuildAdminJob(job))
|
||||
}
|
||||
response := &appv1.ListAdminJobsResponse{
|
||||
Jobs: jobs,
|
||||
Total: result.Total,
|
||||
Offset: int32(result.Offset),
|
||||
Limit: int32(result.Limit),
|
||||
HasMore: result.HasMore,
|
||||
PageSize: int32(result.PageSize),
|
||||
}
|
||||
if result.NextCursor != nil {
|
||||
response.NextCursor = result.NextCursor
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func presentGetAdminJobResponse(job *videodomain.Job) *appv1.GetAdminJobResponse {
|
||||
return &appv1.GetAdminJobResponse{Job: common.BuildAdminJob(job)}
|
||||
}
|
||||
|
||||
func presentCreateAdminJobResponse(job *videodomain.Job) *appv1.CreateAdminJobResponse {
|
||||
return &appv1.CreateAdminJobResponse{Job: common.BuildAdminJob(job)}
|
||||
}
|
||||
|
||||
func presentCancelAdminJobResponse(result *CancelAdminJobResult) *appv1.CancelAdminJobResponse {
|
||||
return &appv1.CancelAdminJobResponse{Status: result.Status, JobId: result.JobID}
|
||||
}
|
||||
|
||||
func presentRetryAdminJobResponse(job *videodomain.Job) *appv1.RetryAdminJobResponse {
|
||||
return &appv1.RetryAdminJobResponse{Job: common.BuildAdminJob(job)}
|
||||
}
|
||||
|
||||
func presentListAdminAgentsResponse(items []*videodomain.AgentWithStats) *appv1.ListAdminAgentsResponse {
|
||||
agents := make([]*appv1.AdminAgent, 0, len(items))
|
||||
for _, item := range items {
|
||||
agents = append(agents, common.BuildAdminAgent(item))
|
||||
}
|
||||
return &appv1.ListAdminAgentsResponse{Agents: agents}
|
||||
}
|
||||
|
||||
func presentAgentCommandResponse(status string) *appv1.AdminAgentCommandResponse {
|
||||
return &appv1.AdminAgentCommandResponse{Status: status}
|
||||
}
|
||||
56
internal/modules/jobs/types.go
Normal file
56
internal/modules/jobs/types.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package jobs
|
||||
|
||||
import videodomain "stream.api/internal/video"
|
||||
|
||||
type ListAdminJobsQuery struct {
|
||||
AgentID string
|
||||
Offset int
|
||||
Limit int
|
||||
Cursor *string
|
||||
PageSize int
|
||||
UseCursorPagination bool
|
||||
}
|
||||
|
||||
type ListAdminJobsResult struct {
|
||||
Jobs []*videodomain.Job
|
||||
Total int64
|
||||
Offset int
|
||||
Limit int
|
||||
HasMore bool
|
||||
PageSize int
|
||||
NextCursor *string
|
||||
}
|
||||
|
||||
type GetAdminJobQuery struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type CreateAdminJobCommand struct {
|
||||
Command string
|
||||
Image string
|
||||
Name string
|
||||
UserID string
|
||||
VideoID *string
|
||||
Env map[string]string
|
||||
Priority int
|
||||
TimeLimit int64
|
||||
}
|
||||
|
||||
type CancelAdminJobCommand struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type CancelAdminJobResult struct {
|
||||
Status string
|
||||
JobID string
|
||||
}
|
||||
|
||||
type RetryAdminJobCommand struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type AgentCommand struct {
|
||||
ID string
|
||||
Command string
|
||||
Success string
|
||||
}
|
||||
Reference in New Issue
Block a user