controllers/testutils.go (252 lines of code) (raw):
package controllers
import (
"bytes"
"context"
"fmt"
"io"
"math/rand"
"net/http"
"strings"
etcdbootstrapv1 "github.com/aws/etcdadm-bootstrap-provider/api/v1beta1"
etcdv1 "github.com/aws/etcdadm-controller/api/v1beta1"
"github.com/google/uuid"
"go.etcd.io/etcd/api/v3/etcdserverpb"
clientv3 "go.etcd.io/etcd/client/v3"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/utils/ptr"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
testClusterName = "testCluster"
testNamespace = "test"
testEtcdadmClusterName = "testEtcdadmCluster"
testInfrastructureTemplateName = "testInfraTemplate"
etcdClusterNameSuffix = "etcd-cluster"
etcdVersion = "v3.4.9"
)
var (
infraTemplate = &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "InfrastructureTemplate",
"apiVersion": "infra.io/v1",
"metadata": map[string]interface{}{
"name": testInfrastructureTemplateName,
"namespace": testNamespace,
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"hello": "world",
},
},
},
},
}
)
type etcdadmClusterTest struct {
replicas int
name string
namespace string
cluster *clusterv1.Cluster
etcdadmCluster *etcdv1.EtcdadmCluster
machines []*clusterv1.Machine
machineCounter int
initSecret *corev1.Secret
}
func newEtcdadmClusterTest(etcdReplicas int) *etcdadmClusterTest {
return &etcdadmClusterTest{
name: testClusterName,
namespace: testNamespace,
replicas: etcdReplicas,
machineCounter: 0,
}
}
func (e *etcdadmClusterTest) buildClusterWithExternalEtcd() *etcdadmClusterTest {
e.cluster = e.newClusterWithExternalEtcd()
e.etcdadmCluster = e.newEtcdadmCluster(e.cluster)
e.machines = []*clusterv1.Machine{}
endpoints := []string{}
for i := 0; i < e.replicas; i++ {
machine := e.newEtcdMachine()
e.machines = append(e.machines, machine)
endpoints = append(endpoints, fmt.Sprintf("https://%v:2379", machine.Status.Addresses[0].Address))
}
e.etcdadmCluster.Status.Endpoints = strings.Join(endpoints, ",")
return e
}
func (e *etcdadmClusterTest) withHealthCheckRetries(retries int) *etcdadmClusterTest {
if e.etcdadmCluster.Annotations == nil {
e.etcdadmCluster.Annotations = map[string]string{}
}
e.etcdadmCluster.Annotations[etcdv1.HealthCheckRetriesAnnotation] = fmt.Sprintf("%d", retries)
return e
}
// newClusterWithExternalEtcd return a CAPI cluster object with managed external etcd ref
func (e *etcdadmClusterTest) newClusterWithExternalEtcd() *clusterv1.Cluster {
return &clusterv1.Cluster{
TypeMeta: metav1.TypeMeta{
Kind: "Cluster",
APIVersion: clusterv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: e.namespace,
Name: e.name,
UID: types.UID(uuid.New().String()),
},
Spec: clusterv1.ClusterSpec{
ManagedExternalEtcdRef: &corev1.ObjectReference{
Kind: "EtcdadmCluster",
Namespace: e.namespace,
Name: e.name,
APIVersion: etcdv1.GroupVersion.String(),
},
InfrastructureRef: &corev1.ObjectReference{
Kind: "InfrastructureTemplate",
Namespace: e.namespace,
Name: testInfrastructureTemplateName,
APIVersion: "infra.io/v1",
},
},
Status: clusterv1.ClusterStatus{
InfrastructureReady: true,
},
}
}
func (e *etcdadmClusterTest) newEtcdadmCluster(cluster *clusterv1.Cluster) *etcdv1.EtcdadmCluster {
return &etcdv1.EtcdadmCluster{
TypeMeta: metav1.TypeMeta{
Kind: "EtcdadmCluster",
APIVersion: etcdv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: e.namespace,
Name: e.getEtcdClusterName(),
UID: types.UID(uuid.New().String()),
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(e.cluster, clusterv1.GroupVersion.WithKind("Cluster")),
},
Finalizers: []string{etcdv1.EtcdadmClusterFinalizer},
},
Spec: etcdv1.EtcdadmClusterSpec{
EtcdadmConfigSpec: etcdbootstrapv1.EtcdadmConfigSpec{
CloudInitConfig: &etcdbootstrapv1.CloudInitConfig{
Version: etcdVersion,
},
},
Replicas: ptr.To(int32(e.replicas)),
InfrastructureTemplate: corev1.ObjectReference{
Kind: infraTemplate.GetKind(),
APIVersion: infraTemplate.GetAPIVersion(),
Name: infraTemplate.GetName(),
Namespace: e.namespace,
},
},
}
}
func (e *etcdadmClusterTest) newEtcdMachine() *clusterv1.Machine {
etcdMachine := &clusterv1.Machine{
TypeMeta: metav1.TypeMeta{
Kind: "Machine",
APIVersion: clusterv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: names.SimpleNameGenerator.GenerateName(e.etcdadmCluster.Name + "-"),
Namespace: e.etcdadmCluster.Namespace,
Labels: EtcdLabelsForCluster(e.cluster.Name, e.etcdadmCluster.Name),
UID: types.UID(uuid.New().String()),
Finalizers: []string{etcdv1.EtcdadmClusterFinalizer},
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(e.etcdadmCluster, etcdv1.GroupVersion.WithKind("EtcdadmCluster")),
},
},
Spec: clusterv1.MachineSpec{
ClusterName: e.cluster.Name,
InfrastructureRef: corev1.ObjectReference{
Kind: infraTemplate.GetKind(),
APIVersion: infraTemplate.GetAPIVersion(),
Name: infraTemplate.GetName(),
Namespace: infraTemplate.GetNamespace(),
},
},
Status: clusterv1.MachineStatus{
Addresses: []clusterv1.MachineAddress{
{
Type: clusterv1.MachineExternalIP,
Address: fmt.Sprintf("%d.%d.%d.%d", rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)),
},
},
},
}
e.machineCounter++
return etcdMachine
}
func (e *etcdadmClusterTest) gatherObjects() []client.Object {
objects := []client.Object{e.cluster, e.etcdadmCluster}
for _, machine := range e.machines {
objects = append(objects, machine)
}
return objects
}
func (e *etcdadmClusterTest) getEtcdClusterName() string {
return fmt.Sprintf("%s-%s", e.name, etcdClusterNameSuffix)
}
func (e *etcdadmClusterTest) getMemberListResponse() *clientv3.MemberListResponse {
members := []*etcdserverpb.Member{}
for _, machine := range e.machines {
members = append(members, &etcdserverpb.Member{
PeerURLs: []string{fmt.Sprintf("https://%s:2380", machine.Status.Addresses[0].Address)},
})
}
return &clientv3.MemberListResponse{
Members: members,
}
}
func (e *etcdadmClusterTest) getMemberRemoveResponse() *clientv3.MemberRemoveResponse {
return &clientv3.MemberRemoveResponse{
Members: []*etcdserverpb.Member{
{
PeerURLs: []string{fmt.Sprintf("https://%s:2380", e.machines[0].Status.Addresses[0].Address)},
},
},
}
}
func (e *etcdadmClusterTest) getDeletedMachines(client client.Client) []*clusterv1.Machine {
machines := []*clusterv1.Machine{}
for _, machine := range e.machines {
m := &clusterv1.Machine{}
_ = client.Get(context.Background(), types.NamespacedName{
Name: machine.Name,
Namespace: machine.Namespace,
}, m)
if m.DeletionTimestamp != nil {
machines = append(machines, m)
}
}
return machines
}
func getHealthyEtcdResponse() *http.Response {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString("{\"Health\": \"true\"}")),
}
}
func (e *etcdadmClusterTest) newInitSecret() {
e.initSecret = &corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: e.etcdadmCluster.Status.InitMachineAddress,
Namespace: e.cluster.Namespace,
},
Data: map[string][]byte{
"address": []byte(getEtcdMachineAddressFromClientURL(e.etcdadmCluster.Status.InitMachineAddress)),
"clientUrls": []byte(e.etcdadmCluster.Status.Endpoints),
},
}
}