Commit d45006be authored by Brad Davidson's avatar Brad Davidson Committed by Brad Davidson

Move etcd ready channel into executor

This eliminates the final channel that was being passed around in an internal struct. The ETCD management code passes in a func that can be polled until etcd is ready; the executor is responsible for polling this after etcd is started and closing the etcd ready channel at the correct time. Signed-off-by: 's avatarBrad Davidson <brad.davidson@rancher.com>
parent 72bbd676
...@@ -597,7 +597,7 @@ func run(app *cli.Context, cfg *cmds.Server, leaderControllers server.CustomCont ...@@ -597,7 +597,7 @@ func run(app *cli.Context, cfg *cmds.Server, leaderControllers server.CustomCont
go func() { go func() {
if !serverConfig.ControlConfig.DisableETCD { if !serverConfig.ControlConfig.DisableETCD {
<-serverConfig.ControlConfig.Runtime.ETCDReady <-executor.ETCDReadyChan()
logrus.Info("ETCD server is now running") logrus.Info("ETCD server is now running")
} }
if !serverConfig.ControlConfig.DisableAPIServer { if !serverConfig.ControlConfig.DisableAPIServer {
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/k3s-io/k3s/pkg/clientaccess" "github.com/k3s-io/k3s/pkg/clientaccess"
"github.com/k3s-io/k3s/pkg/cluster/managed" "github.com/k3s-io/k3s/pkg/cluster/managed"
"github.com/k3s-io/k3s/pkg/daemons/config" "github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/daemons/executor"
"github.com/k3s-io/k3s/pkg/etcd" "github.com/k3s-io/k3s/pkg/etcd"
"github.com/k3s-io/kine/pkg/endpoint" "github.com/k3s-io/kine/pkg/endpoint"
pkgerrors "github.com/pkg/errors" pkgerrors "github.com/pkg/errors"
...@@ -34,36 +35,36 @@ func (c *Cluster) ListenAndServe(ctx context.Context) error { ...@@ -34,36 +35,36 @@ func (c *Cluster) ListenAndServe(ctx context.Context) error {
return c.initClusterAndHTTPS(ctx) return c.initClusterAndHTTPS(ctx)
} }
// Start handles writing/reading bootstrap data, and returns a channel // Start handles writing/reading bootstrap data. If embedded etcd is in use,
// that will be closed when datastore is ready. If embedded etcd is in use,
// a secondary call to Cluster.save is made. // a secondary call to Cluster.save is made.
func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) { func (c *Cluster) Start(ctx context.Context) error {
if c.config.DisableETCD || c.managedDB == nil {
// if etcd is disabled or we're using kine, perform a no-op start of etcd
// to close the etcd ready channel. When etcd is in use, this is handled by
// c.start() -> c.managedDB.Start() -> etcd.Start() -> executor.ETCD()
executor.ETCD(ctx, nil, nil, func(context.Context) error { return nil })
}
if c.config.DisableETCD { if c.config.DisableETCD {
ready := make(chan struct{}) return nil
defer close(ready)
return ready, nil
} }
// start managed etcd database; when kine is in use this is a no-op. // start managed etcd database; when kine is in use this is a no-op.
if err := c.start(ctx); err != nil { if err := c.start(ctx); err != nil {
return nil, pkgerrors.WithMessage(err, "start managed database") return pkgerrors.WithMessage(err, "start managed database")
} }
// get the wait channel for testing etcd server readiness; when kine is in
// use the channel is closed immediately.
ready := c.testClusterDB(ctx)
// set c.config.Datastore and c.config.Runtime.EtcdConfig with values // set c.config.Datastore and c.config.Runtime.EtcdConfig with values
// necessary to build etcd clients, and start kine listener if necessary. // necessary to build etcd clients, and start kine listener if necessary.
if err := c.startStorage(ctx, false); err != nil { if err := c.startStorage(ctx, false); err != nil {
return nil, err return err
} }
// if necessary, store bootstrap data to datastore. saveBootstrap is only set // if necessary, store bootstrap data to datastore. saveBootstrap is only set
// when using kine, so this can be done before the ready channel has been closed. // when using kine, so this can be done before the ready channel has been closed.
if c.saveBootstrap { if c.saveBootstrap {
if err := Save(ctx, c.config, false); err != nil { if err := Save(ctx, c.config, false); err != nil {
return nil, err return err
} }
} }
...@@ -71,7 +72,7 @@ func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) { ...@@ -71,7 +72,7 @@ func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) {
go func() { go func() {
for { for {
select { select {
case <-ready: case <-executor.ETCDReadyChan():
// always save to managed etcd, to ensure that any file modified locally are in sync with the datastore. // always save to managed etcd, to ensure that any file modified locally are in sync with the datastore.
// this will panic if multiple keys exist, to prevent nodes from running with different bootstrap data. // this will panic if multiple keys exist, to prevent nodes from running with different bootstrap data.
if err := Save(ctx, c.config, false); err != nil { if err := Save(ctx, c.config, false); err != nil {
...@@ -104,7 +105,7 @@ func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) { ...@@ -104,7 +105,7 @@ func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) {
}() }()
} }
return ready, nil return nil
} }
// startEtcdProxy starts an etcd load-balancer proxy, for control-plane-only nodes // startEtcdProxy starts an etcd load-balancer proxy, for control-plane-only nodes
......
...@@ -23,36 +23,6 @@ import ( ...@@ -23,36 +23,6 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
) )
// testClusterDB returns a channel that will be closed when the datastore connection is available.
// The datastore is tested for readiness every 5 seconds until the test succeeds.
func (c *Cluster) testClusterDB(ctx context.Context) <-chan struct{} {
result := make(chan struct{})
if c.managedDB == nil {
close(result)
return result
}
go func() {
defer close(result)
for {
if err := c.managedDB.Test(ctx); err != nil {
logrus.Infof("Failed to test data store connection: %v", err)
} else {
logrus.Info(c.managedDB.EndpointName() + " data store connection OK")
return
}
select {
case <-time.After(5 * time.Second):
case <-ctx.Done():
return
}
}
}()
return result
}
// start starts the database, unless a cluster reset has been requested, in which case // start starts the database, unless a cluster reset has been requested, in which case
// it does that instead. // it does that instead.
func (c *Cluster) start(ctx context.Context) error { func (c *Cluster) start(ctx context.Context) error {
......
...@@ -20,7 +20,6 @@ type Driver interface { ...@@ -20,7 +20,6 @@ type Driver interface {
IsReset() (bool, error) IsReset() (bool, error)
ResetFile() string ResetFile() string
Start(ctx context.Context, clientAccessInfo *clientaccess.Info) error Start(ctx context.Context, clientAccessInfo *clientaccess.Info) error
Test(ctx context.Context) error
Restore(ctx context.Context) error Restore(ctx context.Context) error
EndpointName() string EndpointName() string
Snapshot(ctx context.Context) (*SnapshotResult, error) Snapshot(ctx context.Context) (*SnapshotResult, error)
......
...@@ -314,7 +314,6 @@ type ControlRuntimeBootstrap struct { ...@@ -314,7 +314,6 @@ type ControlRuntimeBootstrap struct {
type ControlRuntime struct { type ControlRuntime struct {
ControlRuntimeBootstrap ControlRuntimeBootstrap
ETCDReady <-chan struct{}
StartupHooksWg *sync.WaitGroup StartupHooksWg *sync.WaitGroup
ClusterControllerStarts map[string]leader.Callback ClusterControllerStarts map[string]leader.Callback
LeaderElectedClusterControllerStarts map[string]leader.Callback LeaderElectedClusterControllerStarts map[string]leader.Callback
...@@ -384,7 +383,7 @@ type ControlRuntime struct { ...@@ -384,7 +383,7 @@ type ControlRuntime struct {
type Cluster interface { type Cluster interface {
Bootstrap(ctx context.Context, reset bool) error Bootstrap(ctx context.Context, reset bool) error
ListenAndServe(ctx context.Context) error ListenAndServe(ctx context.Context) error
Start(ctx context.Context) (<-chan struct{}, error) Start(ctx context.Context) error
} }
type CoreFactory interface { type CoreFactory interface {
......
...@@ -70,10 +70,8 @@ func Prepare(ctx context.Context, cfg *config.Control) error { ...@@ -70,10 +70,8 @@ func Prepare(ctx context.Context, cfg *config.Control) error {
// Server starts the apiserver and whatever other control-plane components are // Server starts the apiserver and whatever other control-plane components are
// not disabled on this node. // not disabled on this node.
func Server(ctx context.Context, cfg *config.Control) error { func Server(ctx context.Context, cfg *config.Control) error {
if ready, err := cfg.Cluster.Start(ctx); err != nil { if err := cfg.Cluster.Start(ctx); err != nil {
return pkgerrors.WithMessage(err, "failed to start cluster") return pkgerrors.WithMessage(err, "failed to start cluster")
} else {
cfg.Runtime.ETCDReady = ready
} }
if !cfg.DisableAPIServer { if !cfg.DisableAPIServer {
...@@ -264,7 +262,7 @@ func apiServer(ctx context.Context, cfg *config.Control) error { ...@@ -264,7 +262,7 @@ func apiServer(ctx context.Context, cfg *config.Control) error {
logrus.Infof("Running kube-apiserver %s", config.ArgString(args)) logrus.Infof("Running kube-apiserver %s", config.ArgString(args))
return executor.APIServer(ctx, runtime.ETCDReady, args) return executor.APIServer(ctx, args)
} }
func defaults(config *config.Control) { func defaults(config *config.Control) {
......
...@@ -45,6 +45,7 @@ func init() { ...@@ -45,6 +45,7 @@ func init() {
func (e *Embedded) Bootstrap(ctx context.Context, nodeConfig *daemonconfig.Node, cfg cmds.Agent) error { func (e *Embedded) Bootstrap(ctx context.Context, nodeConfig *daemonconfig.Node, cfg cmds.Agent) error {
e.apiServerReady = util.APIServerReadyChan(ctx, nodeConfig.AgentConfig.KubeConfigK3sController, util.DefaultAPIServerReadyTimeout) e.apiServerReady = util.APIServerReadyChan(ctx, nodeConfig.AgentConfig.KubeConfigK3sController, util.DefaultAPIServerReadyTimeout)
e.etcdReady = make(chan struct{})
e.criReady = make(chan struct{}) e.criReady = make(chan struct{})
e.nodeConfig = nodeConfig e.nodeConfig = nodeConfig
...@@ -119,12 +120,12 @@ func (*Embedded) APIServerHandlers(ctx context.Context) (authenticator.Request, ...@@ -119,12 +120,12 @@ func (*Embedded) APIServerHandlers(ctx context.Context) (authenticator.Request,
return startupConfig.Authenticator, startupConfig.Handler, nil return startupConfig.Authenticator, startupConfig.Handler, nil
} }
func (*Embedded) APIServer(ctx context.Context, etcdReady <-chan struct{}, args []string) error { func (e *Embedded) APIServer(ctx context.Context, args []string) error {
command := apiapp.NewAPIServerCommand(ctx.Done()) command := apiapp.NewAPIServerCommand(ctx.Done())
command.SetArgs(args) command.SetArgs(args)
go func() { go func() {
<-etcdReady <-e.ETCDReadyChan()
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
logrus.WithField("stack", string(debug.Stack())).Fatalf("apiserver panic: %v", err) logrus.WithField("stack", string(debug.Stack())).Fatalf("apiserver panic: %v", err)
...@@ -262,6 +263,13 @@ func (e *Embedded) APIServerReadyChan() <-chan struct{} { ...@@ -262,6 +263,13 @@ func (e *Embedded) APIServerReadyChan() <-chan struct{} {
return e.apiServerReady return e.apiServerReady
} }
func (e *Embedded) ETCDReadyChan() <-chan struct{} {
if e.etcdReady == nil {
panic("executor not bootstrapped")
}
return e.etcdReady
}
func (e *Embedded) CRIReadyChan() <-chan struct{} { func (e *Embedded) CRIReadyChan() <-chan struct{} {
if e.criReady == nil { if e.criReady == nil {
panic("executor not bootstrapped") panic("executor not bootstrapped")
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"errors" "errors"
"os" "os"
"path/filepath" "path/filepath"
"time"
daemonconfig "github.com/k3s-io/k3s/pkg/daemons/config" daemonconfig "github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/version" "github.com/k3s-io/k3s/pkg/version"
...@@ -17,11 +18,40 @@ import ( ...@@ -17,11 +18,40 @@ import (
// of the embedded execututor is disabled by build flags // of the embedded execututor is disabled by build flags
type Embedded struct { type Embedded struct {
apiServerReady <-chan struct{} apiServerReady <-chan struct{}
etcdReady chan struct{}
criReady chan struct{} criReady chan struct{}
nodeConfig *daemonconfig.Node nodeConfig *daemonconfig.Node
} }
func (e *Embedded) ETCD(ctx context.Context, args ETCDConfig, extraArgs []string) error { func (e *Embedded) ETCD(ctx context.Context, args *ETCDConfig, extraArgs []string, test TestFunc) error {
// An unbootstrapped executor is used to start up a temporary embedded etcd when reconciling.
// This temporary executor doesn't have any ready channels set up, so don't bother testing.
if e.etcdReady != nil {
go func() {
defer close(e.etcdReady)
for {
if err := test(ctx); err != nil {
logrus.Infof("Failed to test etcd connection: %v", err)
} else {
logrus.Info("Connection to etcd is ready")
return
}
select {
case <-time.After(5 * time.Second):
case <-ctx.Done():
return
}
}
}()
}
// nil args indicates a no-op start; all we need to do is wait for the test
// func to indicate readiness and close the channel.
if args == nil {
return nil
}
configFile, err := args.ToConfigFile(extraArgs) configFile, err := args.ToConfigFile(extraArgs)
if err != nil { if err != nil {
return err return err
......
...@@ -20,21 +20,25 @@ var ( ...@@ -20,21 +20,25 @@ var (
executor Executor executor Executor
) )
// TestFunc is the signature of a function that returns nil error when the component is ready
type TestFunc func(context.Context) error
type Executor interface { type Executor interface {
Bootstrap(ctx context.Context, nodeConfig *daemonconfig.Node, cfg cmds.Agent) error Bootstrap(ctx context.Context, nodeConfig *daemonconfig.Node, cfg cmds.Agent) error
Kubelet(ctx context.Context, args []string) error Kubelet(ctx context.Context, args []string) error
KubeProxy(ctx context.Context, args []string) error KubeProxy(ctx context.Context, args []string) error
APIServerHandlers(ctx context.Context) (authenticator.Request, http.Handler, error) APIServerHandlers(ctx context.Context) (authenticator.Request, http.Handler, error)
APIServer(ctx context.Context, etcdReady <-chan struct{}, args []string) error APIServer(ctx context.Context, args []string) error
Scheduler(ctx context.Context, nodeReady <-chan struct{}, args []string) error Scheduler(ctx context.Context, nodeReady <-chan struct{}, args []string) error
ControllerManager(ctx context.Context, args []string) error ControllerManager(ctx context.Context, args []string) error
CurrentETCDOptions() (InitialOptions, error) CurrentETCDOptions() (InitialOptions, error)
ETCD(ctx context.Context, args ETCDConfig, extraArgs []string) error ETCD(ctx context.Context, args *ETCDConfig, extraArgs []string, test TestFunc) error
CloudControllerManager(ctx context.Context, ccmRBACReady <-chan struct{}, args []string) error CloudControllerManager(ctx context.Context, ccmRBACReady <-chan struct{}, args []string) error
Containerd(ctx context.Context, node *daemonconfig.Node) error Containerd(ctx context.Context, node *daemonconfig.Node) error
Docker(ctx context.Context, node *daemonconfig.Node) error Docker(ctx context.Context, node *daemonconfig.Node) error
CRI(ctx context.Context, node *daemonconfig.Node) error CRI(ctx context.Context, node *daemonconfig.Node) error
APIServerReadyChan() <-chan struct{} APIServerReadyChan() <-chan struct{}
ETCDReadyChan() <-chan struct{}
CRIReadyChan() <-chan struct{} CRIReadyChan() <-chan struct{}
} }
...@@ -153,8 +157,8 @@ func APIServerHandlers(ctx context.Context) (authenticator.Request, http.Handler ...@@ -153,8 +157,8 @@ func APIServerHandlers(ctx context.Context) (authenticator.Request, http.Handler
return executor.APIServerHandlers(ctx) return executor.APIServerHandlers(ctx)
} }
func APIServer(ctx context.Context, etcdReady <-chan struct{}, args []string) error { func APIServer(ctx context.Context, args []string) error {
return executor.APIServer(ctx, etcdReady, args) return executor.APIServer(ctx, args)
} }
func Scheduler(ctx context.Context, nodeReady <-chan struct{}, args []string) error { func Scheduler(ctx context.Context, nodeReady <-chan struct{}, args []string) error {
...@@ -169,8 +173,8 @@ func CurrentETCDOptions() (InitialOptions, error) { ...@@ -169,8 +173,8 @@ func CurrentETCDOptions() (InitialOptions, error) {
return executor.CurrentETCDOptions() return executor.CurrentETCDOptions()
} }
func ETCD(ctx context.Context, args ETCDConfig, extraArgs []string) error { func ETCD(ctx context.Context, args *ETCDConfig, extraArgs []string, test TestFunc) error {
return executor.ETCD(ctx, args, extraArgs) return executor.ETCD(ctx, args, extraArgs, test)
} }
func CloudControllerManager(ctx context.Context, ccmRBACReady <-chan struct{}, args []string) error { func CloudControllerManager(ctx context.Context, ccmRBACReady <-chan struct{}, args []string) error {
...@@ -193,6 +197,10 @@ func APIServerReadyChan() <-chan struct{} { ...@@ -193,6 +197,10 @@ func APIServerReadyChan() <-chan struct{} {
return executor.APIServerReadyChan() return executor.APIServerReadyChan()
} }
func ETCDReadyChan() <-chan struct{} {
return executor.ETCDReadyChan()
}
func CRIReadyChan() <-chan struct{} { func CRIReadyChan() <-chan struct{} {
return executor.CRIReadyChan() return executor.CRIReadyChan()
} }
...@@ -1009,7 +1009,7 @@ func (e *ETCD) listenClientHTTPURLs() string { ...@@ -1009,7 +1009,7 @@ func (e *ETCD) listenClientHTTPURLs() string {
// cluster calls the executor to start etcd running with the provided configuration. // cluster calls the executor to start etcd running with the provided configuration.
func (e *ETCD) cluster(ctx context.Context, reset bool, options executor.InitialOptions) error { func (e *ETCD) cluster(ctx context.Context, reset bool, options executor.InitialOptions) error {
ctx, e.cancel = context.WithCancel(ctx) ctx, e.cancel = context.WithCancel(ctx)
return executor.ETCD(ctx, executor.ETCDConfig{ return executor.ETCD(ctx, &executor.ETCDConfig{
Name: e.name, Name: e.name,
InitialOptions: options, InitialOptions: options,
ForceNewCluster: reset, ForceNewCluster: reset,
...@@ -1039,7 +1039,7 @@ func (e *ETCD) cluster(ctx context.Context, reset bool, options executor.Initial ...@@ -1039,7 +1039,7 @@ func (e *ETCD) cluster(ctx context.Context, reset bool, options executor.Initial
ExperimentalInitialCorruptCheck: true, ExperimentalInitialCorruptCheck: true,
ExperimentalWatchProgressNotifyInterval: e.config.Datastore.NotifyInterval, ExperimentalWatchProgressNotifyInterval: e.config.Datastore.NotifyInterval,
}, e.config.ExtraEtcdArgs) }, e.config.ExtraEtcdArgs, e.Test)
} }
func (e *ETCD) StartEmbeddedTemporary(ctx context.Context) error { func (e *ETCD) StartEmbeddedTemporary(ctx context.Context) error {
...@@ -1089,7 +1089,7 @@ func (e *ETCD) StartEmbeddedTemporary(ctx context.Context) error { ...@@ -1089,7 +1089,7 @@ func (e *ETCD) StartEmbeddedTemporary(ctx context.Context) error {
embedded := executor.Embedded{} embedded := executor.Embedded{}
ctx, e.cancel = context.WithCancel(ctx) ctx, e.cancel = context.WithCancel(ctx)
return embedded.ETCD(ctx, executor.ETCDConfig{ return embedded.ETCD(ctx, &executor.ETCDConfig{
InitialOptions: executor.InitialOptions{AdvertisePeerURL: peerURL}, InitialOptions: executor.InitialOptions{AdvertisePeerURL: peerURL},
DataDir: tmpDataDir, DataDir: tmpDataDir,
ForceNewCluster: true, ForceNewCluster: true,
...@@ -1106,7 +1106,7 @@ func (e *ETCD) StartEmbeddedTemporary(ctx context.Context) error { ...@@ -1106,7 +1106,7 @@ func (e *ETCD) StartEmbeddedTemporary(ctx context.Context) error {
ExperimentalInitialCorruptCheck: true, ExperimentalInitialCorruptCheck: true,
ExperimentalWatchProgressNotifyInterval: e.config.Datastore.NotifyInterval, ExperimentalWatchProgressNotifyInterval: e.config.Datastore.NotifyInterval,
}, append(e.config.ExtraEtcdArgs, "--max-snapshots=0", "--max-wals=0")) }, append(e.config.ExtraEtcdArgs, "--max-snapshots=0", "--max-wals=0"), e.Test)
} }
func addPort(address string, offset int) (string, error) { func addPort(address string, offset int) (string, error) {
......
...@@ -30,7 +30,7 @@ func (e *Executor) APIServerHandlers(ctx context.Context) (authenticator.Request ...@@ -30,7 +30,7 @@ func (e *Executor) APIServerHandlers(ctx context.Context) (authenticator.Request
return nil, nil, errors.New("not implemented") return nil, nil, errors.New("not implemented")
} }
func (e *Executor) APIServer(ctx context.Context, etcdReady <-chan struct{}, args []string) error { func (e *Executor) APIServer(ctx context.Context, args []string) error {
return errors.New("not implemented") return errors.New("not implemented")
} }
...@@ -46,9 +46,9 @@ func (e *Executor) CurrentETCDOptions() (executor.InitialOptions, error) { ...@@ -46,9 +46,9 @@ func (e *Executor) CurrentETCDOptions() (executor.InitialOptions, error) {
return executor.InitialOptions{}, nil return executor.InitialOptions{}, nil
} }
func (e *Executor) ETCD(ctx context.Context, args executor.ETCDConfig, extraArgs []string) error { func (e *Executor) ETCD(ctx context.Context, args *executor.ETCDConfig, extraArgs []string, test executor.TestFunc) error {
embed := &executor.Embedded{} embed := &executor.Embedded{}
return embed.ETCD(ctx, args, extraArgs) return embed.ETCD(ctx, args, extraArgs, test)
} }
func (e *Executor) CloudControllerManager(ctx context.Context, ccmRBACReady <-chan struct{}, args []string) error { func (e *Executor) CloudControllerManager(ctx context.Context, ccmRBACReady <-chan struct{}, args []string) error {
...@@ -73,6 +73,12 @@ func (e *Executor) APIServerReadyChan() <-chan struct{} { ...@@ -73,6 +73,12 @@ func (e *Executor) APIServerReadyChan() <-chan struct{} {
return c return c
} }
func (e *Executor) ETCDReadyChan() <-chan struct{} {
c := make(chan struct{})
close(c)
return c
}
func (e *Executor) CRIReadyChan() <-chan struct{} { func (e *Executor) CRIReadyChan() <-chan struct{} {
c := make(chan struct{}) c := make(chan struct{})
close(c) close(c)
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/k3s-io/k3s/pkg/daemons/config" "github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/daemons/control/deps" "github.com/k3s-io/k3s/pkg/daemons/control/deps"
"github.com/k3s-io/k3s/pkg/daemons/executor" "github.com/k3s-io/k3s/pkg/daemons/executor"
"github.com/k3s-io/k3s/tests/mock"
) )
// GenerateDataDir creates a temporary directory at "/tmp/k3s/<RANDOM_STRING>/". // GenerateDataDir creates a temporary directory at "/tmp/k3s/<RANDOM_STRING>/".
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment