From baa092e44f101839f83670770d1eb947f5a524cf Mon Sep 17 00:00:00 2001 From: Qiyue Yao Date: Tue, 8 Aug 2023 18:46:24 -0700 Subject: [PATCH] Namespaced Group membership API Currently Antrea supports ClusterGroup and namespaced Group CRD, but only provides API for ClusterGroup membership. This solution adds membership API for namespaced Group at namespacedgroupmembers.controlplane.antrea.io. The group association API is still available for both ClusterGroup and namespaced Group. Fixes #5269 Signed-off-by: Qiyue Yao --- hack/update-codegen-dockerized.sh | 1 + pkg/apis/controlplane/register.go | 1 + pkg/apis/controlplane/types.go | 13 ++ pkg/apis/controlplane/v1beta2/register.go | 1 + pkg/apis/controlplane/v1beta2/types.go | 15 ++ .../v1beta2/zz_generated.conversion.go | 60 +++++ .../v1beta2/zz_generated.deepcopy.go | 40 ++++ .../controlplane/zz_generated.deepcopy.go | 40 ++++ pkg/apiserver/apiserver.go | 3 + pkg/apiserver/openapi/zz_generated.openapi.go | 84 +++++++ .../registry/namespacedgroupmember/rest.go | 99 ++++++++ .../namespacedgroupmember/rest_test.go | 213 ++++++++++++++++++ .../networkpolicy/clustergroupmember/rest.go | 48 +--- pkg/apiserver/registry/networkpolicy/util.go | 46 ++++ .../v1beta2/controlplane_client.go | 5 + .../v1beta2/fake/fake_controlplane_client.go | 4 + .../fake/fake_namespacedgroupmembers.go | 47 ++++ .../v1beta2/generated_expansion.go | 2 + .../v1beta2/namespacedgroupmembers.go | 65 ++++++ pkg/controller/networkpolicy/group.go | 14 ++ 20 files changed, 755 insertions(+), 46 deletions(-) create mode 100644 pkg/apiserver/registry/namespacedgroupmember/rest.go create mode 100644 pkg/apiserver/registry/namespacedgroupmember/rest_test.go create mode 100644 pkg/client/clientset/versioned/typed/controlplane/v1beta2/fake/fake_namespacedgroupmembers.go create mode 100644 pkg/client/clientset/versioned/typed/controlplane/v1beta2/namespacedgroupmembers.go diff --git a/hack/update-codegen-dockerized.sh b/hack/update-codegen-dockerized.sh index 091693f3ca7..ea38ffe0a04 100755 --- a/hack/update-codegen-dockerized.sh +++ b/hack/update-codegen-dockerized.sh @@ -102,6 +102,7 @@ function generate_antrea_client_code { --plural-exceptions "AntreaNetworkPolicyStats:AntreaNetworkPolicyStats" \ --plural-exceptions "AntreaClusterNetworkPolicyStats:AntreaClusterNetworkPolicyStats" \ --plural-exceptions "ClusterGroupMembers:ClusterGroupMembers" \ + --plural-exceptions "NamespacedGroupMembers:NamespacedGroupMembers" \ --go-header-file hack/boilerplate/license_header.go.txt # Generate listers with K8s codegen tools. diff --git a/pkg/apis/controlplane/register.go b/pkg/apis/controlplane/register.go index 6e79ee22d18..7c273f4f2ee 100644 --- a/pkg/apis/controlplane/register.go +++ b/pkg/apis/controlplane/register.go @@ -56,6 +56,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &NetworkPolicyStatus{}, &NodeStatsSummary{}, &ClusterGroupMembers{}, + &NamespacedGroupMembers{}, &PaginationGetOptions{}, &GroupAssociation{}, &IPGroupAssociation{}, diff --git a/pkg/apis/controlplane/types.go b/pkg/apis/controlplane/types.go index 2e81bdd1381..94b0696f648 100644 --- a/pkg/apis/controlplane/types.go +++ b/pkg/apis/controlplane/types.go @@ -106,6 +106,19 @@ type ClusterGroupMembers struct { CurrentPage int64 } +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// NamespacedGroupMembers is a list of GroupMember objects or ipBlocks that are currently selected by a Group. +type NamespacedGroupMembers struct { + metav1.TypeMeta + metav1.ObjectMeta + EffectiveMembers []GroupMember + EffectiveIPBlocks []IPNet + TotalMembers int64 + TotalPages int64 + CurrentPage int64 +} + // +k8s:conversion-gen:explicit-from=net/url.Values // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/controlplane/v1beta2/register.go b/pkg/apis/controlplane/v1beta2/register.go index bc1998f5ada..b411c814438 100644 --- a/pkg/apis/controlplane/v1beta2/register.go +++ b/pkg/apis/controlplane/v1beta2/register.go @@ -71,6 +71,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &NetworkPolicyStatus{}, &NodeStatsSummary{}, &ClusterGroupMembers{}, + &NamespacedGroupMembers{}, &PaginationGetOptions{}, &GroupAssociation{}, &IPGroupAssociation{}, diff --git a/pkg/apis/controlplane/v1beta2/types.go b/pkg/apis/controlplane/v1beta2/types.go index 6e9a7372867..9f955f2cf2d 100644 --- a/pkg/apis/controlplane/v1beta2/types.go +++ b/pkg/apis/controlplane/v1beta2/types.go @@ -109,6 +109,21 @@ type ClusterGroupMembers struct { CurrentPage int64 `json:"currentPage" protobuf:"varint,6,opt,name=currentPage"` } +// +genclient +// +genclient:onlyVerbs=get +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// NamespacedGroupMembers is a list of GroupMember objects or ipBlocks that are currently selected by a Group. +type NamespacedGroupMembers struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + EffectiveMembers []GroupMember `json:"effectiveMembers" protobuf:"bytes,2,rep,name=effectiveMembers"` + EffectiveIPBlocks []IPNet `json:"effectiveIPBlocks" protobuf:"bytes,3,rep,name=effectiveIPBlocks"` + TotalMembers int64 `json:"totalMembers" protobuf:"varint,4,opt,name=totalMembers"` + TotalPages int64 `json:"totalPages" protobuf:"varint,5,opt,name=totalPages"` + CurrentPage int64 `json:"currentPage" protobuf:"varint,6,opt,name=currentPage"` +} + // +k8s:conversion-gen:explicit-from=net/url.Values // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go b/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go index 77f7128878a..cb89c5a3ada 100644 --- a/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go +++ b/pkg/apis/controlplane/v1beta2/zz_generated.conversion.go @@ -279,6 +279,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*NamespacedGroupMembers)(nil), (*controlplane.NamespacedGroupMembers)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_NamespacedGroupMembers_To_controlplane_NamespacedGroupMembers(a.(*NamespacedGroupMembers), b.(*controlplane.NamespacedGroupMembers), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controlplane.NamespacedGroupMembers)(nil), (*NamespacedGroupMembers)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_controlplane_NamespacedGroupMembers_To_v1beta2_NamespacedGroupMembers(a.(*controlplane.NamespacedGroupMembers), b.(*NamespacedGroupMembers), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*NetworkPolicy)(nil), (*controlplane.NetworkPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_NetworkPolicy_To_controlplane_NetworkPolicy(a.(*NetworkPolicy), b.(*controlplane.NetworkPolicy), scope) }); err != nil { @@ -1297,6 +1307,56 @@ func Convert_controlplane_NamedPort_To_v1beta2_NamedPort(in *controlplane.NamedP return autoConvert_controlplane_NamedPort_To_v1beta2_NamedPort(in, out, s) } +func autoConvert_v1beta2_NamespacedGroupMembers_To_controlplane_NamespacedGroupMembers(in *NamespacedGroupMembers, out *controlplane.NamespacedGroupMembers, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if in.EffectiveMembers != nil { + in, out := &in.EffectiveMembers, &out.EffectiveMembers + *out = make([]controlplane.GroupMember, len(*in)) + for i := range *in { + if err := Convert_v1beta2_GroupMember_To_controlplane_GroupMember(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.EffectiveMembers = nil + } + out.EffectiveIPBlocks = *(*[]controlplane.IPNet)(unsafe.Pointer(&in.EffectiveIPBlocks)) + out.TotalMembers = in.TotalMembers + out.TotalPages = in.TotalPages + out.CurrentPage = in.CurrentPage + return nil +} + +// Convert_v1beta2_NamespacedGroupMembers_To_controlplane_NamespacedGroupMembers is an autogenerated conversion function. +func Convert_v1beta2_NamespacedGroupMembers_To_controlplane_NamespacedGroupMembers(in *NamespacedGroupMembers, out *controlplane.NamespacedGroupMembers, s conversion.Scope) error { + return autoConvert_v1beta2_NamespacedGroupMembers_To_controlplane_NamespacedGroupMembers(in, out, s) +} + +func autoConvert_controlplane_NamespacedGroupMembers_To_v1beta2_NamespacedGroupMembers(in *controlplane.NamespacedGroupMembers, out *NamespacedGroupMembers, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if in.EffectiveMembers != nil { + in, out := &in.EffectiveMembers, &out.EffectiveMembers + *out = make([]GroupMember, len(*in)) + for i := range *in { + if err := Convert_controlplane_GroupMember_To_v1beta2_GroupMember(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.EffectiveMembers = nil + } + out.EffectiveIPBlocks = *(*[]IPNet)(unsafe.Pointer(&in.EffectiveIPBlocks)) + out.TotalMembers = in.TotalMembers + out.TotalPages = in.TotalPages + out.CurrentPage = in.CurrentPage + return nil +} + +// Convert_controlplane_NamespacedGroupMembers_To_v1beta2_NamespacedGroupMembers is an autogenerated conversion function. +func Convert_controlplane_NamespacedGroupMembers_To_v1beta2_NamespacedGroupMembers(in *controlplane.NamespacedGroupMembers, out *NamespacedGroupMembers, s conversion.Scope) error { + return autoConvert_controlplane_NamespacedGroupMembers_To_v1beta2_NamespacedGroupMembers(in, out, s) +} + func autoConvert_v1beta2_NetworkPolicy_To_controlplane_NetworkPolicy(in *NetworkPolicy, out *controlplane.NetworkPolicy, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if in.Rules != nil { diff --git a/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go b/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go index 672b6e0d825..8a73caaa7e3 100644 --- a/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go +++ b/pkg/apis/controlplane/v1beta2/zz_generated.deepcopy.go @@ -727,6 +727,46 @@ func (in *NamedPort) DeepCopy() *NamedPort { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedGroupMembers) DeepCopyInto(out *NamespacedGroupMembers) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.EffectiveMembers != nil { + in, out := &in.EffectiveMembers, &out.EffectiveMembers + *out = make([]GroupMember, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.EffectiveIPBlocks != nil { + in, out := &in.EffectiveIPBlocks, &out.EffectiveIPBlocks + *out = make([]IPNet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedGroupMembers. +func (in *NamespacedGroupMembers) DeepCopy() *NamespacedGroupMembers { + if in == nil { + return nil + } + out := new(NamespacedGroupMembers) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedGroupMembers) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkPolicy) DeepCopyInto(out *NetworkPolicy) { *out = *in diff --git a/pkg/apis/controlplane/zz_generated.deepcopy.go b/pkg/apis/controlplane/zz_generated.deepcopy.go index 08e0944de99..e1f28ddc60f 100644 --- a/pkg/apis/controlplane/zz_generated.deepcopy.go +++ b/pkg/apis/controlplane/zz_generated.deepcopy.go @@ -727,6 +727,46 @@ func (in *NamedPort) DeepCopy() *NamedPort { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedGroupMembers) DeepCopyInto(out *NamespacedGroupMembers) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.EffectiveMembers != nil { + in, out := &in.EffectiveMembers, &out.EffectiveMembers + *out = make([]GroupMember, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.EffectiveIPBlocks != nil { + in, out := &in.EffectiveIPBlocks, &out.EffectiveIPBlocks + *out = make([]IPNet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedGroupMembers. +func (in *NamespacedGroupMembers) DeepCopy() *NamespacedGroupMembers { + if in == nil { + return nil + } + out := new(NamespacedGroupMembers) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedGroupMembers) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkPolicy) DeepCopyInto(out *NetworkPolicy) { *out = *in diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 865a8b87b08..b1df6c34976 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -45,6 +45,7 @@ import ( "antrea.io/antrea/pkg/apiserver/registry/controlplane/egressgroup" "antrea.io/antrea/pkg/apiserver/registry/controlplane/nodestatssummary" "antrea.io/antrea/pkg/apiserver/registry/controlplane/supportbundlecollection" + "antrea.io/antrea/pkg/apiserver/registry/namespacedgroupmember" "antrea.io/antrea/pkg/apiserver/registry/networkpolicy/addressgroup" "antrea.io/antrea/pkg/apiserver/registry/networkpolicy/appliedtogroup" "antrea.io/antrea/pkg/apiserver/registry/networkpolicy/clustergroupmember" @@ -192,6 +193,7 @@ func installAPIGroup(s *APIServer, c completedConfig) error { networkPolicyStorage := networkpolicy.NewREST(c.extraConfig.networkPolicyStore) networkPolicyStatusStorage := networkpolicy.NewStatusREST(c.extraConfig.networkPolicyStatusController) clusterGroupMembershipStorage := clustergroupmember.NewREST(c.extraConfig.networkPolicyController) + namespacedGroupMembershipStorage := namespacedgroupmember.NewREST(c.extraConfig.networkPolicyController) groupAssociationStorage := groupassociation.NewREST(c.extraConfig.networkPolicyController) ipGroupAssociationStorage := ipgroupassociation.NewREST(c.extraConfig.podInformer, c.extraConfig.eeInformer, c.extraConfig.networkPolicyController, c.extraConfig.networkPolicyController) nodeStatsSummaryStorage := nodestatssummary.NewREST(c.extraConfig.statsAggregator) @@ -208,6 +210,7 @@ func installAPIGroup(s *APIServer, c completedConfig) error { cpv1beta2Storage["groupassociations"] = groupAssociationStorage cpv1beta2Storage["ipgroupassociations"] = ipGroupAssociationStorage cpv1beta2Storage["clustergroupmembers"] = clusterGroupMembershipStorage + cpv1beta2Storage["namespacedgroupmembers"] = namespacedGroupMembershipStorage cpv1beta2Storage["egressgroups"] = egressGroupStorage cpv1beta2Storage["supportbundlecollections"] = bundleCollectionStorage cpv1beta2Storage["supportbundlecollections/status"] = bundleCollectionStatusStorage diff --git a/pkg/apiserver/openapi/zz_generated.openapi.go b/pkg/apiserver/openapi/zz_generated.openapi.go index 5a2eb174d2e..0d11a6d686f 100644 --- a/pkg/apiserver/openapi/zz_generated.openapi.go +++ b/pkg/apiserver/openapi/zz_generated.openapi.go @@ -54,6 +54,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "antrea.io/antrea/pkg/apis/controlplane/v1beta2.L7Protocol": schema_pkg_apis_controlplane_v1beta2_L7Protocol(ref), "antrea.io/antrea/pkg/apis/controlplane/v1beta2.MulticastGroupInfo": schema_pkg_apis_controlplane_v1beta2_MulticastGroupInfo(ref), "antrea.io/antrea/pkg/apis/controlplane/v1beta2.NamedPort": schema_pkg_apis_controlplane_v1beta2_NamedPort(ref), + "antrea.io/antrea/pkg/apis/controlplane/v1beta2.NamespacedGroupMembers": schema_pkg_apis_controlplane_v1beta2_NamespacedGroupMembers(ref), "antrea.io/antrea/pkg/apis/controlplane/v1beta2.NetworkPolicy": schema_pkg_apis_controlplane_v1beta2_NetworkPolicy(ref), "antrea.io/antrea/pkg/apis/controlplane/v1beta2.NetworkPolicyList": schema_pkg_apis_controlplane_v1beta2_NetworkPolicyList(ref), "antrea.io/antrea/pkg/apis/controlplane/v1beta2.NetworkPolicyNodeStatus": schema_pkg_apis_controlplane_v1beta2_NetworkPolicyNodeStatus(ref), @@ -1465,6 +1466,89 @@ func schema_pkg_apis_controlplane_v1beta2_NamedPort(ref common.ReferenceCallback } } +func schema_pkg_apis_controlplane_v1beta2_NamespacedGroupMembers(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NamespacedGroupMembers is a list of GroupMember objects or ipBlocks that are currently selected by a Group.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "effectiveMembers": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("antrea.io/antrea/pkg/apis/controlplane/v1beta2.GroupMember"), + }, + }, + }, + }, + }, + "effectiveIPBlocks": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("antrea.io/antrea/pkg/apis/controlplane/v1beta2.IPNet"), + }, + }, + }, + }, + }, + "totalMembers": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "totalPages": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "currentPage": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"effectiveMembers", "effectiveIPBlocks", "totalMembers", "totalPages", "currentPage"}, + }, + }, + Dependencies: []string{ + "antrea.io/antrea/pkg/apis/controlplane/v1beta2.GroupMember", "antrea.io/antrea/pkg/apis/controlplane/v1beta2.IPNet", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + func schema_pkg_apis_controlplane_v1beta2_NetworkPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apiserver/registry/namespacedgroupmember/rest.go b/pkg/apiserver/registry/namespacedgroupmember/rest.go new file mode 100644 index 00000000000..4c485252214 --- /dev/null +++ b/pkg/apiserver/registry/namespacedgroupmember/rest.go @@ -0,0 +1,99 @@ +// Copyright 2023 Antrea 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 namespacedgroupmember + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" + + "antrea.io/antrea/pkg/apis/controlplane" + "antrea.io/antrea/pkg/apiserver/registry/networkpolicy" +) + +type REST struct { + querier namespacedGroupMembershipQuerier +} + +var ( + _ rest.Storage = &REST{} + _ rest.Scoper = &REST{} + _ rest.GetterWithOptions = &REST{} +) + +// NewREST returns a REST object that will work against API services. +func NewREST(querier namespacedGroupMembershipQuerier) *REST { + return &REST{querier} +} + +type namespacedGroupMembershipQuerier interface { + GetNamespacedGroupMembers(name, namespace string) (controlplane.GroupMemberSet, []controlplane.IPBlock, error) +} + +func (r *REST) New() runtime.Object { + return &controlplane.NamespacedGroupMembers{} +} + +func (r *REST) Destroy() { +} + +func (r *REST) Get(ctx context.Context, name string, options runtime.Object) (runtime.Object, error) { + ns, ok := request.NamespaceFrom(ctx) + if !ok || len(ns) == 0 { + return nil, errors.NewBadRequest("Namespace parameter required.") + } + groupMembers, ipBlocks, err := r.querier.GetNamespacedGroupMembers(name, ns) + if err != nil { + return nil, errors.NewInternalError(err) + } + // Retrieve options used for pagination. + getOptions, ok := options.(*controlplane.PaginationGetOptions) + if !ok || getOptions == nil { + return nil, errors.NewInternalError(fmt.Errorf("received error while retrieving options for pagination")) + } + memberList := &controlplane.NamespacedGroupMembers{} + if len(ipBlocks) > 0 { + effectiveIPBlocks := make([]controlplane.IPNet, 0, len(ipBlocks)) + for _, ipb := range ipBlocks { + effectiveIPBlocks = append(effectiveIPBlocks, ipb.CIDR) + } + memberList.EffectiveIPBlocks = effectiveIPBlocks + } + if len(groupMembers) > 0 { + effectiveMembers := make([]controlplane.GroupMember, 0, len(groupMembers)) + for _, member := range groupMembers { + effectiveMembers = append(effectiveMembers, *member) + } + memberList.EffectiveMembers = effectiveMembers + } + memberList.Name = name + memberList.Namespace = ns + memberList.TotalMembers = int64(len(memberList.EffectiveMembers)) + memberList.TotalPages, memberList.CurrentPage, err = networkpolicy.PaginateMemberList(&memberList.EffectiveMembers, getOptions) + return memberList, err +} + +// NewGetOptions returns the default options for Get, so options object is never nil. +func (r *REST) NewGetOptions() (runtime.Object, bool, string) { + return &controlplane.PaginationGetOptions{}, false, "" +} + +func (r *REST) NamespaceScoped() bool { + return true +} diff --git a/pkg/apiserver/registry/namespacedgroupmember/rest_test.go b/pkg/apiserver/registry/namespacedgroupmember/rest_test.go new file mode 100644 index 00000000000..7b3a91c2da4 --- /dev/null +++ b/pkg/apiserver/registry/namespacedgroupmember/rest_test.go @@ -0,0 +1,213 @@ +// Copyright 2023 Antrea 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 namespacedgroupmember + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" + + "antrea.io/antrea/pkg/apis/controlplane" +) + +type fakeQuerier struct { + members map[string]map[string]controlplane.GroupMemberSet + ipMembers map[string]map[string][]controlplane.IPBlock +} + +func (q fakeQuerier) GetNamespacedGroupMembers(uid, ns string) (controlplane.GroupMemberSet, []controlplane.IPBlock, error) { + if ipMemberList, ok := q.ipMembers[ns][uid]; ok { + return nil, ipMemberList, nil + } + if memberList, ok := q.members[ns][uid]; ok { + return memberList, nil, nil + } + return nil, nil, nil +} + +func getTestMembers() map[string]map[string]controlplane.GroupMemberSet { + return map[string]map[string]controlplane.GroupMemberSet{ + "default": { + "ngA": { + "memberKey1": &controlplane.GroupMember{ + Pod: &controlplane.PodReference{ + Name: "pod1", + Namespace: "ns1", + }, + IPs: []controlplane.IPAddress{ + []byte{127, 10, 0, 1}, + }, + }, + }, + "ngB": { + "memberKey2": &controlplane.GroupMember{ + ExternalEntity: &controlplane.ExternalEntityReference{ + Name: "ee2", + Namespace: "ns1", + }, + IPs: []controlplane.IPAddress{ + []byte{127, 10, 0, 2}, + }, + }, + }, + }, + } +} + +func getTestIPMembers() map[string]map[string][]controlplane.IPBlock { + testCIDR := controlplane.IPNet{ + IP: controlplane.IPAddress(net.ParseIP("10.0.0.1")), + PrefixLength: int32(24), + } + ipb := []controlplane.IPBlock{{CIDR: testCIDR}} + return map[string]map[string][]controlplane.IPBlock{ + "ns2": {"ngIPBlock": ipb}, + } +} + +func TestREST(t *testing.T) { + r := NewREST(nil) + assert.Equal(t, &controlplane.NamespacedGroupMembers{}, r.New()) + assert.True(t, r.NamespaceScoped()) +} + +func TestRESTGet(t *testing.T) { + tests := []struct { + name string + groupName string + namespace string + expectedObj runtime.Object + expectedErr bool + }{ + { + name: "single-pod-group-member", + groupName: "ngA", + namespace: "default", + expectedObj: &controlplane.NamespacedGroupMembers{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ngA", + Namespace: "default", + }, + EffectiveMembers: []controlplane.GroupMember{ + { + Pod: &controlplane.PodReference{ + Name: "pod1", + Namespace: "ns1", + }, + IPs: []controlplane.IPAddress{ + []byte{127, 10, 0, 1}, + }, + }, + }, + TotalMembers: 1, + TotalPages: 1, + CurrentPage: 1, + }, + expectedErr: false, + }, + { + name: "single-ee-group-member", + groupName: "ngB", + namespace: "default", + expectedObj: &controlplane.NamespacedGroupMembers{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ngB", + Namespace: "default", + }, + EffectiveMembers: []controlplane.GroupMember{ + { + ExternalEntity: &controlplane.ExternalEntityReference{ + Name: "ee2", + Namespace: "ns1", + }, + IPs: []controlplane.IPAddress{ + []byte{127, 10, 0, 2}, + }, + }, + }, + TotalMembers: 1, + TotalPages: 1, + CurrentPage: 1, + }, + expectedErr: false, + }, + { + name: "no-group-member", + groupName: "ngC", + namespace: "default", + expectedObj: &controlplane.NamespacedGroupMembers{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ngC", + Namespace: "default", + }, + TotalMembers: 0, + TotalPages: 0, + CurrentPage: 0, + }, + expectedErr: false, + }, + { + name: "no-group-member-in-namespace", + groupName: "ngA", + namespace: "test", + expectedObj: &controlplane.NamespacedGroupMembers{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ngA", + Namespace: "test", + }, + TotalMembers: 0, + TotalPages: 0, + CurrentPage: 0, + }, + expectedErr: false, + }, + { + name: "ipBlock-ng", + groupName: "ngIPBlock", + namespace: "ns2", + expectedObj: &controlplane.NamespacedGroupMembers{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ngIPBlock", + Namespace: "ns2", + }, + EffectiveIPBlocks: []controlplane.IPNet{ + { + IP: controlplane.IPAddress(net.ParseIP("10.0.0.1")), + PrefixLength: int32(24), + }, + }, + TotalMembers: 0, + TotalPages: 0, + CurrentPage: 0, + }, + expectedErr: false, + }, + } + rest := NewREST(fakeQuerier{members: getTestMembers(), ipMembers: getTestIPMembers()}) + for _, tt := range tests { + actualGroupList, err := rest.Get(request.WithNamespace(request.NewContext(), tt.namespace), tt.groupName, &controlplane.PaginationGetOptions{}) + if tt.expectedErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.expectedObj, actualGroupList) + } +} diff --git a/pkg/apiserver/registry/networkpolicy/clustergroupmember/rest.go b/pkg/apiserver/registry/networkpolicy/clustergroupmember/rest.go index d072f0db2c6..11dd13799c7 100644 --- a/pkg/apiserver/registry/networkpolicy/clustergroupmember/rest.go +++ b/pkg/apiserver/registry/networkpolicy/clustergroupmember/rest.go @@ -17,13 +17,13 @@ package clustergroupmember import ( "context" "fmt" - "sort" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" "antrea.io/antrea/pkg/apis/controlplane" + "antrea.io/antrea/pkg/apiserver/registry/networkpolicy" ) type REST struct { @@ -81,7 +81,7 @@ func (r *REST) Get(ctx context.Context, name string, options runtime.Object) (ru } memberList.Name = name memberList.TotalMembers = int64(len(memberList.EffectiveMembers)) - err = paginateMemberList(memberList, getOptions) + memberList.TotalPages, memberList.CurrentPage, err = networkpolicy.PaginateMemberList(&memberList.EffectiveMembers, getOptions) return memberList, err } @@ -93,47 +93,3 @@ func (r *REST) NewGetOptions() (runtime.Object, bool, string) { func (r *REST) NamespaceScoped() bool { return false } - -// paginateMemberList returns paginated results if meaningful options are provided, options should never be nil. -// Paginated results are continuous only when there is no member change across multiple calls. -// Pagination is not processed if either page number or limit = 0, thus returns full member list. -// Returns an error for invalid options; returns empty list for page number beyond the total pages range. -func paginateMemberList(memberList *controlplane.ClusterGroupMembers, pageInfo *controlplane.PaginationGetOptions) error { - if pageInfo.Limit < 0 { - return errors.NewBadRequest(fmt.Sprintf("received invalid page limit %d for pagination", pageInfo.Limit)) - } else if pageInfo.Page < 0 { - return errors.NewBadRequest(fmt.Sprintf("received invalid page number %d for pagination", pageInfo.Page)) - } - if memberList.TotalMembers == 0 { - memberList.TotalPages, memberList.CurrentPage = 0, 0 - return nil - } - // Sort members based on name of ee/pod to realize consistent pagination support. - sort.SliceStable(memberList.EffectiveMembers, func(i, j int) bool { - if memberList.EffectiveMembers[i].Pod != nil && memberList.EffectiveMembers[j].Pod != nil { - return memberList.EffectiveMembers[i].Pod.Name < memberList.EffectiveMembers[j].Pod.Name - } else if memberList.EffectiveMembers[i].ExternalEntity != nil && memberList.EffectiveMembers[j].ExternalEntity != nil { - return memberList.EffectiveMembers[i].ExternalEntity.Name < memberList.EffectiveMembers[j].ExternalEntity.Name - } else { - return true - } - }) - if pageInfo.Limit == 0 { - memberList.TotalPages, memberList.CurrentPage = 1, 1 - return nil - } - totalPages := (int64(len(memberList.EffectiveMembers)) + pageInfo.Limit - 1) / pageInfo.Limit - memberList.TotalPages = totalPages - memberList.CurrentPage = pageInfo.Page - if totalPages >= pageInfo.Page && pageInfo.Page > 0 { - beginMember := (pageInfo.Page - 1) * pageInfo.Limit - memberList.EffectiveMembers = memberList.EffectiveMembers[beginMember:] - if pageInfo.Limit < int64(len(memberList.EffectiveMembers)) { - memberList.EffectiveMembers = memberList.EffectiveMembers[:pageInfo.Limit] - } - } else if totalPages < pageInfo.Page { - // Returns empty memberList if page number exceeds total pages, to indicate end of list. - memberList.EffectiveMembers = memberList.EffectiveMembers[:0] - } - return nil -} diff --git a/pkg/apiserver/registry/networkpolicy/util.go b/pkg/apiserver/registry/networkpolicy/util.go index 886e2832490..c122d02c6d7 100644 --- a/pkg/apiserver/registry/networkpolicy/util.go +++ b/pkg/apiserver/registry/networkpolicy/util.go @@ -15,9 +15,15 @@ package networkpolicy import ( + "fmt" + "sort" + + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" + + "antrea.io/antrea/pkg/apis/controlplane" ) // GetSelectors extracts label selector, field selector, and key selector from the provided options. @@ -33,3 +39,43 @@ func GetSelectors(options *internalversion.ListOptions) (string, labels.Selector key, _ := field.RequiresExactMatch("metadata.name") return key, label, field } + +// PaginateMemberList returns paginated results if meaningful options are provided, options should never be nil. +// Paginated results are continuous only when there is no member change across multiple calls. +// Pagination is not processed if either page number or limit = 0, thus returns full member list. +// Returns an error for invalid options; returns empty list for page number beyond the total pages range. +func PaginateMemberList(effectiveMembers *[]controlplane.GroupMember, pageInfo *controlplane.PaginationGetOptions) (int64, int64, error) { + if pageInfo.Limit < 0 { + return 0, 0, errors.NewBadRequest(fmt.Sprintf("received invalid page limit %d for pagination", pageInfo.Limit)) + } else if pageInfo.Page < 0 { + return 0, 0, errors.NewBadRequest(fmt.Sprintf("received invalid page number %d for pagination", pageInfo.Page)) + } + if len(*effectiveMembers) == 0 { + return 0, 0, nil + } + // Sort members based on name of ee/pod to realize consistent pagination support. + sort.SliceStable(*effectiveMembers, func(i, j int) bool { + if (*effectiveMembers)[i].Pod != nil && (*effectiveMembers)[j].Pod != nil { + return (*effectiveMembers)[i].Pod.Name < (*effectiveMembers)[j].Pod.Name + } else if (*effectiveMembers)[i].ExternalEntity != nil && (*effectiveMembers)[j].ExternalEntity != nil { + return (*effectiveMembers)[i].ExternalEntity.Name < (*effectiveMembers)[j].ExternalEntity.Name + } else { + return true + } + }) + if pageInfo.Limit == 0 { + return 1, 1, nil + } + totalPages := (int64(len(*effectiveMembers)) + pageInfo.Limit - 1) / pageInfo.Limit + if totalPages >= pageInfo.Page && pageInfo.Page > 0 { + beginMember := (pageInfo.Page - 1) * pageInfo.Limit + *effectiveMembers = (*effectiveMembers)[beginMember:] + if pageInfo.Limit < int64(len(*effectiveMembers)) { + *effectiveMembers = (*effectiveMembers)[:pageInfo.Limit] + } + } else if totalPages < pageInfo.Page { + // Returns empty memberList if page number exceeds total pages, to indicate end of list. + *effectiveMembers = (*effectiveMembers)[:0] + } + return totalPages, pageInfo.Page, nil +} diff --git a/pkg/client/clientset/versioned/typed/controlplane/v1beta2/controlplane_client.go b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/controlplane_client.go index 7bada10f68b..da823d4be61 100644 --- a/pkg/client/clientset/versioned/typed/controlplane/v1beta2/controlplane_client.go +++ b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/controlplane_client.go @@ -32,6 +32,7 @@ type ControlplaneV1beta2Interface interface { EgressGroupsGetter GroupAssociationsGetter IPGroupAssociationsGetter + NamespacedGroupMembersGetter NetworkPoliciesGetter NodeStatsSummariesGetter SupportBundleCollectionsGetter @@ -66,6 +67,10 @@ func (c *ControlplaneV1beta2Client) IPGroupAssociations() IPGroupAssociationInte return newIPGroupAssociations(c) } +func (c *ControlplaneV1beta2Client) NamespacedGroupMembers(namespace string) NamespacedGroupMembersInterface { + return newNamespacedGroupMembers(c, namespace) +} + func (c *ControlplaneV1beta2Client) NetworkPolicies() NetworkPolicyInterface { return newNetworkPolicies(c) } diff --git a/pkg/client/clientset/versioned/typed/controlplane/v1beta2/fake/fake_controlplane_client.go b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/fake/fake_controlplane_client.go index cc417cf9170..6266d6e4081 100644 --- a/pkg/client/clientset/versioned/typed/controlplane/v1beta2/fake/fake_controlplane_client.go +++ b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/fake/fake_controlplane_client.go @@ -50,6 +50,10 @@ func (c *FakeControlplaneV1beta2) IPGroupAssociations() v1beta2.IPGroupAssociati return &FakeIPGroupAssociations{c} } +func (c *FakeControlplaneV1beta2) NamespacedGroupMembers(namespace string) v1beta2.NamespacedGroupMembersInterface { + return &FakeNamespacedGroupMembers{c, namespace} +} + func (c *FakeControlplaneV1beta2) NetworkPolicies() v1beta2.NetworkPolicyInterface { return &FakeNetworkPolicies{c} } diff --git a/pkg/client/clientset/versioned/typed/controlplane/v1beta2/fake/fake_namespacedgroupmembers.go b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/fake/fake_namespacedgroupmembers.go new file mode 100644 index 00000000000..6941d6f5ddb --- /dev/null +++ b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/fake/fake_namespacedgroupmembers.go @@ -0,0 +1,47 @@ +// Copyright 2023 Antrea 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. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1beta2 "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + schema "k8s.io/apimachinery/pkg/runtime/schema" + testing "k8s.io/client-go/testing" +) + +// FakeNamespacedGroupMembers implements NamespacedGroupMembersInterface +type FakeNamespacedGroupMembers struct { + Fake *FakeControlplaneV1beta2 + ns string +} + +var namespacedgroupmembersResource = schema.GroupVersionResource{Group: "controlplane.antrea.io", Version: "v1beta2", Resource: "namespacedgroupmembers"} + +var namespacedgroupmembersKind = schema.GroupVersionKind{Group: "controlplane.antrea.io", Version: "v1beta2", Kind: "NamespacedGroupMembers"} + +// Get takes name of the namespacedGroupMembers, and returns the corresponding namespacedGroupMembers object, and an error if there is any. +func (c *FakeNamespacedGroupMembers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.NamespacedGroupMembers, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(namespacedgroupmembersResource, c.ns, name), &v1beta2.NamespacedGroupMembers{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta2.NamespacedGroupMembers), err +} diff --git a/pkg/client/clientset/versioned/typed/controlplane/v1beta2/generated_expansion.go b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/generated_expansion.go index ab56cd87d39..fa65a6ef3f7 100644 --- a/pkg/client/clientset/versioned/typed/controlplane/v1beta2/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/generated_expansion.go @@ -28,4 +28,6 @@ type GroupAssociationExpansion interface{} type IPGroupAssociationExpansion interface{} +type NamespacedGroupMembersExpansion interface{} + type NodeStatsSummaryExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/controlplane/v1beta2/namespacedgroupmembers.go b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/namespacedgroupmembers.go new file mode 100644 index 00000000000..d7b7ce11e06 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/controlplane/v1beta2/namespacedgroupmembers.go @@ -0,0 +1,65 @@ +// Copyright 2023 Antrea 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. + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + + v1beta2 "antrea.io/antrea/pkg/apis/controlplane/v1beta2" + scheme "antrea.io/antrea/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rest "k8s.io/client-go/rest" +) + +// NamespacedGroupMembersGetter has a method to return a NamespacedGroupMembersInterface. +// A group's client should implement this interface. +type NamespacedGroupMembersGetter interface { + NamespacedGroupMembers(namespace string) NamespacedGroupMembersInterface +} + +// NamespacedGroupMembersInterface has methods to work with NamespacedGroupMembers resources. +type NamespacedGroupMembersInterface interface { + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta2.NamespacedGroupMembers, error) + NamespacedGroupMembersExpansion +} + +// namespacedGroupMembers implements NamespacedGroupMembersInterface +type namespacedGroupMembers struct { + client rest.Interface + ns string +} + +// newNamespacedGroupMembers returns a NamespacedGroupMembers +func newNamespacedGroupMembers(c *ControlplaneV1beta2Client, namespace string) *namespacedGroupMembers { + return &namespacedGroupMembers{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the namespacedGroupMembers, and returns the corresponding namespacedGroupMembers object, and an error if there is any. +func (c *namespacedGroupMembers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta2.NamespacedGroupMembers, err error) { + result = &v1beta2.NamespacedGroupMembers{} + err = c.client.Get(). + Namespace(c.ns). + Resource("namespacedgroupmembers"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} diff --git a/pkg/controller/networkpolicy/group.go b/pkg/controller/networkpolicy/group.go index 4fdb1345792..f9324ed4e78 100644 --- a/pkg/controller/networkpolicy/group.go +++ b/pkg/controller/networkpolicy/group.go @@ -16,6 +16,7 @@ package networkpolicy import ( "context" + "fmt" "net" v1 "k8s.io/api/core/v1" @@ -244,3 +245,16 @@ func (n *NetworkPolicyController) updateGroupStatus(g *crdv1beta1.Group, cStatus _, err := n.crdClient.CrdV1beta1().Groups(g.GetNamespace()).UpdateStatus(context.TODO(), toUpdate, metav1.UpdateOptions{}) return err } + +// GetNamespacedGroupMembers returns the current members of a Group. +// If the Group is defined with IPBlocks, the returned members will be []controlplane.IPBlock. +// Otherwise, the returned members will be of type controlplane.GroupMemberSet. +func (n *NetworkPolicyController) GetNamespacedGroupMembers(name, namespace string) (controlplane.GroupMemberSet, []controlplane.IPBlock, error) { + groupObj, found, _ := n.internalGroupStore.Get(namespace + "/" + name) + if found { + group := groupObj.(*antreatypes.Group) + member, ipb := n.getInternalGroupMembers(group) + return member, ipb, nil + } + return nil, nil, fmt.Errorf("no internal Group with name %s is found", namespace+"/"+name) +}