Kubernetes Fake Client在ObjectMeta中无法处理GenerateName。

huangapple go评论82阅读模式
英文:

Kubernetes Fake Client doesn't handle GenerateName in ObjectMeta

问题

在使用Kubernetes的Fake Client编写单元测试时,我注意到它无法创建两个具有相同ObjectMeta.GenerateName字段设置为某个字符串的相同对象。真实的集群接受此规范并为每个对象生成唯一名称。

运行以下测试代码:

package main

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/fake"
)

func TestFake(t *testing.T) {
	ctx := context.Background()
	client := fake.NewSimpleClientset()

	_, err := client.CoreV1().Secrets("default").Create(ctx, &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "generated",
		},
		StringData: map[string]string{"foo": "bar"},
	}, metav1.CreateOptions{})
	assert.NoError(t, err)

	_, err = client.CoreV1().Secrets("default").Create(ctx, &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "generated",
		},
		StringData: map[string]string{"foo": "bar"},
	}, metav1.CreateOptions{})
	assert.NoError(t, err)
}

会失败并显示以下错误信息:

--- FAIL: TestFake (0.00s)
    /Users/mihaitodor/Projects/kubernetes/main_test.go:44: 
        Error Trace:	main_test.go:44
        Error:      	Received unexpected error:
                    	secrets "" already exists
        Test:       	TestFake
FAIL
FAIL	kubernetes	0.401s
FAIL
英文:

When using the Kubernetes Fake Client to write unit tests, I noticed that it fails to create two identical objects which have their ObjectMeta.GenerateName field set to some string. A real cluster accepts this specification and generates a unique name for each object.

Running the following test code:

package main

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/fake"
)

func TestFake(t *testing.T) {
	ctx := context.Background()
	client := fake.NewSimpleClientset()

	_, err := client.CoreV1().Secrets("default").Create(ctx, &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "generated",
		},
		StringData: map[string]string{"foo": "bar"},
	}, metav1.CreateOptions{})
	assert.NoError(t, err)

	_, err = client.CoreV1().Secrets("default").Create(ctx, &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: "generated",
		},
		StringData: map[string]string{"foo": "bar"},
	}, metav1.CreateOptions{})
	assert.NoError(t, err)
}

fails with

--- FAIL: TestFake (0.00s)
    /Users/mihaitodor/Projects/kubernetes/main_test.go:44: 
        	Error Trace:	main_test.go:44
        	Error:      	Received unexpected error:
        	            	secrets "" already exists
        	Test:       	TestFake
FAIL
FAIL	kubernetes	0.401s
FAIL

答案1

得分: 2

根据这个GitHub问题的评论:

假的客户端集不会尝试复制服务器端的行为,如验证、名称生成、UID分配等。如果你想测试这样的行为,可以添加反应器来模拟这种行为。

要添加所需的反应器,我们可以在创建corev1.Secret对象之前插入以下代码:

client.PrependReactor(
	"create", "*",
	func(action k8sTesting.Action) (handled bool, ret runtime.Object, err error) {
		ret = action.(k8sTesting.CreateAction).GetObject()
		meta, ok := ret.(metav1.Object)
		if !ok {
			return
		}

		if meta.GetName() == "" && meta.GetGenerateName() != "" {
			meta.SetName(names.SimpleNameGenerator.GenerateName(meta.GetGenerateName()))
		}

		return
	},
)

其中有一些要注意的地方:

  • Clientset包含一个嵌入的Fake结构,其中包含我们需要调用的PrependReactor方法(还有其他几个)。当创建这样的对象时,会调用这里的代码。

  • PrependReactor方法有3个参数:verbresourcereaction。对于verbresource,我找不到任何命名常量,所以在这种情况下,"create"和"secrets"(奇怪的是不是"secret")似乎是正确的值,如果我们想要非常具体的话,但在这种情况下,将resource设置为"*"应该是可以接受的。

  • reaction参数的类型是ReactionFunc,它以Action作为参数,并返回handledreterr。经过一些挖掘,我注意到action参数将被转换为CreateAction,它具有返回runtime.Object实例的GetObject()方法,可以将其转换为metav1.Object。这个接口允许我们获取和设置底层对象的各种元数据字段。在设置完对象的Name字段后,我们必须返回handled = falseret = mutatedObjecterr = nil,以指示调用代码执行剩余的反应器。

  • 在浏览apiserver代码时,我注意到ObjectMeta.Name字段是使用names.SimpleNameGenerator.GenerateName工具从ObjectMeta.GenerateName字段生成的

英文:

According to this GitHub issue comment:

> the fake clientset doesn't attempt to duplicate server-side behavior
> like validation, name generation, uid assignment, etc. if you want to
> test things like that, you can add reactors to mock that behavior.

To add the required reactor, we can insert the following code before creating the corev1.Secret objects:

client.PrependReactor(
	"create", "*",
	func(action k8sTesting.Action) (handled bool, ret runtime.Object, err error) {
		ret = action.(k8sTesting.CreateAction).GetObject()
		meta, ok := ret.(metav1.Object)
		if !ok {
			return
		}

		if meta.GetName() == "" && meta.GetGenerateName() != "" {
			meta.SetName(names.SimpleNameGenerator.GenerateName(meta.GetGenerateName()))
		}

		return
	},
)

There are a few gotchas in there:

  • The Clientset contains an embedded Fake structure which has the PrependReactor method we need to call for this use case (there are a few others). This code here is invoked when creating such objects.

  • The PrependReactor method has 3 parameters: verb, resource and reaction. For verb, resource, I couldn't find any named constants, so, in this case, "create" and "secrets" (strange that it's not "secret") seem to be the correct values for them if we want to be super-specific, but setting resource to "*" should be acceptable in this case.

  • The reaction parameter is of type ReactionFunc, which takes an Action as a parameter and returns handled, ret and err. After some digging, I noticed that the action parameter will be cast to CreateAction, which has the GetObject() method that returns a runtime.Object instance, which can be cast to metav1.Object. This interface allows us to get and set the various metadata fields of the underlying object. After setting the object Name field as needed, we have to return handled = false, ret = mutatedObject and err = nil to instruct the calling code to execute the remaining reactors.

  • Digging through the apiserver code, I noticed that the ObjectMeta.Name field is generated from the ObjectMeta.GenerateName field using the names.SimpleNameGenerator.GenerateName utility.

huangapple
  • 本文由 发表于 2021年8月16日 02:59:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/68794562.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定