New JobQueue worker
This commit is contained in:
@ -8,6 +8,7 @@ import (
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/jobqueue"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/model"
|
||||
)
|
||||
@ -172,3 +173,25 @@ func GetByStatus(status string) ([]Model, error) {
|
||||
|
||||
return models, err
|
||||
}
|
||||
|
||||
// AddPendingJobs is intended to be used at startup to add
|
||||
// anything pending to the JobQueue just once, based on
|
||||
// the database row status
|
||||
func AddPendingJobs() {
|
||||
rows, err := GetByStatus(StatusReady)
|
||||
if err != nil {
|
||||
logger.Error("AddPendingJobsError", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
logger.Debug("Adding RequestCertificate job: %+v", row)
|
||||
err := jobqueue.AddJob(jobqueue.Job{
|
||||
Name: "RequestCertificate",
|
||||
Action: row.Request,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("AddPendingJobsError", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ func create(host *Model) (int, error) {
|
||||
paths,
|
||||
upstream_options,
|
||||
advanced_config,
|
||||
status,
|
||||
error_message,
|
||||
is_disabled,
|
||||
is_deleted
|
||||
) VALUES (
|
||||
@ -73,6 +75,8 @@ func create(host *Model) (int, error) {
|
||||
:paths,
|
||||
:upstream_options,
|
||||
:advanced_config,
|
||||
:status,
|
||||
:error_message,
|
||||
:is_disabled,
|
||||
:is_deleted
|
||||
)`, host)
|
||||
@ -86,6 +90,8 @@ func create(host *Model) (int, error) {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
logger.Debug("Created Host: %+v", host)
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
@ -120,10 +126,14 @@ func update(host *Model) error {
|
||||
paths = :paths,
|
||||
upstream_options = :upstream_options,
|
||||
advanced_config = :advanced_config,
|
||||
status = :status,
|
||||
error_message = :error_message,
|
||||
is_disabled = :is_disabled,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, host)
|
||||
|
||||
logger.Debug("Updated Host: %+v", host)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@ -181,3 +191,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AddPendingJobs is intended to be used at startup to add
|
||||
// anything pending to the JobQueue just once, based on
|
||||
// the database row status
|
||||
func AddPendingJobs() {
|
||||
// todo
|
||||
}
|
||||
|
@ -20,6 +20,12 @@ const (
|
||||
RedirectionHostType = "redirection"
|
||||
// DeadHostType is self explanatory
|
||||
DeadHostType = "dead"
|
||||
// StatusReady means a host is ready to configure
|
||||
StatusReady = "ready"
|
||||
// StatusOK means a host is configured within Nginx
|
||||
StatusOK = "ok"
|
||||
// StatusError is self explanatory
|
||||
StatusError = "error"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
@ -45,6 +51,8 @@ type Model struct {
|
||||
Paths string `json:"paths" db:"paths" filter:"paths,string"`
|
||||
UpstreamOptions string `json:"upstream_options" db:"upstream_options" filter:"upstream_options,string"`
|
||||
AdvancedConfig string `json:"advanced_config" db:"advanced_config" filter:"advanced_config,string"`
|
||||
Status string `json:"status" db:"status" filter:"status,string"`
|
||||
ErrorMessage string `json:"error_message" db:"error_message" filter:"error_message,string"`
|
||||
IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
// Expansions
|
||||
@ -81,6 +89,9 @@ func (m *Model) Save() error {
|
||||
return fmt.Errorf("User ID must be specified")
|
||||
}
|
||||
|
||||
// Set this host as requiring reconfiguration
|
||||
m.Status = StatusReady
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = create(m)
|
||||
} else {
|
||||
|
46
backend/internal/jobqueue/main.go
Normal file
46
backend/internal/jobqueue/main.go
Normal file
@ -0,0 +1,46 @@
|
||||
package jobqueue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
worker *Worker
|
||||
)
|
||||
|
||||
// Start ...
|
||||
func Start() {
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
q := &Queue{
|
||||
jobs: make(chan Job),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
// Defines a queue worker, which will execute our queue.
|
||||
worker = newWorker(q)
|
||||
|
||||
// Execute jobs in queue.
|
||||
go worker.doWork()
|
||||
}
|
||||
|
||||
// AddJob adds a job to the queue for processing
|
||||
func AddJob(j Job) error {
|
||||
if worker == nil {
|
||||
return errors.New("Unable to add job, jobqueue has not been started")
|
||||
}
|
||||
worker.Queue.AddJob(j)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown ...
|
||||
func Shutdown() error {
|
||||
if cancel == nil {
|
||||
return errors.New("Unable to shutdown, jobqueue has not been started")
|
||||
}
|
||||
cancel()
|
||||
return nil
|
||||
}
|
58
backend/internal/jobqueue/models.go
Normal file
58
backend/internal/jobqueue/models.go
Normal file
@ -0,0 +1,58 @@
|
||||
package jobqueue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Queue holds name, list of jobs and context with cancel.
|
||||
type Queue struct {
|
||||
jobs chan Job
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// Job - holds logic to perform some operations during queue execution.
|
||||
type Job struct {
|
||||
Name string
|
||||
Action func() error // A function that should be executed when the job is running.
|
||||
}
|
||||
|
||||
// AddJobs adds jobs to the queue and cancels channel.
|
||||
func (q *Queue) AddJobs(jobs []Job) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(jobs))
|
||||
|
||||
for _, job := range jobs {
|
||||
// Goroutine which adds job to the queue.
|
||||
go func(job Job) {
|
||||
q.AddJob(job)
|
||||
wg.Done()
|
||||
}(job)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
// Cancel queue channel, when all goroutines were done.
|
||||
q.cancel()
|
||||
}()
|
||||
}
|
||||
|
||||
// AddJob sends job to the channel.
|
||||
func (q *Queue) AddJob(job Job) {
|
||||
q.jobs <- job
|
||||
log.Printf("New job %s added to queue", job.Name)
|
||||
}
|
||||
|
||||
// Run performs job execution.
|
||||
func (j Job) Run() error {
|
||||
log.Printf("Job running: %s", j.Name)
|
||||
|
||||
err := j.Action()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
36
backend/internal/jobqueue/worker.go
Normal file
36
backend/internal/jobqueue/worker.go
Normal file
@ -0,0 +1,36 @@
|
||||
package jobqueue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"npm/internal/logger"
|
||||
)
|
||||
|
||||
// Worker responsible for queue serving.
|
||||
type Worker struct {
|
||||
Queue *Queue
|
||||
}
|
||||
|
||||
func newWorker(queue *Queue) *Worker {
|
||||
return &Worker{
|
||||
Queue: queue,
|
||||
}
|
||||
}
|
||||
|
||||
// doWork processes jobs from the queue (jobs channel).
|
||||
func (w *Worker) doWork() bool {
|
||||
for {
|
||||
select {
|
||||
// if context was canceled.
|
||||
case <-w.Queue.ctx.Done():
|
||||
logger.Info("JobQueue worker graceful shutdown")
|
||||
return true
|
||||
// if job received.
|
||||
case job := <-w.Queue.jobs:
|
||||
err := job.Run()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%sError", job.Name), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// AppState holds pointers to channels and waitGroups
|
||||
// shared by all goroutines of the application
|
||||
type AppState struct {
|
||||
waitGroup sync.WaitGroup
|
||||
termSig chan bool
|
||||
}
|
||||
|
||||
// NewState creates a new app state
|
||||
func NewState() *AppState {
|
||||
state := &AppState{
|
||||
// buffered channel
|
||||
termSig: make(chan bool, 1),
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// GetWaitGroup returns the state's wg
|
||||
func (state *AppState) GetWaitGroup() *sync.WaitGroup {
|
||||
return &state.waitGroup
|
||||
}
|
||||
|
||||
// GetTermSig returns the state's term signal
|
||||
func (state *AppState) GetTermSig() chan bool {
|
||||
return state.termSig
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"npm/internal/entity/certificate"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/state"
|
||||
)
|
||||
|
||||
type certificateWorker struct {
|
||||
state *state.AppState
|
||||
}
|
||||
|
||||
// StartCertificateWorker starts the CertificateWorker
|
||||
func StartCertificateWorker(state *state.AppState) {
|
||||
worker := newCertificateWorker(state)
|
||||
logger.Info("CertificateWorker Started")
|
||||
worker.Run()
|
||||
}
|
||||
|
||||
func newCertificateWorker(state *state.AppState) *certificateWorker {
|
||||
return &certificateWorker{
|
||||
state: state,
|
||||
}
|
||||
}
|
||||
|
||||
// Run the CertificateWorker
|
||||
func (w *certificateWorker) Run() {
|
||||
// global wait group
|
||||
gwg := w.state.GetWaitGroup()
|
||||
gwg.Add(1)
|
||||
|
||||
ticker := time.NewTicker(15 * time.Second)
|
||||
mainLoop:
|
||||
for {
|
||||
select {
|
||||
case _, more := <-w.state.GetTermSig():
|
||||
if !more {
|
||||
logger.Info("Terminating CertificateWorker ... ")
|
||||
break mainLoop
|
||||
}
|
||||
case <-ticker.C:
|
||||
// Can confirm that this will wait for completion before the next loop
|
||||
requestCertificates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func requestCertificates() {
|
||||
// logger.Debug("requestCertificates fired")
|
||||
rows, err := certificate.GetByStatus(certificate.StatusReady)
|
||||
if err != nil {
|
||||
logger.Error("requestCertificatesError", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
if err := row.Request(); err != nil {
|
||||
logger.Error("CertificateRequestError", err)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user