engine: graph, resources: Change Watch to use ctx

This is a general port. There are many optimizations and cleanups we can
do now that we have a proper context passed in. That's for a future
patch.
This commit is contained in:
James Shubin
2023-08-07 20:17:32 -04:00
parent 53a878bf61
commit 963393e3d9
37 changed files with 139 additions and 130 deletions

View File

@@ -277,7 +277,7 @@ will likely find the state to now be correct.
### Watch ### Watch
```golang ```golang
Watch() error Watch(ctx context.Context) error
``` ```
`Watch` is a main loop that runs and sends messages when it detects that the `Watch` is a main loop that runs and sends messages when it detects that the
@@ -304,23 +304,25 @@ If the resource is activated in `polling` mode, the `Watch` method will not get
executed. As a result, the resource must still work even if the main loop is not executed. As a result, the resource must still work even if the main loop is not
running. running.
You must make sure to cleanup any running code or goroutines before Watch exits.
#### Select #### Select
The lifetime of most resources `Watch` method should be spent in an infinite The lifetime of most resources `Watch` method should be spent in an infinite
loop that is bounded by a `select` call. The `select` call is the point where loop that is bounded by a `select` call. The `select` call is the point where
our method hands back control to the engine (and the kernel) so that we can our method hands back control to the engine (and the kernel) so that we can
sleep until something of interest wakes us up. In this loop we must wait until sleep until something of interest wakes us up. In this loop we must wait until
we get a shutdown event from the engine via the `<-obj.init.Done` channel, which we get a shutdown event from the engine via the `<-ctx.Done()` channel, which
closes when we'd like to shut everything down. At this point you should cleanup, closes when we'd like to shut everything down. At this point you should cleanup,
and let `Watch` close. and let `Watch` close.
#### Events #### Events
If the `<-obj.init.Done` channel closes, we should shutdown our resource. When If the `<-ctx.Done()` channel closes, we should shutdown our resource. When we
When we want to send an event, we use the `Event` helper function. This want to send an event, we use the `Event` helper function. This automatically
automatically marks the resource state as `dirty`. If you're unsure, it's not marks the resource state as `dirty`. If you're unsure, it's not harmful to send
harmful to send the event. This will ultimately cause `CheckApply` to run. This the event. This will ultimately cause `CheckApply` to run. This method can block
method can block if the resource is being paused. if the resource is being paused.
#### Startup #### Startup
@@ -347,7 +349,7 @@ sending out erroneous `Event` messages to keep things alive until it finishes.
```golang ```golang
// Watch is the listener and main loop for this resource. // Watch is the listener and main loop for this resource.
func (obj *FooRes) Watch() error { func (obj *FooRes) Watch(ctx context.Context) error {
// setup the Foo resource // setup the Foo resource
var err error var err error
if err, obj.foo = OpenFoo(); err != nil { if err, obj.foo = OpenFoo(); err != nil {
@@ -371,7 +373,7 @@ func (obj *FooRes) Watch() error {
case err := <-obj.foo.Errors: case err := <-obj.foo.Errors:
return err // will cause a retry or permanent failure return err // will cause a retry or permanent failure
case <-obj.init.Done: // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
return nil return nil
} }
@@ -553,11 +555,6 @@ ready to detect changes.
Event sends an event notifying the engine of a possible state change. It is Event sends an event notifying the engine of a possible state change. It is
only called from within `Watch`. only called from within `Watch`.
### Done
Done is a channel that closes when the engine wants us to shutdown. It is only
called from within `Watch`.
### Refresh ### Refresh
Refresh returns whether the resource received a notification. This flag can be Refresh returns whether the resource received a notification. This flag can be

View File

@@ -308,7 +308,7 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
case <-timer.C: // the wait is over case <-timer.C: // the wait is over
return errDelayExpired // special return errDelayExpired // special
case <-obj.state[vertex].init.DoneCtx.Done(): case <-obj.state[vertex].doneCtx.Done():
return nil return nil
} }
} }
@@ -319,12 +319,12 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
} }
} else if interval := res.MetaParams().Poll; interval > 0 { // poll instead of watching :( } else if interval := res.MetaParams().Poll; interval > 0 { // poll instead of watching :(
obj.state[vertex].cuid.StartTimer() obj.state[vertex].cuid.StartTimer()
err = obj.state[vertex].poll(interval) err = obj.state[vertex].poll(obj.state[vertex].doneCtx, interval)
obj.state[vertex].cuid.StopTimer() // clean up nicely obj.state[vertex].cuid.StopTimer() // clean up nicely
} else { } else {
obj.state[vertex].cuid.StartTimer() obj.state[vertex].cuid.StartTimer()
obj.Logf("Watch(%s)", vertex) obj.Logf("Watch(%s)", vertex)
err = res.Watch() // run the watch normally err = res.Watch(obj.state[vertex].doneCtx) // run the watch normally
obj.Logf("Watch(%s): Exited(%+v)", vertex, err) obj.Logf("Watch(%s): Exited(%+v)", vertex, err)
obj.state[vertex].cuid.StopTimer() // clean up nicely obj.state[vertex].cuid.StopTimer() // clean up nicely
} }

View File

@@ -20,6 +20,7 @@
package autogroup package autogroup
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
"sort" "sort"
@@ -66,7 +67,7 @@ func (obj *NoopResTest) Close() error {
return nil return nil
} }
func (obj *NoopResTest) Watch() error { func (obj *NoopResTest) Watch(context.Context) error {
return nil // not needed return nil // not needed
} }

View File

@@ -165,7 +165,6 @@ func (obj *State) Init() error {
// Watch: // Watch:
Running: obj.event, Running: obj.event,
Event: obj.event, Event: obj.event,
DoneCtx: obj.doneCtx,
// CheckApply: // CheckApply:
Refresh: func() bool { Refresh: func() bool {
@@ -393,7 +392,7 @@ func (obj *State) setDirty() {
} }
// poll is a replacement for Watch when the Poll metaparameter is used. // poll is a replacement for Watch when the Poll metaparameter is used.
func (obj *State) poll(interval uint32) error { func (obj *State) poll(ctx context.Context, interval uint32) error {
// create a time.Ticker for the given interval // create a time.Ticker for the given interval
ticker := time.NewTicker(time.Duration(interval) * time.Second) ticker := time.NewTicker(time.Duration(interval) * time.Second)
defer ticker.Stop() defer ticker.Stop()
@@ -405,7 +404,7 @@ func (obj *State) poll(interval uint32) error {
case <-ticker.C: // received the timer event case <-ticker.C: // received the timer event
obj.init.Logf("polling...") obj.init.Logf("polling...")
case <-obj.init.DoneCtx.Done(): // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
return nil return nil
} }

View File

@@ -102,11 +102,6 @@ type Init struct {
// Event sends an event notifying the engine of a possible state change. // Event sends an event notifying the engine of a possible state change.
Event func() Event func()
// DoneCtx returns a context that will cancel to signal to us that it's
// time for us to shutdown.
// TODO: this is temporary until Watch supports context directly.
DoneCtx context.Context
// Called from within CheckApply: // Called from within CheckApply:
// Refresh returns whether the resource received a notification. This // Refresh returns whether the resource received a notification. This
@@ -200,8 +195,8 @@ type Res interface {
// Watch is run by the engine to monitor for state changes. If it // Watch is run by the engine to monitor for state changes. If it
// detects any, it notifies the engine which will usually run CheckApply // detects any, it notifies the engine which will usually run CheckApply
// in response. // in response. If the input context cancels, we must shutdown.
Watch() error Watch(context.Context) error
// CheckApply determines if the state of the resource is correct and if // CheckApply determines if the state of the resource is correct and if
// asked to with the `apply` variable, applies the requested state. // asked to with the `apply` variable, applies the requested state.

View File

@@ -20,6 +20,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@@ -125,7 +126,7 @@ func (obj *AugeasRes) Close() error {
// Watch is the primary listener for this resource and it outputs events. This // Watch is the primary listener for this resource and it outputs events. This
// was taken from the File resource. // was taken from the File resource.
// FIXME: DRY - This is taken from the file resource // FIXME: DRY - This is taken from the file resource
func (obj *AugeasRes) Watch() error { func (obj *AugeasRes) Watch(ctx context.Context) error {
var err error var err error
obj.recWatcher, err = recwatch.NewRecWatcher(obj.File, false) obj.recWatcher, err = recwatch.NewRecWatcher(obj.File, false)
if err != nil { if err != nil {
@@ -154,7 +155,7 @@ func (obj *AugeasRes) Watch() error {
} }
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -409,16 +409,16 @@ func (obj *AwsEc2Res) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *AwsEc2Res) Watch() error { func (obj *AwsEc2Res) Watch(ctx context.Context) error {
if obj.WatchListenAddr != "" { if obj.WatchListenAddr != "" {
return obj.snsWatch() return obj.snsWatch(ctx)
} }
return obj.longpollWatch() return obj.longpollWatch(ctx)
} }
// longpollWatch uses the ec2 api's built in methods to watch ec2 resource // longpollWatch uses the ec2 api's built in methods to watch ec2 resource
// state. // state.
func (obj *AwsEc2Res) longpollWatch() error { func (obj *AwsEc2Res) longpollWatch(ctx context.Context) error {
send := false send := false
// We tell the engine that we're running right away. This is not correct, // We tell the engine that we're running right away. This is not correct,
@@ -426,7 +426,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
// cancellable context used for exiting cleanly // cancellable context used for exiting cleanly
ctx, cancel := context.WithCancel(context.TODO()) innerCtx, cancel := context.WithCancel(context.TODO())
// clean up when we're done // clean up when we're done
defer obj.wg.Wait() defer obj.wg.Wait()
@@ -461,7 +461,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
} }
// wait for the instance state to change // wait for the instance state to change
state, err := stateWaiter(ctx, instance, obj.client) state, err := stateWaiter(innerCtx, instance, obj.client)
if err != nil { if err != nil {
select { select {
case obj.awsChan <- &chanStruct{ case obj.awsChan <- &chanStruct{
@@ -502,7 +502,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
send = true send = true
} }
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -518,7 +518,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
// Init() a CloudWatch rule is created along with a corresponding SNS topic that // Init() a CloudWatch rule is created along with a corresponding SNS topic that
// it can publish to. snsWatch creates an http server which listens for messages // it can publish to. snsWatch creates an http server which listens for messages
// published to the topic and processes them accordingly. // published to the topic and processes them accordingly.
func (obj *AwsEc2Res) snsWatch() error { func (obj *AwsEc2Res) snsWatch(ctx context.Context) error {
send := false send := false
defer obj.wg.Wait() defer obj.wg.Wait()
// create the sns listener // create the sns listener
@@ -533,9 +533,9 @@ func (obj *AwsEc2Res) snsWatch() error {
} }
// close the listener and shutdown the sns server when we're done // close the listener and shutdown the sns server when we're done
defer func() { defer func() {
ctx, cancel := context.WithTimeout(context.TODO(), SnsServerShutdownTimeout*time.Second) innerCtx, cancel := context.WithTimeout(context.TODO(), SnsServerShutdownTimeout*time.Second)
defer cancel() defer cancel()
if err := snsServer.Shutdown(ctx); err != nil { if err := snsServer.Shutdown(innerCtx); err != nil {
if err != context.Canceled { if err != context.Canceled {
obj.init.Logf("error stopping sns endpoint: %s", err) obj.init.Logf("error stopping sns endpoint: %s", err)
return return
@@ -596,7 +596,7 @@ func (obj *AwsEc2Res) snsWatch() error {
obj.init.Logf("State: %v", msg.event) obj.init.Logf("State: %v", msg.event)
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -718,7 +718,7 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
} }
// context to cancel the waiter if it takes too long // context to cancel the waiter if it takes too long
ctx, cancel := context.WithTimeout(context.TODO(), waitTimeout*time.Second) innerCtx, cancel := context.WithTimeout(context.TODO(), waitTimeout*time.Second)
defer cancel() defer cancel()
// wait until the state converges // wait until the state converges
@@ -727,11 +727,11 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
} }
switch obj.State { switch obj.State {
case ec2.InstanceStateNameRunning: case ec2.InstanceStateNameRunning:
err = obj.client.WaitUntilInstanceRunningWithContext(ctx, waitInput) err = obj.client.WaitUntilInstanceRunningWithContext(innerCtx, waitInput)
case ec2.InstanceStateNameStopped: case ec2.InstanceStateNameStopped:
err = obj.client.WaitUntilInstanceStoppedWithContext(ctx, waitInput) err = obj.client.WaitUntilInstanceStoppedWithContext(innerCtx, waitInput)
case ec2.InstanceStateNameTerminated: case ec2.InstanceStateNameTerminated:
err = obj.client.WaitUntilInstanceTerminatedWithContext(ctx, waitInput) err = obj.client.WaitUntilInstanceTerminatedWithContext(innerCtx, waitInput)
default: default:
return false, errwrap.Wrapf(err, "unrecognized instance state") return false, errwrap.Wrapf(err, "unrecognized instance state")
} }

View File

@@ -95,11 +95,11 @@ func (obj *ConfigEtcdRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *ConfigEtcdRes) Watch() error { func (obj *ConfigEtcdRes) Watch(ctx context.Context) error {
obj.wg.Add(1) obj.wg.Add(1)
defer obj.wg.Done() defer obj.wg.Done()
// FIXME: add timeout to context // FIXME: add timeout to context
ctx, cancel := context.WithCancel(obj.init.DoneCtx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
ch, err := obj.init.World.IdealClusterSizeWatch(util.CtxWithWg(ctx, obj.wg)) ch, err := obj.init.World.IdealClusterSizeWatch(util.CtxWithWg(ctx, obj.wg))
if err != nil { if err != nil {
@@ -120,7 +120,7 @@ Loop:
} }
// pass through and send an event // pass through and send an event
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
} }
obj.init.Event() // notify engine of an event (this can block) obj.init.Event() // notify engine of an event (this can block)

View File

@@ -117,7 +117,7 @@ func (obj *ConsulKVRes) Close() error {
} }
// Watch is the listener and main loop for this resource and it outputs events. // Watch is the listener and main loop for this resource and it outputs events.
func (obj *ConsulKVRes) Watch() error { func (obj *ConsulKVRes) Watch(ctx context.Context) error {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
defer wg.Wait() defer wg.Wait()
@@ -132,9 +132,9 @@ func (obj *ConsulKVRes) Watch() error {
defer wg.Done() defer wg.Done()
opts := &api.QueryOptions{RequireConsistent: true} opts := &api.QueryOptions{RequireConsistent: true}
ctx, cancel := util.ContextWithCloser(context.Background(), exit) innerCtx, cancel := util.ContextWithCloser(context.Background(), exit)
defer cancel() defer cancel()
opts = opts.WithContext(ctx) opts = opts.WithContext(innerCtx)
for { for {
_, meta, err := kv.Get(obj.key, opts) _, meta, err := kv.Get(obj.key, opts)
@@ -162,10 +162,10 @@ func (obj *ConsulKVRes) Watch() error {
// Unexpected situation, bug in consul API... // Unexpected situation, bug in consul API...
select { select {
case ch <- fmt.Errorf("unexpected behaviour in Consul API"): case ch <- fmt.Errorf("unexpected behaviour in Consul API"):
case <-obj.init.DoneCtx.Done(): // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
} }
case <-obj.init.DoneCtx.Done(): // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
} }
return return
} }
@@ -186,7 +186,7 @@ func (obj *ConsulKVRes) Watch() error {
} }
obj.init.Event() obj.init.Event()
case <-obj.init.DoneCtx.Done(): // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
return nil return nil
} }
} }

View File

@@ -221,7 +221,7 @@ func (obj *CronRes) Close() error {
} }
// Watch for state changes and sends a message to the bus if there is a change. // Watch for state changes and sends a message to the bus if there is a change.
func (obj *CronRes) Watch() error { func (obj *CronRes) Watch(ctx context.Context) error {
var bus *dbus.Conn var bus *dbus.Conn
var err error var err error
@@ -296,7 +296,7 @@ func (obj *CronRes) Watch() error {
} }
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
// do all our event sending all together to avoid duplicate msgs // do all our event sending all together to avoid duplicate msgs

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@@ -385,7 +386,7 @@ func (obj *DHCPServerRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *DHCPServerRes) Watch() error { func (obj *DHCPServerRes) Watch(ctx context.Context) error {
addr, err := net.ResolveUDPAddr("udp", obj.getAddress()) // *net.UDPAddr addr, err := net.ResolveUDPAddr("udp", obj.getAddress()) // *net.UDPAddr
if err != nil { if err != nil {
return errwrap.Wrapf(err, "could not resolve address") return errwrap.Wrapf(err, "could not resolve address")
@@ -461,7 +462,7 @@ func (obj *DHCPServerRes) Watch() error {
case <-closeSignal: // something shut us down early case <-closeSignal: // something shut us down early
return closeError return closeError
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -1052,11 +1053,11 @@ func (obj *DHCPHostRes) Close() error {
// Watch is the primary listener for this resource and it outputs events. This // Watch is the primary listener for this resource and it outputs events. This
// particular one does absolutely nothing but block until we've received a done // particular one does absolutely nothing but block until we've received a done
// signal. // signal.
func (obj *DHCPHostRes) Watch() error { func (obj *DHCPHostRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
select { select {
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
} }
//obj.init.Event() // notify engine of an event (this can block) //obj.init.Event() // notify engine of an event (this can block)

View File

@@ -170,11 +170,11 @@ func (obj *DockerContainerRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *DockerContainerRes) Watch() error { func (obj *DockerContainerRes) Watch(ctx context.Context) error {
ctx, cancel := context.WithCancel(context.Background()) innerCtx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
eventChan, errChan := obj.client.Events(ctx, types.EventsOptions{}) eventChan, errChan := obj.client.Events(innerCtx, types.EventsOptions{})
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
@@ -196,7 +196,7 @@ func (obj *DockerContainerRes) Watch() error {
} }
return err return err
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -131,11 +131,11 @@ func (obj *DockerImageRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *DockerImageRes) Watch() error { func (obj *DockerImageRes) Watch(ctx context.Context) error {
ctx, cancel := context.WithCancel(context.Background()) innerCtx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
eventChan, errChan := obj.client.Events(ctx, types.EventsOptions{}) eventChan, errChan := obj.client.Events(innerCtx, types.EventsOptions{})
// notify engine that we're running // notify engine that we're running
obj.init.Running() obj.init.Running()
@@ -158,7 +158,7 @@ func (obj *DockerImageRes) Watch() error {
} }
return err return err
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -167,7 +167,7 @@ func (obj *ExecRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *ExecRes) Watch() error { func (obj *ExecRes) Watch(ctx context.Context) error {
ioChan := make(chan *cmdOutput) ioChan := make(chan *cmdOutput)
defer obj.wg.Wait() defer obj.wg.Wait()
@@ -187,9 +187,9 @@ func (obj *ExecRes) Watch() error {
cmdArgs = []string{"-c", obj.WatchCmd} cmdArgs = []string{"-c", obj.WatchCmd}
} }
ctx, cancel := context.WithCancel(context.Background()) innerCtx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
cmd := exec.CommandContext(ctx, cmdName, cmdArgs...) cmd := exec.CommandContext(innerCtx, cmdName, cmdArgs...)
cmd.Dir = obj.WatchCwd // run program in pwd if "" cmd.Dir = obj.WatchCwd // run program in pwd if ""
// ignore signals sent to parent process (we're in our own group) // ignore signals sent to parent process (we're in our own group)
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
@@ -203,7 +203,7 @@ func (obj *ExecRes) Watch() error {
return errwrap.Wrapf(err, "error while setting credential") return errwrap.Wrapf(err, "error while setting credential")
} }
if ioChan, err = obj.cmdOutputRunner(ctx, cmd); err != nil { if ioChan, err = obj.cmdOutputRunner(innerCtx, cmd); err != nil {
return errwrap.Wrapf(err, "error starting WatchCmd") return errwrap.Wrapf(err, "error starting WatchCmd")
} }
} }
@@ -252,7 +252,7 @@ func (obj *ExecRes) Watch() error {
send = true send = true
} }
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -19,6 +19,7 @@ package resources
import ( import (
"bytes" "bytes"
"context"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@@ -359,7 +360,7 @@ func (obj *FileRes) Close() error {
// probably important to write some test cases first! If the Watch returns an // probably important to write some test cases first! If the Watch returns an
// error, it means that something has gone wrong, and it must be restarted. On a // error, it means that something has gone wrong, and it must be restarted. On a
// clean exit it returns nil. // clean exit it returns nil.
func (obj *FileRes) Watch() error { func (obj *FileRes) Watch(ctx context.Context) error {
// TODO: chan *recwatch.Event instead? // TODO: chan *recwatch.Event instead?
inputEvents := make(chan recwatch.Event) inputEvents := make(chan recwatch.Event)
defer close(inputEvents) defer close(inputEvents)
@@ -497,7 +498,7 @@ func (obj *FileRes) Watch() error {
} }
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os/exec" "os/exec"
@@ -76,7 +77,7 @@ func (obj *GroupRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *GroupRes) Watch() error { func (obj *GroupRes) Watch(ctx context.Context) error {
var err error var err error
obj.recWatcher, err = recwatch.NewRecWatcher(groupFile, false) obj.recWatcher, err = recwatch.NewRecWatcher(groupFile, false)
if err != nil { if err != nil {
@@ -105,7 +106,7 @@ func (obj *GroupRes) Watch() error {
} }
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -371,7 +371,7 @@ func (obj *HetznerVMRes) Close() error {
// Watch is not implemented for this resource, since the Hetzner API does not // Watch is not implemented for this resource, since the Hetzner API does not
// provide any event streams. Instead, always use polling. // provide any event streams. Instead, always use polling.
// NOTE: HetznerPollLimit sets an explicit minimum on the polling interval. // NOTE: HetznerPollLimit sets an explicit minimum on the polling interval.
func (obj *HetznerVMRes) Watch() error { func (obj *HetznerVMRes) Watch(context.Context) error {
return fmt.Errorf("invalid Watch call: requires poll metaparam") return fmt.Errorf("invalid Watch call: requires poll metaparam")
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
@@ -106,7 +107,7 @@ func (obj *HostnameRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *HostnameRes) Watch() error { func (obj *HostnameRes) Watch(ctx context.Context) error {
// if we share the bus with others, we will get each others messages!! // if we share the bus with others, we will get each others messages!!
bus, err := util.SystemBusPrivateUsable() // don't share the bus connection! bus, err := util.SystemBusPrivateUsable() // don't share the bus connection!
if err != nil { if err != nil {
@@ -135,7 +136,7 @@ func (obj *HostnameRes) Watch() error {
case <-signals: case <-signals:
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -230,7 +230,7 @@ func (obj *HTTPServerRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *HTTPServerRes) Watch() error { func (obj *HTTPServerRes) Watch(ctx context.Context) error {
// TODO: I think we could replace all this with: // TODO: I think we could replace all this with:
//obj.conn, err := net.Listen("tcp", obj.getAddress()) //obj.conn, err := net.Listen("tcp", obj.getAddress())
// ...but what is the advantage? // ...but what is the advantage?
@@ -304,13 +304,13 @@ func (obj *HTTPServerRes) Watch() error {
// exit and waits instead for Shutdown to return. // exit and waits instead for Shutdown to return.
defer func() { defer func() {
defer close(shutdownChan) // signal that shutdown is finished defer close(shutdownChan) // signal that shutdown is finished
ctx := context.Background() innerCtx := context.Background()
if i := obj.getShutdownTimeout(); i != nil && *i > 0 { if i := obj.getShutdownTimeout(); i != nil && *i > 0 {
var cancel context.CancelFunc var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(*i)*time.Second) innerCtx, cancel = context.WithTimeout(innerCtx, time.Duration(*i)*time.Second)
defer cancel() defer cancel()
} }
err := obj.server.Shutdown(ctx) // shutdown gracefully err := obj.server.Shutdown(innerCtx) // shutdown gracefully
if err == context.DeadlineExceeded { if err == context.DeadlineExceeded {
// TODO: should we bubble up the error from Close? // TODO: should we bubble up the error from Close?
// TODO: do we need a mutex around this Close? // TODO: do we need a mutex around this Close?
@@ -335,7 +335,7 @@ func (obj *HTTPServerRes) Watch() error {
case <-closeSignal: // something shut us down early case <-closeSignal: // something shut us down early
return closeError return closeError
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -721,11 +721,11 @@ func (obj *HTTPFileRes) Close() error {
// Watch is the primary listener for this resource and it outputs events. This // Watch is the primary listener for this resource and it outputs events. This
// particular one does absolutely nothing but block until we've received a done // particular one does absolutely nothing but block until we've received a done
// signal. // signal.
func (obj *HTTPFileRes) Watch() error { func (obj *HTTPFileRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
select { select {
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
} }
//obj.init.Event() // notify engine of an event (this can block) //obj.init.Event() // notify engine of an event (this can block)

View File

@@ -129,10 +129,8 @@ func (obj *KVRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *KVRes) Watch() error { func (obj *KVRes) Watch(ctx context.Context) error {
// FIXME: add timeout to context ctx, cancel := context.WithCancel(ctx)
// The obj.init.DoneCtx context is closed by the engine to signal shutdown.
ctx, cancel := context.WithCancel(obj.init.DoneCtx)
defer cancel() defer cancel()
ch, err := obj.init.World.StrMapWatch(ctx, obj.getKey()) // get possible events! ch, err := obj.init.World.StrMapWatch(ctx, obj.getKey()) // get possible events!
@@ -158,7 +156,7 @@ func (obj *KVRes) Watch() error {
} }
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -192,7 +192,7 @@ func (obj *MountRes) Close() error {
// Watch listens for signals from the mount unit associated with the resource. // Watch listens for signals from the mount unit associated with the resource.
// It also watch for changes to /etc/fstab, where mounts are defined. // It also watch for changes to /etc/fstab, where mounts are defined.
func (obj *MountRes) Watch() error { func (obj *MountRes) Watch(ctx context.Context) error {
// make sure systemd is running // make sure systemd is running
if !systemdUtil.IsRunningSystemd() { if !systemdUtil.IsRunningSystemd() {
return fmt.Errorf("systemd is not running") return fmt.Errorf("systemd is not running")
@@ -266,7 +266,7 @@ func (obj *MountRes) Watch() error {
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
@@ -93,13 +94,13 @@ func (obj *MsgRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *MsgRes) Watch() error { func (obj *MsgRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
//var send = false // send event? //var send = false // send event?
for { for {
select { select {
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -21,6 +21,7 @@ package resources
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
@@ -202,7 +203,7 @@ func (obj *NetRes) Close() error {
// Watch listens for events from the specified interface via a netlink socket. // Watch listens for events from the specified interface via a netlink socket.
// TODO: currently gets events from ALL interfaces, would be nice to reject // TODO: currently gets events from ALL interfaces, would be nice to reject
// events from other interfaces. // events from other interfaces.
func (obj *NetRes) Watch() error { func (obj *NetRes) Watch(ctx context.Context) error {
// create a netlink socket for receiving network interface events // create a netlink socket for receiving network interface events
conn, err := socketset.NewSocketSet(rtmGrps, obj.socketFile, unix.NETLINK_ROUTE) conn, err := socketset.NewSocketSet(rtmGrps, obj.socketFile, unix.NETLINK_ROUTE)
if err != nil { if err != nil {
@@ -299,7 +300,7 @@ func (obj *NetRes) Watch() error {
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
@@ -62,11 +63,11 @@ func (obj *NoopRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *NoopRes) Watch() error { func (obj *NoopRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
select { select {
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
} }
//obj.init.Event() // notify engine of an event (this can block) //obj.init.Event() // notify engine of an event (this can block)

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
@@ -140,7 +141,7 @@ func (obj *NspawnRes) Close() error {
} }
// Watch for state changes and sends a message to the bus if there is a change. // Watch for state changes and sends a message to the bus if there is a change.
func (obj *NspawnRes) Watch() error { func (obj *NspawnRes) Watch(ctx context.Context) error {
// this resource depends on systemd to ensure that it's running // this resource depends on systemd to ensure that it's running
if !systemdUtil.IsRunningSystemd() { if !systemdUtil.IsRunningSystemd() {
return fmt.Errorf("systemd is not running") return fmt.Errorf("systemd is not running")
@@ -184,7 +185,7 @@ func (obj *NspawnRes) Watch() error {
send = true send = true
} }
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -173,7 +174,7 @@ Loop:
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *PasswordRes) Watch() error { func (obj *PasswordRes) Watch(ctx context.Context) error {
var err error var err error
obj.recWatcher, err = recwatch.NewRecWatcher(obj.path, false) obj.recWatcher, err = recwatch.NewRecWatcher(obj.path, false)
if err != nil { if err != nil {
@@ -196,7 +197,7 @@ func (obj *PasswordRes) Watch() error {
} }
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -92,11 +93,11 @@ func (obj *PippetRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *PippetRes) Watch() error { func (obj *PippetRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
select { select {
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
} }
//obj.init.Event() // notify engine of an event (this can block) //obj.init.Event() // notify engine of an event (this can block)

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"path" "path"
"strings" "strings"
@@ -104,7 +105,7 @@ func (obj *PkgRes) Close() error {
// uses the PackageKit UpdatesChanged signal to watch for changes. // uses the PackageKit UpdatesChanged signal to watch for changes.
// TODO: https://github.com/hughsie/PackageKit/issues/109 // TODO: https://github.com/hughsie/PackageKit/issues/109
// TODO: https://github.com/hughsie/PackageKit/issues/110 // TODO: https://github.com/hughsie/PackageKit/issues/110
func (obj *PkgRes) Watch() error { func (obj *PkgRes) Watch(ctx context.Context) error {
bus := packagekit.NewBus() bus := packagekit.NewBus()
if bus == nil { if bus == nil {
return fmt.Errorf("can't connect to PackageKit bus") return fmt.Errorf("can't connect to PackageKit bus")
@@ -143,7 +144,7 @@ func (obj *PkgRes) Watch() error {
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
@@ -69,11 +70,11 @@ func (obj *PrintRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *PrintRes) Watch() error { func (obj *PrintRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
select { select {
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
} }
//obj.init.Event() // notify engine of an event (this can block) //obj.init.Event() // notify engine of an event (this can block)

View File

@@ -510,7 +510,6 @@ func TestResources1(t *testing.T) {
}, },
// Watch listens on this for close/pause events. // Watch listens on this for close/pause events.
DoneCtx: doneCtx,
Debug: debug, Debug: debug,
Logf: logf, Logf: logf,
@@ -591,7 +590,7 @@ func TestResources1(t *testing.T) {
go func() { go func() {
defer wg.Done() defer wg.Done()
t.Logf("test #%d: running Watch", index) t.Logf("test #%d: running Watch", index)
if err := res.Watch(); err != nil { if err := res.Watch(doneCtx); err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: Watch failed: %s", index, err.Error()) t.Errorf("test #%d: Watch failed: %s", index, err.Error())
} }

View File

@@ -20,6 +20,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"os/user" "os/user"
"path" "path"
@@ -81,7 +82,7 @@ func (obj *SvcRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *SvcRes) Watch() error { func (obj *SvcRes) Watch(ctx context.Context) error {
// obj.Name: svc name // obj.Name: svc name
if !systemdUtil.IsRunningSystemd() { if !systemdUtil.IsRunningSystemd() {
return fmt.Errorf("systemd is not running") return fmt.Errorf("systemd is not running")
@@ -172,7 +173,7 @@ func (obj *SvcRes) Watch() error {
// loop so that we can see the changed invalid signal // loop so that we can see the changed invalid signal
obj.init.Logf("daemon reload") obj.init.Logf("daemon reload")
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
} else { } else {
@@ -215,7 +216,7 @@ func (obj *SvcRes) Watch() error {
case err := <-subErrors: case err := <-subErrors:
return errwrap.Wrapf(err, "unknown %s error", obj) return errwrap.Wrapf(err, "unknown %s error", obj)
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
@@ -124,11 +125,11 @@ func (obj *TestRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *TestRes) Watch() error { func (obj *TestRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
select { select {
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
} }
//obj.init.Event() // notify engine of an event (this can block) //obj.init.Event() // notify engine of an event (this can block)

View File

@@ -19,6 +19,7 @@ package resources
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -139,7 +140,7 @@ func (obj *TFTPServerRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *TFTPServerRes) Watch() error { func (obj *TFTPServerRes) Watch(ctx context.Context) error {
addr, err := net.ResolveUDPAddr("udp", obj.getAddress()) addr, err := net.ResolveUDPAddr("udp", obj.getAddress())
if err != nil { if err != nil {
return errwrap.Wrapf(err, "could not resolve address") return errwrap.Wrapf(err, "could not resolve address")
@@ -200,7 +201,7 @@ func (obj *TFTPServerRes) Watch() error {
case <-closeSignal: // something shut us down early case <-closeSignal: // something shut us down early
return closeError return closeError
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -547,11 +548,11 @@ func (obj *TFTPFileRes) Close() error {
// Watch is the primary listener for this resource and it outputs events. This // Watch is the primary listener for this resource and it outputs events. This
// particular one does absolutely nothing but block until we've received a done // particular one does absolutely nothing but block until we've received a done
// signal. // signal.
func (obj *TFTPFileRes) Watch() error { func (obj *TFTPFileRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
select { select {
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
} }
//obj.init.Event() // notify engine of an event (this can block) //obj.init.Event() // notify engine of an event (this can block)

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"time" "time"
@@ -70,7 +71,7 @@ func (obj *TimerRes) newTicker() *time.Ticker {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *TimerRes) Watch() error { func (obj *TimerRes) Watch(ctx context.Context) error {
// create a time.Ticker for the given interval // create a time.Ticker for the given interval
obj.ticker = obj.newTicker() obj.ticker = obj.newTicker()
defer obj.ticker.Stop() defer obj.ticker.Stop()
@@ -84,7 +85,7 @@ func (obj *TimerRes) Watch() error {
send = true send = true
obj.init.Logf("received tick") obj.init.Logf("received tick")
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -18,6 +18,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os/exec" "os/exec"
@@ -110,7 +111,7 @@ func (obj *UserRes) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *UserRes) Watch() error { func (obj *UserRes) Watch(ctx context.Context) error {
var err error var err error
obj.recWatcher, err = recwatch.NewRecWatcher(passwdFile, false) obj.recWatcher, err = recwatch.NewRecWatcher(passwdFile, false)
if err != nil { if err != nil {
@@ -139,7 +140,7 @@ func (obj *UserRes) Watch() error {
} }
send = true send = true
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -20,6 +20,7 @@
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"math/rand" "math/rand"
"net/url" "net/url"
@@ -294,7 +295,7 @@ func (obj *VirtRes) connect() (conn *libvirt.Connect, err error) {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *VirtRes) Watch() error { func (obj *VirtRes) Watch(ctx context.Context) error {
// FIXME: how will this work if we're polling? // FIXME: how will this work if we're polling?
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
defer wg.Wait() // wait until everyone has exited before we exit! defer wg.Wait() // wait until everyone has exited before we exit!
@@ -441,7 +442,7 @@ func (obj *VirtRes) Watch() error {
case err := <-errorChan: case err := <-errorChan:
return errwrap.Wrapf(err, "unknown libvirt error") return errwrap.Wrapf(err, "unknown libvirt error")
case <-obj.init.DoneCtx.Done(): // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }

View File

@@ -20,6 +20,7 @@
package util package util
import ( import (
"context"
"os/user" "os/user"
"reflect" "reflect"
"strconv" "strconv"
@@ -201,7 +202,7 @@ func (t *testEngineRes) String() string { return "test-string" }
func (t *testEngineRes) Validate() error { return nil } func (t *testEngineRes) Validate() error { return nil }
func (t *testEngineRes) Watch() error { return nil } func (t *testEngineRes) Watch(context.Context) error { return nil }
func TestStructKindToFieldNameTypeMap(t *testing.T) { func TestStructKindToFieldNameTypeMap(t *testing.T) {
k := "test-kind" k := "test-kind"