-
Notifications
You must be signed in to change notification settings - Fork 271
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1.分离 client 中的真实和 mock 调用 k8s api,以便于 ut 不依赖于当前的 k8s 环境。
factory.go: 将client的实例管理逻辑统一,方便调用和切换fake。兼容当前 KubeClient 全局变量的使用。 k8s_client.go: 统一实现 k8s 真实调用行为, 记录调用日志,去掉全局变量。 2.修改 util 和 util_test 中的相关调用, 统一 util_test 中测试用例的命名风格。 本次修改仅关注于 real 和 mock 分离,后续将完善更多 ut 测试,逐步替换当前 client 中的直接调用方式。 3.兼容当前新老调用 KubeClient 的用法,特别是 fake 替换全部变量时。 Signed-off-by: yangshiqi <[email protected]>
- Loading branch information
Showing
7 changed files
with
315 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
Copyright 2024 The HAMi 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 client | ||
|
||
import ( | ||
"sync" | ||
|
||
"k8s.io/client-go/kubernetes/fake" | ||
) | ||
|
||
// KubeClientFactory is a factory class (singleton pattern). | ||
type KubeClientFactory struct { | ||
client KubeInterface | ||
} | ||
|
||
var factoryOnce sync.Once | ||
|
||
// GetFactory gets the singleton factory object. | ||
// Uses closure to maintain instance as a local variable. | ||
var GetFactory = (func() func() *KubeClientFactory { | ||
var instance *KubeClientFactory | ||
|
||
return func() *KubeClientFactory { | ||
factoryOnce.Do(func() { | ||
instance = &KubeClientFactory{} | ||
instance.SetReal() // Use the real client by default. | ||
}) | ||
return instance | ||
} | ||
})() | ||
|
||
// NewInstance directly gets the Kubernetes client instance. | ||
func NewInstance() KubeInterface { | ||
return GetFactory().GetClient() | ||
} | ||
|
||
func (f *KubeClientFactory) GetClient() KubeInterface { | ||
//TODO: For compatibility with other direct "KubeClient" assignment call points, this line needs to be removed after replacement. | ||
if KubeClient == nil { | ||
f.client = &K8sClient{ | ||
client: GetK8sClient().client, | ||
} | ||
KubeClient = GetK8sClient().client | ||
} else { | ||
f.client = &K8sClient{ | ||
client: KubeClient, | ||
} | ||
} | ||
return f.client | ||
} | ||
|
||
func (f *KubeClientFactory) SetFake() *KubeClientFactory { | ||
f.client = &K8sClient{ | ||
client: fake.NewSimpleClientset(), | ||
} | ||
//TODO: For compatibility with other direct "KubeClient" assignment call points, this line needs to be removed after replacement. | ||
KubeClient = fake.NewSimpleClientset() | ||
return f | ||
} | ||
|
||
func (f *KubeClientFactory) SetReal() *KubeClientFactory { | ||
//TODO: For compatibility with other direct "KubeClient" assignment call points, this line needs to be removed after replacement. | ||
if KubeClient == nil { | ||
f.client = &K8sClient{ | ||
client: GetK8sClient().client, | ||
} | ||
KubeClient = GetK8sClient().client | ||
} else { | ||
f.client = &K8sClient{ | ||
client: KubeClient, | ||
} | ||
} | ||
return f | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
Copyright 2024 The HAMi 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 client | ||
|
||
import ( | ||
"context" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
) | ||
|
||
type KubeInterface interface { | ||
GetNode(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Node, error) | ||
ListPods(ctx context.Context, namespace string, opts metav1.ListOptions) (*corev1.PodList, error) | ||
GetPod(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.Pod, error) | ||
PatchNode(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions) (*corev1.Node, error) | ||
PatchPod(ctx context.Context, namespace string, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions) (*corev1.Pod, error) | ||
CreateNode(ctx context.Context, node *corev1.Node, opts metav1.CreateOptions) (*corev1.Node, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
/* | ||
Copyright 2024 The HAMi 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 client | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"sync" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/client-go/kubernetes" | ||
"k8s.io/client-go/rest" | ||
"k8s.io/client-go/tools/clientcmd" | ||
"k8s.io/klog/v2" | ||
) | ||
|
||
var ClientOnce sync.Once | ||
|
||
// GetK8sClient creates a real Kubernetes client (singleton pattern). | ||
// Uses closure to maintain client as a local variable. | ||
var GetK8sClient = (func() func() *K8sClient { | ||
var client *K8sClient | ||
|
||
return func() *K8sClient { | ||
var err error | ||
ClientOnce.Do(func() { | ||
client, err = createRealClient() | ||
if err != nil { | ||
klog.Fatalf("Failed to create Kubernetes client: %v", err) | ||
} | ||
}) | ||
return client | ||
} | ||
})() | ||
|
||
type K8sClient struct { | ||
client kubernetes.Interface | ||
} | ||
|
||
var _ KubeInterface = (*K8sClient)(nil) | ||
|
||
func createRealClient() (*K8sClient, error) { | ||
kubeConfigPath := os.Getenv("KUBECONFIG") | ||
if kubeConfigPath == "" { | ||
kubeConfigPath = filepath.Join(os.Getenv("HOME"), ".kube", "config") | ||
} | ||
|
||
config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath) | ||
if err != nil { | ||
klog.ErrorS(err, "BuildConfigFromFlags failed for file %s: %v. Using in-cluster config.", kubeConfigPath, err) | ||
config, err = rest.InClusterConfig() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get in-cluster config: %w", err) | ||
} | ||
} | ||
|
||
clientset, err := kubernetes.NewForConfig(config) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create kubernetes client: %w", err) | ||
} | ||
|
||
return &K8sClient{client: clientset}, nil | ||
} | ||
|
||
func (c *K8sClient) GetNode(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Node, error) { | ||
klog.V(4).InfoS("Retrieving node", "node", name) | ||
node, err := c.client.CoreV1().Nodes().Get(ctx, name, opts) | ||
if err != nil { | ||
klog.ErrorS(err, "Failed to get node", "node", name) | ||
return nil, fmt.Errorf("failed to get node %s: %w", name, err) | ||
} | ||
klog.V(4).InfoS("Successfully retrieved node", "node", name) | ||
return node, nil | ||
} | ||
|
||
func (c *K8sClient) GetPod(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*corev1.Pod, error) { | ||
klog.V(4).InfoS("Retrieving pod", "namespace", namespace, "pod", name) | ||
pod, err := c.client.CoreV1().Pods(namespace).Get(ctx, name, opts) | ||
if err != nil { | ||
klog.ErrorS(err, "Failed to get pod", "namespace", namespace, "pod", name) | ||
return nil, fmt.Errorf("failed to get pod %s/%s: %w", namespace, name, err) | ||
} | ||
klog.V(4).InfoS("Successfully retrieved pod", "namespace", namespace, "pod", name) | ||
return pod, nil | ||
} | ||
|
||
func (c *K8sClient) ListPods(ctx context.Context, namespace string, opts metav1.ListOptions) (*corev1.PodList, error) { | ||
klog.V(4).InfoS("Listing pods", "namespace", namespace, "options", opts) | ||
pods, err := c.client.CoreV1().Pods(namespace).List(ctx, opts) | ||
if err != nil { | ||
klog.ErrorS(err, "Failed to list pods", "namespace", namespace) | ||
return nil, fmt.Errorf("failed to list pods in namespace %s: %w", namespace, err) | ||
} | ||
klog.V(4).InfoS("Successfully listed pods", "namespace", namespace, "count", len(pods.Items)) | ||
return pods, nil | ||
} | ||
|
||
func (c *K8sClient) PatchNode(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions) (*corev1.Node, error) { | ||
klog.V(4).InfoS("Patching node", "node", name, "patchType", pt) | ||
node, err := c.client.CoreV1().Nodes().Patch(ctx, name, pt, data, opts) | ||
if err != nil { | ||
klog.ErrorS(err, "Failed to patch node", "node", name) | ||
return nil, fmt.Errorf("failed to patch node %s: %w", name, err) | ||
} | ||
klog.V(4).InfoS("Successfully patched node", "node", name) | ||
return node, nil | ||
} | ||
|
||
func (c *K8sClient) PatchPod(ctx context.Context, namespace string, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions) (*corev1.Pod, error) { | ||
klog.V(4).InfoS("Patching pod", "namespace", namespace, "pod", name, "patchType", pt) | ||
pod, err := c.client.CoreV1().Pods(namespace).Patch(ctx, name, pt, data, opts) | ||
if err != nil { | ||
klog.ErrorS(err, "Failed to patch pod", "namespace", namespace, "pod", name) | ||
return nil, fmt.Errorf("failed to patch pod %s/%s: %w", namespace, name, err) | ||
} | ||
klog.V(4).InfoS("Successfully patched pod", "namespace", namespace, "pod", name) | ||
return pod, nil | ||
} | ||
|
||
func (c *K8sClient) CreateNode(ctx context.Context, node *corev1.Node, opts metav1.CreateOptions) (*corev1.Node, error) { | ||
klog.V(4).InfoS("Creating node", "node", node.Name) | ||
createdNode, err := c.client.CoreV1().Nodes().Create(ctx, node, opts) | ||
if err != nil { | ||
klog.ErrorS(err, "Failed to create node", "node", node.Name) | ||
return nil, fmt.Errorf("failed to create node %s: %w", node.Name, err) | ||
} | ||
klog.V(4).InfoS("Successfully created node", "node", node.Name) | ||
return createdNode, nil | ||
} | ||
|
||
func (c *K8sClient) UpdateNode(ctx context.Context, node *corev1.Node, opts metav1.UpdateOptions) (*corev1.Node, error) { | ||
klog.V(4).InfoS("Updating node", "node", node.Name) | ||
updatedNode, err := c.client.CoreV1().Nodes().Update(ctx, node, opts) | ||
if err != nil { | ||
klog.ErrorS(err, "Failed to update node", "node", node.Name) | ||
return nil, fmt.Errorf("failed to update node %s: %w", node.Name, err) | ||
} | ||
klog.V(4).InfoS("Successfully updated node", "node", node.Name) | ||
return updatedNode, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.