mirror of https://github.com/k3s-io/k3s
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
7.7 KiB
272 lines
7.7 KiB
package nodepassword |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"log" |
|
"os" |
|
"runtime" |
|
"testing" |
|
|
|
"github.com/rancher/wrangler/pkg/generic" |
|
v1 "k8s.io/api/core/v1" |
|
apierrors "k8s.io/apimachinery/pkg/api/errors" |
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
|
"k8s.io/apimachinery/pkg/runtime/schema" |
|
"k8s.io/apimachinery/pkg/types" |
|
"k8s.io/apimachinery/pkg/watch" |
|
"k8s.io/client-go/rest" |
|
) |
|
|
|
const migrateNumNodes = 10 |
|
const createNumNodes = 3 |
|
|
|
func Test_UnitAsserts(t *testing.T) { |
|
assertEqual(t, 1, 1) |
|
assertNotEqual(t, 1, 0) |
|
} |
|
|
|
func Test_UnitEnsureDelete(t *testing.T) { |
|
logMemUsage(t) |
|
|
|
secretClient := &mockSecretClient{} |
|
assertEqual(t, Ensure(secretClient, "node1", "Hello World"), nil) |
|
assertEqual(t, Ensure(secretClient, "node1", "Hello World"), nil) |
|
assertNotEqual(t, Ensure(secretClient, "node1", "Goodbye World"), nil) |
|
assertEqual(t, secretClient.created, 1) |
|
|
|
assertEqual(t, Delete(secretClient, "node1"), nil) |
|
assertNotEqual(t, Delete(secretClient, "node1"), nil) |
|
assertEqual(t, secretClient.deleted, 1) |
|
|
|
assertEqual(t, Ensure(secretClient, "node1", "Hello Universe"), nil) |
|
assertNotEqual(t, Ensure(secretClient, "node1", "Hello World"), nil) |
|
assertEqual(t, Ensure(secretClient, "node1", "Hello Universe"), nil) |
|
assertEqual(t, secretClient.created, 2) |
|
|
|
logMemUsage(t) |
|
} |
|
|
|
func Test_UnitMigrateFile(t *testing.T) { |
|
nodePasswordFile := generateNodePasswordFile(migrateNumNodes) |
|
defer os.Remove(nodePasswordFile) |
|
|
|
secretClient := &mockSecretClient{} |
|
nodeClient := &mockNodeClient{} |
|
|
|
logMemUsage(t) |
|
if err := MigrateFile(secretClient, nodeClient, nodePasswordFile); err != nil { |
|
log.Fatal(err) |
|
} |
|
logMemUsage(t) |
|
|
|
assertEqual(t, secretClient.created, migrateNumNodes) |
|
assertNotEqual(t, Ensure(secretClient, "node1", "Hello World"), nil) |
|
assertEqual(t, Ensure(secretClient, "node1", "node1"), nil) |
|
} |
|
|
|
func Test_UnitMigrateFileNodes(t *testing.T) { |
|
nodePasswordFile := generateNodePasswordFile(migrateNumNodes) |
|
defer os.Remove(nodePasswordFile) |
|
|
|
secretClient := &mockSecretClient{} |
|
nodeClient := &mockNodeClient{} |
|
nodeClient.nodes = make([]v1.Node, createNumNodes, createNumNodes) |
|
for i := range nodeClient.nodes { |
|
nodeClient.nodes[i].Name = fmt.Sprintf("node%d", i+1) |
|
} |
|
|
|
logMemUsage(t) |
|
if err := MigrateFile(secretClient, nodeClient, nodePasswordFile); err != nil { |
|
log.Fatal(err) |
|
} |
|
logMemUsage(t) |
|
|
|
assertEqual(t, secretClient.created, createNumNodes) |
|
for _, node := range nodeClient.nodes { |
|
assertNotEqual(t, Ensure(secretClient, node.Name, "wrong-password"), nil) |
|
assertEqual(t, Ensure(secretClient, node.Name, node.Name), nil) |
|
} |
|
newNode := fmt.Sprintf("node%d", createNumNodes+1) |
|
assertEqual(t, Ensure(secretClient, newNode, "new-password"), nil) |
|
assertNotEqual(t, Ensure(secretClient, newNode, "wrong-password"), nil) |
|
} |
|
|
|
func Test_PasswordError(t *testing.T) { |
|
err := &passwordError{node: "test", err: fmt.Errorf("inner error")} |
|
assertEqual(t, errors.Is(err, ErrVerifyFailed), true) |
|
assertEqual(t, errors.Is(err, fmt.Errorf("different error")), false) |
|
assertNotEqual(t, errors.Unwrap(err), nil) |
|
} |
|
|
|
// -------------------------- |
|
|
|
// mock secret client interface |
|
|
|
type mockSecretClient struct { |
|
entries map[string]map[string]v1.Secret |
|
created int |
|
deleted int |
|
} |
|
|
|
func (m *mockSecretClient) Create(secret *v1.Secret) (*v1.Secret, error) { |
|
if m.entries == nil { |
|
m.entries = map[string]map[string]v1.Secret{} |
|
} |
|
if _, ok := m.entries[secret.Namespace]; !ok { |
|
m.entries[secret.Namespace] = map[string]v1.Secret{} |
|
} |
|
if _, ok := m.entries[secret.Namespace][secret.Name]; ok { |
|
return nil, errorAlreadyExists() |
|
} |
|
m.created++ |
|
m.entries[secret.Namespace][secret.Name] = *secret |
|
return secret, nil |
|
} |
|
|
|
func (m *mockSecretClient) Update(secret *v1.Secret) (*v1.Secret, error) { |
|
return nil, errorNotImplemented() |
|
} |
|
|
|
func (m *mockSecretClient) Delete(namespace, name string, options *metav1.DeleteOptions) error { |
|
if m.entries == nil { |
|
return errorNotFound() |
|
} |
|
if _, ok := m.entries[namespace]; !ok { |
|
return errorNotFound() |
|
} |
|
if _, ok := m.entries[namespace][name]; !ok { |
|
return errorNotFound() |
|
} |
|
m.deleted++ |
|
delete(m.entries[namespace], name) |
|
return nil |
|
} |
|
|
|
func (m *mockSecretClient) Get(namespace, name string, options metav1.GetOptions) (*v1.Secret, error) { |
|
if m.entries == nil { |
|
return nil, errorNotFound() |
|
} |
|
if _, ok := m.entries[namespace]; !ok { |
|
return nil, errorNotFound() |
|
} |
|
if secret, ok := m.entries[namespace][name]; ok { |
|
return &secret, nil |
|
} |
|
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() |
|
} |
|
|
|
func (m *mockSecretClient) UpdateStatus(secret *v1.Secret) (*v1.Secret, error) { |
|
return nil, errorNotImplemented() |
|
} |
|
|
|
func (m *mockSecretClient) WithImpersonation(rest.ImpersonationConfig) (generic.ClientInterface[*v1.Secret, *v1.SecretList], error) { |
|
return nil, errorNotImplemented() |
|
} |
|
|
|
// -------------------------- |
|
|
|
// mock node client interface |
|
|
|
type mockNodeClient struct { |
|
nodes []v1.Node |
|
} |
|
|
|
func (m *mockNodeClient) Create(node *v1.Node) (*v1.Node, error) { |
|
return nil, errorNotImplemented() |
|
} |
|
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() |
|
} |
|
|
|
func (m *mockNodeClient) WithImpersonation(rest.ImpersonationConfig) (generic.NonNamespacedClientInterface[*v1.Node, *v1.NodeList], error) { |
|
return nil, errorNotImplemented() |
|
} |
|
|
|
// -------------------------- |
|
|
|
// utility functions |
|
|
|
func assertEqual(t *testing.T, a interface{}, b interface{}) { |
|
if a != b { |
|
t.Fatalf("[ %v != %v ]", a, b) |
|
} |
|
} |
|
|
|
func assertNotEqual(t *testing.T, a interface{}, b interface{}) { |
|
if a == b { |
|
t.Fatalf("[ %v == %v ]", a, b) |
|
} |
|
} |
|
|
|
func generateNodePasswordFile(migrateNumNodes int) string { |
|
tempFile, err := os.CreateTemp("", "node-password-test.*") |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
tempFile.Close() |
|
|
|
var passwordEntries string |
|
for i := 1; i <= migrateNumNodes; i++ { |
|
passwordEntries += fmt.Sprintf("node%d,node%d\n", i, i) |
|
} |
|
if err := os.WriteFile(tempFile.Name(), []byte(passwordEntries), 0600); err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
return tempFile.Name() |
|
} |
|
|
|
func errorNotFound() error { |
|
return apierrors.NewNotFound(schema.GroupResource{}, "not-found") |
|
} |
|
|
|
func errorAlreadyExists() error { |
|
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) { |
|
var stats runtime.MemStats |
|
runtime.ReadMemStats(&stats) |
|
t.Logf("Memory Usage: Alloc=%d MB, Sys=%d MB, NumGC=%d", |
|
toMB(stats.Alloc), toMB(stats.Sys), stats.NumGC) |
|
} |
|
|
|
func toMB(bytes uint64) uint64 { |
|
return bytes / (1024 * 1024) |
|
}
|
|
|