599 lines
17 KiB
Go
599 lines
17 KiB
Go
package controller
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/go-logr/logr"
|
|
"github.com/stretchr/testify/assert"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
|
|
|
ecv1alpha1 "go.etcd.io/etcd-operator/api/v1alpha1"
|
|
"go.etcd.io/etcd-operator/internal/etcdutils"
|
|
"go.etcd.io/etcd/api/v3/etcdserverpb"
|
|
clientv3 "go.etcd.io/etcd/client/v3"
|
|
)
|
|
|
|
func TestPrepareOwnerReference(t *testing.T) {
|
|
scheme := runtime.NewScheme()
|
|
ec := &ecv1alpha1.EtcdCluster{}
|
|
ec.SetName("test-etcd")
|
|
ec.SetNamespace("default")
|
|
ec.SetUID("1234")
|
|
|
|
scheme.AddKnownTypes(schema.GroupVersion{Group: "etcd.database.coreos.com", Version: "v1alpha1"}, ec)
|
|
|
|
owners, err := prepareOwnerReference(ec, scheme)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if len(owners) != 1 {
|
|
t.Fatalf("expected 1 owner reference, got %d", len(owners))
|
|
}
|
|
|
|
if owners[0].Name != "test-etcd" || owners[0].Controller == nil || !*owners[0].Controller {
|
|
t.Fatalf("owner reference properties not set correctly")
|
|
}
|
|
}
|
|
|
|
func pointerToInt32(value int32) *int32 {
|
|
return &value
|
|
}
|
|
|
|
func TestReconcileStatefulSet(t *testing.T) {
|
|
scheme := runtime.NewScheme()
|
|
_ = ecv1alpha1.AddToScheme(scheme)
|
|
|
|
fakeClient := fake.NewClientBuilder().Build()
|
|
logger := log.FromContext(t.Context())
|
|
|
|
ec := &ecv1alpha1.EtcdCluster{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-etcd",
|
|
Namespace: "default",
|
|
},
|
|
Spec: ecv1alpha1.EtcdClusterSpec{
|
|
Size: 3,
|
|
Version: "3.5.17",
|
|
},
|
|
}
|
|
|
|
_, _ = reconcileStatefulSet(t.Context(), logger, ec, fakeClient, 3, scheme)
|
|
|
|
sts := &appsv1.StatefulSet{}
|
|
err := fakeClient.Get(t.Context(), client.ObjectKey{Name: "test-etcd", Namespace: "default"}, sts)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if *sts.Spec.Replicas != 3 {
|
|
t.Fatalf("expected 3 replicas, got %d", *sts.Spec.Replicas)
|
|
}
|
|
}
|
|
|
|
func TestWaitForStatefulSetReady(t *testing.T) {
|
|
// Create a scheme and register the necessary types
|
|
scheme := runtime.NewScheme()
|
|
_ = corev1.AddToScheme(scheme)
|
|
_ = ecv1alpha1.AddToScheme(scheme)
|
|
_ = appsv1.AddToScheme(scheme)
|
|
|
|
tests := []struct {
|
|
name string
|
|
statefulSet *appsv1.StatefulSet
|
|
expectedResult bool
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "StatefulSet is ready",
|
|
statefulSet: &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-sts",
|
|
Namespace: "default",
|
|
},
|
|
Spec: appsv1.StatefulSetSpec{
|
|
Replicas: pointerToInt32(3),
|
|
},
|
|
Status: appsv1.StatefulSetStatus{
|
|
ReadyReplicas: 3,
|
|
},
|
|
},
|
|
expectedResult: true,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "StatefulSet is not ready",
|
|
statefulSet: &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-sts",
|
|
Namespace: "default",
|
|
},
|
|
Spec: appsv1.StatefulSetSpec{
|
|
Replicas: pointerToInt32(3),
|
|
},
|
|
Status: appsv1.StatefulSetStatus{
|
|
ReadyReplicas: 2,
|
|
},
|
|
},
|
|
expectedResult: false,
|
|
expectedError: errors.New("StatefulSet default/test-sts did not become ready: timed out waiting for the condition"),
|
|
},
|
|
{
|
|
name: "StatefulSet does not exist",
|
|
statefulSet: nil,
|
|
expectedResult: false,
|
|
expectedError: errors.New("statefulsets.apps \"test-sts\" not found"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var clientBuilder *fake.ClientBuilder
|
|
if tt.statefulSet != nil {
|
|
clientBuilder = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.statefulSet)
|
|
} else {
|
|
clientBuilder = fake.NewClientBuilder().WithScheme(scheme)
|
|
}
|
|
fakeClient := clientBuilder.Build()
|
|
|
|
ctx := t.Context()
|
|
logger := log.FromContext(ctx)
|
|
|
|
err := waitForStatefulSetReady(ctx, logger, fakeClient, "test-sts", "default")
|
|
if tt.expectedError != nil {
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.expectedError.Error())
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateHeadlessServiceIfNotExist(t *testing.T) {
|
|
ctx := t.Context()
|
|
logger := log.FromContext(ctx)
|
|
|
|
// Create a scheme and register the necessary types
|
|
scheme := runtime.NewScheme()
|
|
_ = corev1.AddToScheme(scheme)
|
|
_ = ecv1alpha1.AddToScheme(scheme)
|
|
|
|
// Create a fake client
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
|
|
|
|
// Create an EtcdCluster instance
|
|
ec := &ecv1alpha1.EtcdCluster{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-etcd",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
|
|
t.Run("creates headless service if it does not exist", func(t *testing.T) {
|
|
err := createHeadlessServiceIfNotExist(ctx, logger, fakeClient, ec, scheme)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify that the service was created
|
|
service := &corev1.Service{}
|
|
err = fakeClient.Get(ctx, client.ObjectKey{Name: "test-etcd", Namespace: "default"}, service)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "None", service.Spec.ClusterIP)
|
|
assert.Equal(t, map[string]string{
|
|
"app": "test-etcd",
|
|
"controller": "test-etcd",
|
|
}, service.Spec.Selector)
|
|
})
|
|
|
|
t.Run("does not create service if it already exists", func(t *testing.T) {
|
|
// Service was already created in previous test. Call the function again to ensure no error
|
|
err := createHeadlessServiceIfNotExist(ctx, logger, fakeClient, ec, scheme)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestClientEndpointForOrdinalIndex(t *testing.T) {
|
|
sts := &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-sts",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
index int
|
|
expectedResult string
|
|
}{
|
|
{index: 0, expectedResult: "http://test-sts-0.test-sts.default.svc.cluster.local:2379"},
|
|
{index: 1, expectedResult: "http://test-sts-1.test-sts.default.svc.cluster.local:2379"},
|
|
{index: 2, expectedResult: "http://test-sts-2.test-sts.default.svc.cluster.local:2379"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(fmt.Sprintf("index %d", tt.index), func(t *testing.T) {
|
|
result := clientEndpointForOrdinalIndex(sts, tt.index)
|
|
assert.Equal(t, tt.expectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsLearnerReady(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
leaderStatus *clientv3.StatusResponse
|
|
learnerStatus *clientv3.StatusResponse
|
|
expectedResult bool
|
|
}{
|
|
{
|
|
name: "Learner is ready",
|
|
leaderStatus: &clientv3.StatusResponse{
|
|
Header: &etcdserverpb.ResponseHeader{Revision: 100},
|
|
},
|
|
learnerStatus: &clientv3.StatusResponse{
|
|
Header: &etcdserverpb.ResponseHeader{Revision: 95},
|
|
},
|
|
expectedResult: true,
|
|
},
|
|
{
|
|
name: "Learner is not ready",
|
|
leaderStatus: &clientv3.StatusResponse{
|
|
Header: &etcdserverpb.ResponseHeader{Revision: 100},
|
|
},
|
|
learnerStatus: &clientv3.StatusResponse{
|
|
Header: &etcdserverpb.ResponseHeader{Revision: 80},
|
|
},
|
|
expectedResult: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := etcdutils.IsLearnerReady(tt.leaderStatus, tt.learnerStatus)
|
|
assert.Equal(t, tt.expectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckStatefulSetControlledByEtcdOperator(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ec *ecv1alpha1.EtcdCluster
|
|
sts *appsv1.StatefulSet
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "StatefulSet controlled by EtcdCluster",
|
|
ec: &ecv1alpha1.EtcdCluster{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "etcd-cluster",
|
|
Namespace: "default",
|
|
UID: "1234",
|
|
},
|
|
},
|
|
sts: &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "etcd-sts",
|
|
Namespace: "default",
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "ecv1alpha1/v1alpha1",
|
|
Kind: "EtcdCluster",
|
|
Name: "etcd-cluster",
|
|
UID: "1234",
|
|
Controller: pointerToBool(true),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "StatefulSet not controlled by EtcdCluster",
|
|
ec: &ecv1alpha1.EtcdCluster{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "etcd-cluster",
|
|
Namespace: "default",
|
|
UID: "1234",
|
|
},
|
|
},
|
|
sts: &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "etcd-sts",
|
|
Namespace: "default",
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "ecv1alpha1/v1alpha1",
|
|
Kind: "EtcdCluster",
|
|
Name: "other-etcd-cluster",
|
|
UID: "5678",
|
|
Controller: pointerToBool(true),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectedError: fmt.Errorf("StatefulSet default/etcd-sts is not controlled by EtcdCluster default/etcd-cluster"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := checkStatefulSetControlledByEtcdOperator(tt.ec, tt.sts)
|
|
|
|
if (err != nil) != (tt.expectedError != nil) {
|
|
t.Errorf("expected error: %v, got: %v", tt.expectedError, err)
|
|
return
|
|
}
|
|
|
|
if err != nil && err.Error() != tt.expectedError.Error() {
|
|
t.Errorf("unexpected error: got %v, want %v", err, tt.expectedError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func pointerToBool(value bool) *bool {
|
|
return &value
|
|
}
|
|
|
|
func TestClientEndpointsFromStatefulsets(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
statefulSet *appsv1.StatefulSet
|
|
expectedResult []string
|
|
}{
|
|
{
|
|
name: "StatefulSet with 3 replicas",
|
|
statefulSet: &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-sts",
|
|
Namespace: "default",
|
|
},
|
|
Spec: appsv1.StatefulSetSpec{
|
|
Replicas: pointerToInt32(3),
|
|
},
|
|
},
|
|
expectedResult: []string{
|
|
"http://test-sts-0.test-sts.default.svc.cluster.local:2379",
|
|
"http://test-sts-1.test-sts.default.svc.cluster.local:2379",
|
|
"http://test-sts-2.test-sts.default.svc.cluster.local:2379",
|
|
},
|
|
},
|
|
{
|
|
name: "StatefulSet with 1 replica",
|
|
statefulSet: &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-sts",
|
|
Namespace: "default",
|
|
},
|
|
Spec: appsv1.StatefulSetSpec{
|
|
Replicas: pointerToInt32(1),
|
|
},
|
|
},
|
|
expectedResult: []string{
|
|
"http://test-sts-0.test-sts.default.svc.cluster.local:2379",
|
|
},
|
|
},
|
|
{
|
|
name: "StatefulSet with 0 replicas",
|
|
statefulSet: &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-sts",
|
|
Namespace: "default",
|
|
},
|
|
Spec: appsv1.StatefulSetSpec{
|
|
Replicas: pointerToInt32(0),
|
|
},
|
|
},
|
|
expectedResult: []string(nil),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := clientEndpointsFromStatefulsets(tt.statefulSet)
|
|
assert.Equal(t, tt.expectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAreAllMembersHealthy(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
statefulSet *appsv1.StatefulSet
|
|
healthInfos []etcdutils.EpHealth
|
|
expectedResult bool
|
|
expectedError error
|
|
}{
|
|
// TODO: Add test cases for healthy members and non healthy members
|
|
{
|
|
name: "Error during health check",
|
|
statefulSet: &appsv1.StatefulSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-sts",
|
|
Namespace: "default",
|
|
},
|
|
Spec: appsv1.StatefulSetSpec{
|
|
Replicas: pointerToInt32(3),
|
|
},
|
|
Status: appsv1.StatefulSetStatus{
|
|
ReadyReplicas: 3,
|
|
},
|
|
},
|
|
healthInfos: nil,
|
|
expectedResult: false,
|
|
expectedError: errors.New("context deadline exceeded"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
logger := logr.Discard() // Use a no-op logger for testing
|
|
|
|
result, err := areAllMembersHealthy(tt.statefulSet, logger)
|
|
assert.Equal(t, tt.expectedResult, result)
|
|
if tt.expectedError != nil {
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.expectedError.Error())
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApplyEtcdClusterState(t *testing.T) {
|
|
ctx := t.Context()
|
|
logger := log.FromContext(ctx)
|
|
|
|
// Create a scheme and register the necessary types
|
|
scheme := runtime.NewScheme()
|
|
_ = corev1.AddToScheme(scheme)
|
|
_ = ecv1alpha1.AddToScheme(scheme)
|
|
|
|
// Create a fake client
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
|
|
|
|
// Create an EtcdCluster instance
|
|
ec := &ecv1alpha1.EtcdCluster{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-etcd",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
|
|
t.Run("creates configmap if it does not exist", func(t *testing.T) {
|
|
err := applyEtcdClusterState(ctx, ec, 3, fakeClient, scheme, logger)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify that the configmap was created
|
|
configMap := &corev1.ConfigMap{}
|
|
err = fakeClient.Get(ctx, client.ObjectKey{Name: configMapNameForEtcdCluster(ec), Namespace: "default"}, configMap)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "existing", configMap.Data["ETCD_INITIAL_CLUSTER_STATE"])
|
|
assert.Contains(t, configMap.Data["ETCD_INITIAL_CLUSTER"], "test-etcd-0=http://test-etcd-0.test-etcd.default.svc.cluster.local:2380")
|
|
err = fakeClient.Delete(ctx, configMap) // Delete the configmap to avoid conflicts in future tests
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("updates configmap if it already exists", func(t *testing.T) {
|
|
// Create the configmap first
|
|
configMap := newEtcdClusterState(ec, 3)
|
|
err := fakeClient.Create(ctx, configMap)
|
|
assert.NoError(t, err)
|
|
|
|
// Call the function again to ensure it updates the configmap
|
|
err = applyEtcdClusterState(ctx, ec, 3, fakeClient, scheme, logger)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify that the configmap was updated
|
|
updatedConfigMap := &corev1.ConfigMap{}
|
|
err = fakeClient.Get(ctx, client.ObjectKey{Name: configMapNameForEtcdCluster(ec), Namespace: "default"}, updatedConfigMap)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "existing", updatedConfigMap.Data["ETCD_INITIAL_CLUSTER_STATE"])
|
|
assert.Contains(t, updatedConfigMap.Data["ETCD_INITIAL_CLUSTER"], "test-etcd-0=http://test-etcd-0.test-etcd.default.svc.cluster.local:2380")
|
|
})
|
|
}
|
|
|
|
func TestCreatingArgs(t *testing.T) {
|
|
tests := []struct {
|
|
testName string
|
|
etcdOptions []string
|
|
clusterName string
|
|
expectedResult []string
|
|
}{
|
|
{
|
|
testName: "No etcdOptions provided",
|
|
etcdOptions: nil,
|
|
clusterName: "testCluster",
|
|
expectedResult: []string{
|
|
"--name=$(POD_NAME)",
|
|
"--listen-peer-urls=http://0.0.0.0:2380",
|
|
"--listen-client-urls=http://0.0.0.0:2379",
|
|
"--initial-advertise-peer-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2380",
|
|
"--advertise-client-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2379",
|
|
},
|
|
},
|
|
{
|
|
testName: "Etcd options with = sign",
|
|
etcdOptions: []string{
|
|
"--max-wals=7",
|
|
"--discovery-failbox=proxy",
|
|
},
|
|
clusterName: "testCluster",
|
|
expectedResult: []string{
|
|
"--name=$(POD_NAME)",
|
|
"--listen-peer-urls=http://0.0.0.0:2380",
|
|
"--listen-client-urls=http://0.0.0.0:2379",
|
|
"--initial-advertise-peer-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2380",
|
|
"--advertise-client-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2379",
|
|
"--max-wals=7",
|
|
"--discovery-failbox=proxy",
|
|
},
|
|
},
|
|
{
|
|
testName: "Etcd options with spaces",
|
|
etcdOptions: []string{
|
|
"--max-wals 7",
|
|
"--discovery-failbox proxy",
|
|
},
|
|
clusterName: "testCluster",
|
|
expectedResult: []string{
|
|
"--name=$(POD_NAME)",
|
|
"--listen-peer-urls=http://0.0.0.0:2380",
|
|
"--listen-client-urls=http://0.0.0.0:2379",
|
|
"--initial-advertise-peer-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2380",
|
|
"--advertise-client-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2379",
|
|
"--max-wals 7",
|
|
"--discovery-failbox proxy",
|
|
},
|
|
},
|
|
{
|
|
testName: "Etcd switch options",
|
|
etcdOptions: []string{
|
|
"--experimental-peer-skip-client-san-verification",
|
|
},
|
|
clusterName: "testCluster",
|
|
expectedResult: []string{
|
|
"--name=$(POD_NAME)",
|
|
"--listen-peer-urls=http://0.0.0.0:2380",
|
|
"--listen-client-urls=http://0.0.0.0:2379",
|
|
"--initial-advertise-peer-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2380",
|
|
"--advertise-client-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2379",
|
|
"--experimental-peer-skip-client-san-verification",
|
|
},
|
|
},
|
|
{
|
|
testName: "Overwrite default arg",
|
|
etcdOptions: []string{
|
|
"--listen-peer-urls=http://0.0.0.0:3200",
|
|
"--experimental-peer-skip-client-san-verification",
|
|
},
|
|
clusterName: "testCluster",
|
|
expectedResult: []string{
|
|
"--name=$(POD_NAME)",
|
|
"--listen-client-urls=http://0.0.0.0:2379",
|
|
"--initial-advertise-peer-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2380",
|
|
"--advertise-client-urls=http://$(POD_NAME).testCluster.$(POD_NAMESPACE).svc.cluster.local:2379",
|
|
"--listen-peer-urls=http://0.0.0.0:3200",
|
|
"--experimental-peer-skip-client-san-verification",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.testName, func(t *testing.T) {
|
|
result := createArgs(tt.clusterName, tt.etcdOptions)
|
|
assert.Equal(t, tt.expectedResult, result)
|
|
})
|
|
}
|
|
|
|
}
|