blob: 9815b75efafb62a6c49b48b101b71cb2200b2f8a [file] [log] [blame]
// Copyright Istio 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 k8s
import (
"context"
"fmt"
"testing"
"time"
)
import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/informers"
informersv1 "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes/fake"
ktesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config/constants"
)
const (
configMapName = "test-configmap-name"
namespaceName = "test-ns"
dataName = "test-data-name"
)
func TestUpdateDataInConfigMap(t *testing.T) {
gvr := schema.GroupVersionResource{
Resource: "configmaps",
Version: "v1",
}
testMeta := metav1.ObjectMeta{Namespace: namespaceName, Name: configMapName}
caBundle := "test-data"
testData := map[string]string{
constants.CACertNamespaceConfigMapDataName: "test-data",
}
testCases := []struct {
name string
existingConfigMap *v1.ConfigMap
expectedActions []ktesting.Action
expectedErr string
}{
{
name: "non-existing ConfigMap",
expectedErr: "cannot update nil configmap",
},
{
name: "existing empty ConfigMap",
existingConfigMap: createConfigMap(namespaceName, configMapName, map[string]string{}),
expectedActions: []ktesting.Action{
ktesting.NewUpdateAction(gvr, namespaceName, createConfigMap(namespaceName, configMapName, testData)),
},
expectedErr: "",
},
{
name: "existing nop ConfigMap",
existingConfigMap: createConfigMap(namespaceName, configMapName, testData),
expectedActions: []ktesting.Action{},
expectedErr: "",
},
{
name: "existing with other keys",
existingConfigMap: createConfigMap(namespaceName, configMapName, map[string]string{"foo": "bar"}),
expectedActions: []ktesting.Action{
ktesting.NewUpdateAction(gvr, namespaceName, createConfigMap(namespaceName, configMapName,
map[string]string{"test-key": "test-data", "foo": "bar"})),
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := fake.NewSimpleClientset()
if tc.existingConfigMap != nil {
if _, err := client.CoreV1().ConfigMaps(testMeta.Namespace).Create(context.TODO(), tc.existingConfigMap, metav1.CreateOptions{}); err != nil {
t.Errorf("failed to create configmap %v", err)
}
}
client.ClearActions()
err := updateDataInConfigMap(client.CoreV1(), tc.existingConfigMap, []byte(caBundle))
if err != nil && err.Error() != tc.expectedErr {
t.Errorf("actual error (%s) different from expected error (%s).", err.Error(), tc.expectedErr)
}
if err == nil {
if tc.expectedErr != "" {
t.Errorf("expecting error %s but got no error", tc.expectedErr)
} else if err := checkActions(client.Actions(), tc.expectedActions); err != nil {
t.Error(err)
}
}
})
}
}
func TestInsertDataToConfigMap(t *testing.T) {
gvr := schema.GroupVersionResource{
Resource: "configmaps",
Version: "v1",
}
caBundle := []byte("test-data")
testData := map[string]string{
constants.CACertNamespaceConfigMapDataName: "test-data",
}
testCases := []struct {
name string
meta metav1.ObjectMeta
existingConfigMap *v1.ConfigMap
caBundle []byte
expectedActions []ktesting.Action
expectedErr string
client *fake.Clientset
}{
{
name: "non-existing ConfigMap",
existingConfigMap: nil,
caBundle: caBundle,
meta: metav1.ObjectMeta{Namespace: namespaceName, Name: configMapName},
expectedActions: []ktesting.Action{
ktesting.NewCreateAction(gvr, namespaceName, createConfigMap(namespaceName,
configMapName, testData)),
},
expectedErr: "",
},
{
name: "existing ConfigMap",
meta: metav1.ObjectMeta{Namespace: namespaceName, Name: configMapName},
existingConfigMap: createConfigMap(namespaceName, configMapName, map[string]string{}),
caBundle: caBundle,
expectedActions: []ktesting.Action{
ktesting.NewUpdateAction(gvr, namespaceName, createConfigMap(namespaceName, configMapName, testData)),
},
expectedErr: "",
},
{
name: "creation failure for ConfigMap",
existingConfigMap: nil,
caBundle: caBundle,
meta: metav1.ObjectMeta{Namespace: namespaceName, Name: configMapName},
expectedActions: []ktesting.Action{
ktesting.NewGetAction(gvr, namespaceName, configMapName),
ktesting.NewGetAction(gvr, namespaceName, configMapName),
ktesting.NewCreateAction(gvr, namespaceName, createConfigMap(namespaceName, configMapName,
map[string]string{dataName: "test-data"})),
},
expectedErr: fmt.Sprintf("error when creating configmap %v: no permission to create configmap",
configMapName),
client: createConfigMapDisabledClient(),
},
{
name: "creation: concurrently created by other client",
existingConfigMap: nil,
caBundle: caBundle,
meta: metav1.ObjectMeta{Namespace: namespaceName, Name: configMapName},
expectedActions: []ktesting.Action{
ktesting.NewCreateAction(gvr, namespaceName, createConfigMap(namespaceName, configMapName,
map[string]string{dataName: "test-data"})),
},
expectedErr: "",
client: createConfigMapAlreadyExistClient(),
},
{
name: "creation: namespace is deleting",
existingConfigMap: nil,
caBundle: caBundle,
meta: metav1.ObjectMeta{Namespace: namespaceName, Name: configMapName},
expectedActions: []ktesting.Action{
ktesting.NewCreateAction(gvr, namespaceName, createConfigMap(namespaceName, configMapName,
map[string]string{dataName: "test-data"})),
},
expectedErr: "",
client: createConfigMapNamespaceDeletingClient(),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var client *fake.Clientset
if tc.client == nil {
client = fake.NewSimpleClientset()
} else {
client = tc.client
}
lister := createFakeLister(client)
if tc.existingConfigMap != nil {
if _, err := client.CoreV1().ConfigMaps(tc.meta.Namespace).Create(context.TODO(), tc.existingConfigMap, metav1.CreateOptions{}); err != nil {
t.Errorf("failed to create configmap %v", err)
}
if err := lister.Informer().GetIndexer().Add(tc.existingConfigMap); err != nil {
t.Errorf("failed to add configmap to informer %v", err)
}
}
client.ClearActions()
err := InsertDataToConfigMap(client.CoreV1(), lister.Lister(), tc.meta, tc.caBundle)
if err != nil && err.Error() != tc.expectedErr {
t.Errorf("actual error (%s) different from expected error (%s).", err.Error(), tc.expectedErr)
}
if err == nil {
if tc.expectedErr != "" {
t.Errorf("expecting error %s but got no error", tc.expectedErr)
} else if err := checkActions(client.Actions(), tc.expectedActions); err != nil {
t.Error(err)
}
}
})
}
}
func createConfigMapDisabledClient() *fake.Clientset {
client := &fake.Clientset{}
fakeWatch := watch.NewFake()
client.AddWatchReactor("configmaps", ktesting.DefaultWatchReactor(fakeWatch, nil))
client.AddReactor("get", "configmaps", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, &v1.ConfigMap{}, errors.NewNotFound(v1.Resource("configmaps"), configMapName)
})
client.AddReactor("create", "configmaps", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, &v1.ConfigMap{}, errors.NewUnauthorized("no permission to create configmap")
})
return client
}
func createConfigMapAlreadyExistClient() *fake.Clientset {
client := &fake.Clientset{}
fakeWatch := watch.NewFake()
client.AddWatchReactor("configmaps", ktesting.DefaultWatchReactor(fakeWatch, nil))
client.AddReactor("get", "configmaps", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, &v1.ConfigMap{}, errors.NewNotFound(v1.Resource("configmaps"), configMapName)
})
client.AddReactor("create", "configmaps", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, &v1.ConfigMap{}, errors.NewAlreadyExists(v1.Resource("configmaps"), configMapName)
})
return client
}
func createConfigMapNamespaceDeletingClient() *fake.Clientset {
client := &fake.Clientset{}
fakeWatch := watch.NewFake()
client.AddWatchReactor("configmaps", ktesting.DefaultWatchReactor(fakeWatch, nil))
client.AddReactor("get", "configmaps", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, &v1.ConfigMap{}, errors.NewNotFound(v1.Resource("configmaps"), configMapName)
})
err := errors.NewForbidden(v1.Resource("configmaps"), configMapName,
fmt.Errorf("unable to create new content in namespace %s because it is being terminated", namespaceName))
err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{
Type: v1.NamespaceTerminatingCause,
Message: fmt.Sprintf("namespace %s is being terminated", namespaceName),
Field: "metadata.namespace",
})
client.AddReactor("create", "configmaps", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, &v1.ConfigMap{}, err
})
return client
}
// nolint: unparam
func createConfigMap(namespace, configName string, data map[string]string) *v1.ConfigMap {
return &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configName,
Namespace: namespace,
},
Data: data,
}
}
func checkActions(actual, expected []ktesting.Action) error {
if len(actual) != len(expected) {
return fmt.Errorf("unexpected number of actions, want %d but got %d, %v", len(expected), len(actual), actual)
}
for i, action := range actual {
expectedAction := expected[i]
verb := expectedAction.GetVerb()
resource := expectedAction.GetResource().Resource
if !action.Matches(verb, resource) {
return fmt.Errorf("unexpected %dth action, want \n%+v but got \n%+v", i, expectedAction, action)
}
}
return nil
}
func createFakeLister(kubeClient *fake.Clientset) informersv1.ConfigMapInformer {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
informerFactory := informers.NewSharedInformerFactory(kubeClient, time.Second)
configmapInformer := informerFactory.Core().V1().ConfigMaps().Informer()
go configmapInformer.Run(ctx.Done())
cache.WaitForCacheSync(ctx.Done(), configmapInformer.HasSynced)
return informerFactory.Core().V1().ConfigMaps()
}