Ported lots of stuff from gitlab.com/dr.sybren/flamenco-worker-go
Much isn't working though.
This commit is contained in:
parent
28a56f3d91
commit
c501899185
60
cmd/flamenco-worker-poc/cliargs.go
Normal file
60
cmd/flamenco-worker-poc/cliargs.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
/* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* Original Code Copyright (C) 2022 Blender Foundation.
|
||||
*
|
||||
* This file is part of Flamenco.
|
||||
*
|
||||
* Flamenco is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"net/url"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/blender/flamenco-ng-poc/internal/worker"
|
||||
)
|
||||
|
||||
var errURLWithoutHostName = errors.New("manager URL should contain a host name")
|
||||
|
||||
var cliArgs struct {
|
||||
version bool
|
||||
verbose bool
|
||||
debug bool
|
||||
managerURL *url.URL
|
||||
manager string
|
||||
register bool
|
||||
}
|
||||
|
||||
func parseCliArgs() {
|
||||
flag.BoolVar(&cliArgs.version, "version", false, "Shows the application version, then exits.")
|
||||
flag.BoolVar(&cliArgs.verbose, "verbose", false, "Enable info-level logging.")
|
||||
flag.BoolVar(&cliArgs.debug, "debug", false, "Enable debug-level logging.")
|
||||
|
||||
flag.StringVar(&cliArgs.manager, "manager", "", "URL of the Flamenco Manager.")
|
||||
flag.BoolVar(&cliArgs.register, "register", false, "(Re-)register at the Manager.")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if cliArgs.manager != "" {
|
||||
var err error
|
||||
cliArgs.managerURL, err = worker.ParseURL(cliArgs.manager)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("invalid manager URL")
|
||||
}
|
||||
}
|
||||
}
|
@ -22,76 +22,59 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/deepmap/oapi-codegen/pkg/securityprovider"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"gitlab.com/blender/flamenco-ng-poc/internal/appinfo"
|
||||
"gitlab.com/blender/flamenco-ng-poc/internal/worker"
|
||||
"gitlab.com/blender/flamenco-ng-poc/internal/worker/ssdp"
|
||||
"gitlab.com/blender/flamenco-ng-poc/pkg/api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
parseCliArgs()
|
||||
if cliArgs.version {
|
||||
fmt.Println(appinfo.ApplicationVersion)
|
||||
return
|
||||
}
|
||||
|
||||
output := zerolog.ConsoleWriter{Out: colorable.NewColorableStdout(), TimeFormat: time.RFC3339}
|
||||
log.Logger = log.Output(output)
|
||||
|
||||
log.Info().Str("version", appinfo.ApplicationVersion).Msgf("starting %v Worker", appinfo.ApplicationName)
|
||||
|
||||
basicAuthProvider, err := securityprovider.NewSecurityProviderBasicAuth("MY_USER", "MY_PASS")
|
||||
if err != nil {
|
||||
log.Panic().Err(err).Msg("unable to create basic authr")
|
||||
}
|
||||
// configWrangler := worker.NewConfigWrangler()
|
||||
managerFinder := ssdp.NewManagerFinder(cliArgs.managerURL)
|
||||
// taskRunner := struct{}{}
|
||||
findManager(managerFinder)
|
||||
|
||||
flamenco, err := api.NewClientWithResponses(
|
||||
"http://localhost:8080/",
|
||||
api.WithRequestEditorFn(basicAuthProvider.Intercept),
|
||||
api.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
|
||||
req.Header.Set("User-Agent", appinfo.UserAgent())
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error creating client")
|
||||
}
|
||||
// basicAuthProvider, err := securityprovider.NewSecurityProviderBasicAuth("MY_USER", "MY_PASS")
|
||||
// if err != nil {
|
||||
// log.Panic().Err(err).Msg("unable to create basic authr")
|
||||
// }
|
||||
|
||||
ctx := context.Background()
|
||||
registerWorker(ctx, flamenco)
|
||||
obtainTask(ctx, flamenco)
|
||||
}
|
||||
// flamenco, err := api.NewClientWithResponses(
|
||||
// "http://localhost:8080/",
|
||||
// api.WithRequestEditorFn(basicAuthProvider.Intercept),
|
||||
// api.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
|
||||
// req.Header.Set("User-Agent", appinfo.UserAgent())
|
||||
// return nil
|
||||
// }),
|
||||
// )
|
||||
// if err != nil {
|
||||
// log.Fatal().Err(err).Msg("error creating client")
|
||||
// }
|
||||
|
||||
func registerWorker(ctx context.Context, flamenco *api.ClientWithResponses) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error getting hostname")
|
||||
}
|
||||
|
||||
req := api.RegisterWorkerJSONRequestBody{
|
||||
Nickname: hostname,
|
||||
Platform: runtime.GOOS,
|
||||
Secret: "secret",
|
||||
SupportedTaskTypes: []string{"sleep", "blender-render", "ffmpeg", "file-management"},
|
||||
}
|
||||
resp, err := flamenco.RegisterWorkerWithResponse(ctx, req)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error registering at Manager")
|
||||
}
|
||||
switch {
|
||||
case resp.JSON200 != nil:
|
||||
log.Info().
|
||||
Int("code", resp.StatusCode()).
|
||||
Interface("resp", resp.JSON200).
|
||||
Msg("registered at Manager")
|
||||
default:
|
||||
log.Fatal().
|
||||
Int("code", resp.StatusCode()).
|
||||
Interface("resp", resp.JSONDefault).
|
||||
Msg("unable to register at Manager")
|
||||
}
|
||||
// w := worker.NewWorker(flamenco, configWrangler, managerFinder, taskRunner)
|
||||
// ctx := context.Background()
|
||||
// registerWorker(ctx, flamenco)
|
||||
// obtainTask(ctx, flamenco)
|
||||
}
|
||||
|
||||
func obtainTask(ctx context.Context, flamenco *api.ClientWithResponses) {
|
||||
@ -118,3 +101,16 @@ func obtainTask(ctx context.Context, flamenco *api.ClientWithResponses) {
|
||||
Msg("unable to obtain task")
|
||||
}
|
||||
}
|
||||
|
||||
func findManager(managerFinder worker.ManagerFinder) *url.URL {
|
||||
finder := managerFinder.FindFlamencoManager()
|
||||
select {
|
||||
case manager := <-finder:
|
||||
log.Info().Str("manager", manager.String()).Msg("found Manager")
|
||||
return manager
|
||||
case <-time.After(10 * time.Second):
|
||||
log.Fatal().Msg("unable to autodetect Flamenco Manager via UPnP/SSDP; configure the URL explicitly")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -19,7 +19,9 @@ require (
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/ziflex/lecho/v3 v3.1.0
|
||||
gitlab.com/blender-institute/gossdp v0.0.0-20181214124559-074ccf115d76
|
||||
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gorm.io/driver/postgres v1.0.8
|
||||
gorm.io/gorm v1.21.4
|
||||
)
|
||||
|
2
go.sum
2
go.sum
@ -227,6 +227,8 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziflex/lecho/v3 v3.1.0 h1:65bSzSc0yw7EEhi44lMnkOI877ZzbE7tGDWfYCQXZwI=
|
||||
github.com/ziflex/lecho/v3 v3.1.0/go.mod h1:dwQ6xCAKmSBHhwZ6XmiAiDptD7iklVkW7xQYGUncX0Q=
|
||||
gitlab.com/blender-institute/gossdp v0.0.0-20181214124559-074ccf115d76 h1:ASbeHgntCaY+Q/qRUX1y6T12WncACelKVRUFGjyIOVM=
|
||||
gitlab.com/blender-institute/gossdp v0.0.0-20181214124559-074ccf115d76/go.mod h1:+j3oHEe07Rw8lFbVhESVy83XVW51AndFrjbUMb2JI4k=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
|
@ -35,10 +35,7 @@ import (
|
||||
// RegisterWorker registers a new worker and stores it in the database.
|
||||
func (f *Flamenco) RegisterWorker(e echo.Context) error {
|
||||
remoteIP := e.RealIP()
|
||||
|
||||
logger := log.With().
|
||||
Str("ip", remoteIP).
|
||||
Logger()
|
||||
logger := log.With().Str("ip", remoteIP).Logger()
|
||||
|
||||
var req api.RegisterWorkerJSONBody
|
||||
err := e.Bind(&req)
|
||||
@ -47,11 +44,14 @@ func (f *Flamenco) RegisterWorker(e echo.Context) error {
|
||||
return sendAPIError(e, http.StatusBadRequest, "invalid format")
|
||||
}
|
||||
|
||||
// TODO: validate the request, should at least have non-empty name, secret, and platform.
|
||||
|
||||
logger.Info().Str("nickname", req.Nickname).Msg("registering new worker")
|
||||
|
||||
dbWorker := persistence.Worker{
|
||||
UUID: uuid.New().String(),
|
||||
Name: req.Nickname,
|
||||
Secret: req.Secret,
|
||||
Platform: req.Platform,
|
||||
Address: remoteIP,
|
||||
SupportedTaskTypes: strings.Join(req.SupportedTaskTypes, ","),
|
||||
@ -73,6 +73,25 @@ func (f *Flamenco) RegisterWorker(e echo.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (f *Flamenco) SignOn(e echo.Context) error {
|
||||
remoteIP := e.RealIP()
|
||||
logger := log.With().Str("ip", remoteIP).Logger()
|
||||
|
||||
var req api.SignOnJSONBody
|
||||
err := e.Bind(&req)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("bad request received")
|
||||
return sendAPIError(e, http.StatusBadRequest, "invalid format")
|
||||
}
|
||||
|
||||
logger.Info().Str("nickname", req.Nickname).Msg("worker signing on")
|
||||
|
||||
return e.JSON(http.StatusOK, &api.WorkerStateChange{
|
||||
// TODO: look up proper status in DB.
|
||||
StatusRequested: api.WorkerStatusAwake,
|
||||
})
|
||||
}
|
||||
|
||||
func (f *Flamenco) ScheduleTask(e echo.Context) error {
|
||||
return e.JSON(http.StatusOK, &api.AssignedTask{
|
||||
Uuid: uuid.New().String(),
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
type Worker struct {
|
||||
gorm.Model
|
||||
UUID string `gorm:"type:char(36);not null;unique;index"`
|
||||
Secret string `gorm:"type:varchar(255);not null"`
|
||||
Name string `gorm:"type:varchar(64);not null"`
|
||||
|
||||
Address string `gorm:"type:varchar(39);not null;index"` // 39 = max length of IPv6 address.
|
||||
|
167
internal/worker/config.go
Normal file
167
internal/worker/config.go
Normal file
@ -0,0 +1,167 @@
|
||||
package worker
|
||||
|
||||
/* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* Original Code Copyright (C) 2022 Blender Foundation.
|
||||
*
|
||||
* This file is part of Flamenco.
|
||||
*
|
||||
* Flamenco is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
errURLWithoutHostName = errors.New("manager URL should contain a host name")
|
||||
)
|
||||
|
||||
// WorkerConfig represents the configuration of a single worker.
|
||||
// It does not include authentication credentials.
|
||||
type WorkerConfig struct {
|
||||
Manager string `yaml:"manager_url"`
|
||||
TaskTypes []string `yaml:"task_types"`
|
||||
}
|
||||
|
||||
type workerCredentials struct {
|
||||
WorkerID string `yaml:"worker_id"`
|
||||
Secret string `yaml:"worker_secret"`
|
||||
}
|
||||
|
||||
// ConfigWrangler makes it simple to load and write configuration files.
|
||||
type ConfigWrangler interface {
|
||||
DefaultConfig() WorkerConfig
|
||||
WriteConfig(filename string, filetype string, config interface{}) error
|
||||
LoadConfig(filename string, config interface{}) error
|
||||
}
|
||||
|
||||
// FileConfigWrangler is the default config wrangler that actually reads & writes files.
|
||||
type FileConfigWrangler struct{}
|
||||
|
||||
// NewConfigWrangler returns a new ConfigWrangler instance of the default type FileConfigWrangler.
|
||||
func NewConfigWrangler() ConfigWrangler {
|
||||
return FileConfigWrangler{}
|
||||
}
|
||||
|
||||
// DefaultConfig returns a fairly sane default configuration.
|
||||
func (fcw FileConfigWrangler) DefaultConfig() WorkerConfig {
|
||||
return WorkerConfig{
|
||||
Manager: "",
|
||||
TaskTypes: []string{"sleep", "blender-render", "file-management", "exr-merge", "debug"},
|
||||
}
|
||||
}
|
||||
|
||||
// WriteConfig stores a struct as YAML file.
|
||||
func (fcw FileConfigWrangler) WriteConfig(filename string, filetype string, config interface{}) error {
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tempFilename := filename + "~"
|
||||
f, err := os.OpenFile(tempFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(f, "# %s file for Flamenco Worker.\n", filetype)
|
||||
fmt.Fprintln(f, "# For an explanation of the fields, refer to flamenco-worker-example.yaml")
|
||||
fmt.Fprintln(f, "#")
|
||||
fmt.Fprintln(f, "# NOTE: this file can be overwritten by Flamenco Worker.")
|
||||
fmt.Fprintln(f, "#")
|
||||
now := time.Now()
|
||||
fmt.Fprintf(f, "# This file was written on %s\n\n", now.Format("2006-01-02 15:04:05 -07:00"))
|
||||
|
||||
n, err := f.Write(data)
|
||||
if err != nil {
|
||||
f.Close() // ignore errors here
|
||||
return err
|
||||
}
|
||||
if n < len(data) {
|
||||
f.Close() // ignore errors here
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug().Str("filename", tempFilename).Msg("config file written")
|
||||
log.Debug().
|
||||
Str("from", tempFilename).
|
||||
Str("to", filename).
|
||||
Msg("renaming config file")
|
||||
if err := os.Rename(tempFilename, filename); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Str("filename", filename).Msg("Saved configuration file")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadConfig loads a YAML configuration file into 'config'
|
||||
func (fcw FileConfigWrangler) LoadConfig(filename string, config interface{}) error {
|
||||
log.Debug().Str("filename", filename).Msg("loading config file")
|
||||
f, err := os.OpenFile(filename, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dec := yaml.NewDecoder(f)
|
||||
if err = dec.Decode(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseURL allows URLs without scheme (assumes HTTP).
|
||||
func ParseURL(rawURL string) (*url.URL, error) {
|
||||
var err error
|
||||
var parsedURL *url.URL
|
||||
|
||||
parsedURL, err = url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// url.Parse() is a bit weird when there is no scheme.
|
||||
if parsedURL.Host == "" && parsedURL.Path != "" {
|
||||
// This case happens when you just enter a hostname, like manager='thehost'
|
||||
parsedURL.Host = parsedURL.Path
|
||||
parsedURL.Path = "/"
|
||||
}
|
||||
if parsedURL.Host == "" && parsedURL.Scheme != "" && parsedURL.Opaque != "" {
|
||||
// This case happens when you just enter a hostname:port, like manager='thehost:8083'
|
||||
parsedURL.Host = parsedURL.Scheme + ":" + parsedURL.Opaque
|
||||
parsedURL.Opaque = ""
|
||||
parsedURL.Scheme = "http"
|
||||
}
|
||||
if parsedURL.Scheme == "" {
|
||||
parsedURL.Scheme = "http"
|
||||
}
|
||||
if parsedURL.Host == "" {
|
||||
return nil, errURLWithoutHostName
|
||||
}
|
||||
|
||||
return parsedURL, nil
|
||||
}
|
39
internal/worker/config_test.go
Normal file
39
internal/worker/config_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package worker
|
||||
|
||||
/* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* Original Code Copyright (C) 2022 Blender Foundation.
|
||||
*
|
||||
* This file is part of Flamenco.
|
||||
*
|
||||
* Flamenco is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseURL(t *testing.T) {
|
||||
test := func(expected, input string) {
|
||||
actualURL, err := ParseURL(input)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, actualURL.String())
|
||||
}
|
||||
|
||||
test("http://jemoeder:1234", "jemoeder:1234")
|
||||
test("http://jemoeder/", "jemoeder")
|
||||
test("opjehoofd://jemoeder:4213/xxx", "opjehoofd://jemoeder:4213/xxx")
|
||||
}
|
131
internal/worker/registration.go
Normal file
131
internal/worker/registration.go
Normal file
@ -0,0 +1,131 @@
|
||||
package worker
|
||||
|
||||
/* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* Original Code Copyright (C) 2022 Blender Foundation.
|
||||
*
|
||||
* This file is part of Flamenco.
|
||||
*
|
||||
* Flamenco is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/blender/flamenco-ng-poc/pkg/api"
|
||||
)
|
||||
|
||||
// (Re-)register ourselves at the Manager.
|
||||
func (w *Worker) register(ctx context.Context) {
|
||||
// Construct our new password.
|
||||
secret := make([]byte, 32)
|
||||
if _, err := rand.Read(secret); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to generate secret key")
|
||||
}
|
||||
secretKey := hex.EncodeToString(secret)
|
||||
|
||||
// TODO: load taskTypes from config file.
|
||||
taskTypes := []string{"unknown", "sleep", "blender-render", "debug", "ffmpeg"}
|
||||
|
||||
req := api.RegisterWorkerJSONRequestBody{
|
||||
Nickname: mustHostname(),
|
||||
Platform: runtime.GOOS,
|
||||
Secret: secretKey,
|
||||
SupportedTaskTypes: taskTypes,
|
||||
}
|
||||
resp, err := w.client.RegisterWorkerWithResponse(ctx, req)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error registering at Manager")
|
||||
}
|
||||
switch {
|
||||
case resp.JSON200 != nil:
|
||||
log.Info().
|
||||
Int("code", resp.StatusCode()).
|
||||
Interface("resp", resp.JSON200).
|
||||
Msg("registered at Manager")
|
||||
default:
|
||||
log.Fatal().
|
||||
Int("code", resp.StatusCode()).
|
||||
Interface("resp", resp.JSONDefault).
|
||||
Msg("unable to register at Manager")
|
||||
}
|
||||
|
||||
// store ID and secretKey in config file when registration is complete.
|
||||
err = w.configWrangler.WriteConfig(credentialsFilename, "Credentials", workerCredentials{
|
||||
WorkerID: resp.JSON200.Uuid,
|
||||
Secret: secretKey,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Str("file", credentialsFilename).
|
||||
Msg("unable to write credentials configuration file")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) reregister(ctx context.Context) {
|
||||
w.register(ctx)
|
||||
w.loadConfig()
|
||||
}
|
||||
|
||||
// signOn tells the Manager we're alive and returns the status the Manager tells us to go to.
|
||||
// Failure to sign on is fatal.
|
||||
func (w *Worker) signOn(ctx context.Context) api.WorkerStatus {
|
||||
logger := log.With().Str("manager", w.manager.String()).Logger()
|
||||
logger.Info().Msg("signing on at Manager")
|
||||
|
||||
if w.creds == nil {
|
||||
logger.Fatal().Msg("no credentials, unable to sign on")
|
||||
}
|
||||
|
||||
// TODO: load taskTypes from config file.
|
||||
taskTypes := []string{"unknown", "sleep", "blender-render", "debug", "ffmpeg"}
|
||||
|
||||
req := api.SignOnJSONRequestBody{
|
||||
Nickname: mustHostname(),
|
||||
SupportedTaskTypes: taskTypes,
|
||||
}
|
||||
resp, err := w.client.SignOnWithResponse(ctx, req)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error registering at Manager")
|
||||
}
|
||||
switch {
|
||||
case resp.JSON200 != nil:
|
||||
log.Info().
|
||||
Int("code", resp.StatusCode()).
|
||||
Interface("resp", resp.JSON200).
|
||||
Msg("signed on at Manager")
|
||||
default:
|
||||
log.Fatal().
|
||||
Int("code", resp.StatusCode()).
|
||||
Interface("resp", resp.JSONDefault).
|
||||
Msg("unable to sign on at Manager")
|
||||
}
|
||||
|
||||
startupState := resp.JSON200.StatusRequested
|
||||
log.Info().Str("startup_state", string(startupState)).Msg("manager accepted sign-on")
|
||||
return startupState
|
||||
}
|
||||
|
||||
// mustHostname either the hostname or logs a fatal error.
|
||||
func mustHostname() string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("error getting hostname")
|
||||
}
|
||||
return hostname
|
||||
}
|
104
internal/worker/ssdp/client.go
Normal file
104
internal/worker/ssdp/client.go
Normal file
@ -0,0 +1,104 @@
|
||||
package ssdp
|
||||
|
||||
/* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* Original Code Copyright (C) 2022 Blender Foundation.
|
||||
*
|
||||
* This file is part of Flamenco.
|
||||
*
|
||||
* Flamenco is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/blender-institute/gossdp"
|
||||
)
|
||||
|
||||
// Finder is a uses UPnP/SSDP to find a Flamenco Manager on the local network.
|
||||
type Finder struct {
|
||||
overrideURL *url.URL
|
||||
}
|
||||
|
||||
type ssdpClient struct {
|
||||
response chan interface{}
|
||||
}
|
||||
|
||||
// NewManagerFinder returns a default SSDP/UPnP based finder.
|
||||
func NewManagerFinder(managerURL *url.URL) Finder {
|
||||
return Finder{
|
||||
overrideURL: managerURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *ssdpClient) NotifyAlive(message gossdp.AliveMessage) {
|
||||
log.Info().Interface("message", message).Msg("UPnP/SSDP NotifyAlive")
|
||||
}
|
||||
func (b *ssdpClient) NotifyBye(message gossdp.ByeMessage) {
|
||||
log.Info().Interface("message", message).Msg("UPnP/SSDP NotifyBye")
|
||||
}
|
||||
func (b *ssdpClient) Response(message gossdp.ResponseMessage) {
|
||||
log.Debug().Interface("message", message).Msg("UPnP/SSDP response")
|
||||
url, err := url.Parse(message.Location)
|
||||
if err != nil {
|
||||
b.response <- err
|
||||
return
|
||||
}
|
||||
b.response <- url
|
||||
}
|
||||
|
||||
// FindFlamencoManager tries to find a Manager, sending its URL to the returned channel.
|
||||
func (f Finder) FindFlamencoManager() <-chan *url.URL {
|
||||
reporter := make(chan *url.URL)
|
||||
|
||||
go func() {
|
||||
defer close(reporter)
|
||||
|
||||
if f.overrideURL != nil {
|
||||
log.Debug().Str("url", f.overrideURL.String()).Msg("Using configured Flamenco Manager URL")
|
||||
reporter <- f.overrideURL
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Msg("finding Flamenco Manager via UPnP/SSDP")
|
||||
b := ssdpClient{make(chan interface{})}
|
||||
|
||||
client, err := gossdp.NewSsdpClientWithLogger(&b, ZeroLogWrapper{})
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Unable to create UPnP/SSDP client")
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug().Msg("Starting UPnP/SSDP client")
|
||||
go client.Start()
|
||||
defer client.Stop()
|
||||
|
||||
if err := client.ListenFor("urn:flamenco:manager:0"); err != nil {
|
||||
log.Error().Err(err).Msg("unable to find Manager")
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug().Msg("Waiting for UPnP/SSDP answer")
|
||||
urlOrErr := <-b.response
|
||||
switch v := urlOrErr.(type) {
|
||||
case *url.URL:
|
||||
reporter <- v
|
||||
case error:
|
||||
log.Fatal().Err(v).Msg("Error waiting for UPnP/SSDP response from Manager")
|
||||
}
|
||||
}()
|
||||
|
||||
return reporter
|
||||
}
|
44
internal/worker/ssdp/zerolog.go
Normal file
44
internal/worker/ssdp/zerolog.go
Normal file
@ -0,0 +1,44 @@
|
||||
package ssdp
|
||||
|
||||
/* ***** BEGIN GPL LICENSE BLOCK *****
|
||||
*
|
||||
* Original Code Copyright (C) 2022 Blender Foundation.
|
||||
*
|
||||
* This file is part of Flamenco.
|
||||
*
|
||||
* Flamenco is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ***** END GPL LICENSE BLOCK ***** */
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/blender-institute/gossdp"
|
||||
)
|
||||
|
||||
var _ gossdp.LoggerInterface = ZeroLogWrapper{}
|
||||
|
||||
type ZeroLogWrapper struct{}
|
||||
|
||||
func (l ZeroLogWrapper) Debugf(msg string, args ...interface{}) {
|
||||
log.Debug().Msg(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
func (l ZeroLogWrapper) Infof(msg string, args ...interface{}) {
|
||||
log.Info().Msg(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
func (l ZeroLogWrapper) Warnf(msg string, args ...interface{}) {
|
||||
log.Warn().Msg(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
func (l ZeroLogWrapper) Errorf(msg string, args ...interface{}) {
|
||||
log.Error().Msg(fmt.Sprintf(msg, args...))
|
||||
}
|
145
internal/worker/worker.go
Normal file
145
internal/worker/worker.go
Normal file
@ -0,0 +1,145 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gitlab.com/blender/flamenco-ng-poc/pkg/api"
|
||||
)
|
||||
|
||||
const (
|
||||
requestRetry = 5 * time.Second
|
||||
credentialsFilename = "flamenco-worker-credentials.yaml"
|
||||
configFilename = "flamenco-worker.yaml"
|
||||
)
|
||||
|
||||
var (
|
||||
errRequestAborted = errors.New("request to Manager aborted")
|
||||
)
|
||||
|
||||
// Worker performs regular Flamenco Worker operations.
|
||||
type Worker struct {
|
||||
doneChan chan struct{}
|
||||
doneWg *sync.WaitGroup
|
||||
|
||||
manager *url.URL
|
||||
client api.ClientWithResponsesInterface
|
||||
creds *workerCredentials
|
||||
|
||||
state api.WorkerStatus
|
||||
stateStarters map[string]func() // gotoStateXXX functions
|
||||
stateMutex *sync.Mutex
|
||||
|
||||
taskRunner TaskRunner
|
||||
|
||||
configWrangler ConfigWrangler
|
||||
config WorkerConfig
|
||||
managerFinder ManagerFinder
|
||||
}
|
||||
|
||||
type ManagerFinder interface {
|
||||
FindFlamencoManager() <-chan *url.URL
|
||||
}
|
||||
|
||||
type TaskRunner interface{}
|
||||
|
||||
// NewWorker constructs and returns a new Worker.
|
||||
func NewWorker(
|
||||
flamenco api.ClientWithResponsesInterface,
|
||||
configWrangler ConfigWrangler,
|
||||
managerFinder ManagerFinder,
|
||||
taskRunner TaskRunner,
|
||||
) *Worker {
|
||||
|
||||
worker := &Worker{
|
||||
doneChan: make(chan struct{}),
|
||||
doneWg: new(sync.WaitGroup),
|
||||
|
||||
client: flamenco,
|
||||
|
||||
state: api.WorkerStatusStarting,
|
||||
stateStarters: make(map[string]func()),
|
||||
stateMutex: new(sync.Mutex),
|
||||
|
||||
// taskRunner: taskRunner,
|
||||
|
||||
configWrangler: configWrangler,
|
||||
managerFinder: managerFinder,
|
||||
}
|
||||
// worker.setupStateMachine()
|
||||
worker.loadConfig()
|
||||
return worker
|
||||
}
|
||||
|
||||
func (w *Worker) start(ctx context.Context, register bool) {
|
||||
w.doneWg.Add(1)
|
||||
defer w.doneWg.Done()
|
||||
|
||||
w.loadCredentials()
|
||||
|
||||
if w.creds == nil || register {
|
||||
w.register(ctx)
|
||||
}
|
||||
|
||||
startState := w.signOn(ctx)
|
||||
log.Error().Str("state", string(startState)).Msg("here the road ends, nothing else is implemented")
|
||||
// w.changeState(startState)
|
||||
}
|
||||
|
||||
func (w *Worker) loadCredentials() {
|
||||
log.Debug().Msg("loading credentials")
|
||||
|
||||
w.creds = &workerCredentials{}
|
||||
err := w.configWrangler.LoadConfig(credentialsFilename, w.creds)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("file", credentialsFilename).
|
||||
Msg("unable to load credentials configuration file")
|
||||
w.creds = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) loadConfig() {
|
||||
logger := log.With().Str("filename", configFilename).Logger()
|
||||
err := w.configWrangler.LoadConfig(configFilename, &w.config)
|
||||
if os.IsNotExist(err) {
|
||||
logger.Info().Msg("writing default configuration file")
|
||||
w.config = w.configWrangler.DefaultConfig()
|
||||
w.saveConfig()
|
||||
err = w.configWrangler.LoadConfig(configFilename, &w.config)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msg("unable to load config file")
|
||||
}
|
||||
|
||||
if w.config.Manager != "" {
|
||||
w.manager, err = ParseURL(w.config.Manager)
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Str("url", w.config.Manager).
|
||||
Msg("unable to parse manager URL")
|
||||
}
|
||||
logger.Debug().Str("url", w.config.Manager).Msg("parsed manager URL")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (w *Worker) saveConfig() {
|
||||
err := w.configWrangler.WriteConfig(configFilename, "Configuration", w.config)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("filename", configFilename).
|
||||
Msg("unable to write configuration file")
|
||||
}
|
||||
}
|
||||
|
||||
// Close gracefully shuts down the Worker.
|
||||
func (w *Worker) Close() {
|
||||
log.Debug().Msg("worker gracefully shutting down")
|
||||
close(w.doneChan)
|
||||
w.doneWg.Wait()
|
||||
log.Debug().Msg("worker shut down")
|
||||
}
|
@ -39,6 +39,34 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/api/worker/sign-on:
|
||||
summary: Called by Workers to let the Manager know they're ready to work, and to update their metadata.
|
||||
post:
|
||||
summary: Authenticate & sign in the worker.
|
||||
operationId: signOn
|
||||
security: [{worker_auth: []}]
|
||||
tags: [worker]
|
||||
requestBody:
|
||||
description: Worker metadata
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkerSignOn"
|
||||
responses:
|
||||
"200":
|
||||
description: normal response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkerStateChange"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/api/worker/task:
|
||||
summary: Task scheduler endpoint.
|
||||
post:
|
||||
@ -154,6 +182,21 @@ components:
|
||||
type: string
|
||||
enum: [starting, awake, asleep, error, shutting-down, testing]
|
||||
|
||||
WorkerSignOn:
|
||||
type: object
|
||||
properties:
|
||||
nickname: {type: string}
|
||||
supported_task_types:
|
||||
type: array
|
||||
items: {type: string}
|
||||
required: [nickname, supported_task_types]
|
||||
|
||||
WorkerStateChange:
|
||||
type: object
|
||||
properties:
|
||||
status_requested: {$ref: "#/components/schemas/WorkerStatus"}
|
||||
required: [status_requested]
|
||||
|
||||
AssignedTask:
|
||||
type: object
|
||||
description: AssignedTask is a task as it is received by the Worker.
|
||||
|
@ -106,6 +106,11 @@ type ClientInterface interface {
|
||||
|
||||
RegisterWorker(ctx context.Context, body RegisterWorkerJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// SignOn request with any body
|
||||
SignOnWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
SignOn(ctx context.Context, body SignOnJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// ScheduleTask request
|
||||
ScheduleTask(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
}
|
||||
@ -182,6 +187,30 @@ func (c *Client) RegisterWorker(ctx context.Context, body RegisterWorkerJSONRequ
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) SignOnWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewSignOnRequestWithBody(c.Server, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) SignOn(ctx context.Context, body SignOnJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewSignOnRequest(c.Server, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) ScheduleTask(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewScheduleTaskRequest(c.Server)
|
||||
if err != nil {
|
||||
@ -335,6 +364,46 @@ func NewRegisterWorkerRequestWithBody(server string, contentType string, body io
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewSignOnRequest calls the generic SignOn builder with application/json body
|
||||
func NewSignOnRequest(server string, body SignOnJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
buf, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewReader(buf)
|
||||
return NewSignOnRequestWithBody(server, "application/json", bodyReader)
|
||||
}
|
||||
|
||||
// NewSignOnRequestWithBody generates requests for SignOn with any type of body
|
||||
func NewSignOnRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/worker/sign-on")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", queryURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewScheduleTaskRequest generates requests for ScheduleTask
|
||||
func NewScheduleTaskRequest(server string) (*http.Request, error) {
|
||||
var err error
|
||||
@ -421,6 +490,11 @@ type ClientWithResponsesInterface interface {
|
||||
|
||||
RegisterWorkerWithResponse(ctx context.Context, body RegisterWorkerJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterWorkerResponse, error)
|
||||
|
||||
// SignOn request with any body
|
||||
SignOnWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SignOnResponse, error)
|
||||
|
||||
SignOnWithResponse(ctx context.Context, body SignOnJSONRequestBody, reqEditors ...RequestEditorFn) (*SignOnResponse, error)
|
||||
|
||||
// ScheduleTask request
|
||||
ScheduleTaskWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ScheduleTaskResponse, error)
|
||||
}
|
||||
@ -515,6 +589,29 @@ func (r RegisterWorkerResponse) StatusCode() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
type SignOnResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *WorkerStateChange
|
||||
JSONDefault *Error
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r SignOnResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r SignOnResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ScheduleTaskResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
@ -590,6 +687,23 @@ func (c *ClientWithResponses) RegisterWorkerWithResponse(ctx context.Context, bo
|
||||
return ParseRegisterWorkerResponse(rsp)
|
||||
}
|
||||
|
||||
// SignOnWithBodyWithResponse request with arbitrary body returning *SignOnResponse
|
||||
func (c *ClientWithResponses) SignOnWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SignOnResponse, error) {
|
||||
rsp, err := c.SignOnWithBody(ctx, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseSignOnResponse(rsp)
|
||||
}
|
||||
|
||||
func (c *ClientWithResponses) SignOnWithResponse(ctx context.Context, body SignOnJSONRequestBody, reqEditors ...RequestEditorFn) (*SignOnResponse, error) {
|
||||
rsp, err := c.SignOn(ctx, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseSignOnResponse(rsp)
|
||||
}
|
||||
|
||||
// ScheduleTaskWithResponse request returning *ScheduleTaskResponse
|
||||
func (c *ClientWithResponses) ScheduleTaskWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ScheduleTaskResponse, error) {
|
||||
rsp, err := c.ScheduleTask(ctx, reqEditors...)
|
||||
@ -717,6 +831,39 @@ func ParseRegisterWorkerResponse(rsp *http.Response) (*RegisterWorkerResponse, e
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseSignOnResponse parses an HTTP response from a SignOnWithResponse call
|
||||
func ParseSignOnResponse(rsp *http.Response) (*SignOnResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &SignOnResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest WorkerStateChange
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
|
||||
var dest Error
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSONDefault = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseScheduleTaskResponse parses an HTTP response from a ScheduleTaskWithResponse call
|
||||
func ParseScheduleTaskResponse(rsp *http.Response) (*ScheduleTaskResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
|
@ -25,6 +25,9 @@ type ServerInterface interface {
|
||||
// Register a new worker
|
||||
// (POST /api/worker/register-worker)
|
||||
RegisterWorker(ctx echo.Context) error
|
||||
// Authenticate & sign in the worker.
|
||||
// (POST /api/worker/sign-on)
|
||||
SignOn(ctx echo.Context) error
|
||||
// Obtain a new task to execute
|
||||
// (POST /api/worker/task)
|
||||
ScheduleTask(ctx echo.Context) error
|
||||
@ -78,6 +81,17 @@ func (w *ServerInterfaceWrapper) RegisterWorker(ctx echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// SignOn converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) SignOn(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
ctx.Set(Worker_authScopes, []string{""})
|
||||
|
||||
// Invoke the callback with all the unmarshalled arguments
|
||||
err = w.Handler.SignOn(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ScheduleTask converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) ScheduleTask(ctx echo.Context) error {
|
||||
var err error
|
||||
@ -121,6 +135,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||
router.GET(baseURL+"/api/jobs/types", wrapper.GetJobTypes)
|
||||
router.GET(baseURL+"/api/jobs/:job_id", wrapper.FetchJob)
|
||||
router.POST(baseURL+"/api/worker/register-worker", wrapper.RegisterWorker)
|
||||
router.POST(baseURL+"/api/worker/sign-on", wrapper.SignOn)
|
||||
router.POST(baseURL+"/api/worker/task", wrapper.ScheduleTask)
|
||||
|
||||
}
|
||||
|
@ -18,50 +18,53 @@ import (
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"LGCnLfko99Xp1X8CAAD//yqfdC3jJwAA",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
@ -242,6 +242,17 @@ type WorkerRegistration struct {
|
||||
SupportedTaskTypes []string `json:"supported_task_types"`
|
||||
}
|
||||
|
||||
// WorkerSignOn defines model for WorkerSignOn.
|
||||
type WorkerSignOn struct {
|
||||
Nickname string `json:"nickname"`
|
||||
SupportedTaskTypes []string `json:"supported_task_types"`
|
||||
}
|
||||
|
||||
// WorkerStateChange defines model for WorkerStateChange.
|
||||
type WorkerStateChange struct {
|
||||
StatusRequested WorkerStatus `json:"status_requested"`
|
||||
}
|
||||
|
||||
// WorkerStatus defines model for WorkerStatus.
|
||||
type WorkerStatus string
|
||||
|
||||
@ -251,12 +262,18 @@ type SubmitJobJSONBody SubmittedJob
|
||||
// RegisterWorkerJSONBody defines parameters for RegisterWorker.
|
||||
type RegisterWorkerJSONBody WorkerRegistration
|
||||
|
||||
// SignOnJSONBody defines parameters for SignOn.
|
||||
type SignOnJSONBody WorkerSignOn
|
||||
|
||||
// SubmitJobJSONRequestBody defines body for SubmitJob for application/json ContentType.
|
||||
type SubmitJobJSONRequestBody SubmitJobJSONBody
|
||||
|
||||
// RegisterWorkerJSONRequestBody defines body for RegisterWorker for application/json ContentType.
|
||||
type RegisterWorkerJSONRequestBody RegisterWorkerJSONBody
|
||||
|
||||
// SignOnJSONRequestBody defines body for SignOn for application/json ContentType.
|
||||
type SignOnJSONRequestBody SignOnJSONBody
|
||||
|
||||
// Getter for additional properties for JobMetadata. Returns the specified
|
||||
// element and whether it was found
|
||||
func (a JobMetadata) Get(fieldName string) (value string, found bool) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user