Add jitter to client config retry

Also:
* Replaces labeled for/continue RETRY loops with wait helpers for improved readability
* Pulls secrets and nodes from cache for node password verification
* Migrate nodepassword tests to wrangler mocks for better code reuse

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
pull/8889/head
Brad Davidson 2023-11-15 01:24:58 +00:00 committed by Brad Davidson
parent d8c98539f1
commit 0b7b789529
7 changed files with 138 additions and 142 deletions

6
go.mod
View File

@ -23,7 +23,7 @@ replace (
github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.8 github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.8
github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
github.com/opencontainers/selinux => github.com/opencontainers/selinux v1.10.1 github.com/opencontainers/selinux => github.com/opencontainers/selinux v1.10.1
github.com/rancher/wrangler => github.com/rancher/wrangler v1.1.1-0.20230425173236-39a4707f0689 github.com/rancher/wrangler => github.com/rancher/wrangler v1.1.1-0.20230807182002-35cb42e6a915
go.etcd.io/etcd/api/v3 => github.com/k3s-io/etcd/api/v3 v3.5.3-k3s1 go.etcd.io/etcd/api/v3 => github.com/k3s-io/etcd/api/v3 v3.5.3-k3s1
go.etcd.io/etcd/client/pkg/v3 => github.com/k3s-io/etcd/client/pkg/v3 v3.5.3-k3s1 go.etcd.io/etcd/client/pkg/v3 => github.com/k3s-io/etcd/client/pkg/v3 v3.5.3-k3s1
go.etcd.io/etcd/client/v3 => github.com/k3s-io/etcd/client/v3 v3.5.3-k3s1 go.etcd.io/etcd/client/v3 => github.com/k3s-io/etcd/client/v3 v3.5.3-k3s1
@ -103,6 +103,7 @@ require (
github.com/go-bindata/go-bindata v3.1.2+incompatible github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/go-sql-driver/mysql v1.7.1 github.com/go-sql-driver/mysql v1.7.1
github.com/go-test/deep v1.0.7 github.com/go-test/deep v1.0.7
github.com/golang/mock v1.6.0
github.com/google/cadvisor v0.47.1 github.com/google/cadvisor v0.47.1
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
@ -123,7 +124,7 @@ require (
github.com/otiai10/copy v1.7.0 github.com/otiai10/copy v1.7.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rancher/dynamiclistener v0.3.6-rc2 github.com/rancher/dynamiclistener v0.3.6-rc2
github.com/rancher/lasso v0.0.0-20221227210133-6ea88ca2fbcc github.com/rancher/lasso v0.0.0-20230629200414-8a54b32e6792
github.com/rancher/remotedialer v0.3.0 github.com/rancher/remotedialer v0.3.0
github.com/rancher/wharfie v0.5.3 github.com/rancher/wharfie v0.5.3
github.com/rancher/wrangler v1.1.1 github.com/rancher/wrangler v1.1.1
@ -254,7 +255,6 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/cel-go v0.12.6 // indirect github.com/google/cel-go v0.12.6 // indirect

8
go.sum
View File

@ -982,14 +982,14 @@ github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPH
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rancher/dynamiclistener v0.3.6-rc2 h1:Y1nai+Xv+4qqlB3c+hmrY2uBo1EcCDU9kmN5hbnmZhA= github.com/rancher/dynamiclistener v0.3.6-rc2 h1:Y1nai+Xv+4qqlB3c+hmrY2uBo1EcCDU9kmN5hbnmZhA=
github.com/rancher/dynamiclistener v0.3.6-rc2/go.mod h1:wOh62hdJIgyqTdD/VAHO77UPKAbUsJJ5gYRjzgBL3Wo= github.com/rancher/dynamiclistener v0.3.6-rc2/go.mod h1:wOh62hdJIgyqTdD/VAHO77UPKAbUsJJ5gYRjzgBL3Wo=
github.com/rancher/lasso v0.0.0-20221227210133-6ea88ca2fbcc h1:29VHrInLV4qSevvcvhBj5UhQWkPShxrxv4AahYg2Scw= github.com/rancher/lasso v0.0.0-20230629200414-8a54b32e6792 h1:IaPhDqppVYX2v/nCR8j2i0nqOLD5yggzzy39QUlcqDw=
github.com/rancher/lasso v0.0.0-20221227210133-6ea88ca2fbcc/go.mod h1:dEfC9eFQigj95lv/JQ8K5e7+qQCacWs1aIA6nLxKzT8= github.com/rancher/lasso v0.0.0-20230629200414-8a54b32e6792/go.mod h1:dNcwXjcqgdOuKFIVETNAPURRh3e5PAi/nWUjj+MLVZA=
github.com/rancher/remotedialer v0.3.0 h1:y1EO8JCsgZo0RcqTUp6U8FXcBAv27R+TLnWRcpvX1sM= github.com/rancher/remotedialer v0.3.0 h1:y1EO8JCsgZo0RcqTUp6U8FXcBAv27R+TLnWRcpvX1sM=
github.com/rancher/remotedialer v0.3.0/go.mod h1:BwwztuvViX2JrLLUwDlsYt5DiyUwHLlzynRwkZLAY0Q= github.com/rancher/remotedialer v0.3.0/go.mod h1:BwwztuvViX2JrLLUwDlsYt5DiyUwHLlzynRwkZLAY0Q=
github.com/rancher/wharfie v0.5.3 h1:6hiO26H7YTgChbLAE6JppxFRjaH3tbKfMItv/LqV0Q0= github.com/rancher/wharfie v0.5.3 h1:6hiO26H7YTgChbLAE6JppxFRjaH3tbKfMItv/LqV0Q0=
github.com/rancher/wharfie v0.5.3/go.mod h1:Ebpai7digxegLroBseeC54XRBt5we3DgFS6kAE2ho+o= github.com/rancher/wharfie v0.5.3/go.mod h1:Ebpai7digxegLroBseeC54XRBt5we3DgFS6kAE2ho+o=
github.com/rancher/wrangler v1.1.1-0.20230425173236-39a4707f0689 h1:otb4OjgXH2b8a4C9g76jCDuTF3opjaYffZ55SiVe7KU= github.com/rancher/wrangler v1.1.1-0.20230807182002-35cb42e6a915 h1:CNwXnngrb5PgPe4nr7G5jS298YkhS6udMnNvyBOboFQ=
github.com/rancher/wrangler v1.1.1-0.20230425173236-39a4707f0689/go.mod h1:D6Tu6oVX8aGtCHsMCtYaysgVK3ad920MTSeAu7rzb5U= github.com/rancher/wrangler v1.1.1-0.20230807182002-35cb42e6a915/go.mod h1:0oPjv01nvzeavcmeuT0xMlGKs9IJaNk5NCPPQq1n8Ro=
github.com/rasky/go-xdr v0.0.0-20170217172119-4930550ba2e2/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o= github.com/rasky/go-xdr v0.0.0-20170217172119-4930550ba2e2/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=

View File

@ -33,6 +33,7 @@ import (
"github.com/rancher/wrangler/pkg/slice" "github.com/rancher/wrangler/pkg/slice"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/wait"
utilsnet "k8s.io/utils/net" utilsnet "k8s.io/utils/net"
) )
@ -42,22 +43,28 @@ const (
// Get returns a pointer to a completed Node configuration struct, // Get returns a pointer to a completed Node configuration struct,
// containing a merging of the local CLI configuration with settings from the server. // containing a merging of the local CLI configuration with settings from the server.
// Node configuration includes client certificates, which requires node password verification,
// so this is somewhat computationally expensive on the server side, and is retried with jitter
// to avoid having clients hammer on the server at fixed periods.
// A call to this will bock until agent configuration is successfully returned by the // A call to this will bock until agent configuration is successfully returned by the
// server. // server.
func Get(ctx context.Context, agent cmds.Agent, proxy proxy.Proxy) *config.Node { func Get(ctx context.Context, agent cmds.Agent, proxy proxy.Proxy) *config.Node {
ticker := time.NewTicker(5 * time.Second) var agentConfig *config.Node
defer ticker.Stop() var err error
RETRY:
for { // This would be more clear as wait.PollImmediateUntilWithContext, but that function
agentConfig, err := get(ctx, &agent, proxy) // does not support jittering, so we instead use wait.JitterUntilWithContext, and cancel
// the context on success.
ctx, cancel := context.WithCancel(ctx)
wait.JitterUntilWithContext(ctx, func(ctx context.Context) {
agentConfig, err = get(ctx, &agent, proxy)
if err != nil { if err != nil {
logrus.Infof("Waiting to retrieve agent configuration; server is not ready: %v", err) logrus.Infof("Waiting to retrieve agent configuration; server is not ready: %v", err)
for range ticker.C { } else {
continue RETRY cancel()
}
} }
return agentConfig }, 5*time.Second, 1.0, true)
} return agentConfig
} }
// KubeProxyDisabled returns a bool indicating whether or not kube-proxy has been disabled in the // KubeProxyDisabled returns a bool indicating whether or not kube-proxy has been disabled in the
@ -65,42 +72,40 @@ RETRY:
// after all startup hooks have completed, so a call to this will block until after the server's // after all startup hooks have completed, so a call to this will block until after the server's
// readyz endpoint returns OK. // readyz endpoint returns OK.
func KubeProxyDisabled(ctx context.Context, node *config.Node, proxy proxy.Proxy) bool { func KubeProxyDisabled(ctx context.Context, node *config.Node, proxy proxy.Proxy) bool {
ticker := time.NewTicker(5 * time.Second) var disabled bool
defer ticker.Stop() var err error
RETRY:
for { wait.PollImmediateUntilWithContext(ctx, 5*time.Second, func(ctx context.Context) (bool, error) {
disabled, err := getKubeProxyDisabled(ctx, node, proxy) disabled, err = getKubeProxyDisabled(ctx, node, proxy)
if err != nil { if err != nil {
logrus.Infof("Waiting to retrieve kube-proxy configuration; server is not ready: %v", err) logrus.Infof("Waiting to retrieve kube-proxy configuration; server is not ready: %v", err)
for range ticker.C { return false, nil
continue RETRY
}
} }
return disabled return true, nil
} })
return disabled
} }
// APIServers returns a list of apiserver endpoints, suitable for seeding client loadbalancer configurations. // APIServers returns a list of apiserver endpoints, suitable for seeding client loadbalancer configurations.
// This function will block until it can return a populated list of apiservers, or if the remote server returns // This function will block until it can return a populated list of apiservers, or if the remote server returns
// an error (indicating that it does not support this functionality). // an error (indicating that it does not support this functionality).
func APIServers(ctx context.Context, node *config.Node, proxy proxy.Proxy) []string { func APIServers(ctx context.Context, node *config.Node, proxy proxy.Proxy) []string {
ticker := time.NewTicker(5 * time.Second) var addresses []string
defer ticker.Stop() var err error
RETRY:
for { wait.PollImmediateUntilWithContext(ctx, 5*time.Second, func(ctx context.Context) (bool, error) {
addresses, err := getAPIServers(ctx, node, proxy) addresses, err = getAPIServers(ctx, node, proxy)
if err != nil { if err != nil {
logrus.Infof("Failed to retrieve list of apiservers from server: %v", err) logrus.Infof("Failed to retrieve list of apiservers from server: %v", err)
return nil return false, err
} }
if len(addresses) == 0 { if len(addresses) == 0 {
logrus.Infof("Waiting for apiserver addresses") logrus.Infof("Waiting for apiserver addresses")
for range ticker.C { return false, nil
continue RETRY
}
} }
return addresses return true, nil
} })
return addresses
} }
type HTTPRequester func(u string, client *http.Client, username, password, token string) ([]byte, error) type HTTPRequester func(u string, client *http.Client, username, password, token string) ([]byte, error)

View File

@ -13,15 +13,14 @@ import (
func Register(ctx context.Context, func Register(ctx context.Context,
modCoreDNS bool, modCoreDNS bool,
secretClient coreclient.SecretClient, secrets coreclient.SecretController,
configMap coreclient.ConfigMapController, configMaps coreclient.ConfigMapController,
nodes coreclient.NodeController, nodes coreclient.NodeController,
) error { ) error {
h := &handler{ h := &handler{
modCoreDNS: modCoreDNS, modCoreDNS: modCoreDNS,
secretClient: secretClient, secrets: secrets,
configCache: configMap.Cache(), configMaps: configMaps,
configClient: configMap,
} }
nodes.OnChange(ctx, "node", h.onChange) nodes.OnChange(ctx, "node", h.onChange)
nodes.OnRemove(ctx, "node", h.onRemove) nodes.OnRemove(ctx, "node", h.onRemove)
@ -30,10 +29,9 @@ func Register(ctx context.Context,
} }
type handler struct { type handler struct {
modCoreDNS bool modCoreDNS bool
secretClient coreclient.SecretClient secrets coreclient.SecretController
configCache coreclient.ConfigMapCache configMaps coreclient.ConfigMapController
configClient coreclient.ConfigMapClient
} }
func (h *handler) onChange(key string, node *core.Node) (*core.Node, error) { func (h *handler) onChange(key string, node *core.Node) (*core.Node, error) {
@ -78,7 +76,7 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b
return nil return nil
} }
configMapCache, err := h.configCache.Get("kube-system", "coredns") configMapCache, err := h.configMaps.Cache().Get("kube-system", "coredns")
if err != nil || configMapCache == nil { if err != nil || configMapCache == nil {
logrus.Warn(errors.Wrap(err, "Unable to fetch coredns config map")) logrus.Warn(errors.Wrap(err, "Unable to fetch coredns config map"))
return nil return nil
@ -120,7 +118,7 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b
} }
configMap.Data["NodeHosts"] = newHosts configMap.Data["NodeHosts"] = newHosts
if _, err := h.configClient.Update(configMap); err != nil { if _, err := h.configMaps.Update(configMap); err != nil {
return err return err
} }
@ -135,5 +133,5 @@ func (h *handler) updateCoreDNSConfigMap(nodeName, nodeAddress string, removed b
} }
func (h *handler) removeNodePassword(nodeName string) error { func (h *handler) removeNodePassword(nodeName string) error {
return nodepassword.Delete(h.secretClient, nodeName) return nodepassword.Delete(h.secrets, nodeName)
} }

View File

@ -51,9 +51,9 @@ func getSecretName(nodeName string) string {
return strings.ToLower(nodeName + ".node-password." + version.Program) return strings.ToLower(nodeName + ".node-password." + version.Program)
} }
func verifyHash(secretClient coreclient.SecretClient, nodeName, pass string) error { func verifyHash(secretClient coreclient.SecretController, nodeName, pass string) error {
name := getSecretName(nodeName) name := getSecretName(nodeName)
secret, err := secretClient.Get(metav1.NamespaceSystem, name, metav1.GetOptions{}) secret, err := secretClient.Cache().Get(metav1.NamespaceSystem, name)
if err != nil { if err != nil {
return &passwordError{node: nodeName, err: err} return &passwordError{node: nodeName, err: err}
} }
@ -67,7 +67,7 @@ func verifyHash(secretClient coreclient.SecretClient, nodeName, pass string) err
} }
// Ensure will verify a node-password secret if it exists, otherwise it will create one // Ensure will verify a node-password secret if it exists, otherwise it will create one
func Ensure(secretClient coreclient.SecretClient, nodeName, pass string) error { func Ensure(secretClient coreclient.SecretController, nodeName, pass string) error {
err := verifyHash(secretClient, nodeName, pass) err := verifyHash(secretClient, nodeName, pass)
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
var hash string var hash string
@ -88,12 +88,12 @@ func Ensure(secretClient coreclient.SecretClient, nodeName, pass string) error {
} }
// Delete will remove a node-password secret // Delete will remove a node-password secret
func Delete(secretClient coreclient.SecretClient, nodeName string) error { func Delete(secretClient coreclient.SecretController, nodeName string) error {
return secretClient.Delete(metav1.NamespaceSystem, getSecretName(nodeName), &metav1.DeleteOptions{}) return secretClient.Delete(metav1.NamespaceSystem, getSecretName(nodeName), &metav1.DeleteOptions{})
} }
// MigrateFile moves password file entries to secrets // MigrateFile moves password file entries to secrets
func MigrateFile(secretClient coreclient.SecretClient, nodeClient coreclient.NodeClient, passwordFile string) error { func MigrateFile(secretClient coreclient.SecretController, nodeClient coreclient.NodeController, passwordFile string) error {
_, err := os.Stat(passwordFile) _, err := os.Stat(passwordFile)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil return nil
@ -108,11 +108,9 @@ func MigrateFile(secretClient coreclient.SecretClient, nodeClient coreclient.Nod
} }
nodeNames := []string{} nodeNames := []string{}
nodeList, _ := nodeClient.List(metav1.ListOptions{}) nodeList, _ := nodeClient.Cache().List(nil)
if nodeList != nil { for _, node := range nodeList {
for _, node := range nodeList.Items { nodeNames = append(nodeNames, node.Name)
nodeNames = append(nodeNames, node.Name)
}
} }
if len(nodeNames) == 0 { if len(nodeNames) == 0 {
nodeNames = append(nodeNames, passwd.Users()...) nodeNames = append(nodeNames, passwd.Users()...)

View File

@ -8,12 +8,13 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/golang/mock/gomock"
"github.com/rancher/wrangler/pkg/generic/fake"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
) )
const migrateNumNodes = 10 const migrateNumNodes = 10
@ -27,20 +28,29 @@ func Test_UnitAsserts(t *testing.T) {
func Test_UnitEnsureDelete(t *testing.T) { func Test_UnitEnsureDelete(t *testing.T) {
logMemUsage(t) logMemUsage(t)
secretClient := &mockSecretClient{} ctrl := gomock.NewController(t)
secretClient := fake.NewMockControllerInterface[*v1.Secret, *v1.SecretList](ctrl)
secretCache := fake.NewMockCacheInterface[*v1.Secret](ctrl)
secretStore := &mockSecretStore{}
// Set up expected call counts for tests
// Expect to see 2 creates, any number of cache gets, and 2 deletes.
secretClient.EXPECT().Create(gomock.Any()).Times(2).DoAndReturn(secretStore.Create)
secretClient.EXPECT().Delete(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).DoAndReturn(secretStore.Delete)
secretClient.EXPECT().Cache().AnyTimes().Return(secretCache)
secretCache.EXPECT().Get(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(secretStore.Get)
// Run tests
assertEqual(t, Ensure(secretClient, "node1", "Hello World"), nil) assertEqual(t, Ensure(secretClient, "node1", "Hello World"), nil)
assertEqual(t, Ensure(secretClient, "node1", "Hello World"), nil) assertEqual(t, Ensure(secretClient, "node1", "Hello World"), nil)
assertNotEqual(t, Ensure(secretClient, "node1", "Goodbye World"), nil) assertNotEqual(t, Ensure(secretClient, "node1", "Goodbye World"), nil)
assertEqual(t, secretClient.created, 1)
assertEqual(t, Delete(secretClient, "node1"), nil) assertEqual(t, Delete(secretClient, "node1"), nil)
assertNotEqual(t, Delete(secretClient, "node1"), nil) assertNotEqual(t, Delete(secretClient, "node1"), nil)
assertEqual(t, secretClient.deleted, 1)
assertEqual(t, Ensure(secretClient, "node1", "Hello Universe"), nil) assertEqual(t, Ensure(secretClient, "node1", "Hello Universe"), nil)
assertNotEqual(t, Ensure(secretClient, "node1", "Hello World"), nil) assertNotEqual(t, Ensure(secretClient, "node1", "Hello World"), nil)
assertEqual(t, Ensure(secretClient, "node1", "Hello Universe"), nil) assertEqual(t, Ensure(secretClient, "node1", "Hello Universe"), nil)
assertEqual(t, secretClient.created, 2)
logMemUsage(t) logMemUsage(t)
} }
@ -49,16 +59,32 @@ func Test_UnitMigrateFile(t *testing.T) {
nodePasswordFile := generateNodePasswordFile(migrateNumNodes) nodePasswordFile := generateNodePasswordFile(migrateNumNodes)
defer os.Remove(nodePasswordFile) defer os.Remove(nodePasswordFile)
secretClient := &mockSecretClient{} ctrl := gomock.NewController(t)
nodeClient := &mockNodeClient{}
secretClient := fake.NewMockControllerInterface[*v1.Secret, *v1.SecretList](ctrl)
secretCache := fake.NewMockCacheInterface[*v1.Secret](ctrl)
secretStore := &mockSecretStore{}
nodeClient := fake.NewMockNonNamespacedControllerInterface[*v1.Node, *v1.NodeList](ctrl)
nodeCache := fake.NewMockNonNamespacedCacheInterface[*v1.Node](ctrl)
nodeStore := &mockNodeStore{}
// Set up expected call counts for tests
// Expect to see 1 node list, any number of cache gets, and however many
// creates as we are migrating.
secretClient.EXPECT().Create(gomock.Any()).Times(migrateNumNodes).DoAndReturn(secretStore.Create)
secretClient.EXPECT().Cache().AnyTimes().Return(secretCache)
secretCache.EXPECT().Get(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(secretStore.Get)
nodeClient.EXPECT().Cache().AnyTimes().Return(nodeCache)
nodeCache.EXPECT().List(gomock.Any()).Times(1).DoAndReturn(nodeStore.List)
// Run tests
logMemUsage(t) logMemUsage(t)
if err := MigrateFile(secretClient, nodeClient, nodePasswordFile); err != nil { if err := MigrateFile(secretClient, nodeClient, nodePasswordFile); err != nil {
log.Fatal(err) t.Fatal(err)
} }
logMemUsage(t) logMemUsage(t)
assertEqual(t, secretClient.created, migrateNumNodes)
assertNotEqual(t, Ensure(secretClient, "node1", "Hello World"), nil) assertNotEqual(t, Ensure(secretClient, "node1", "Hello World"), nil)
assertEqual(t, Ensure(secretClient, "node1", "node1"), nil) assertEqual(t, Ensure(secretClient, "node1", "node1"), nil)
} }
@ -67,25 +93,43 @@ func Test_UnitMigrateFileNodes(t *testing.T) {
nodePasswordFile := generateNodePasswordFile(migrateNumNodes) nodePasswordFile := generateNodePasswordFile(migrateNumNodes)
defer os.Remove(nodePasswordFile) defer os.Remove(nodePasswordFile)
secretClient := &mockSecretClient{} ctrl := gomock.NewController(t)
nodeClient := &mockNodeClient{}
nodeClient.nodes = make([]v1.Node, createNumNodes, createNumNodes) secretClient := fake.NewMockControllerInterface[*v1.Secret, *v1.SecretList](ctrl)
for i := range nodeClient.nodes { secretCache := fake.NewMockCacheInterface[*v1.Secret](ctrl)
nodeClient.nodes[i].Name = fmt.Sprintf("node%d", i+1) secretStore := &mockSecretStore{}
nodeClient := fake.NewMockNonNamespacedControllerInterface[*v1.Node, *v1.NodeList](ctrl)
nodeCache := fake.NewMockNonNamespacedCacheInterface[*v1.Node](ctrl)
nodeStore := &mockNodeStore{}
nodeStore.nodes = make([]v1.Node, createNumNodes, createNumNodes)
for i := range nodeStore.nodes {
nodeStore.nodes[i].Name = fmt.Sprintf("node%d", i+1)
} }
// Set up expected call counts for tests
// Expect to see 1 node list, any number of cache gets, and however many
// creates as we are migrating - plus an extra new node at the end.
secretClient.EXPECT().Create(gomock.Any()).Times(migrateNumNodes + 1).DoAndReturn(secretStore.Create)
secretClient.EXPECT().Cache().AnyTimes().Return(secretCache)
secretCache.EXPECT().Get(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(secretStore.Get)
nodeClient.EXPECT().Cache().AnyTimes().Return(nodeCache)
nodeCache.EXPECT().List(gomock.Any()).Times(1).DoAndReturn(nodeStore.List)
// Run tests
logMemUsage(t) logMemUsage(t)
if err := MigrateFile(secretClient, nodeClient, nodePasswordFile); err != nil { if err := MigrateFile(secretClient, nodeClient, nodePasswordFile); err != nil {
log.Fatal(err) t.Fatal(err)
} }
logMemUsage(t) logMemUsage(t)
assertEqual(t, secretClient.created, createNumNodes) for _, node := range nodeStore.nodes {
for _, node := range nodeClient.nodes {
assertNotEqual(t, Ensure(secretClient, node.Name, "wrong-password"), nil) assertNotEqual(t, Ensure(secretClient, node.Name, "wrong-password"), nil)
assertEqual(t, Ensure(secretClient, node.Name, node.Name), nil) assertEqual(t, Ensure(secretClient, node.Name, node.Name), nil)
} }
newNode := fmt.Sprintf("node%d", createNumNodes+1)
newNode := fmt.Sprintf("node%d", migrateNumNodes+1)
assertEqual(t, Ensure(secretClient, newNode, "new-password"), nil) assertEqual(t, Ensure(secretClient, newNode, "new-password"), nil)
assertNotEqual(t, Ensure(secretClient, newNode, "wrong-password"), nil) assertNotEqual(t, Ensure(secretClient, newNode, "wrong-password"), nil)
} }
@ -98,16 +142,13 @@ func Test_PasswordError(t *testing.T) {
} }
// -------------------------- // --------------------------
// mock secret store interface
// mock secret client interface type mockSecretStore struct {
type mockSecretClient struct {
entries map[string]map[string]v1.Secret entries map[string]map[string]v1.Secret
created int
deleted int
} }
func (m *mockSecretClient) Create(secret *v1.Secret) (*v1.Secret, error) { func (m *mockSecretStore) Create(secret *v1.Secret) (*v1.Secret, error) {
if m.entries == nil { if m.entries == nil {
m.entries = map[string]map[string]v1.Secret{} m.entries = map[string]map[string]v1.Secret{}
} }
@ -117,16 +158,11 @@ func (m *mockSecretClient) Create(secret *v1.Secret) (*v1.Secret, error) {
if _, ok := m.entries[secret.Namespace][secret.Name]; ok { if _, ok := m.entries[secret.Namespace][secret.Name]; ok {
return nil, errorAlreadyExists() return nil, errorAlreadyExists()
} }
m.created++
m.entries[secret.Namespace][secret.Name] = *secret m.entries[secret.Namespace][secret.Name] = *secret
return secret, nil return secret, nil
} }
func (m *mockSecretClient) Update(secret *v1.Secret) (*v1.Secret, error) { func (m *mockSecretStore) Delete(namespace, name string, options *metav1.DeleteOptions) error {
return nil, errorNotImplemented()
}
func (m *mockSecretClient) Delete(namespace, name string, options *metav1.DeleteOptions) error {
if m.entries == nil { if m.entries == nil {
return errorNotFound() return errorNotFound()
} }
@ -136,12 +172,11 @@ func (m *mockSecretClient) Delete(namespace, name string, options *metav1.Delete
if _, ok := m.entries[namespace][name]; !ok { if _, ok := m.entries[namespace][name]; !ok {
return errorNotFound() return errorNotFound()
} }
m.deleted++
delete(m.entries[namespace], name) delete(m.entries[namespace], name)
return nil return nil
} }
func (m *mockSecretClient) Get(namespace, name string, options metav1.GetOptions) (*v1.Secret, error) { func (m *mockSecretStore) Get(namespace, name string) (*v1.Secret, error) {
if m.entries == nil { if m.entries == nil {
return nil, errorNotFound() return nil, errorNotFound()
} }
@ -154,53 +189,18 @@ func (m *mockSecretClient) Get(namespace, name string, options metav1.GetOptions
return nil, errorNotFound() return nil, errorNotFound()
} }
func (m *mockSecretClient) List(namespace string, opts metav1.ListOptions) (*v1.SecretList, error) {
return nil, errorNotImplemented()
}
func (m *mockSecretClient) Watch(namespace string, opts metav1.ListOptions) (watch.Interface, error) {
return nil, errorNotImplemented()
}
func (m *mockSecretClient) Patch(namespace, name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Secret, err error) {
return nil, errorNotImplemented()
}
// -------------------------- // --------------------------
// mock node store interface
// mock node client interface type mockNodeStore struct {
type mockNodeClient struct {
nodes []v1.Node nodes []v1.Node
} }
func (m *mockNodeClient) Create(node *v1.Node) (*v1.Node, error) { func (m *mockNodeStore) List(ls labels.Selector) ([]v1.Node, error) {
return nil, errorNotImplemented() return m.nodes, nil
}
func (m *mockNodeClient) Update(node *v1.Node) (*v1.Node, error) {
return nil, errorNotImplemented()
}
func (m *mockNodeClient) UpdateStatus(node *v1.Node) (*v1.Node, error) {
return nil, errorNotImplemented()
}
func (m *mockNodeClient) Delete(name string, options *metav1.DeleteOptions) error {
return errorNotImplemented()
}
func (m *mockNodeClient) Get(name string, options metav1.GetOptions) (*v1.Node, error) {
return nil, errorNotImplemented()
}
func (m *mockNodeClient) List(opts metav1.ListOptions) (*v1.NodeList, error) {
return &v1.NodeList{Items: m.nodes}, nil
}
func (m *mockNodeClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
return nil, errorNotImplemented()
}
func (m *mockNodeClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Node, err error) {
return nil, errorNotImplemented()
} }
// -------------------------- // --------------------------
// utility functions // utility functions
func assertEqual(t *testing.T, a interface{}, b interface{}) { func assertEqual(t *testing.T, a interface{}, b interface{}) {
@ -241,11 +241,6 @@ func errorAlreadyExists() error {
return apierrors.NewAlreadyExists(schema.GroupResource{}, "already-exists") return apierrors.NewAlreadyExists(schema.GroupResource{}, "already-exists")
} }
func errorNotImplemented() error {
log.Fatal("not implemented")
return apierrors.NewMethodNotSupported(schema.GroupResource{}, "not-implemented")
}
func logMemUsage(t *testing.T) { func logMemUsage(t *testing.T) {
var stats runtime.MemStats var stats runtime.MemStats
runtime.ReadMemStats(&stats) runtime.ReadMemStats(&stats)

View File

@ -431,8 +431,8 @@ type nodeInfo struct {
func passwordBootstrap(ctx context.Context, config *Config) nodePassBootstrapper { func passwordBootstrap(ctx context.Context, config *Config) nodePassBootstrapper {
runtime := config.ControlConfig.Runtime runtime := config.ControlConfig.Runtime
deferredNodes := map[string]bool{} deferredNodes := map[string]bool{}
var secretClient coreclient.SecretClient var secretClient coreclient.SecretController
var nodeClient coreclient.NodeClient var nodeClient coreclient.NodeController
var mu sync.Mutex var mu sync.Mutex
return nodePassBootstrapper(func(req *http.Request) (string, int, error) { return nodePassBootstrapper(func(req *http.Request) (string, int, error) {
@ -535,9 +535,9 @@ func verifyRemotePassword(ctx context.Context, config *Config, mu *sync.Mutex, d
return node.Name, http.StatusOK, nil return node.Name, http.StatusOK, nil
} }
func verifyNode(ctx context.Context, nodeClient coreclient.NodeClient, node *nodeInfo) error { func verifyNode(ctx context.Context, nodeClient coreclient.NodeController, node *nodeInfo) error {
if nodeName, isNodeAuth := identifier.NodeIdentity(node.User); isNodeAuth { if nodeName, isNodeAuth := identifier.NodeIdentity(node.User); isNodeAuth {
if _, err := nodeClient.Get(nodeName, metav1.GetOptions{}); err != nil { if _, err := nodeClient.Cache().Get(nodeName); err != nil {
return errors.Wrap(err, "unable to verify node identity") return errors.Wrap(err, "unable to verify node identity")
} }
} }