447 lines
13 KiB
Go
447 lines
13 KiB
Go
/*
|
|
Copyright 2024 The Karmada Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package apiclient
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
clientset "k8s.io/client-go/kubernetes"
|
|
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
|
"k8s.io/client-go/rest"
|
|
fakerest "k8s.io/client-go/rest/fake"
|
|
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
|
aggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
|
fakeAggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
|
|
|
|
"github.com/karmada-io/karmada/operator/pkg/constants"
|
|
)
|
|
|
|
func TestWaitForAPI(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
karmadaWriter *KarmadaWaiter
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "WaitForAPI_WaitingForAPIServerHealthyStatus_Timeout",
|
|
karmadaWriter: &KarmadaWaiter{
|
|
karmadaConfig: &rest.Config{},
|
|
client: &MockK8SRESTClient{
|
|
RESTClientConnector: &fakerest.RESTClient{
|
|
NegotiatedSerializer: runtime.NewSimpleNegotiatedSerializer(runtime.SerializerInfo{}),
|
|
Client: fakerest.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
|
return nil, fmt.Errorf("unexpected error, endpoint %s does not exist", req.URL.Path)
|
|
}),
|
|
},
|
|
},
|
|
timeout: time.Second,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "WaitForAPI_WaitingForAPIServerHealthyStatus_APIServerIsHealthy",
|
|
karmadaWriter: &KarmadaWaiter{
|
|
karmadaConfig: &rest.Config{},
|
|
client: &MockK8SRESTClient{
|
|
RESTClientConnector: &fakerest.RESTClient{
|
|
NegotiatedSerializer: runtime.NewSimpleNegotiatedSerializer(runtime.SerializerInfo{}),
|
|
Client: fakerest.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
|
if req.URL.Path == "/healthz" {
|
|
// Return a fake 200 OK response.
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Body: http.NoBody,
|
|
}, nil
|
|
}
|
|
return nil, fmt.Errorf("unexpected error, endpoint %s does not exist", req.URL.Path)
|
|
}),
|
|
},
|
|
},
|
|
timeout: time.Millisecond,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
err := test.karmadaWriter.WaitForAPI()
|
|
if err == nil && test.wantErr {
|
|
t.Errorf("expected an error, but got none")
|
|
}
|
|
if err != nil && !test.wantErr {
|
|
t.Errorf("unexpected error, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWaitForAPIService(t *testing.T) {
|
|
name := "karmada-demo-apiservice"
|
|
tests := []struct {
|
|
name string
|
|
karmadaWriter *KarmadaWaiter
|
|
apiService *apiregistrationv1.APIService
|
|
client aggregator.Interface
|
|
prep func(aggregator.Interface, *apiregistrationv1.APIService) error
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "WaitForAPIService_WaitingForKarmadaAPIServiceAvailableStatus_Timeout",
|
|
karmadaWriter: &KarmadaWaiter{
|
|
karmadaConfig: &rest.Config{},
|
|
timeout: time.Second,
|
|
},
|
|
apiService: &apiregistrationv1.APIService{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Spec: apiregistrationv1.APIServiceSpec{
|
|
Service: &apiregistrationv1.ServiceReference{
|
|
Name: "karmada-demo-service",
|
|
Namespace: "test",
|
|
},
|
|
Version: "v1beta1",
|
|
},
|
|
},
|
|
client: fakeAggregator.NewSimpleClientset(),
|
|
prep: func(client aggregator.Interface, _ *apiregistrationv1.APIService) error {
|
|
aggregateClientFromConfigBuilder = func(*rest.Config) (aggregator.Interface, error) {
|
|
return client, nil
|
|
}
|
|
return nil
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "WaitForAPIService_WaitingForKarmadaAPIServiceAvailableStatus_KarmadaAPIServiceIsAvailable",
|
|
karmadaWriter: &KarmadaWaiter{
|
|
karmadaConfig: &rest.Config{},
|
|
timeout: time.Millisecond,
|
|
},
|
|
apiService: &apiregistrationv1.APIService{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Spec: apiregistrationv1.APIServiceSpec{
|
|
Service: &apiregistrationv1.ServiceReference{
|
|
Name: "karmada-demo-service",
|
|
Namespace: "test",
|
|
},
|
|
Version: "v1beta1",
|
|
},
|
|
},
|
|
client: fakeAggregator.NewSimpleClientset(),
|
|
prep: func(client aggregator.Interface, apiService *apiregistrationv1.APIService) error {
|
|
apiServiceCreated, err := client.ApiregistrationV1().APIServices().Create(context.TODO(), apiService, metav1.CreateOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create api service %s, got err: %v", apiService.Name, err)
|
|
}
|
|
apiServiceCreated.Status = apiregistrationv1.APIServiceStatus{
|
|
Conditions: []apiregistrationv1.APIServiceCondition{
|
|
{
|
|
Type: apiregistrationv1.Available,
|
|
Status: apiregistrationv1.ConditionTrue,
|
|
},
|
|
},
|
|
}
|
|
if _, err = client.ApiregistrationV1().APIServices().Update(context.TODO(), apiServiceCreated, metav1.UpdateOptions{}); err != nil {
|
|
return fmt.Errorf("failed to update api service with available status, got err: %v", err)
|
|
}
|
|
aggregateClientFromConfigBuilder = func(*rest.Config) (aggregator.Interface, error) {
|
|
return client, nil
|
|
}
|
|
return nil
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := test.prep(test.client, test.apiService); err != nil {
|
|
t.Errorf("failed to prep waiting for Karmada API Service, got err: %v", err)
|
|
}
|
|
err := test.karmadaWriter.WaitForAPIService(name)
|
|
if err == nil && test.wantErr {
|
|
t.Errorf("expected an error, but got none")
|
|
}
|
|
if err != nil && !test.wantErr {
|
|
t.Errorf("unexpected error, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWaitForPods(t *testing.T) {
|
|
name, namespace := "karmada-demo-apiserver", "test"
|
|
karmadaAPIServerLabels := labels.Set{constants.AppNameLabel: constants.KarmadaAPIServer}
|
|
var replicas int32 = 2
|
|
tests := []struct {
|
|
name string
|
|
karmadaWriter *KarmadaWaiter
|
|
prep func(client clientset.Interface) error
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "WaitForPods_WaitingForAllKarmadaAPIServerPods_Timeout",
|
|
karmadaWriter: &KarmadaWaiter{
|
|
karmadaConfig: &rest.Config{},
|
|
client: fakeclientset.NewSimpleClientset(),
|
|
timeout: time.Second,
|
|
},
|
|
prep: func(client clientset.Interface) error {
|
|
_, err := CreatePods(client, namespace, name, replicas, karmadaAPIServerLabels, false)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create pods, got err: %v", err)
|
|
}
|
|
return nil
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "WaitForPods_WaitingForAllKarmadaAPIServerPods_AllAreUpAndRunning",
|
|
karmadaWriter: &KarmadaWaiter{
|
|
karmadaConfig: &rest.Config{},
|
|
client: fakeclientset.NewSimpleClientset(),
|
|
timeout: time.Second * 2,
|
|
},
|
|
prep: func(client clientset.Interface) error {
|
|
pods, err := CreatePods(client, namespace, name, replicas, karmadaAPIServerLabels, false)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create pods, got err: %v", err)
|
|
}
|
|
time.AfterFunc(time.Second, func() {
|
|
for _, pod := range pods {
|
|
if err := UpdatePodStatus(client, pod); err != nil {
|
|
fmt.Printf("failed to update pod status, got err: %v", err)
|
|
return
|
|
}
|
|
}
|
|
})
|
|
return nil
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := test.prep(test.karmadaWriter.client); err != nil {
|
|
t.Errorf("failed to prep before waiting for all Karmada APIServer pods , got err: %v", err)
|
|
}
|
|
err := test.karmadaWriter.WaitForPods(karmadaAPIServerLabels.String(), namespace)
|
|
if err == nil && test.wantErr {
|
|
t.Errorf("expected an error, but got none")
|
|
}
|
|
if err != nil && !test.wantErr {
|
|
t.Errorf("unexpected error, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWaitForSomePods(t *testing.T) {
|
|
name, namespace := "karmada-demo-apiserver", "test"
|
|
karmadaAPIServerLabels := labels.Set{constants.AppNameLabel: constants.KarmadaAPIServer}
|
|
var replicas int32 = 2
|
|
tests := []struct {
|
|
name string
|
|
karmadaWriter *KarmadaWaiter
|
|
prep func(client clientset.Interface) error
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "WaitForSomePods_WaitingForSomeKarmadaAPIServerPods_Timeout",
|
|
karmadaWriter: &KarmadaWaiter{
|
|
karmadaConfig: &rest.Config{},
|
|
client: fakeclientset.NewSimpleClientset(),
|
|
timeout: time.Second,
|
|
},
|
|
prep: func(client clientset.Interface) error {
|
|
_, err := CreatePods(client, namespace, name, replicas, karmadaAPIServerLabels, false)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create pods, got err: %v", err)
|
|
}
|
|
return nil
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "WaitForSomePods_WaitingForSomeKarmadaAPIServerPods_SomeAreUpAndRunning",
|
|
karmadaWriter: &KarmadaWaiter{
|
|
karmadaConfig: &rest.Config{},
|
|
client: fakeclientset.NewSimpleClientset(),
|
|
timeout: time.Millisecond,
|
|
},
|
|
prep: func(client clientset.Interface) error {
|
|
pods, err := CreatePods(client, namespace, name, replicas, karmadaAPIServerLabels, false)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create pods, got err: %v", err)
|
|
}
|
|
for _, pod := range pods[:1] {
|
|
if err := UpdatePodStatus(client, pod); err != nil {
|
|
return fmt.Errorf("failed to update pod status, got err: %v", err)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := test.prep(test.karmadaWriter.client); err != nil {
|
|
t.Errorf("failed to prep before waiting for some Karmada APIServer pods , got err: %v", err)
|
|
}
|
|
err := test.karmadaWriter.WaitForSomePods(karmadaAPIServerLabels.String(), namespace, 1)
|
|
if err == nil && test.wantErr {
|
|
t.Errorf("expected an error, but got none")
|
|
}
|
|
if err != nil && !test.wantErr {
|
|
t.Errorf("unexpected error, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTryRunCommand(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
failureThreshold int
|
|
targetFunc func() error
|
|
prep func() error
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "TryRunCommand_HitTheFailureThreshold_CommandTimedOut",
|
|
failureThreshold: 2,
|
|
targetFunc: func() error {
|
|
return errors.New("unexpected error")
|
|
},
|
|
prep: func() error {
|
|
initialBackoffDuration = time.Millisecond
|
|
return nil
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "TryRunCommand_BelowFailureThreshold_CommandRunSuccessfully",
|
|
failureThreshold: 2,
|
|
targetFunc: func() error { return nil },
|
|
prep: func() error {
|
|
initialBackoffDuration = time.Millisecond
|
|
return nil
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if err := test.prep(); err != nil {
|
|
t.Errorf("failed to prep before trying to running command, got err: %v", err)
|
|
}
|
|
err := TryRunCommand(test.targetFunc, test.failureThreshold)
|
|
if err == nil && test.wantErr {
|
|
t.Errorf("expected an error, but got none")
|
|
}
|
|
if err != nil && !test.wantErr {
|
|
t.Errorf("unexpected error, got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsPodRunning(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pod *corev1.Pod
|
|
want bool
|
|
}{
|
|
{
|
|
name: "IsPodRunning_PodInPendingState_PodIsNotRunningYet",
|
|
pod: &corev1.Pod{
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodPending,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "IsPodRunning_WithDeletionTimestamp_PodIsNotRunningYet",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
DeletionTimestamp: &metav1.Time{Time: time.Now()},
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "IsPodRunning_PodReadyConditionReadinessIsFalse_PodIsNotRunningYet",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
DeletionTimestamp: nil,
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
Conditions: []corev1.PodCondition{
|
|
{
|
|
Type: corev1.PodReady,
|
|
Status: corev1.ConditionFalse,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "IsPodRunning_PodSatisfyAllRunningConditions_PodIsAlreadyRunning",
|
|
pod: &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
DeletionTimestamp: nil,
|
|
},
|
|
Status: corev1.PodStatus{
|
|
Phase: corev1.PodRunning,
|
|
Conditions: []corev1.PodCondition{
|
|
{
|
|
Type: corev1.PodReady,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if got := isPodRunning(*test.pod); got != test.want {
|
|
t.Errorf("expected pod running status %t, but got %t", test.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|