unit-testing - Kubernetes Fake Client 不处理 ObjectMeta 中的 GenerateName
问题描述
在使用 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
解决方案
根据这个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 个参数verb
:resource
和reaction
。对于verb
,resource
,我找不到任何命名常量,因此,在这种情况下,如果我们想要超级具体,“create”和“secrets”(奇怪的是它不是“secret”)似乎是它们的正确值, 但resource
在这种情况下设置为“*”应该是可以接受的。 reaction
参数是ReactionFunc类型,它接受 anAction
作为参数并返回handled
,ret
和err
。经过一番挖掘,我注意到action
参数将被强制转换为CreateAction
,其中具有GetObject()
返回runtime.Object
实例的方法,可以强制转换为metav1.Object
。这个接口允许我们获取和设置底层对象的各种元数据字段。根据需要设置对象Name
字段后,我们必须返回handled = false
,ret = mutatedObject
并err = nil
指示调用代码执行剩余的反应器。- 挖掘
apiserver
代码,我注意到该ObjectMeta.Name
字段是使用该实用程序从该字段生成的。ObjectMeta.GenerateName
names.SimpleNameGenerator.GenerateName
推荐阅读
- c# - 在 Asp.Net Core 中使用 Swagger 在请求中未发送授权承载令牌
- datatable - 具有动态模型和列的 Primefaces 8.0 数据表
- python - 如何解压缩可能为空的“zip”结果?
- elasticsearch - ElasticSearch 何时执行刷新
- getelementsbyname - 为什么 document.getElementsByName().length 总是返回 0?
- python - 如何使用python请求发送不带参数的post类型请求
- qaf - QAF 报告未填充到 dashboard.htm
- python - Django:“UtilisateurUpdateView”对象没有属性“对象”
- .net - .Net Core appsettings.json 最佳实践 - 覆盖开发设置(反之亦然)?
- javascript - 从 url 哈希中删除字符