Kubernetes Fake Client 不处理 ObjectMeta 中的 GenerateName
Kubernetes Fake Client doesn't handle GenerateName in ObjectMeta
当使用 Kubernetes Fake Client to write unit tests, I noticed that it fails to create two identical objects which have their 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
根据thisGitHub问题评论:
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.
要添加所需的反应器,我们可以在创建 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
structure which has the PrependReactor
method we need to call for this use case (there are a few others). This code here 在创建此类对象时被调用。
PrependReactor
方法有3个参数:verb
、resource
和reaction
。对于 verb
、resource
,我找不到任何命名常量,因此,在这种情况下,“create”和“secrets”(奇怪的是它不是“secret”)似乎是正确的值对于他们来说,如果我们想要超级具体,但是在这种情况下将 resource
设置为“*”应该是可以接受的。
reaction
参数的类型为 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
。该接口允许我们获取和设置底层对象的各种元数据字段。根据需要设置对象 Name
字段后,我们必须 return handled = false
、ret = mutatedObject
和 err = nil
来指示调用代码执行剩余的反应器。
- 深入研究
apiserver
代码,我注意到 ObjectMeta.Name
字段 is generated from the ObjectMeta.GenerateName
field using the names.SimpleNameGenerator.GenerateName
实用程序。
当使用 Kubernetes Fake Client to write unit tests, I noticed that it fails to create two identical objects which have their 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
根据thisGitHub问题评论:
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.
要添加所需的反应器,我们可以在创建 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
structure which has thePrependReactor
method we need to call for this use case (there are a few others). This code here 在创建此类对象时被调用。PrependReactor
方法有3个参数:verb
、resource
和reaction
。对于verb
、resource
,我找不到任何命名常量,因此,在这种情况下,“create”和“secrets”(奇怪的是它不是“secret”)似乎是正确的值对于他们来说,如果我们想要超级具体,但是在这种情况下将resource
设置为“*”应该是可以接受的。reaction
参数的类型为 ReactionFunc, which takes anAction
as a parameter and returnshandled
,ret
anderr
. After some digging, I noticed that theaction
parameter will be cast toCreateAction
, which has theGetObject()
method that returns aruntime.Object
instance, which can be cast tometav1.Object
。该接口允许我们获取和设置底层对象的各种元数据字段。根据需要设置对象Name
字段后,我们必须 returnhandled = false
、ret = mutatedObject
和err = nil
来指示调用代码执行剩余的反应器。- 深入研究
apiserver
代码,我注意到ObjectMeta.Name
字段 is generated from theObjectMeta.GenerateName
field using thenames.SimpleNameGenerator.GenerateName
实用程序。