From 34afb48336384b8c1112867b82ec2f264bb4bc4a Mon Sep 17 00:00:00 2001 From: Shubham Singh Date: Wed, 14 Aug 2024 08:48:22 +0000 Subject: [PATCH] added tests for dynamic controller Signed-off-by: GitHub typo fixes for github workflow stes names. Signed-off-by: Tomoya Fujita Test coverage for csidriver Signed-off-by: GitHub Test coverage for csidriver Signed-off-by: Shubham Singh Fixed the k8s client impersonation for authroization Signed-off-by: WillardHu changing apt source after multiple failures Signed-off-by: wbc6080 Optimize the syncPod startup time for edge nodes. Signed-off-by: luomengY <2938893385@qq.com> --- .github/workflows/main.yaml | 12 +- .github/workflows/schedule.yml | 4 +- cloud/pkg/common/client/impersonation.go | 38 +- cloud/pkg/common/client/impersonation_test.go | 67 ++++ cloud/pkg/csidriver/controllerserver_test.go | 173 +++++++++ cloud/pkg/csidriver/identityserver_test.go | 116 ++++++ cloud/pkg/csidriver/uds_test.go | 39 ++ cloud/pkg/csidriver/utils_test.go | 189 ++++++++++ .../application/listener_manager_test.go | 107 +++++- .../application/selector_test.go | 341 ++++++++++++++++++ .../defaultmaster/defaultmaster_test.go | 128 +++++++ .../endpointresource/endpointresource_test.go | 81 +++++ edge/pkg/edged/edged.go | 34 +- hack/local-up-kubeedge.sh | 5 + 14 files changed, 1282 insertions(+), 52 deletions(-) create mode 100644 cloud/pkg/common/client/impersonation_test.go create mode 100644 cloud/pkg/csidriver/controllerserver_test.go create mode 100644 cloud/pkg/csidriver/identityserver_test.go create mode 100644 cloud/pkg/csidriver/uds_test.go create mode 100644 cloud/pkg/csidriver/utils_test.go create mode 100644 cloud/pkg/dynamiccontroller/application/selector_test.go create mode 100644 cloud/pkg/dynamiccontroller/filter/defaultmaster/defaultmaster_test.go create mode 100644 cloud/pkg/dynamiccontroller/filter/endpointresource/endpointresource_test.go diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 1b5feaec04b..c4fdc60a2bf 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -45,7 +45,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - - name: Install dependences + - name: Install dependencies run: | sudo apt-get install -y jq @@ -127,7 +127,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - - name: Install dependences + - name: Install dependencies run: | command -v ginkgo || go install github.com/onsi/ginkgo/v2/ginkgo@${{ env.GINKGO_VERSION }} @@ -192,7 +192,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - - name: Install dependences + - name: Install dependencies run: | command -v ginkgo || go install github.com/onsi/ginkgo/v2/ginkgo@${{ env.GINKGO_VERSION }} go install sigs.k8s.io/kind@v0.22.0 @@ -255,7 +255,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - - name: Install dependences + - name: Install dependencies run: | command -v ginkgo || go install github.com/onsi/ginkgo/v2/ginkgo@${{ env.GINKGO_VERSION }} go install sigs.k8s.io/kind@v0.22.0 @@ -302,7 +302,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - - name: Install dependences + - name: Install dependencies run: | command -v ginkgo || go install github.com/onsi/ginkgo/v2/ginkgo@${{ env.GINKGO_VERSION }} go install sigs.k8s.io/kind@v0.22.0 @@ -369,7 +369,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - - name: Install dependences + - name: Install dependencies run: | command -v ginkgo || go install github.com/onsi/ginkgo/v2/ginkgo@${{ env.GINKGO_VERSION }} go install sigs.k8s.io/kind@v0.22.0 diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml index b34ba8aa054..4e908126d03 100644 --- a/.github/workflows/schedule.yml +++ b/.github/workflows/schedule.yml @@ -33,7 +33,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - - name: Install dependences + - name: Install dependencies run: | sudo apt-get install -y jq @@ -119,7 +119,7 @@ jobs: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - - name: Install dependences + - name: Install dependencies run: | command -v ginkgo || go install github.com/onsi/ginkgo/v2/ginkgo@${{ env.GINKGO_VERSION }} go install sigs.k8s.io/kind@v0.22.0 diff --git a/cloud/pkg/common/client/impersonation.go b/cloud/pkg/common/client/impersonation.go index 2d2dcd3ed69..10f87d98587 100644 --- a/cloud/pkg/common/client/impersonation.go +++ b/cloud/pkg/common/client/impersonation.go @@ -17,6 +17,7 @@ limitations under the License. package client import ( + "fmt" "net/http" "strings" @@ -38,12 +39,12 @@ func newForK8sConfigOrDie(c *rest.Config, enableImpersonation bool) *kubernetes. httpClient, err := httpClientFor(&configShallowCopy, enableImpersonation) if err != nil { - panic(err) + panic(fmt.Errorf("failed to create a httpclient for the clientset, err: %v", err)) } cs, err := kubernetes.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { - panic(err) + panic(fmt.Errorf("failed to create a clientset, err: %v", err)) } return cs } @@ -52,12 +53,12 @@ func newForDynamicConfigOrDie(c *rest.Config, enableImpersonation bool) *dynamic configShallowCopy := dynamic.ConfigFor(c) httpClient, err := httpClientFor(configShallowCopy, enableImpersonation) if err != nil { - panic(err) + panic(fmt.Errorf("failed to create a httpclient for the dynamic-client, err: %v", err)) } cs, err := dynamic.NewForConfigAndClient(configShallowCopy, httpClient) if err != nil { - panic(err) + panic(fmt.Errorf("failed to create a dynamic-client, err: %v", err)) } return cs } @@ -71,12 +72,12 @@ func newForCrdConfigOrDie(c *rest.Config, enableImpersonation bool) *crdClientse httpClient, err := httpClientFor(&configShallowCopy, enableImpersonation) if err != nil { - panic(err) + panic(fmt.Errorf("failed to create a httpclient for the crd clientset, err: %v", err)) } cs, err := crdClientset.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { - panic(err) + panic(fmt.Errorf("failed to create a crd clientset, err: %v", err)) } return cs } @@ -102,21 +103,18 @@ type impersonationRoundTripper struct { } func (r *impersonationRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - // extract user and group from context and set impersonation headers - var userStr, groupStr string - user := req.Context().Value(authenticationv1.ImpersonateUserHeader) - if user != nil && r.enable { - userStr = user.(string) - req.Header.Set(authenticationv1.ImpersonateUserHeader, userStr) - } - group := req.Context().Value(authenticationv1.ImpersonateGroupHeader) - if group != nil && r.enable { - groupStr = group.(string) - for _, g := range strings.Split(groupStr, "|") { - req.Header.Set(authenticationv1.ImpersonateGroupHeader, g) + var user, group string + if r.enable { + if v := req.Context().Value(authenticationv1.ImpersonateUserHeader); v != nil { + user = v.(string) + req.Header.Set(authenticationv1.ImpersonateUserHeader, user) + } + if v := req.Context().Value(authenticationv1.ImpersonateGroupHeader); v != nil { + group = v.(string) + req.Header[authenticationv1.ImpersonateGroupHeader] = strings.Split(group, "|") } } - - klog.V(4).Infof("KubeClient: request.method=%s, request.path=%s, user=%q, group= %q", req.Method, req.URL.Path, userStr, groupStr) + klog.V(4).Infof("KubeClient: request.method=%s, request.path=%s, user=%q, group= %q", + req.Method, req.URL.Path, user, group) return r.rt.RoundTrip(req) } diff --git a/cloud/pkg/common/client/impersonation_test.go b/cloud/pkg/common/client/impersonation_test.go new file mode 100644 index 00000000000..1733f0cc42a --- /dev/null +++ b/cloud/pkg/common/client/impersonation_test.go @@ -0,0 +1,67 @@ +package client + +import ( + "context" + "fmt" + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + authenticationv1 "k8s.io/api/authentication/v1" + + ctxutl "github.com/kubeedge/kubeedge/cloud/pkg/common/context" +) + +type fakeNextRoundTripper struct { + enable bool +} + +func (f *fakeNextRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + if f.enable { + if vals := req.Header[authenticationv1.ImpersonateUserHeader]; len(vals) == 0 || vals[0] == "" { + return nil, fmt.Errorf("invalid request header %s", authenticationv1.ImpersonateUserHeader) + } + if vals := req.Header[authenticationv1.ImpersonateGroupHeader]; len(vals) == 0 || vals[0] == "" { + return nil, fmt.Errorf("invalid request header %s", authenticationv1.ImpersonateGroupHeader) + } + } else { + if vals := req.Header[authenticationv1.ImpersonateUserHeader]; len(vals) > 0 { + return nil, fmt.Errorf("invalid request header %s", authenticationv1.ImpersonateUserHeader) + } + if vals := req.Header[authenticationv1.ImpersonateGroupHeader]; len(vals) > 0 { + return nil, fmt.Errorf("invalid request header %s", authenticationv1.ImpersonateGroupHeader) + } + } + return nil, nil +} + +func TestRoundTrip(t *testing.T) { + cases := []struct { + name string + enable bool + }{ + {name: "enable impersonation", enable: true}, + {name: "disable impersonation", enable: false}, + } + + url, err := url.Parse("http://localhost:6443/apis") + assert.NoError(t, err) + ctx := ctxutl.WithEdgeNode(context.TODO(), "test-node") + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req := &http.Request{ + Method: http.MethodGet, + URL: url, + Header: make(http.Header), + } + r := &impersonationRoundTripper{ + enable: c.enable, + rt: &fakeNextRoundTripper{enable: c.enable}, + } + _, err := r.RoundTrip(req.WithContext(ctx)) + assert.NoError(t, err) + }) + } +} diff --git a/cloud/pkg/csidriver/controllerserver_test.go b/cloud/pkg/csidriver/controllerserver_test.go new file mode 100644 index 00000000000..618c24f7a6d --- /dev/null +++ b/cloud/pkg/csidriver/controllerserver_test.go @@ -0,0 +1,173 @@ +/* +Copyright 2024 The KubeEdge 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 csidriver + +import ( + "testing" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestNewControllerServer(t *testing.T) { + assert := assert.New(t) + + nodeID := "test-node" + kubeEdgeEndpoint := "http://localhost:8080/test" + + cs := newControllerServer(nodeID, kubeEdgeEndpoint) + assert.NotNil(cs) + + assert.Equal(nodeID, cs.nodeID) + assert.Equal(kubeEdgeEndpoint, cs.kubeEdgeEndpoint) + + expectedCaps := getControllerServiceCapabilities( + []csi.ControllerServiceCapability_RPC_Type{ + csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + }) + assert.Equal(expectedCaps, cs.caps) + + assert.Equal(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + cs.caps[0].GetRpc().GetType()) + assert.Equal(csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + cs.caps[1].GetRpc().GetType()) +} + +func TestValidateVolumeCapabilities(t *testing.T) { + assert := assert.New(t) + + cs := &controllerServer{ + nodeID: "test-node", + kubeEdgeEndpoint: "http://localhost:8080/test", + } + + // Test case 1: Invalid request (missing volume ID) + invalidReq := &csi.ValidateVolumeCapabilitiesRequest{ + VolumeCapabilities: []*csi.VolumeCapability{ + { + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{}, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + }, + } + + result, err := cs.ValidateVolumeCapabilities(context.Background(), invalidReq) + assert.Error(err) + assert.Nil(result) + assert.Equal(codes.InvalidArgument, status.Code(err)) + assert.Contains(err.Error(), "Volume ID cannot be empty") + + // Test case 2: Invalid request (missing volume capabilities) + invalidReq2 := &csi.ValidateVolumeCapabilitiesRequest{ + VolumeId: "test-volume-id", + } + + result, err = cs.ValidateVolumeCapabilities(context.Background(), invalidReq2) + assert.Error(err) + assert.Nil(result) + assert.Equal(codes.InvalidArgument, status.Code(err)) + assert.Contains(err.Error(), "test-volume-id") + + // Test case 3: Valid request + validReq := &csi.ValidateVolumeCapabilitiesRequest{ + VolumeId: "test-volume-id", + VolumeCapabilities: []*csi.VolumeCapability{ + { + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{}, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + }, + } + + result, err = cs.ValidateVolumeCapabilities(context.Background(), validReq) + assert.NoError(err) + assert.NotNil(result) + assert.NotNil(result.Confirmed) + assert.NotEmpty(result.Confirmed.VolumeCapabilities) + assert.Equal(csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + result.Confirmed.VolumeCapabilities[0].AccessMode.Mode) +} + +func TestControllerGetCapabilities(t *testing.T) { + assert := assert.New(t) + + cs := &controllerServer{ + nodeID: "test-node", + kubeEdgeEndpoint: "http://localhost:8080/test", + caps: getControllerServiceCapabilities( + []csi.ControllerServiceCapability_RPC_Type{ + csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + }, + ), + } + + req := &csi.ControllerGetCapabilitiesRequest{} + resp, err := cs.ControllerGetCapabilities(context.Background(), req) + assert.NoError(err) + assert.NotNil(resp) + + expectedCaps := []csi.ControllerServiceCapability_RPC_Type{ + csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + } + + for i, cap := range resp.Capabilities { + assert.Equal(expectedCaps[i], cap.GetRpc().Type, + "Capability %d should be %v", i, expectedCaps[i]) + } +} + +func TestGetControllerServiceCapabilities(t *testing.T) { + assert := assert.New(t) + + // Test case 1: Empty capability list + emptyCaps := getControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{}) + assert.Empty(emptyCaps) + + // Test case 2: One capability + singleCapType := csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME + singleCap := getControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{singleCapType}) + + assert.Equal(singleCapType, singleCap[0].GetRpc().Type) + + // Test case 3: Multiple capabilities + multiCapTypes := []csi.ControllerServiceCapability_RPC_Type{ + csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + csi.ControllerServiceCapability_RPC_LIST_VOLUMES, + } + multiCaps := getControllerServiceCapabilities(multiCapTypes) + + assert.Len(multiCaps, 3) + for i, capType := range multiCapTypes { + assert.Equal(capType, multiCaps[i].GetRpc().Type, + "Capability %d should be %v", i, capType) + } +} diff --git a/cloud/pkg/csidriver/identityserver_test.go b/cloud/pkg/csidriver/identityserver_test.go new file mode 100644 index 00000000000..fd0bb6fa1a2 --- /dev/null +++ b/cloud/pkg/csidriver/identityserver_test.go @@ -0,0 +1,116 @@ +/* +Copyright 2024 The KubeEdge 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 csidriver + +import ( + "context" + "testing" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestNewIdentityServer(t *testing.T) { + assert := assert.New(t) + + name := "test-server" + version := "v1.0.0" + + ids := newIdentityServer(name, version) + + assert.NotNil(ids) + assert.Equal(name, ids.name) + assert.Equal(version, ids.version) +} + +func TestGetPluginInfo(t *testing.T) { + assert := assert.New(t) + + testCases := []struct { + name string + version string + expectError bool + errorCode codes.Code + }{ + { + name: "test-driver", + version: "v1.0.0", + expectError: false, + errorCode: codes.OK, + }, + { + name: "", + version: "v1.0.0", + expectError: true, + errorCode: codes.Unavailable, + }, + { + name: "test-driver", + version: "", + expectError: true, + errorCode: codes.Unavailable, + }, + { + name: "", + version: "", + expectError: true, + errorCode: codes.Unavailable, + }, + } + + for _, tc := range testCases { + ids := newIdentityServer(tc.name, tc.version) + result, err := ids.GetPluginInfo(context.Background(), &csi.GetPluginInfoRequest{}) + + if tc.expectError { + assert.Error(err) + assert.Equal(tc.errorCode, status.Code(err)) + } else { + assert.NoError(err) + assert.NotNil(result) + assert.Equal(tc.name, result.Name) + assert.Equal(tc.version, result.VendorVersion) + } + } +} + +func TestProbe(t *testing.T) { + assert := assert.New(t) + + ids := newIdentityServer("test-driver", "v1.0.0") + resp, err := ids.Probe(context.Background(), &csi.ProbeRequest{}) + + assert.NoError(err) + assert.NotNil(resp) +} + +func TestGetPluginCapabilities(t *testing.T) { + assert := assert.New(t) + + ids := newIdentityServer("test-driver", "v1.0.0") + result, err := ids.GetPluginCapabilities(context.Background(), &csi.GetPluginCapabilitiesRequest{}) + + assert.NoError(err) + assert.NotNil(result) + assert.Len(result.Capabilities, 1) + + capabilities := result.Capabilities[0] + assert.NotNil(capabilities.GetService()) + assert.Equal(csi.PluginCapability_Service_CONTROLLER_SERVICE, capabilities.GetService().Type) +} diff --git a/cloud/pkg/csidriver/uds_test.go b/cloud/pkg/csidriver/uds_test.go new file mode 100644 index 00000000000..dafd118beb9 --- /dev/null +++ b/cloud/pkg/csidriver/uds_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 The KubeEdge 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 csidriver + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewUnixDomainSocket(t *testing.T) { + assert := assert.New(t) + + // Using default buffer size + us := NewUnixDomainSocket("/tmp/test.sock") + assert.NotNil(us) + assert.Equal("/tmp/test.sock", us.filename) + assert.Equal(DefaultBufferSize, us.buffersize) + + // Using custom buffer size + us = NewUnixDomainSocket("/tmp/test.sock", 2048) + assert.NotNil(us) + assert.Equal("/tmp/test.sock", us.filename) + assert.Equal(2048, us.buffersize) +} diff --git a/cloud/pkg/csidriver/utils_test.go b/cloud/pkg/csidriver/utils_test.go new file mode 100644 index 00000000000..3fe5189edab --- /dev/null +++ b/cloud/pkg/csidriver/utils_test.go @@ -0,0 +1,189 @@ +/* +Copyright 2024 The KubeEdge 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 csidriver + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/kubeedge/beehive/pkg/core/model" +) + +func TestParseEndpoint(t *testing.T) { + assert := assert.New(t) + + testCases := []struct { + input string + expectedStrOne string + expectedStrTwo string + expectError bool + }{ + { + input: "unix:///tmp/test.sock", + expectedStrOne: "unix", + expectedStrTwo: "/tmp/test.sock", + expectError: false, + }, + { + input: "unix://tmp/test.sock", + expectedStrOne: "unix", + expectedStrTwo: "tmp/test.sock", + expectError: false, + }, + { + input: "tcp://127.0.0.1:8080", + expectedStrOne: "tcp", + expectedStrTwo: "127.0.0.1:8080", + expectError: false, + }, + { + input: "/tmp/test.sock", + expectedStrOne: "", + expectedStrTwo: "", + expectError: true, + }, + { + input: "unix://", + expectedStrOne: "", + expectedStrTwo: "", + expectError: true, + }, + } + + for _, tc := range testCases { + firstStr, secondStr, err := parseEndpoint(tc.input) + if tc.expectError { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.expectedStrOne, firstStr) + assert.Equal(tc.expectedStrTwo, secondStr) + } + } +} + +func TestBuildResource(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + name string + nodeID string + namespace string + resourceType string + resourceID string + want string + wantErr bool + }{ + { + name: "Valid resource without resourceID", + nodeID: "node1", + namespace: "default", + resourceType: "volume", + resourceID: "", + want: "node/node1/default/volume", + wantErr: false, + }, + { + name: "Valid resource with resourceID", + nodeID: "node1", + namespace: "default", + resourceType: "volume", + resourceID: "vol1", + want: "node/node1/default/volume/vol1", + wantErr: false, + }, + { + name: "Resource without nodeID", + nodeID: "", + namespace: "default", + resourceType: "volume", + resourceID: "", + want: "", + wantErr: true, + }, + { + name: "Resource missing namespace", + nodeID: "node1", + namespace: "", + resourceType: "volume", + resourceID: "", + want: "", + wantErr: true, + }, + { + name: "Resource without resourceType", + nodeID: "node1", + namespace: "default", + resourceType: "", + resourceID: "", + want: "", + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := buildResource(test.nodeID, test.namespace, test.resourceType, test.resourceID) + if test.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(test.want, got) + } + }) + } +} + +func TestExtractMessage(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + name string + context string + wantErr bool + }{ + { + name: "Valid JSON", + context: `{"header":{"namespace":"default"},"router":{"resource":"test"},"content":"test"}`, + wantErr: false, + }, + { + name: "Invalid JSON", + context: `{invalid json}`, + wantErr: true, + }, + { + name: "Empty context", + context: "", + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + msg, err := extractMessage(test.context) + if test.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.NotNil(msg) + assert.IsType(&model.Message{}, msg) + } + }) + } +} diff --git a/cloud/pkg/dynamiccontroller/application/listener_manager_test.go b/cloud/pkg/dynamiccontroller/application/listener_manager_test.go index 3dc503395a9..90f84978702 100644 --- a/cloud/pkg/dynamiccontroller/application/listener_manager_test.go +++ b/cloud/pkg/dynamiccontroller/application/listener_manager_test.go @@ -17,9 +17,9 @@ limitations under the License. package application import ( - "reflect" "testing" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -32,7 +32,22 @@ var testGVR = schema.GroupVersionResource{ var selector1 = NewSelector("key1=value1", "") var selector2 = NewSelector("key2=value2", "") -func TestAddGetListener(t *testing.T) { +func TestNewListenerManager(t *testing.T) { + assert := assert.New(t) + + lm := newListenerManager() + + assert.NotNil(lm) + assert.NotNil(lm.listenerByNodeID) + assert.Empty(lm.listenerByNodeID) + + assert.NotNil(lm.listenerByGVR) + assert.Empty(lm.listenerByGVR) +} + +func TestAddListener(t *testing.T) { + assert := assert.New(t) + listener1 := NewSelectorListener("testID1", "node1", testGVR, selector1) listener2 := NewSelectorListener("testID2", "node2", testGVR, selector2) @@ -42,32 +57,26 @@ func TestAddGetListener(t *testing.T) { lm.AddListener(listener2) listenerByNodeID := lm.GetListenersForNode("node1") - if len(listenerByNodeID) != 1 { - t.Errorf("listenerByNodeID expected length 1. but got %v", len(listenerByNodeID)) - } + assert.Len(listenerByNodeID, 1) for _, v := range listenerByNodeID { - if !reflect.DeepEqual(v, listener1) { - t.Errorf("expected %v. but got %v", listener1, v) - } + assert.Equal(listener1, v) } listenerByGVR := lm.GetListenersForGVR(testGVR) - if len(listenerByGVR) != 2 { - t.Errorf("listenerByGVR expected length 2. but got %v", len(listenerByNodeID)) - } + assert.Len(listenerByGVR, 2) expected := map[string]*SelectorListener{ listener1.id: listener1, listener2.id: listener2, } - if !reflect.DeepEqual(expected, listenerByGVR) { - t.Errorf("expected %v. but got %v", expected, listenerByGVR) - } + assert.Equal(expected, listenerByGVR) } func TestDeleteListener(t *testing.T) { + assert := assert.New(t) + listener1 := NewSelectorListener("testID1", "node1", testGVR, selector1) listener2 := NewSelectorListener("testID2", "node2", testGVR, selector2) @@ -78,13 +87,71 @@ func TestDeleteListener(t *testing.T) { lm.DeleteListener(listener1) listenerByNodeID := lm.GetListenersForNode("node1") - if len(listenerByNodeID) != 0 { - t.Errorf("listenerByNodeID expected length 0. but got %v", len(listenerByNodeID)) - } + assert.Len(listenerByNodeID, 0) lm.DeleteListener(listener2) listenerByGVR := lm.GetListenersForGVR(testGVR) - if len(listenerByGVR) != 0 { - t.Errorf("listenerByGVR expected length 0. but got %v", len(listenerByNodeID)) - } + assert.Len(listenerByGVR, 0) +} + +func TestGetListenersForNode(t *testing.T) { + assert := assert.New(t) + + lm := newListenerManager() + + listener1 := NewSelectorListener("testID1", "node1", testGVR, selector1) + listener2 := NewSelectorListener("testID2", "node1", testGVR, selector2) + listener3 := NewSelectorListener("testID3", "node2", testGVR, selector1) + + lm.AddListener(listener1) + lm.AddListener(listener2) + lm.AddListener(listener3) + + node1Listeners := lm.GetListenersForNode("node1") + assert.Len(node1Listeners, 2) + assert.Contains(node1Listeners, listener1.id) + assert.Contains(node1Listeners, listener2.id) + assert.Equal(listener1, node1Listeners[listener1.id]) + assert.Equal(listener2, node1Listeners[listener2.id]) + + node2Listeners := lm.GetListenersForNode("node2") + assert.Len(node2Listeners, 1) + assert.Contains(node2Listeners, listener3.id) + assert.Equal(listener3, node2Listeners[listener3.id]) + + // Get listeners for non existent node + nonExistentNodeListeners := lm.GetListenersForNode("node3") + assert.Nil(nonExistentNodeListeners) +} + +func TestGetListenersForGVR(t *testing.T) { + assert := assert.New(t) + + lm := newListenerManager() + + listener1 := NewSelectorListener("testID1", "node1", testGVR, selector1) + listener2 := NewSelectorListener("testID2", "node2", testGVR, selector2) + differentGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"} + listener3 := NewSelectorListener("testID3", "node1", differentGVR, selector1) + + lm.AddListener(listener1) + lm.AddListener(listener2) + lm.AddListener(listener3) + + gvrListeners := lm.GetListenersForGVR(testGVR) + assert.Len(gvrListeners, 2) + assert.Contains(gvrListeners, listener1.id) + assert.Contains(gvrListeners, listener2.id) + assert.Equal(listener1, gvrListeners[listener1.id]) + assert.Equal(listener2, gvrListeners[listener2.id]) + + differentGVRListeners := lm.GetListenersForGVR(differentGVR) + assert.Len(differentGVRListeners, 1) + assert.Contains(differentGVRListeners, listener3.id) + assert.Equal(listener3, differentGVRListeners[listener3.id]) + + // Get listeners for non existent GVR + nonExistentGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nonexistent"} + nonExistentGVRListeners := lm.GetListenersForGVR(nonExistentGVR) + assert.Nil(nonExistentGVRListeners) } diff --git a/cloud/pkg/dynamiccontroller/application/selector_test.go b/cloud/pkg/dynamiccontroller/application/selector_test.go new file mode 100644 index 00000000000..a391d56fb47 --- /dev/null +++ b/cloud/pkg/dynamiccontroller/application/selector_test.go @@ -0,0 +1,341 @@ +/* +Copyright 2024 The KubeEdge 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 application + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestNewSelector(t *testing.T) { + assert := assert.New(t) + + testCases := []struct { + name string + labelSelector string + fieldSelector string + expectedLabel string + expectedField string + }{ + { + name: "Empty selectors", + labelSelector: "", + fieldSelector: "", + expectedLabel: labels.Everything().String(), + expectedField: fields.Everything().String(), + }, + { + name: "Empty field selector", + labelSelector: "app=myapp", + fieldSelector: "", + expectedLabel: "app=myapp", + expectedField: fields.Everything().String(), + }, + { + name: "Empty label selector", + labelSelector: "", + fieldSelector: "metadata.name=pod1", + expectedLabel: labels.Everything().String(), + expectedField: "metadata.name=pod1", + }, + { + name: "Valid label and field selectors", + labelSelector: "app=myapp,tier=frontend", + fieldSelector: "metadata.namespace=default", + expectedLabel: "app=myapp,tier=frontend", + expectedField: "metadata.namespace=default", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + selector := NewSelector(tc.labelSelector, tc.fieldSelector) + + assert.Equal(tc.expectedLabel, selector.Label.String()) + assert.Equal(tc.expectedField, selector.Field.String()) + }) + } +} + +func TestLabelFieldSelector_Labels(t *testing.T) { + assert := assert.New(t) + + testCases := []struct { + name string + labelSelector string + expected string + }{ + { + name: "Empty label selector", + labelSelector: "", + expected: labels.Everything().String(), + }, + { + name: "Valid label selector", + labelSelector: "app=myapp", + expected: "app=myapp", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + selector := NewSelector(tc.labelSelector, "") + result := selector.Labels() + + assert.Equal(tc.expected, result.String()) + }) + } +} + +func TestLabelFieldSelector_Fields(t *testing.T) { + assert := assert.New(t) + + testCases := []struct { + name string + fieldSelector string + expected string + }{ + { + name: "Empty field selector", + fieldSelector: "", + expected: fields.Everything().String(), + }, + { + name: "Valid field selector", + fieldSelector: "metadata.name=pod1", + expected: "metadata.name=pod1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + selector := NewSelector("", tc.fieldSelector) + result := selector.Fields() + + assert.Equal(tc.expected, result.String()) + }) + } +} + +func TestLabelFieldSelector_String(t *testing.T) { + assert := assert.New(t) + + testCases := []struct { + name string + labelSelector string + fieldSelector string + expectedString string + }{ + { + name: "Empty selectors", + labelSelector: "", + fieldSelector: "", + expectedString: ";", + }, + { + name: "Label selector only", + labelSelector: "app=myapp", + fieldSelector: "", + expectedString: "app=myapp;", + }, + { + name: "Field selector only", + labelSelector: "", + fieldSelector: "metadata.name=pod1", + expectedString: ";metadata.name=pod1", + }, + { + name: "Both selectors", + labelSelector: "app=myapp", + fieldSelector: "metadata.name=pod1", + expectedString: "app=myapp;metadata.name=pod1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + selector := NewSelector(tc.labelSelector, tc.fieldSelector) + result := selector.String() + + assert.Equal(tc.expectedString, result) + }) + } +} + +func TestLabelFieldSelector_Match(t *testing.T) { + assert := assert.New(t) + + testCases := []struct { + name string + labelSelector string + fieldSelector string + labelSet labels.Set + fieldSet fields.Set + expected bool + }{ + { + name: "Match both label and field", + labelSelector: "app=myapp", + fieldSelector: "metadata.name=pod1", + labelSet: labels.Set{"app": "myapp"}, + fieldSet: fields.Set{"metadata.name": "pod1"}, + expected: true, + }, + { + name: "Match label but not field", + labelSelector: "app=myapp", + fieldSelector: "metadata.name=pod1", + labelSet: labels.Set{"app": "myapp"}, + fieldSet: fields.Set{"metadata.name": "pod2"}, + expected: false, + }, + { + name: "Match field but not label", + labelSelector: "app=myapp", + fieldSelector: "metadata.name=pod1", + labelSet: labels.Set{"app": "otherapp"}, + fieldSet: fields.Set{"metadata.name": "pod1"}, + expected: false, + }, + { + name: "Empty selector matches everything", + labelSelector: "", + fieldSelector: "", + labelSet: labels.Set{"app": "myapp"}, + fieldSet: fields.Set{"metadata.name": "pod1"}, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + selector := NewSelector(tc.labelSelector, tc.fieldSelector) + result := selector.Match(tc.labelSet, tc.fieldSet) + + assert.Equal(tc.expected, result) + }) + } +} + +func TestLabelFieldSelector_MatchObj(t *testing.T) { + assert := assert.New(t) + + testCases := []struct { + name string + labelSelector string + fieldSelector string + obj *unstructured.Unstructured + expected bool + }{ + { + name: "Match both label and field", + labelSelector: "app=myapp", + fieldSelector: "metadata.name=pod1", + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "pod1", + "labels": map[string]interface{}{ + "app": "myapp", + }, + }, + }, + }, + expected: true, + }, + { + name: "Match label but not field", + labelSelector: "app=myapp", + fieldSelector: "metadata.name=pod1", + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "pod2", + "labels": map[string]interface{}{ + "app": "myapp", + }, + }, + }, + }, + expected: false, + }, + { + name: "Match field but not label", + labelSelector: "app=myapp", + fieldSelector: "metadata.name=pod1", + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "pod1", + "labels": map[string]interface{}{ + "app": "otherapp", + }, + }, + }, + }, + expected: false, + }, + { + name: "Match neither label nor field", + labelSelector: "app=myapp", + fieldSelector: "metadata.name=pod1", + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "pod2", + "labels": map[string]interface{}{ + "app": "otherapp", + }, + }, + }, + }, + expected: false, + }, + { + name: "Empty selector matches everything", + labelSelector: "", + fieldSelector: "", + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "pod1", + "labels": map[string]interface{}{ + "app": "myapp", + }, + }, + }, + }, + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.obj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Pod"}) + + selector := NewSelector(tc.labelSelector, tc.fieldSelector) + result := selector.MatchObj(tc.obj) + + assert.Equal(tc.expected, result) + }) + } +} diff --git a/cloud/pkg/dynamiccontroller/filter/defaultmaster/defaultmaster_test.go b/cloud/pkg/dynamiccontroller/filter/defaultmaster/defaultmaster_test.go new file mode 100644 index 00000000000..caea84d28d3 --- /dev/null +++ b/cloud/pkg/dynamiccontroller/filter/defaultmaster/defaultmaster_test.go @@ -0,0 +1,128 @@ +/* +Copyright 2024 The KubeEdge 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 defaultmaster + +import ( + "testing" + + "github.com/stretchr/testify/assert" + discovery "k8s.io/api/discovery/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestNewDefaultMasterFilter(t *testing.T) { + assert := assert.New(t) + + filter := newDefaultMasterFilter() + + assert.NotNil(filter) + assert.Equal(defaultMetaServerIP, filter.hostIP) + assert.Equal(int32(defaultMetaServerPort), filter.port) +} + +func TestNeedFilter(t *testing.T) { + assert := assert.New(t) + filter := newDefaultMasterFilter() + + // Case 1: UnstructuredList with EndpointSlice objects + objList := &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "EndpointSlice", + }, + }, + }, + } + assert.True(filter.NeedFilter(objList)) + + // Case 2: Unstructured with matching EndpointSlice object + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "EndpointSlice", + "metadata": map[string]interface{}{"name": defaultEndpointSliceName, "namespace": defaultEndpointSliceNameSpace}, + }, + } + obj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "EndpointSlice"}) + assert.True(filter.NeedFilter(obj)) + + // Case 3: Unstructured with non-matching EndpointSlice object (wrong name) + nonMatchingObj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "EndpointSlice", + "metadata": map[string]interface{}{"name": "nonMatchingName", "namespace": defaultEndpointSliceNameSpace}, + }, + } + nonMatchingObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "EndpointSlice"}) + assert.False(filter.NeedFilter(nonMatchingObj)) + + // Case 4: Unstructured with a non-EndpointSlice object + nonEndpointSliceObj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Pod", + }, + } + assert.False(filter.NeedFilter(nonEndpointSliceObj)) + + // Case 5: Invalid input (nil object) + assert.False(filter.NeedFilter(nil)) +} + +func TestFilterResource(t *testing.T) { + assert := assert.New(t) + filter := newDefaultMasterFilter() + + // EndpointSlice object with multiple endpoints + endpointSlice := &discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultEndpointSliceName, + Namespace: defaultEndpointSliceNameSpace, + }, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"192.168.1.1", "192.168.1.2"}, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: func(s string) *string { return &s }(defaultEndpointSlicePortName), + Port: func(i int32) *int32 { return &i }(8080), + }, + }, + } + + // Convert the EndpointSlice to unstructured format + unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(endpointSlice) + assert.NoError(err, "Failed to convert EndpointSlice to unstructured") + unstructuredObj := &unstructured.Unstructured{Object: unstr} + unstructuredObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "EndpointSlice"}) + + filter.FilterResource("", unstructuredObj) + + var filteredEndpointSlice discovery.EndpointSlice + err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, &filteredEndpointSlice) + assert.NoError(err, "Failed to convert unstructured back to EndpointSlice") + + assert.Len(filteredEndpointSlice.Endpoints, 1) + assert.Len(filteredEndpointSlice.Endpoints[0].Addresses, 1) + assert.Equal(defaultMetaServerIP, filteredEndpointSlice.Endpoints[0].Addresses[0]) + assert.Equal(int32(defaultMetaServerPort), *filteredEndpointSlice.Ports[0].Port) + assert.Equal(defaultEndpointSlicePortName, *filteredEndpointSlice.Ports[0].Name) +} diff --git a/cloud/pkg/dynamiccontroller/filter/endpointresource/endpointresource_test.go b/cloud/pkg/dynamiccontroller/filter/endpointresource/endpointresource_test.go new file mode 100644 index 00000000000..9f992e20d60 --- /dev/null +++ b/cloud/pkg/dynamiccontroller/filter/endpointresource/endpointresource_test.go @@ -0,0 +1,81 @@ +/* +Copyright 2024 The KubeEdge 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 endpointresource + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestNewEndpointsliceFilter(t *testing.T) { + assert := assert.New(t) + + fi := newEndpointsliceFilter() + assert.NotNil(fi) +} + +func TestName(t *testing.T) { + assert := assert.New(t) + fi := newEndpointsliceFilter() + name := fi.Name() + + assert.Equal(name, filterName) +} + +func TestNeedFilter(t *testing.T) { + assert := assert.New(t) + filter := newEndpointsliceFilter() + + // Test case 1: UnstructuredList containing an EndpointSlice object + endpointSliceObj := &unstructured.Unstructured{} + endpointSliceObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: resourceEpSliceName}) + unstructuredList := &unstructured.UnstructuredList{ + Items: []unstructured.Unstructured{*endpointSliceObj}, + } + + result := filter.NeedFilter(unstructuredList) + assert.True(result) + + // Test case 2: UnstructuredList containing a non EndpointSlice object + nonEndpointSliceObj := &unstructured.Unstructured{} + nonEndpointSliceObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Pod"}) + unstructuredList.Items = []unstructured.Unstructured{*nonEndpointSliceObj} + + result = filter.NeedFilter(unstructuredList) + assert.False(result) + + // Test case 3: Unstructured object of type EndpointSlice + result = filter.NeedFilter(endpointSliceObj) + assert.True(result) + + // Test case 4: Unstructured object of type Endpoints + endpointsObj := &unstructured.Unstructured{} + endpointsObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: resourceEpName}) + + result = filter.NeedFilter(endpointsObj) + assert.True(result) + + // Test case 5: Unstructured object of another type + podObj := &unstructured.Unstructured{} + podObj.SetGroupVersionKind(schema.GroupVersionKind{Kind: "Pod"}) + + result = filter.NeedFilter(podObj) + assert.False(result) +} diff --git a/edge/pkg/edged/edged.go b/edge/pkg/edged/edged.go index 065f7c30f47..d023597404e 100644 --- a/edge/pkg/edged/edged.go +++ b/edge/pkg/edged/edged.go @@ -28,6 +28,7 @@ import ( "context" "encoding/json" "fmt" + "net/http" "os" "reflect" "strconv" @@ -148,9 +149,8 @@ func (e *edged) Start() { } }() - // block until kubelet is ready to sync pods - startWaiter := time.NewTimer(10 * time.Second) - defer startWaiter.Stop() + kubeletReadyChan := make(chan struct{}, 1) + go kubeletHealthCheck(e.KubeletServer.ReadOnlyPort, kubeletReadyChan) select { case <-beehiveContext.Done(): @@ -159,7 +159,7 @@ func (e *edged) Start() { case err := <-kubeletErrChan: klog.Errorf("Failed to start edged, err: %v", err) return - case <-startWaiter.C: + case <-kubeletReadyChan: klog.Info("Start sync pod") } @@ -491,3 +491,29 @@ func (e *edged) controllerUnpublishVolume(content []byte) (interface{}, error) { func filterPodByNodeName(pod *v1.Pod, nodeName string) bool { return pod.Spec.NodeName == nodeName } + +func kubeletHealthCheck(port int32, kubeletReadyChan chan struct{}) { + url := fmt.Sprintf("http://localhost:%d/healthz/syncloop", port) + for { + resp, err := http.Get(url) + if err != nil { + klog.Warningf("failed to get kubelet healthz syncloop, err: %v", err) + time.Sleep(50 * time.Millisecond) + continue + } + + statusCode := resp.StatusCode + err = resp.Body.Close() + if err != nil { + klog.Errorf("failed to close response's body with err:%v", err) + } + + if statusCode != http.StatusOK { + klog.Warningf("internal error and status code: %d", resp.StatusCode) + } else { + kubeletReadyChan <- struct{}{} + break + } + time.Sleep(50 * time.Millisecond) + } +} diff --git a/hack/local-up-kubeedge.sh b/hack/local-up-kubeedge.sh index a99635e7319..cefe2009bb3 100755 --- a/hack/local-up-kubeedge.sh +++ b/hack/local-up-kubeedge.sh @@ -36,6 +36,11 @@ function install_cr() { while [ $attempt_num -lt $max_attempts ] do + if [ $attempt_num -eq 3 ]; then + echo "Download failed multiple times, try to change apt source ..." + sudo sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list + sudo apt-get update + fi if [[ "${CONTAINER_RUNTIME}" = "docker" ]]; then install_docker verify_docker_installed