Skip to content
This repository was archived by the owner on Feb 27, 2020. It is now read-only.
9 changes: 9 additions & 0 deletions engines/enginetest/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func (p *engineProvider) ensureEngine(engineName string) {
// Create Engine instance
engine, err := engineProvider(extpoints.EngineOptions{
Environment: p.environment,
Log: p.environment.Log.WithField("engine", engineName),
})
nilOrpanic(err, "Failed to create Engine")
p.engine = engine
Expand Down Expand Up @@ -77,9 +78,17 @@ func newTestEnvironment() *runtime.Environment {
rt.SetFinalizer(folder, func(f runtime.TemporaryFolder) {
f.Remove()
})

logger, err := runtime.CreateLogger(os.Getenv("LOGGING_LEVEL"))
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating logger. %s", err)
os.Exit(1)
}

return &runtime.Environment{
GarbageCollector: &gc.GarbageCollector{},
TemporaryStorage: folder,
Log: logger,
}
}

Expand Down
3 changes: 3 additions & 0 deletions engines/extpoints/extpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func UnregisterExtension(name string) []string {
return ifaces
}


// Base extension point

type extensionPoint struct {
Expand Down Expand Up @@ -175,3 +176,5 @@ func (ep *engineProviderExt) Names() []string {
}
return names
}


2 changes: 2 additions & 0 deletions engines/extpoints/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package extpoints

import (
"github.com/Sirupsen/logrus"
"github.com/taskcluster/taskcluster-worker/engines"
"github.com/taskcluster/taskcluster-worker/runtime"
)
Expand All @@ -20,6 +21,7 @@ type EngineOptions struct {
// Note: This is intended to be a simple argument wrapper, do not add methods
// to this struct.
Environment *runtime.Environment
Log *logrus.Entry
}

// EngineProvider is the interface engine implementors must implement and
Expand Down
11 changes: 8 additions & 3 deletions engines/mock/mockengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@
package mockengine

import (
"fmt"
"net/http"

"github.com/Sirupsen/logrus"
"github.com/taskcluster/taskcluster-worker/engines"
"github.com/taskcluster/taskcluster-worker/engines/extpoints"
"github.com/taskcluster/taskcluster-worker/runtime"
)

type engine struct {
engines.EngineBase
Log *logrus.Entry
}

func init() {
// Register the mock engine as an import side-effect
extpoints.EngineProviders.Register(func(
options extpoints.EngineOptions,
) (engines.Engine, error) {
return engine{}, nil
fmt.Println(options.Log)
return engine{Log: options.Log}, nil
}, "mock")
}

Expand All @@ -30,7 +34,7 @@ type payload struct {
Delay int64 `json:"delay"`
}

func (engine) PayloadSchema() runtime.CompositeSchema {
func (e engine) PayloadSchema() runtime.CompositeSchema {
// Declare the schema for the "task.payload.start" property
schema, err := runtime.NewCompositeSchema("start", `{
"type": "object",
Expand Down Expand Up @@ -60,10 +64,11 @@ func (engine) PayloadSchema() runtime.CompositeSchema {
return schema
}

func (engine) NewSandboxBuilder(options engines.SandboxOptions) (engines.SandboxBuilder, error) {
func (e engine) NewSandboxBuilder(options engines.SandboxOptions) (engines.SandboxBuilder, error) {
// We know that payload was created with CompositeSchema.Parse() from the
// schema returned by PayloadSchema(), so here we type assert that it is
// indeed a pointer to such a thing.
e.Log.Debug("Building Sandbox")
p, valid := options.Payload.(*payload)
if !valid {
// TODO: Write to some sort of log if the type assertion fails
Expand Down
53 changes: 38 additions & 15 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,68 @@ package main

import (
"fmt"
"log"
"os"

"github.com/docopt/docopt-go"
"github.com/taskcluster/taskcluster-worker/engines/extpoints"
"github.com/taskcluster/taskcluster-worker/runtime"
)

const version = "taskcluster-worker 0.0.1"
const usage = `
Usage: taskcluster-worker [options]
Runs a worker with the given options.
Options:
-V, --version Display the version of go-import-subtree and exit.
-h, --help Print this help information.
-e, --engine <engine> Execution engine to run tasks in
TaskCluster worker
This worker is meant to be used with the taskcluster platform for the execution and
resolution of tasks.

Usage:
taskcluster-worker --help
taskcluster-worker --version
taskcluster-worker --engine <engine>
taskcluster-worker --engine <engine> --logging <level>

Options:
--help Show this help screen.
--version Display the version of go-import-subtree and exit.
-e --engine <engine> Engine to use for task execution sandboxes.
-l --logging-level <level> Set logging at <level>.
`

func main() {
args, err := docopt.Parse(usage, nil, true, version, false, true)
if err != nil {
log.Fatalf("Error parsing arguments. %v", err)
fmt.Fprintf(os.Stderr, "Error parsing arguments. %v", err)
os.Exit(1)
}

e := args["--engine"]
if e == nil {
panic("Must supply engine type")
var level string
if l := args["--logging-level"]; l != nil {
level = l.(string)
}
logger, err := runtime.CreateLogger(level)
if err != nil {
os.Stderr.WriteString(err.Error())
os.Exit(1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, but panic(err) is just more elegant...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think panic is appropriate for crashes, but not appropriate for handling bad input - i.e. a user should never see a stack trace because he/she passed in an unrecognised logging level. In any case, I think we should process the underlying cause and crash if really an internal error, or report the bad value, if it is just a bad value. We could also consider creating and documenting different exit codes for different failures, although I believe exit code 1 is appropriate for invalid arguments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not need a stack trace, nor do we need to run deferred functions, so panic does not seem like the best suited option here. This is an error we can't recover from, and should exit immediately. It also allows us to define exit codes for things we know went wrong.

Panic seems more appropriate when it happens lower down in the execution of the worker (like if we had a plugin hit some unrecoverable state and we need to unwind the stack all the way up) and complete any deferred functions for cleanup.

For my understanding of what is idiomatic in Go, and in the case of using panic here, what is elegant about using panic vs this method of exiting?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree... sorry I'm wrong... panic is bad here ! :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for raising the question about it though, it really gets me thinking about why something is better than the alternative rather than just copying from other places :)

}

e := args["--engine"]
engine := e.(string)

engineProvider := extpoints.EngineProviders.Lookup(engine)

if engineProvider == nil {
engineNames := extpoints.EngineProviders.Names()
log.Fatalf("Must supply a valid engine. Supported Engines %v", engineNames)
logger.Fatalf("Must supply a valid engine. Supported Engines %v", engineNames)
}

engineInstance, err := engineProvider(extpoints.EngineOptions{})
runtimeEnvironment := runtime.Environment{Log: logger}

_, err = engineProvider(extpoints.EngineOptions{
Environment: &runtimeEnvironment,
Log: logger.WithField("engine", engine),
})
if err != nil {
panic(err)
logger.Fatal(err.Error())
}
fmt.Println(engineInstance)

runtimeEnvironment.Log.Info("Worker started up")
}
3 changes: 3 additions & 0 deletions plugins/extpoints/extpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func UnregisterExtension(name string) []string {
return ifaces
}


// Base extension point

type extensionPoint struct {
Expand Down Expand Up @@ -175,3 +176,5 @@ func (ep *pluginProviderExt) Names() []string {
}
return names
}


10 changes: 6 additions & 4 deletions plugins/extpoints/interfaces.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package extpoints

import (
"github.com/Sirupsen/logrus"
"github.com/taskcluster/taskcluster-worker/engines"
"github.com/taskcluster/taskcluster-worker/plugins"
"github.com/taskcluster/taskcluster-worker/runtime"
Expand All @@ -11,13 +12,14 @@ import (
//
// We wrap all arguments so that we can add additional properties without
// breaking source compatibility with older plugins.
// Note: This is passed by-value for efficiency (and to prohibit nil), if
// adding any large fields please consider adding them as pointers.
// Note: This is intended to be a simple argument wrapper, do not add methods
// to this struct.
type PluginOptions struct {
environment *runtime.Environment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, but this wasn't really intended as a doc comment... more as a source comment for people to read when they were editing the struct...

engine *engines.Engine
// Note: This is passed by-value for efficiency (and to prohibit nil), if
// adding any large fields please consider adding them as pointers.
// Note: This is intended to be a simple argument wrapper, do not add methods
// to this struct.
log *logrus.Entry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if we did the interface thing... We could to just hide the WithFields method as it requires logrus.Fields... Looking at this code I think WithField(string, ínterface{}) is sufficient... In rare cases where we have two fields we can just do log.WithField(...).WithField(...)..

}

// The PluginProvider interface must be implemented and registered by anyone
Expand Down
6 changes: 5 additions & 1 deletion plugins/extpoints/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ func NewPluginManager(pluginsToLoad []string, options PluginOptions) (plugins.Pl
if pluginProvider == nil {
return nil, errors.New("Missing plugin")
}
plugin, err := pluginProvider(options)
plugin, err := pluginProvider(PluginOptions{
environment: options.environment,
engine: options.engine,
log: options.log.WithField("plugin", p),
})
if err != nil {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion runtime/environment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package runtime

import "github.com/taskcluster/taskcluster-worker/runtime/gc"
import (
"github.com/Sirupsen/logrus"
"github.com/taskcluster/taskcluster-worker/runtime/gc"
)

// Environment is a collection of objects that makes up a runtime environment.
type Environment struct {
Expand All @@ -9,4 +12,5 @@ type Environment struct {
//TODO: Add some interface to submit statistics for influxdb/signalfx
//TODO: Add some interface to attach a http.Handler to public facing server
TemporaryStorage TemporaryStorage
Log *logrus.Logger
}
26 changes: 26 additions & 0 deletions runtime/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package runtime

import (
"fmt"

"github.com/Sirupsen/logrus"
)

// Create a logger that can be passed around through the environment.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start docs comments like this with CreateLogger .... I think go fmt or one of its friends keeps telling me so...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go vet does that (which is currently in the make test target)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there something I should enable for it to warn me about things like this? 'make test' nor running 'go vet' directly has indicating any problems.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call on changing this documentation by the way, still getting used to how things should be documented.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I think it is https://github.com/golang/lint

// Loggers can be created based on the one returned from this method by calling
// WithField or WithFields and specifying additional fields that the package
// would like.
func CreateLogger(level string) (*logrus.Logger, error) {
if level == "" {
level = "warn"
}

lvl, err := logrus.ParseLevel(level)
if err != nil {
return nil, fmt.Errorf("Unable to parse logging level: %s\n", level)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, didn't know about fmt.Errorf I like that...

Note: not sure if it's always wise to wrap errors... I guess it can't really hurt here...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all errors that we wrap, we should include the underlying error in the message, so we don't lose the root cause - i.e. include an extra %s for err.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currrently the only error that method returns is "not a valid logrus Level: ". My attempt here was to remove the use of the 'logrus' word in the error message as the user doesn't care about what that package is or what it means.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all errors that we wrap, we should include the underlying error in the message,

Unless we're explicitly handling the error... because we know what it means

but yeah, generally I agree...

}

logger := logrus.New()
logger.Level = lvl
return logger, nil
}
29 changes: 29 additions & 0 deletions runtime/log_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package runtime

import (
"fmt"
"reflect"
"testing"

"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestCreateLogger(t *testing.T) {
logger, err := CreateLogger("debug")
assert.Equal(t, err, nil, fmt.Sprintf("Error should not have been returned. %s", err))
assert.Equal(t, reflect.TypeOf(logger).String(), "*logrus.Logger")
assert.Equal(t, logger.Level, logrus.DebugLevel)
}

func TestLoggerNotCreatedWithInvalidLevel(t *testing.T) {
_, err := CreateLogger("debug1234")
assert.NotEqual(t, err, nil, fmt.Sprintf("Error should not have been returned. %s", err))
}

func TestDefaultWarnLevel(t *testing.T) {
logger, err := CreateLogger("")
assert.Equal(t, err, nil, fmt.Sprintf("Error should not have been returned. %s", err))
assert.Equal(t, reflect.TypeOf(logger).String(), "*logrus.Logger")
assert.Equal(t, logger.Level, logrus.WarnLevel)
}