From fcc3afe64f40eae4e710eeaf911b47cc5601546d Mon Sep 17 00:00:00 2001 From: Shaun Crampton Date: Tue, 7 Jan 2025 11:45:54 +0000 Subject: [PATCH] Add UT for custom resource List/Get. --- libcalico-go/lib/backend/k8s/k8s.go | 64 +------------ .../backend/k8s/resources/customresource.go | 9 +- .../k8s/resources/customresource_test.go | 81 +++++++++++++++++ libcalico-go/lib/backend/k8s/scheme/scheme.go | 90 +++++++++++++++++++ 4 files changed, 179 insertions(+), 65 deletions(-) create mode 100644 libcalico-go/lib/backend/k8s/scheme/scheme.go diff --git a/libcalico-go/lib/backend/k8s/k8s.go b/libcalico-go/lib/backend/k8s/k8s.go index bc103aafccc..0dcf297f79a 100644 --- a/libcalico-go/lib/backend/k8s/k8s.go +++ b/libcalico-go/lib/backend/k8s/k8s.go @@ -20,7 +20,6 @@ import ( "path/filepath" "reflect" "strings" - "sync" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -41,6 +40,7 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" + calischeme "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/scheme" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -517,8 +517,6 @@ func (c *KubeClient) Close() error { return nil } -var addToSchemeOnce sync.Once - // buildK8SAdminPolicyClient builds a RESTClient configured to interact (Baseline) Admin Network Policy. func buildK8SAdminPolicyClient(cfg *rest.Config) (*adminpolicyclient.PolicyV1alpha1Client, error) { return adminpolicyclient.NewForConfig(cfg) @@ -540,64 +538,8 @@ func buildCRDClientV1(cfg rest.Config) (*rest.RESTClient, error) { return nil, err } - // We're operating on the pkg level scheme.Scheme, so make sure that multiple - // calls to this function don't do this simultaneously, which can cause crashes - // due to concurrent access to underlying maps. For good measure, use a once - // since this really only needs to happen one time. - addToSchemeOnce.Do(func() { - // We also need to register resources. - schemeBuilder := runtime.NewSchemeBuilder( - func(scheme *runtime.Scheme) error { - scheme.AddKnownTypes( - *cfg.GroupVersion, - &apiv3.FelixConfiguration{}, - &apiv3.FelixConfigurationList{}, - &apiv3.IPPool{}, - &apiv3.IPPoolList{}, - &apiv3.IPReservation{}, - &apiv3.IPReservationList{}, - &apiv3.BGPPeer{}, - &apiv3.BGPPeerList{}, - &apiv3.BGPConfiguration{}, - &apiv3.BGPConfigurationList{}, - &apiv3.ClusterInformation{}, - &apiv3.ClusterInformationList{}, - &apiv3.GlobalNetworkSet{}, - &apiv3.GlobalNetworkSetList{}, - &apiv3.GlobalNetworkPolicy{}, - &apiv3.GlobalNetworkPolicyList{}, - &apiv3.NetworkPolicy{}, - &apiv3.NetworkPolicyList{}, - &apiv3.NetworkSet{}, - &apiv3.NetworkSetList{}, - &apiv3.Tier{}, - &apiv3.TierList{}, - &apiv3.HostEndpoint{}, - &apiv3.HostEndpointList{}, - &libapiv3.BlockAffinity{}, - &libapiv3.BlockAffinityList{}, - &libapiv3.IPAMBlock{}, - &libapiv3.IPAMBlockList{}, - &libapiv3.IPAMHandle{}, - &libapiv3.IPAMHandleList{}, - &libapiv3.IPAMConfig{}, - &libapiv3.IPAMConfigList{}, - &apiv3.KubeControllersConfiguration{}, - &apiv3.KubeControllersConfigurationList{}, - &apiv3.CalicoNodeStatus{}, - &apiv3.CalicoNodeStatusList{}, - &apiv3.BGPFilter{}, - &apiv3.BGPFilterList{}, - ) - return nil - }) - - err := schemeBuilder.AddToScheme(scheme.Scheme) - if err != nil { - log.WithError(err).Fatal("failed to add calico resources to scheme") - } - metav1.AddToGroupVersion(scheme.Scheme, schema.GroupVersion{Group: "crd.projectcalico.org", Version: "v1"}) - }) + calischeme.AddCalicoResourcesToScheme() + return cli, nil } diff --git a/libcalico-go/lib/backend/k8s/resources/customresource.go b/libcalico-go/lib/backend/k8s/resources/customresource.go index 674a003649c..6c135b7d754 100644 --- a/libcalico-go/lib/backend/k8s/resources/customresource.go +++ b/libcalico-go/lib/backend/k8s/resources/customresource.go @@ -343,7 +343,8 @@ func (c *customK8sResourceClient) List(ctx context.Context, list model.ListInter logContext.Debug("Received List request") // If it is a namespaced resource, then we'll need the namespace. - namespace := list.(model.ResourceListOptions).Namespace + resList := list.(model.ResourceListOptions) + namespace := resList.Namespace key := c.listInterfaceToKey(list) // listFunc performs a list with the given options. @@ -369,12 +370,12 @@ func (c *customK8sResourceClient) List(ctx context.Context, list model.ListInter // If the prefix is specified, look for the resources with the label // of prefix. - if list.(model.ResourceListOptions).Prefix { + if resList.Prefix { // The prefix has a trailing "." character, remove it, since it is not valid for k8s labels - if !strings.HasSuffix(list.(model.ResourceListOptions).Name, ".") { + if !strings.HasSuffix(resList.Name, ".") { return nil, errors.New("internal error: custom resource list invoked for a prefix not in the form '.'") } - name := list.(model.ResourceListOptions).Name[:len(list.(model.ResourceListOptions).Name)-1] + name := resList.Name[:len(resList.Name)-1] if name == "default" { req = req.VersionedParams(&metav1.ListOptions{ LabelSelector: "!" + apiv3.LabelTier, diff --git a/libcalico-go/lib/backend/k8s/resources/customresource_test.go b/libcalico-go/lib/backend/k8s/resources/customresource_test.go index 3a5a2aff6ca..c1978c94dde 100644 --- a/libcalico-go/lib/backend/k8s/resources/customresource_test.go +++ b/libcalico-go/lib/backend/k8s/resources/customresource_test.go @@ -15,16 +15,29 @@ package resources import ( + "context" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest/fake" + calischeme "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/scheme" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/net" ) +func init() { + // Need to set up the scheme in order to use the fake REST client. + calischeme.AddCalicoResourcesToScheme() +} + var _ = Describe("Custom resource conversion methods (tested using BGPPeer)", func() { // Create an empty client since we are only testing conversion functions. client := NewBGPPeerClient(nil, nil).(*customK8sResourceClient) @@ -225,3 +238,71 @@ var _ = Describe("Custom resource conversion methods (tested using BGPPeer)", fu Expect(resConverted.(*apiv3.BGPPeer).ObjectMeta.Labels).NotTo(HaveKey("foo2")) }) }) + +var _ = Describe("Custom resource conversion methods (tested using namespaced NetworkSet)", func() { + var client *customK8sResourceClient + var fakeREST *fake.RESTClient + + BeforeEach(func() { + fakeREST = &fake.RESTClient{ + NegotiatedSerializer: serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}, + GroupVersion: schema.GroupVersion{ + Group: "crd.projectcalico.org", + Version: "v1", + }, + VersionedAPIPath: "/apis", + } + client = NewNetworkSetClient(nil, fakeREST).(*customK8sResourceClient) + }) + + It("should get by name", func() { + o, err := client.Get(context.TODO(), model.ResourceKey{ + Name: "mynetset", + Namespace: "mynamespace", + Kind: apiv3.KindNetworkSet, + }, "") + + // Expect an error since the client is not implemented. + Expect(err).To(HaveOccurred()) + Expect(o).To(BeNil()) + + // But we should be able to check the request... + url := fakeREST.Req.URL + logrus.Debug("URL: ", url) + Expect(url.Path).To(Equal("/apis/namespaces/mynamespace/networksets/mynetset")) + }) + + It("should list all", func() { + l, err := client.List(context.TODO(), model.ResourceListOptions{ + Kind: apiv3.KindNetworkSet, + }, "") + + // Expect an error since the client is not implemented. + Expect(err).To(HaveOccurred()) + Expect(l).To(BeNil()) + + // But we should be able to check the request... + url := fakeREST.Req.URL + logrus.Debug("URL: ", url) + Expect(url.Path).To(Equal("/apis/networksets")) + Expect(url.Query()).NotTo(HaveKey("metadata.name")) + }) + + It("should use a fieldSelector for a list name match", func() { + l, err := client.List(context.TODO(), model.ResourceListOptions{ + Name: "foo", + Namespace: "mynamespace", + Kind: apiv3.KindNetworkSet, + }, "") + + // Expect an error since the client is not implemented. + Expect(err).To(HaveOccurred()) + Expect(l).To(BeNil()) + + // But we should be able to check the request... + url := fakeREST.Req.URL + logrus.Debug("URL: ", url) + Expect(url.Path).To(Equal("/apis/namespaces/mynamespace/networksets")) + Expect(url.Query().Get("fieldSelector")).To(Equal("metadata.name=foo")) + }) +}) \ No newline at end of file diff --git a/libcalico-go/lib/backend/k8s/scheme/scheme.go b/libcalico-go/lib/backend/k8s/scheme/scheme.go new file mode 100644 index 00000000000..8b34eae72bc --- /dev/null +++ b/libcalico-go/lib/backend/k8s/scheme/scheme.go @@ -0,0 +1,90 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 scheme + +import ( + "sync" + + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes/scheme" + + libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" +) + +var addToSchemeOnce sync.Once + +func AddCalicoResourcesToScheme() { + addToSchemeOnce.Do(func() { + // We also need to register resources. + schemeBuilder := runtime.NewSchemeBuilder( + func(scheme *runtime.Scheme) error { + scheme.AddKnownTypes( + schema.GroupVersion{ + Group: "crd.projectcalico.org", + Version: "v1", + }, + &apiv3.FelixConfiguration{}, + &apiv3.FelixConfigurationList{}, + &apiv3.IPPool{}, + &apiv3.IPPoolList{}, + &apiv3.IPReservation{}, + &apiv3.IPReservationList{}, + &apiv3.BGPPeer{}, + &apiv3.BGPPeerList{}, + &apiv3.BGPConfiguration{}, + &apiv3.BGPConfigurationList{}, + &apiv3.ClusterInformation{}, + &apiv3.ClusterInformationList{}, + &apiv3.GlobalNetworkSet{}, + &apiv3.GlobalNetworkSetList{}, + &apiv3.GlobalNetworkPolicy{}, + &apiv3.GlobalNetworkPolicyList{}, + &apiv3.NetworkPolicy{}, + &apiv3.NetworkPolicyList{}, + &apiv3.NetworkSet{}, + &apiv3.NetworkSetList{}, + &apiv3.Tier{}, + &apiv3.TierList{}, + &apiv3.HostEndpoint{}, + &apiv3.HostEndpointList{}, + &libapiv3.BlockAffinity{}, + &libapiv3.BlockAffinityList{}, + &libapiv3.IPAMBlock{}, + &libapiv3.IPAMBlockList{}, + &libapiv3.IPAMHandle{}, + &libapiv3.IPAMHandleList{}, + &libapiv3.IPAMConfig{}, + &libapiv3.IPAMConfigList{}, + &apiv3.KubeControllersConfiguration{}, + &apiv3.KubeControllersConfigurationList{}, + &apiv3.CalicoNodeStatus{}, + &apiv3.CalicoNodeStatusList{}, + &apiv3.BGPFilter{}, + &apiv3.BGPFilterList{}, + ) + return nil + }) + + err := schemeBuilder.AddToScheme(scheme.Scheme) + if err != nil { + log.WithError(err).Fatal("failed to add calico resources to scheme") + } + metav1.AddToGroupVersion(scheme.Scheme, schema.GroupVersion{Group: "crd.projectcalico.org", Version: "v1"}) + }) +}