diff --git a/.gitignore b/.gitignore index ab7f1041986..97b780e36a1 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ kube-ovn.yaml !/charts/kube-ovn-v2/crds/kube-ovn-crd.yaml !/charts/kube-ovn/templates/kube-ovn-crd.yaml kube-ovn-crd.yaml +yamls/crds ovn.yaml ovn-ic-controller.yaml ovn-ic-server.yaml @@ -53,6 +54,7 @@ kube-ovn-cni-sa.yaml kube-ovn-sa.yaml ovn-ovs-sa.yaml ovs-ovn-ds.yaml +kube-ovn-pinger.yaml cakey.pem cacert.pem ovn-req.pem diff --git a/hack/update-codegen-crd.sh b/hack/update-codegen-crd.sh new file mode 100755 index 00000000000..243c7971976 --- /dev/null +++ b/hack/update-codegen-crd.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# usage: bash -x ./hack/update-codegen-crd.sh +set -eux +cd "$(dirname "$0")/.." + +# set GOPROXY you like +export GOPROXY=${GOPROXY:-"https://goproxy.cn"} +# use controller-gen to generate CRDs +# ensure controller-gen is installed +CONTROLLER_TOOLS_VERSION=${CONTROLLER_TOOLS_VERSION:-"v0.19.0"} +go install sigs.k8s.io/controller-tools/cmd/controller-gen@"${CONTROLLER_TOOLS_VERSION}" +go mod tidy + +# generate CRDs +controller-gen crd:allowDangerousTypes=true paths=./pkg/apis/kubeovn/v1 output:crd:artifacts:config=./yamls/crds diff --git a/pkg/apis/kubeovn/v1/ippool.go b/pkg/apis/kubeovn/v1/ippool.go index 538eff57031..d3f377193af 100644 --- a/pkg/apis/kubeovn/v1/ippool.go +++ b/pkg/apis/kubeovn/v1/ippool.go @@ -8,7 +8,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog/v2" - "github.com/kubeovn/kube-ovn/pkg/internal" + kotypes "github.com/kubeovn/kube-ovn/pkg/types" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -37,14 +37,18 @@ type IPPoolSpec struct { } type IPPoolStatus struct { - V4AvailableIPs internal.BigInt `json:"v4AvailableIPs"` - V4AvailableIPRange string `json:"v4AvailableIPRange"` - V4UsingIPs internal.BigInt `json:"v4UsingIPs"` - V4UsingIPRange string `json:"v4UsingIPRange"` - V6AvailableIPs internal.BigInt `json:"v6AvailableIPs"` - V6AvailableIPRange string `json:"v6AvailableIPRange"` - V6UsingIPs internal.BigInt `json:"v6UsingIPs"` - V6UsingIPRange string `json:"v6UsingIPRange"` + // +kubebuilder:validation:Type=string + V4AvailableIPs kotypes.BigInt `json:"v4AvailableIPs"` + V4AvailableIPRange string `json:"v4AvailableIPRange"` + // +kubebuilder:validation:Type=string + V4UsingIPs kotypes.BigInt `json:"v4UsingIPs"` + V4UsingIPRange string `json:"v4UsingIPRange"` + // +kubebuilder:validation:Type=string + V6AvailableIPs kotypes.BigInt `json:"v6AvailableIPs"` + V6AvailableIPRange string `json:"v6AvailableIPRange"` + // +kubebuilder:validation:Type=string + V6UsingIPs kotypes.BigInt `json:"v6UsingIPs"` + V6UsingIPRange string `json:"v6UsingIPRange"` // Conditions represents the latest state of the object // +optional diff --git a/pkg/apis/kubeovn/v1/provider-network.go b/pkg/apis/kubeovn/v1/provider-network.go index 593913892c2..9f96f3087c7 100644 --- a/pkg/apis/kubeovn/v1/provider-network.go +++ b/pkg/apis/kubeovn/v1/provider-network.go @@ -42,7 +42,8 @@ type ProviderNetworkSpec struct { type ProviderNetworkCondition struct { // Node name Node string `json:"node"` - Condition + + Condition `json:"condition"` } type ProviderNetworkStatus struct { diff --git a/pkg/apis/kubeovn/v1/subnet.go b/pkg/apis/kubeovn/v1/subnet.go index d0c5176072d..25a3a49232b 100644 --- a/pkg/apis/kubeovn/v1/subnet.go +++ b/pkg/apis/kubeovn/v1/subnet.go @@ -7,6 +7,8 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog/v2" + + kotypes "github.com/kubeovn/kube-ovn/pkg/types" ) const ( @@ -110,7 +112,8 @@ type NatOutGoingPolicyMatch struct { type NatOutgoingPolicyRuleStatus struct { RuleID string `json:"ruleID"` - NatOutgoingPolicyRule + + NatOutgoingPolicyRule `json:"natOutgoingPolicyRule"` } type SubnetStatus struct { // Conditions represents the latest state of the object @@ -119,13 +122,17 @@ type SubnetStatus struct { // +patchStrategy=merge Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` - V4AvailableIPs float64 `json:"v4availableIPs"` - V4AvailableIPRange string `json:"v4availableIPrange"` - V4UsingIPs float64 `json:"v4usingIPs"` - V4UsingIPRange string `json:"v4usingIPrange"` - V6AvailableIPs float64 `json:"v6availableIPs"` - V6AvailableIPRange string `json:"v6availableIPrange"` - V6UsingIPs float64 `json:"v6usingIPs"` + // +kubebuilder:validation:Type=string + V4AvailableIPs kotypes.BigInt `json:"v4availableIPs"` + V4AvailableIPRange string `json:"v4availableIPrange"` + // +kubebuilder:validation:Type=string + V4UsingIPs kotypes.BigInt `json:"v4usingIPs"` + V4UsingIPRange string `json:"v4usingIPrange"` + // +kubebuilder:validation:Type=string + V6AvailableIPs kotypes.BigInt `json:"v6availableIPs"` + V6AvailableIPRange string `json:"v6availableIPrange"` + // +kubebuilder:validation:Type=string + V6UsingIPs kotypes.BigInt `json:"v6usingIPs"` V6UsingIPRange string `json:"v6usingIPrange"` ActivateGateway string `json:"activateGateway"` DHCPv4OptionsUUID string `json:"dhcpV4OptionsUUID"` diff --git a/pkg/apis/kubeovn/v1/subnet_bigint_test.go b/pkg/apis/kubeovn/v1/subnet_bigint_test.go new file mode 100644 index 00000000000..6ce3766e3e1 --- /dev/null +++ b/pkg/apis/kubeovn/v1/subnet_bigint_test.go @@ -0,0 +1,132 @@ +package v1 + +import ( + "encoding/json" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + kotypes "github.com/kubeovn/kube-ovn/pkg/types" +) + +// TestSubnetStatusBigIntJSONSerialization 验证 SubnetStatus 的 BigInt 字段正确序列化为 JSON 字符串 +func TestSubnetStatusBigIntJSONSerialization(t *testing.T) { + subnet := &Subnet{ + TypeMeta: metav1.TypeMeta{ + Kind: "Subnet", + APIVersion: "kubeovn.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-subnet", + }, + Spec: SubnetSpec{ + CIDRBlock: "10.16.0.0/16", + }, + Status: SubnetStatus{ + V4AvailableIPs: kotypes.NewBigInt(65533), + V4UsingIPs: kotypes.NewBigInt(3), + V6AvailableIPs: kotypes.NewBigInt(0), + V6UsingIPs: kotypes.NewBigInt(0), + }, + } + + // Marshal to JSON + data, err := json.Marshal(subnet) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + jsonStr := string(data) + t.Logf("Serialized Subnet JSON (truncated): %s", jsonStr[:min(200, len(jsonStr))]) + + // Verify the status field contains quoted strings + var decoded map[string]any + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("Unmarshal to map failed: %v", err) + } + + status, ok := decoded["status"].(map[string]any) + if !ok { + t.Fatal("status field not found or not an object") + } + + // Check each BigInt field is a string + checkStringField := func(name, expected string) { + value, ok := status[name] + if !ok { + t.Errorf("Field %s not found in status", name) + return + } + strValue, ok := value.(string) + if !ok { + t.Errorf("Field %s should be string, got %T: %v", name, value, value) + return + } + if strValue != expected { + t.Errorf("Field %s = %q, want %q", name, strValue, expected) + } + } + + checkStringField("v4availableIPs", "65533") + checkStringField("v4usingIPs", "3") + checkStringField("v6availableIPs", "0") + checkStringField("v6usingIPs", "0") + + // Unmarshal back to Subnet + var decodedSubnet Subnet + if err := json.Unmarshal(data, &decodedSubnet); err != nil { + t.Fatalf("Unmarshal to Subnet failed: %v", err) + } + + // Verify values match + if !decodedSubnet.Status.V4AvailableIPs.Equal(subnet.Status.V4AvailableIPs) { + t.Errorf("V4AvailableIPs mismatch after round-trip: got %v, want %v", + decodedSubnet.Status.V4AvailableIPs.String(), subnet.Status.V4AvailableIPs.String()) + } +} + +// TestSubnetStatusPatchJSON 模拟 Kubernetes patch 操作的 JSON 序列化 +func TestSubnetStatusPatchJSON(t *testing.T) { + status := SubnetStatus{ + V4AvailableIPs: kotypes.NewBigInt(253), + V4UsingIPs: kotypes.NewBigInt(1), + V6AvailableIPs: kotypes.NewBigInt(0), + V6UsingIPs: kotypes.NewBigInt(0), + } + + data, err := json.Marshal(status) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + t.Logf("SubnetStatus JSON: %s", string(data)) + + // Parse as generic map to verify field types + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Unmarshal to map failed: %v", err) + } + + // All BigInt fields must be strings, not numbers + for fieldName, expectedValue := range map[string]string{ + "v4availableIPs": "253", + "v4usingIPs": "1", + "v6availableIPs": "0", + "v6usingIPs": "0", + } { + value, ok := m[fieldName] + if !ok { + t.Errorf("Field %s not found", fieldName) + continue + } + strValue, ok := value.(string) + if !ok { + t.Errorf("Field %s should be string (for K8s CRD validation), got %T: %v", + fieldName, value, value) + continue + } + if strValue != expectedValue { + t.Errorf("Field %s = %q, want %q", fieldName, strValue, expectedValue) + } + } +} diff --git a/pkg/controller/exporter.go b/pkg/controller/exporter.go index 3cfab0ac749..b4dc0d8b969 100644 --- a/pkg/controller/exporter.go +++ b/pkg/controller/exporter.go @@ -58,11 +58,11 @@ func (c *Controller) exportSubnetAvailableIPsGauge(subnet *kubeovnv1.Subnet) { var availableIPs float64 switch subnet.Spec.Protocol { case kubeovnv1.ProtocolIPv4: - availableIPs = subnet.Status.V4AvailableIPs + availableIPs = subnet.Status.V4AvailableIPs.Float64() case kubeovnv1.ProtocolIPv6: - availableIPs = subnet.Status.V6AvailableIPs + availableIPs = subnet.Status.V6AvailableIPs.Float64() default: - availableIPs = math.Min(subnet.Status.V4AvailableIPs, subnet.Status.V6AvailableIPs) + availableIPs = math.Min(subnet.Status.V4AvailableIPs.Float64(), subnet.Status.V6AvailableIPs.Float64()) } metricSubnetAvailableIPs.WithLabelValues(subnet.Name, subnet.Spec.Protocol, subnet.Spec.CIDRBlock).Set(availableIPs) } @@ -70,9 +70,9 @@ func (c *Controller) exportSubnetAvailableIPsGauge(subnet *kubeovnv1.Subnet) { func (c *Controller) exportSubnetUsedIPsGauge(subnet *kubeovnv1.Subnet) { var usingIPs float64 if subnet.Spec.Protocol == kubeovnv1.ProtocolIPv6 { - usingIPs = subnet.Status.V6UsingIPs + usingIPs = subnet.Status.V6UsingIPs.Float64() } else { - usingIPs = subnet.Status.V4UsingIPs + usingIPs = subnet.Status.V4UsingIPs.Float64() } metricSubnetUsedIPs.WithLabelValues(subnet.Name, subnet.Spec.Protocol, subnet.Spec.CIDRBlock).Set(usingIPs) } diff --git a/pkg/controller/pod.go b/pkg/controller/pod.go index 1be1b4b1d98..9389910af99 100644 --- a/pkg/controller/pod.go +++ b/pkg/controller/pod.go @@ -1681,18 +1681,18 @@ func (c *Controller) getPodDefaultSubnet(pod *v1.Pod) (*kubeovnv1.Subnet, error) switch subnet.Spec.Protocol { case kubeovnv1.ProtocolDual: - if subnet.Status.V6AvailableIPs == 0 && !c.podCanUseExcludeIPs(pod, subnet) { + if subnet.Status.V6AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) { klog.Infof("there's no available ipv6 address in subnet %s, try next one", subnet.Name) continue } fallthrough case kubeovnv1.ProtocolIPv4: - if subnet.Status.V4AvailableIPs == 0 && !c.podCanUseExcludeIPs(pod, subnet) { + if subnet.Status.V4AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) { klog.Infof("there's no available ipv4 address in subnet %s, try next one", subnet.Name) continue } case kubeovnv1.ProtocolIPv6: - if subnet.Status.V6AvailableIPs == 0 && !c.podCanUseExcludeIPs(pod, subnet) { + if subnet.Status.V6AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) { klog.Infof("there's no available ipv6 address in subnet %s, try next one", subnet.Name) continue } diff --git a/pkg/controller/subnet.go b/pkg/controller/subnet.go index d940d67f243..6c2d1652d73 100644 --- a/pkg/controller/subnet.go +++ b/pkg/controller/subnet.go @@ -29,6 +29,7 @@ import ( "github.com/kubeovn/kube-ovn/pkg/ipam" "github.com/kubeovn/kube-ovn/pkg/ovs" "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + kotypes "github.com/kubeovn/kube-ovn/pkg/types" "github.com/kubeovn/kube-ovn/pkg/util" ) @@ -60,7 +61,7 @@ func (c *Controller) enqueueDeleteSubnet(obj any) { } func (c *Controller) enqueueUpdateSubnet(oldObj, newObj any) { - var usingIPs float64 + var usingIPs kotypes.BigInt var u2oInterconnIP string oldSubnet := oldObj.(*kubeovnv1.Subnet) newSubnet := newObj.(*kubeovnv1.Subnet) @@ -87,7 +88,7 @@ func (c *Controller) enqueueUpdateSubnet(oldObj, newObj any) { } u2oInterconnIP = newSubnet.Status.U2OInterconnectionIP - if !newSubnet.DeletionTimestamp.IsZero() && (usingIPs == 0 || (usingIPs == 1 && u2oInterconnIP != "")) { + if !newSubnet.DeletionTimestamp.IsZero() && (usingIPs.EqualInt64(0) || (usingIPs.EqualInt64(1) && u2oInterconnIP != "")) { c.addOrUpdateSubnetQueue.Add(key) return } @@ -420,7 +421,7 @@ func (c *Controller) handleSubnetFinalizer(subnet *kubeovnv1.Subnet) (*kubeovnv1 } u2oInterconnIP := subnet.Status.U2OInterconnectionIP - if !subnet.DeletionTimestamp.IsZero() && (usingIPs == 0 || (usingIPs == 1 && u2oInterconnIP != "")) { + if !subnet.DeletionTimestamp.IsZero() && (usingIPs.EqualInt64(0) || (usingIPs.EqualInt64(1) && u2oInterconnIP != "")) { newSubnet := subnet.DeepCopy() controllerutil.RemoveFinalizer(newSubnet, util.KubeOVNControllerFinalizer) patch, err := util.GenerateMergePatchPayload(subnet, newSubnet) @@ -2150,10 +2151,10 @@ func (c *Controller) calcDualSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv v6toSubIPs := util.ExpandExcludeIPs(v6ExcludeIPs, cidrBlocks[1]) _, v4CIDR, _ := net.ParseCIDR(cidrBlocks[0]) _, v6CIDR, _ := net.ParseCIDR(cidrBlocks[1]) - v4availableIPs := util.AddressCount(v4CIDR) - util.CountIPNums(v4toSubIPs) - v6availableIPs := util.AddressCount(v6CIDR) - util.CountIPNums(v6toSubIPs) + v4availableIPs := util.AddressCount(v4CIDR).Sub(util.CountIPNums(v4toSubIPs)) + v6availableIPs := util.AddressCount(v6CIDR).Sub(util.CountIPNums(v6toSubIPs)) - usingIPs := float64(usingIPNums) + usingIPs := kotypes.NewBigInt(int64(usingIPNums)) vips, err := c.virtualIpsLister.List(labels.SelectorFromSet(labels.Set{ util.SubnetNameLabel: subnet.Name, @@ -2164,7 +2165,7 @@ func (c *Controller) calcDualSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv return nil, err } lenVip = len(vips) - usingIPs += float64(lenVip) + usingIPs = usingIPs.Add(kotypes.NewBigInt(int64(lenVip))) if !isOvnSubnet(subnet) { eips, err := c.iptablesEipsLister.List( @@ -2174,7 +2175,7 @@ func (c *Controller) calcDualSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv return nil, err } lenIptablesEip = len(eips) - usingIPs += float64(lenIptablesEip) + usingIPs = usingIPs.Add(kotypes.NewBigInt(int64(lenIptablesEip))) } if subnet.Spec.Vlan != "" { ovnEips, err := c.ovnEipsLister.List(labels.SelectorFromSet(labels.Set{ @@ -2185,24 +2186,24 @@ func (c *Controller) calcDualSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv return nil, err } lenOvnEip = len(ovnEips) - usingIPs += float64(lenOvnEip) + usingIPs = usingIPs.Add(kotypes.NewBigInt(int64(lenOvnEip))) } - v4availableIPs -= usingIPs - if v4availableIPs < 0 { - v4availableIPs = 0 + v4availableIPs = v4availableIPs.Sub(usingIPs) + if v4availableIPs.Cmp(kotypes.NewBigInt(0)) < 0 { + v4availableIPs = kotypes.NewBigInt(0) } - v6availableIPs -= usingIPs - if v6availableIPs < 0 { - v6availableIPs = 0 + v6availableIPs = v6availableIPs.Sub(usingIPs) + if v6availableIPs.Cmp(kotypes.NewBigInt(0)) < 0 { + v6availableIPs = kotypes.NewBigInt(0) } v4UsingIPStr, v6UsingIPStr, v4AvailableIPStr, v6AvailableIPStr := c.ipam.GetSubnetIPRangeString(subnet.Name, subnet.Spec.ExcludeIps) - if subnet.Status.V4AvailableIPs == v4availableIPs && - subnet.Status.V6AvailableIPs == v6availableIPs && - subnet.Status.V4UsingIPs == usingIPs && - subnet.Status.V6UsingIPs == usingIPs && + if subnet.Status.V4AvailableIPs.Equal(v4availableIPs) && + subnet.Status.V6AvailableIPs.Equal(v6availableIPs) && + subnet.Status.V4UsingIPs.Equal(usingIPs) && + subnet.Status.V6UsingIPs.Equal(usingIPs) && subnet.Status.V4UsingIPRange == v4UsingIPStr && subnet.Status.V6UsingIPRange == v6UsingIPStr && subnet.Status.V4AvailableIPRange == v4AvailableIPStr && @@ -2266,8 +2267,8 @@ func (c *Controller) calcSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv1.Su // gateway always in excludeIPs toSubIPs := util.ExpandExcludeIPs(subnet.Spec.ExcludeIps, subnet.Spec.CIDRBlock) - availableIPs := util.AddressCount(cidr) - util.CountIPNums(toSubIPs) - usingIPs := float64(usingIPNums) + availableIPs := util.AddressCount(cidr).Sub(util.CountIPNums(toSubIPs)) + usingIPs := kotypes.NewBigInt(int64(usingIPNums)) vips, err := c.virtualIpsLister.List(labels.SelectorFromSet(labels.Set{ util.SubnetNameLabel: subnet.Name, util.IPReservedLabel: "", @@ -2277,7 +2278,7 @@ func (c *Controller) calcSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv1.Su return nil, err } lenVip = len(vips) - usingIPs += float64(lenVip) + usingIPs = usingIPs.Add(kotypes.NewBigInt(int64(lenVip))) if !isOvnSubnet(subnet) { eips, err := c.iptablesEipsLister.List( labels.SelectorFromSet(labels.Set{util.SubnetNameLabel: subnet.Name})) @@ -2286,7 +2287,7 @@ func (c *Controller) calcSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv1.Su return nil, err } lenIptablesEip = len(eips) - usingIPs += float64(lenIptablesEip) + usingIPs = usingIPs.Add(kotypes.NewBigInt(int64(lenIptablesEip))) } if subnet.Spec.Vlan != "" { ovnEips, err := c.ovnEipsLister.List(labels.SelectorFromSet(labels.Set{ @@ -2297,16 +2298,16 @@ func (c *Controller) calcSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv1.Su return nil, err } lenOvnEip = len(ovnEips) - usingIPs += float64(lenOvnEip) + usingIPs = usingIPs.Add(kotypes.NewBigInt(int64(lenOvnEip))) } - availableIPs -= usingIPs - if availableIPs < 0 { - availableIPs = 0 + availableIPs = availableIPs.Sub(usingIPs) + if availableIPs.Cmp(kotypes.NewBigInt(0)) < 0 { + availableIPs = kotypes.NewBigInt(0) } v4UsingIPStr, v6UsingIPStr, v4AvailableIPStr, v6AvailableIPStr := c.ipam.GetSubnetIPRangeString(subnet.Name, subnet.Spec.ExcludeIps) - cachedFloatFields := [4]float64{ + cachedBigIntFields := [4]kotypes.BigInt{ subnet.Status.V4AvailableIPs, subnet.Status.V4UsingIPs, subnet.Status.V6AvailableIPs, @@ -2324,27 +2325,28 @@ func (c *Controller) calcSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv1.Su subnet.Status.V4UsingIPs = usingIPs subnet.Status.V4UsingIPRange = v4UsingIPStr subnet.Status.V4AvailableIPRange = v4AvailableIPStr - subnet.Status.V6AvailableIPs = 0 - subnet.Status.V6UsingIPs = 0 + subnet.Status.V6AvailableIPs = kotypes.NewBigInt(0) + subnet.Status.V6UsingIPs = kotypes.NewBigInt(0) } else { subnet.Status.V6AvailableIPs = availableIPs subnet.Status.V6UsingIPs = usingIPs subnet.Status.V6UsingIPRange = v6UsingIPStr subnet.Status.V6AvailableIPRange = v6AvailableIPStr - subnet.Status.V4AvailableIPs = 0 - subnet.Status.V4UsingIPs = 0 - } - if cachedFloatFields == [4]float64{ - subnet.Status.V4AvailableIPs, - subnet.Status.V4UsingIPs, - subnet.Status.V6AvailableIPs, - subnet.Status.V6UsingIPs, - } && cachedStringFields == [4]string{ - subnet.Status.V4UsingIPRange, - subnet.Status.V4AvailableIPRange, - subnet.Status.V6UsingIPRange, - subnet.Status.V6AvailableIPRange, - } { + subnet.Status.V4AvailableIPs = kotypes.NewBigInt(0) + subnet.Status.V4UsingIPs = kotypes.NewBigInt(0) + } + // Check each field individually since BigInt doesn't support array comparison + // If status hasn't changed, no need to update + if cachedBigIntFields[0].Equal(subnet.Status.V4AvailableIPs) && + cachedBigIntFields[1].Equal(subnet.Status.V4UsingIPs) && + cachedBigIntFields[2].Equal(subnet.Status.V6AvailableIPs) && + cachedBigIntFields[3].Equal(subnet.Status.V6UsingIPs) && + cachedStringFields == [4]string{ + subnet.Status.V4UsingIPRange, + subnet.Status.V4AvailableIPRange, + subnet.Status.V6UsingIPRange, + subnet.Status.V6AvailableIPRange, + } { return subnet, nil } @@ -2358,13 +2360,13 @@ func (c *Controller) calcSubnetStatusIP(subnet *kubeovnv1.Subnet) (*kubeovnv1.Su } func (c *Controller) checkSubnetUsingIPs(subnet *kubeovnv1.Subnet) error { - if subnet.Status.V4UsingIPs != 0 && subnet.Status.V4UsingIPRange == "" { - err := fmt.Errorf("subnet %s has %.0f v4 ip in use, while the v4 using ip range is empty", subnet.Name, subnet.Status.V4UsingIPs) + if !subnet.Status.V4UsingIPs.EqualInt64(0) && subnet.Status.V4UsingIPRange == "" { + err := fmt.Errorf("subnet %s has %s v4 ip in use, while the v4 using ip range is empty", subnet.Name, subnet.Status.V4UsingIPs.String()) klog.Error(err) return err } - if subnet.Status.V6UsingIPs != 0 && subnet.Status.V6UsingIPRange == "" { - err := fmt.Errorf("subnet %s has %.0f v6 ip in use, while the v6 using ip range is empty", subnet.Name, subnet.Status.V6UsingIPs) + if !subnet.Status.V6UsingIPs.EqualInt64(0) && subnet.Status.V6UsingIPRange == "" { + err := fmt.Errorf("subnet %s has %s v6 ip in use, while the v6 using ip range is empty", subnet.Name, subnet.Status.V6UsingIPs.String()) klog.Error(err) return err } diff --git a/pkg/internal/big_int.go b/pkg/internal/big_int.go index 4a43203c207..e1404bf2fa0 100644 --- a/pkg/internal/big_int.go +++ b/pkg/internal/big_int.go @@ -5,8 +5,9 @@ import ( "math/big" ) +// +kubebuilder:validation:Type=string type BigInt struct { - big.Int + big.Int `json:"-"` } func (b BigInt) DeepCopyInto(n *BigInt) { diff --git a/pkg/ipam/ip_range.go b/pkg/ipam/ip_range.go index bc31ae5ac14..a63a9d467eb 100644 --- a/pkg/ipam/ip_range.go +++ b/pkg/ipam/ip_range.go @@ -6,7 +6,7 @@ import ( "math/big" "net" - "github.com/kubeovn/kube-ovn/pkg/internal" + "github.com/kubeovn/kube-ovn/pkg/types" ) // IPRange represents an IP range of [start, end] @@ -48,9 +48,9 @@ func (r *IPRange) SetEnd(ip IP) { r.end = ip } -func (r *IPRange) Count() internal.BigInt { +func (r *IPRange) Count() types.BigInt { n := big.NewInt(0).Sub(big.NewInt(0).SetBytes([]byte(r.end)), big.NewInt(0).SetBytes([]byte(r.start))) - return internal.BigInt{Int: *n.Add(n, big.NewInt(1))} + return types.BigInt{Int: *n.Add(n, big.NewInt(1))} } func (r *IPRange) Random() IP { diff --git a/pkg/ipam/ip_range_list.go b/pkg/ipam/ip_range_list.go index 56922a1092e..78eed984914 100644 --- a/pkg/ipam/ip_range_list.go +++ b/pkg/ipam/ip_range_list.go @@ -9,7 +9,7 @@ import ( "k8s.io/klog/v2" - "github.com/kubeovn/kube-ovn/pkg/internal" + "github.com/kubeovn/kube-ovn/pkg/types" ) type IPRangeList struct { @@ -87,8 +87,8 @@ func (r *IPRangeList) Len() int { return len(r.ranges) } -func (r *IPRangeList) Count() internal.BigInt { - var count internal.BigInt +func (r *IPRangeList) Count() types.BigInt { + var count types.BigInt for _, v := range r.ranges { count = count.Add(v.Count()) } diff --git a/pkg/ipam/ip_range_test.go b/pkg/ipam/ip_range_test.go index 9bb2da550e3..e368f563b20 100644 --- a/pkg/ipam/ip_range_test.go +++ b/pkg/ipam/ip_range_test.go @@ -6,7 +6,7 @@ import ( "reflect" "testing" - "github.com/kubeovn/kube-ovn/pkg/internal" + "github.com/kubeovn/kube-ovn/pkg/types" ) func TestNewIPRange(t *testing.T) { @@ -65,19 +65,19 @@ func TestIPRangeCount(t *testing.T) { name string start IP end IP - want internal.BigInt + want types.BigInt }{ { name: "IPv4 range", start: IP(net.ParseIP("192.168.1.1")), end: IP(net.ParseIP("192.168.1.10")), - want: internal.BigInt{Int: *big.NewInt(10)}, + want: types.BigInt{Int: *big.NewInt(10)}, }, { name: "IPv6 range", start: IP(net.ParseIP("2001:db8::1")), end: IP(net.ParseIP("2001:db8::10")), - want: internal.BigInt{Int: *big.NewInt(16)}, + want: types.BigInt{Int: *big.NewInt(16)}, }, } diff --git a/pkg/ipam/ipam.go b/pkg/ipam/ipam.go index 789a3eee6d3..3fa7b60bc6d 100644 --- a/pkg/ipam/ipam.go +++ b/pkg/ipam/ipam.go @@ -11,7 +11,7 @@ import ( "k8s.io/klog/v2" kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" - "github.com/kubeovn/kube-ovn/pkg/internal" + "github.com/kubeovn/kube-ovn/pkg/types" "github.com/kubeovn/kube-ovn/pkg/util" ) @@ -459,7 +459,7 @@ func (ipam *IPAM) RemoveIPPool(subnet, ippool string) { } func (ipam *IPAM) IPPoolStatistics(subnet, ippool string) ( - v4Available, v4Using, v6Available, v6Using internal.BigInt, + v4Available, v4Using, v6Available, v6Using types.BigInt, v4AvailableRange, v4UsingRange, v6AvailableRange, v6UsingRange string, ) { ipam.mutex.RLock() diff --git a/pkg/ipam/subnet.go b/pkg/ipam/subnet.go index 2f7d695e431..c3478afcf4b 100644 --- a/pkg/ipam/subnet.go +++ b/pkg/ipam/subnet.go @@ -10,7 +10,7 @@ import ( "k8s.io/klog/v2" kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" - "github.com/kubeovn/kube-ovn/pkg/internal" + "github.com/kubeovn/kube-ovn/pkg/types" "github.com/kubeovn/kube-ovn/pkg/util" ) @@ -799,7 +799,7 @@ func (s *Subnet) RemoveIPPool(name string) { } func (s *Subnet) IPPoolStatistics(ippool string) ( - v4Available, v4Using, v6Available, v6Using internal.BigInt, + v4Available, v4Using, v6Available, v6Using types.BigInt, v4AvailableRange, v4UsingRange, v6AvailableRange, v6UsingRange string, ) { s.Mutex.Lock() diff --git a/pkg/types/big_int.go b/pkg/types/big_int.go new file mode 100644 index 00000000000..56effd3466f --- /dev/null +++ b/pkg/types/big_int.go @@ -0,0 +1,70 @@ +package types + +import ( + "fmt" + "math/big" +) + +// +kubebuilder:validation:Type=string +type BigInt struct { + big.Int `json:"-"` +} + +func (b BigInt) DeepCopyInto(n *BigInt) { + n.Set(&b.Int) +} + +func (b BigInt) Equal(n BigInt) bool { + return b.Cmp(n) == 0 +} + +func (b BigInt) EqualInt64(n int64) bool { + return b.Int.Cmp(big.NewInt(n)) == 0 +} + +func (b BigInt) Cmp(n BigInt) int { + return b.Int.Cmp(&n.Int) +} + +func (b BigInt) Add(n BigInt) BigInt { + return BigInt{*big.NewInt(0).Add(&b.Int, &n.Int)} +} + +func (b BigInt) Sub(n BigInt) BigInt { + return BigInt{*big.NewInt(0).Sub(&b.Int, &n.Int)} +} + +func (b BigInt) String() string { + return b.Int.String() +} + +func (b BigInt) MarshalJSON() ([]byte, error) { + return b.Int.MarshalJSON() +} + +func (b *BigInt) UnmarshalJSON(p []byte) error { + if string(p) == "null" { + return nil + } + // Remove quotes if present + s := string(p) + if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' { + s = s[1 : len(s)-1] + } + var z big.Int + _, ok := z.SetString(s, 10) + if !ok { + return fmt.Errorf("invalid big integer: %q", p) + } + b.Int = z + return nil +} + +func (b BigInt) Float64() float64 { + f, _ := new(big.Float).SetInt(&b.Int).Float64() + return f +} + +func NewBigInt(n int64) BigInt { + return BigInt{*big.NewInt(n)} +} diff --git a/pkg/types/big_int_test.go b/pkg/types/big_int_test.go new file mode 100644 index 00000000000..7004361f674 --- /dev/null +++ b/pkg/types/big_int_test.go @@ -0,0 +1,306 @@ +package types + +import ( + "encoding/json" + "math/big" + "testing" +) + +func TestBigIntMarshalJSON(t *testing.T) { + tests := []struct { + name string + value BigInt + expected string + }{ + { + name: "zero", + value: NewBigInt(0), + expected: `"0"`, + }, + { + name: "positive small", + value: NewBigInt(253), + expected: `"253"`, + }, + { + name: "positive large", + value: NewBigInt(1<<62 - 1), + expected: `"4611686018427387903"`, + }, + { + name: "negative", + value: NewBigInt(-100), + expected: `"-100"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(tt.value) + if err != nil { + t.Fatalf("MarshalJSON failed: %v", err) + } + if string(data) != tt.expected { + t.Errorf("MarshalJSON() = %q, want %q", string(data), tt.expected) + } + }) + } +} + +func TestBigIntUnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + expected BigInt + wantErr bool + }{ + { + name: "quoted zero", + input: `"0"`, + expected: NewBigInt(0), + }, + { + name: "quoted positive", + input: `"253"`, + expected: NewBigInt(253), + }, + { + name: "quoted large", + input: `"4611686018427387903"`, + expected: NewBigInt(1<<62 - 1), + }, + { + name: "quoted negative", + input: `"-100"`, + expected: NewBigInt(-100), + }, + { + name: "unquoted number (backward compat)", + input: `253`, + expected: NewBigInt(253), + }, + { + name: "invalid string", + input: `"abc"`, + wantErr: true, + }, + { + name: "null", + input: `null`, + expected: NewBigInt(0), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b BigInt + err := json.Unmarshal([]byte(tt.input), &b) + if tt.wantErr { + if err == nil { + t.Fatalf("UnmarshalJSON() should have failed") + } + return + } + if err != nil { + t.Fatalf("UnmarshalJSON() failed: %v", err) + } + if !b.Equal(tt.expected) { + t.Errorf("UnmarshalJSON() = %v, want %v", b.String(), tt.expected.String()) + } + }) + } +} + +func TestBigIntRoundTrip(t *testing.T) { + original := NewBigInt(123456789) + + // Marshal + data, err := json.Marshal(original) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + + // Unmarshal + var decoded BigInt + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + + // Verify + if !decoded.Equal(original) { + t.Errorf("Round trip failed: got %v, want %v", decoded.String(), original.String()) + } +} + +func TestBigIntInStruct(t *testing.T) { + type TestStruct struct { + Count BigInt `json:"count"` + } + + // Test marshal + s := TestStruct{Count: NewBigInt(9999)} + data, err := json.Marshal(s) + if err != nil { + t.Fatalf("Marshal failed: %v", err) + } + expected := `{"count":"9999"}` + if string(data) != expected { + t.Errorf("Marshal struct = %q, want %q", string(data), expected) + } + + // Test unmarshal + var decoded TestStruct + if err := json.Unmarshal([]byte(expected), &decoded); err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + if !decoded.Count.Equal(s.Count) { + t.Errorf("Unmarshal struct failed: got %v, want %v", decoded.Count.String(), s.Count.String()) + } +} + +func TestBigIntArithmetic(t *testing.T) { + a := NewBigInt(100) + b := NewBigInt(50) + + // Test Add + sum := a.Add(b) + if !sum.EqualInt64(150) { + t.Errorf("Add: got %v, want 150", sum.String()) + } + + // Test Sub + diff := a.Sub(b) + if !diff.EqualInt64(50) { + t.Errorf("Sub: got %v, want 50", diff.String()) + } + + // Test original values unchanged + if !a.EqualInt64(100) { + t.Errorf("Add/Sub modified original: a = %v, want 100", a.String()) + } + if !b.EqualInt64(50) { + t.Errorf("Add/Sub modified original: b = %v, want 50", b.String()) + } +} + +func TestBigIntComparison(t *testing.T) { + tests := []struct { + name string + a BigInt + b BigInt + wantCmp int + wantEq bool + }{ + { + name: "equal", + a: NewBigInt(100), + b: NewBigInt(100), + wantCmp: 0, + wantEq: true, + }, + { + name: "less than", + a: NewBigInt(50), + b: NewBigInt(100), + wantCmp: -1, + wantEq: false, + }, + { + name: "greater than", + a: NewBigInt(200), + b: NewBigInt(100), + wantCmp: 1, + wantEq: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.a.Cmp(tt.b); got != tt.wantCmp { + t.Errorf("Cmp() = %v, want %v", got, tt.wantCmp) + } + if got := tt.a.Equal(tt.b); got != tt.wantEq { + t.Errorf("Equal() = %v, want %v", got, tt.wantEq) + } + }) + } +} + +func TestBigIntEqualInt64(t *testing.T) { + b := NewBigInt(253) + if !b.EqualInt64(253) { + t.Errorf("EqualInt64(253) = false, want true") + } + if b.EqualInt64(100) { + t.Errorf("EqualInt64(100) = true, want false") + } +} + +func TestBigIntDeepCopy(t *testing.T) { + original := NewBigInt(12345) + var copied BigInt + original.DeepCopyInto(&copied) + + if !copied.Equal(original) { + t.Errorf("DeepCopyInto failed: got %v, want %v", copied.String(), original.String()) + } + + // Modify copy, original should be unchanged + copied = copied.Add(NewBigInt(1)) + if !original.EqualInt64(12345) { + t.Errorf("DeepCopyInto created shallow copy: original = %v", original.String()) + } + if !copied.EqualInt64(12346) { + t.Errorf("Modified copy = %v, want 12346", copied.String()) + } +} + +func TestBigIntFloat64(t *testing.T) { + tests := []struct { + name string + value BigInt + expected float64 + }{ + { + name: "zero", + value: NewBigInt(0), + expected: 0.0, + }, + { + name: "positive", + value: NewBigInt(253), + expected: 253.0, + }, + { + name: "negative", + value: NewBigInt(-100), + expected: -100.0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.value.Float64(); got != tt.expected { + t.Errorf("Float64() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestBigIntString(t *testing.T) { + tests := []struct { + value BigInt + expected string + }{ + {NewBigInt(0), "0"}, + {NewBigInt(253), "253"}, + {NewBigInt(-100), "-100"}, + {BigInt{*big.NewInt(1 << 62)}, "4611686018427387904"}, + } + + for _, tt := range tests { + if got := tt.value.String(); got != tt.expected { + t.Errorf("String() = %q, want %q", got, tt.expected) + } + } +} diff --git a/pkg/util/net.go b/pkg/util/net.go index b27735b0e3c..03fd20e24ca 100644 --- a/pkg/util/net.go +++ b/pkg/util/net.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "errors" "fmt" - "math" "math/big" "net" "os" @@ -17,6 +16,7 @@ import ( "k8s.io/klog/v2" kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + kotypes "github.com/kubeovn/kube-ovn/pkg/types" ) // #nosec G101 @@ -212,16 +212,22 @@ func CheckProtocol(address string) string { return "" } -func AddressCount(network *net.IPNet) float64 { +func AddressCount(network *net.IPNet) kotypes.BigInt { prefixLen, bits := network.Mask.Size() + zeros := uint(bits - prefixLen) // #nosec G115 + // Special case handling for /31 and /32 subnets - switch bits - prefixLen { + switch zeros { case 1: - return 2 // /31 subnet + return kotypes.NewBigInt(2) // /31 subnet case 0: - return 1 // /32 subnet + return kotypes.NewBigInt(1) // /32 subnet } - return math.Pow(2, float64(bits-prefixLen)) - 2 + + // Calculate 2^zeros - 2 using big.Int + count := new(big.Int).Lsh(big.NewInt(1), zeros) + count.Sub(count, big.NewInt(2)) + return kotypes.BigInt{Int: *count} } func GenerateRandomIP(cidr string) string { @@ -463,18 +469,18 @@ func ContainsIPs(excludeIP, ip string) bool { return false } -func CountIPNums(excludeIPs []string) float64 { - var count float64 +func CountIPNums(excludeIPs []string) kotypes.BigInt { + count := kotypes.NewBigInt(0) for _, excludeIP := range excludeIPs { if strings.Contains(excludeIP, "..") { - var val big.Int parts := strings.Split(excludeIP, "..") s := IP2BigInt(parts[0]) e := IP2BigInt(parts[1]) - v, _ := new(big.Float).SetInt(val.Add(val.Sub(e, s), big.NewInt(1))).Float64() - count += v + diff := new(big.Int).Sub(e, s) + diff.Add(diff, big.NewInt(1)) + count = count.Add(kotypes.BigInt{Int: *diff}) } else { - count++ + count = count.Add(kotypes.NewBigInt(1)) } } return count diff --git a/pkg/util/net_test.go b/pkg/util/net_test.go index fa273b03865..3884780a44e 100644 --- a/pkg/util/net_test.go +++ b/pkg/util/net_test.go @@ -449,7 +449,7 @@ func TestAddressCount(t *testing.T) { tests := []struct { name string network *net.IPNet - want float64 + want int64 }{ { name: "base", @@ -470,7 +470,7 @@ func TestAddressCount(t *testing.T) { } for _, c := range tests { t.Run(c.name, func(t *testing.T) { - if ans := AddressCount(c.network); ans != c.want { + if ans := AddressCount(c.network); !ans.EqualInt64(c.want) { t.Errorf("%v expected %v, but %v got", c.network, c.want, ans) } @@ -1103,7 +1103,7 @@ func TestCountIpNums(t *testing.T) { tests := []struct { name string excl []string - want float64 + want int64 }{ { name: "base", @@ -1119,7 +1119,7 @@ func TestCountIpNums(t *testing.T) { for _, c := range tests { t.Run(c.name, func(t *testing.T) { ans := CountIPNums(c.excl) - if ans != c.want { + if !ans.EqualInt64(c.want) { t.Errorf("%v expected %v but %v got", c.excl, c.want, ans) } diff --git a/pkg/util/validator.go b/pkg/util/validator.go index b7e4a3d0be2..9291ca7c8b3 100644 --- a/pkg/util/validator.go +++ b/pkg/util/validator.go @@ -359,7 +359,7 @@ func ValidateNetworkBroadcast(cidr, ip string) error { continue } _, network, _ := net.ParseCIDR(cidrBlock) - if AddressCount(network) == 1 { + if AddressCount(network).EqualInt64(1) { return fmt.Errorf("subnet %s is configured with /32 or /128 netmask", cidrBlock) } diff --git a/pkg/webhook/subnet.go b/pkg/webhook/subnet.go index dd8367562f1..9c9300a5e8d 100644 --- a/pkg/webhook/subnet.go +++ b/pkg/webhook/subnet.go @@ -65,7 +65,7 @@ func (v *ValidatingHook) SubnetUpdateHook(ctx context.Context, req admission.Req if err := v.decoder.DecodeRaw(req.OldObject, &oldSubnet); err != nil { return ctrlwebhook.Errored(http.StatusBadRequest, err) } - if (o.Spec.Gateway != oldSubnet.Spec.Gateway) && (o.Status.V4UsingIPs != 0 || o.Status.V6UsingIPs != 0) { + if (o.Spec.Gateway != oldSubnet.Spec.Gateway) && (!o.Status.V4UsingIPs.EqualInt64(0) || !o.Status.V6UsingIPs.EqualInt64(0)) { return ctrlwebhook.Denied("can't update gateway of cidr when any IPs in Using") } @@ -89,7 +89,7 @@ func (v *ValidatingHook) SubnetDeleteHook(_ context.Context, req admission.Reque if err := v.decoder.DecodeRaw(req.OldObject, &subnet); err != nil { return ctrlwebhook.Errored(http.StatusBadRequest, err) } - if subnet.Status.V4UsingIPs != 0 || subnet.Status.V6UsingIPs != 0 { + if !subnet.Status.V4UsingIPs.EqualInt64(0) || !subnet.Status.V6UsingIPs.EqualInt64(0) { return ctrlwebhook.Denied("can't delete subnet when any IPs in Using") } return ctrlwebhook.Allowed("by pass") diff --git a/test/e2e/kube-ovn/subnet/subnet.go b/test/e2e/kube-ovn/subnet/subnet.go index 345ecb0125b..596583bc9fd 100644 --- a/test/e2e/kube-ovn/subnet/subnet.go +++ b/test/e2e/kube-ovn/subnet/subnet.go @@ -23,6 +23,7 @@ import ( "github.com/onsi/gomega" apiv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + kotypes "github.com/kubeovn/kube-ovn/pkg/types" "github.com/kubeovn/kube-ovn/pkg/util" "github.com/kubeovn/kube-ovn/test/e2e/framework" "github.com/kubeovn/kube-ovn/test/e2e/framework/docker" @@ -176,13 +177,13 @@ var _ = framework.Describe("[group:subnet]", func() { framework.ExpectZero(subnet.Status.V4AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV4) - framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } if cidrV6 == "" { framework.ExpectZero(subnet.Status.V6AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV6) - framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } // TODO: check routes on ovn0 @@ -234,13 +235,13 @@ var _ = framework.Describe("[group:subnet]", func() { framework.ExpectZero(subnet.Status.V4AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV4) - framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } if cidrV6 == "" { framework.ExpectZero(subnet.Status.V6AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV6) - framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } // TODO: check routes on ovn0 @@ -279,14 +280,14 @@ var _ = framework.Describe("[group:subnet]", func() { framework.ExpectZero(subnet.Status.V4AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV4) - expected := util.AddressCount(ipnet) - util.CountIPNums(excludeIPv4) - 1 + expected := util.AddressCount(ipnet).Sub(util.CountIPNums(excludeIPv4)).Sub(kotypes.NewBigInt(1)) framework.ExpectEqual(subnet.Status.V4AvailableIPs, expected) } if cidrV6 == "" { framework.ExpectZero(subnet.Status.V6AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV6) - expected := util.AddressCount(ipnet) - util.CountIPNums(excludeIPv6) - 1 + expected := util.AddressCount(ipnet).Sub(util.CountIPNums(excludeIPv6)).Sub(kotypes.NewBigInt(1)) framework.ExpectEqual(subnet.Status.V6AvailableIPs, expected) } }) @@ -318,7 +319,7 @@ var _ = framework.Describe("[group:subnet]", func() { smallSubnet = subnetClient.CreateSync(smallSubnet) ginkgo.By("Verifying available IPs is 0 after excluding the only usable IPs") - framework.ExpectZero(smallSubnet.Status.V4AvailableIPs + smallSubnet.Status.V6AvailableIPs) + framework.ExpectZero(smallSubnet.Status.V4AvailableIPs.Add(smallSubnet.Status.V6AvailableIPs)) // Test cases: both fixed IP and IP pool annotations testCases := []struct { @@ -403,13 +404,13 @@ var _ = framework.Describe("[group:subnet]", func() { framework.ExpectZero(subnet.Status.V4AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV4) - framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } if cidrV6 == "" { framework.ExpectZero(subnet.Status.V6AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV6) - framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } ginkgo.By("Creating pod " + podName) @@ -454,13 +455,13 @@ var _ = framework.Describe("[group:subnet]", func() { framework.ExpectZero(subnet.Status.V4AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV4) - framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } if cidrV6 == "" { framework.ExpectZero(subnet.Status.V6AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV6) - framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } ginkgo.By("Converting gateway mode to centralized") @@ -510,13 +511,13 @@ var _ = framework.Describe("[group:subnet]", func() { framework.ExpectZero(subnet.Status.V4AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV4) - framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V4AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } if cidrV6 == "" { framework.ExpectZero(subnet.Status.V6AvailableIPs) } else { _, ipnet, _ := net.ParseCIDR(cidrV6) - framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet)-1) + framework.ExpectEqual(subnet.Status.V6AvailableIPs, util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1))) } ginkgo.By("Creating pod " + podName) diff --git a/test/e2e/kube-ovn/subnet/subnet_bigint_e2e_test.go b/test/e2e/kube-ovn/subnet/subnet_bigint_e2e_test.go new file mode 100644 index 00000000000..8da2fad3c4f --- /dev/null +++ b/test/e2e/kube-ovn/subnet/subnet_bigint_e2e_test.go @@ -0,0 +1,177 @@ +package subnet + +import ( + "context" + "encoding/json" + "fmt" + "net" + + "github.com/onsi/ginkgo/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + kotypes "github.com/kubeovn/kube-ovn/pkg/types" + "github.com/kubeovn/kube-ovn/pkg/util" + "github.com/kubeovn/kube-ovn/test/e2e/framework" +) + +var _ = framework.Describe("[group:subnet]", func() { + f := framework.NewDefaultFramework("subnet-bigint") + + var subnetClient *framework.SubnetClient + var subnetName, cidr, cidrV4, cidrV6 string + + ginkgo.BeforeEach(func() { + subnetClient = f.SubnetClient() + subnetName = "subnet-bigint-" + framework.RandomSuffix() + cidr = framework.RandomCIDR(f.ClusterIPFamily) + cidrV4, cidrV6 = util.SplitStringIP(cidr) + }) + + ginkgo.AfterEach(func() { + ginkgo.By("Deleting subnet " + subnetName) + subnetClient.DeleteSync(subnetName) + }) + + framework.ConformanceIt("should correctly serialize BigInt fields in K8s API operations", func() { + ginkgo.By("Creating subnet " + subnetName) + subnet := framework.MakeSubnet(subnetName, "", cidr, "", "", "", nil, nil, nil) + subnet = subnetClient.CreateSync(subnet) + + ginkgo.By("Verifying BigInt fields are populated after creation") + if cidrV4 != "" { + _, ipnet, _ := net.ParseCIDR(cidrV4) + expectedV4Available := util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1)) + framework.ExpectEqual(subnet.Status.V4AvailableIPs, expectedV4Available, + "V4AvailableIPs should match expected value after creation") + framework.ExpectZero(subnet.Status.V4UsingIPs, + "V4UsingIPs should be zero initially") + } + + if cidrV6 != "" { + _, ipnet, _ := net.ParseCIDR(cidrV6) + expectedV6Available := util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1)) + framework.ExpectEqual(subnet.Status.V6AvailableIPs, expectedV6Available, + "V6AvailableIPs should match expected value after creation") + framework.ExpectZero(subnet.Status.V6UsingIPs, + "V6UsingIPs should be zero initially") + } + + ginkgo.By("Fetching subnet raw JSON from K8s API to verify BigInt serialization format") + rawSubnet, err := f.KubeOVNClientSet.KubeovnV1().Subnets().Get(context.Background(), subnetName, metav1.GetOptions{}) + framework.ExpectNoError(err, "should get subnet from API") + + // Marshal to JSON to verify serialization format + subnetJSON, err := json.Marshal(rawSubnet) + framework.ExpectNoError(err, "should marshal subnet to JSON") + + // Parse as generic map to check field types + var subnetMap map[string]any + err = json.Unmarshal(subnetJSON, &subnetMap) + framework.ExpectNoError(err, "should unmarshal subnet JSON to map") + + statusMap, ok := subnetMap["status"].(map[string]any) + framework.ExpectTrue(ok, "subnet should have status field") + + // Verify BigInt fields are serialized as strings (not numbers) + checkBigIntField := func(fieldName string, shouldExist bool) { + value, exists := statusMap[fieldName] + if !shouldExist { + return + } + framework.ExpectTrue(exists, fmt.Sprintf("status.%s should exist", fieldName)) + + // CRITICAL: Verify it's a string, not a number + strValue, isString := value.(string) + framework.ExpectTrue(isString, + fmt.Sprintf("status.%s should be JSON string (for CRD type:string), got %T: %v", + fieldName, value, value)) + + framework.Logf("✓ status.%s = %q (correctly serialized as string)", fieldName, strValue) + } + + checkBigIntField("v4availableIPs", cidrV4 != "") + checkBigIntField("v4usingIPs", cidrV4 != "") + checkBigIntField("v6availableIPs", cidrV6 != "") + checkBigIntField("v6usingIPs", cidrV6 != "") + + ginkgo.By("Testing K8s API patch operation with BigInt fields") + modifiedSubnet := subnet.DeepCopy() + modifiedSubnet.Spec.Private = true + + // This patch should succeed without BigInt serialization errors + patchedSubnet := subnetClient.PatchSync(subnet, modifiedSubnet) + framework.ExpectTrue(patchedSubnet.Spec.Private, "patch should update spec fields") + + // Verify BigInt status fields remain correct after patch + if cidrV4 != "" { + _, ipnet, _ := net.ParseCIDR(cidrV4) + expectedV4Available := util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1)) + framework.ExpectEqual(patchedSubnet.Status.V4AvailableIPs, expectedV4Available, + "V4AvailableIPs should remain correct after patch") + } + + if cidrV6 != "" { + _, ipnet, _ := net.ParseCIDR(cidrV6) + expectedV6Available := util.AddressCount(ipnet).Sub(kotypes.NewBigInt(1)) + framework.ExpectEqual(patchedSubnet.Status.V6AvailableIPs, expectedV6Available, + "V6AvailableIPs should remain correct after patch") + } + + ginkgo.By("Testing direct status update via K8s API client") + // Create a status-only patch + statusPatch := map[string]any{ + "status": map[string]any{ + "v4usingIPs": "999", + }, + } + statusPatchBytes, err := json.Marshal(statusPatch) + framework.ExpectNoError(err) + + // Apply status patch via K8s API + _, err = f.KubeOVNClientSet.KubeovnV1().Subnets().Patch( + context.Background(), + subnetName, + types.MergePatchType, + statusPatchBytes, + metav1.PatchOptions{}, + "status", + ) + + // This should succeed if BigInt JSON serialization is correct + // If MarshalJSON returns bare number instead of quoted string, this will fail + framework.ExpectNoError(err, "status patch with BigInt fields should succeed") + + ginkgo.By("Verifying patched status can be read back correctly") + updatedSubnet, err := f.KubeOVNClientSet.KubeovnV1().Subnets().Get(context.Background(), subnetName, metav1.GetOptions{}) + framework.ExpectNoError(err) + + if cidrV4 != "" { + framework.ExpectEqual(updatedSubnet.Status.V4UsingIPs.String(), "999", + "V4UsingIPs should be updated via status patch") + } + + ginkgo.By("Testing round-trip serialization through K8s API") + // Create a new subnet with large IP counts + largeSubnet := framework.MakeSubnet("subnet-bigint-large-"+framework.RandomSuffix(), "", "10.200.0.0/16", "", "", "", nil, nil, nil) + largeSubnet = subnetClient.CreateSync(largeSubnet) + + // For /16 IPv4 subnet: 2^16 - 2 (network + broadcast) = 65534 IPs + // After excluding gateway: 65533 available + expectedLargeCount := kotypes.NewBigInt(65533) + framework.ExpectEqual(largeSubnet.Status.V4AvailableIPs, expectedLargeCount, + "large subnet should have correct V4AvailableIPs") + + // Read it back through API + retrievedLargeSubnet, err := f.KubeOVNClientSet.KubeovnV1().Subnets().Get(context.Background(), largeSubnet.Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + + framework.ExpectEqual(retrievedLargeSubnet.Status.V4AvailableIPs, expectedLargeCount, + "retrieved subnet should preserve BigInt values") + + // Clean up large subnet + subnetClient.DeleteSync(largeSubnet.Name) + + framework.Logf("✅ All BigInt K8s API operations succeeded") + }) +}) diff --git a/test/e2e/kube-ovn/underlay/underlay.go b/test/e2e/kube-ovn/underlay/underlay.go index 928f628facc..bb47fe73728 100644 --- a/test/e2e/kube-ovn/underlay/underlay.go +++ b/test/e2e/kube-ovn/underlay/underlay.go @@ -23,6 +23,7 @@ import ( apiv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" "github.com/kubeovn/kube-ovn/pkg/ipam" + kotypes "github.com/kubeovn/kube-ovn/pkg/types" "github.com/kubeovn/kube-ovn/pkg/util" "github.com/kubeovn/kube-ovn/test/e2e/framework" "github.com/kubeovn/kube-ovn/test/e2e/framework/docker" @@ -53,16 +54,17 @@ func makeProviderNetwork(providerNetworkName string, exchangeLinkName bool, link return framework.MakeProviderNetwork(providerNetworkName, exchangeLinkName, defaultInterface, customInterfaces, nil) } -func waitSubnetStatusUpdate(subnetName string, subnetClient *framework.SubnetClient, expectedUsingIPs float64) { +func waitSubnetStatusUpdate(subnetName string, subnetClient *framework.SubnetClient, expectedUsingIPs int64) { ginkgo.GinkgoHelper() - ginkgo.By("Waiting for using ips count of subnet " + subnetName + " to be " + fmt.Sprintf("%.0f", expectedUsingIPs)) + expected := kotypes.NewBigInt(expectedUsingIPs) + ginkgo.By("Waiting for using ips count of subnet " + subnetName + " to be " + strconv.FormatInt(expectedUsingIPs, 10)) framework.WaitUntil(2*time.Second, 30*time.Second, func(_ context.Context) (bool, error) { subnet := subnetClient.Get(subnetName) - if (subnet.Status.V4AvailableIPs != 0 && subnet.Status.V4UsingIPs != expectedUsingIPs) || - (subnet.Status.V6AvailableIPs != 0 && subnet.Status.V6UsingIPs != expectedUsingIPs) { - framework.Logf("current subnet status: v4AvailableIPs = %.0f, v4UsingIPs = %.0f, v6AvailableIPs = %.0f, v6UsingIPs = %.0f", - subnet.Status.V4AvailableIPs, subnet.Status.V4UsingIPs, subnet.Status.V6AvailableIPs, subnet.Status.V6UsingIPs) + if (!subnet.Status.V4AvailableIPs.EqualInt64(0) && !subnet.Status.V4UsingIPs.Equal(expected)) || + (!subnet.Status.V6AvailableIPs.EqualInt64(0) && !subnet.Status.V6UsingIPs.Equal(expected)) { + framework.Logf("current subnet status: v4AvailableIPs = %s, v4UsingIPs = %s, v6AvailableIPs = %s, v6UsingIPs = %s", + subnet.Status.V4AvailableIPs.String(), subnet.Status.V4UsingIPs.String(), subnet.Status.V6AvailableIPs.String(), subnet.Status.V6UsingIPs.String()) return false, nil } return true, nil @@ -1453,18 +1455,18 @@ func checkU2OItems(f *framework.Framework, subnet *apiv1.Subnet, underlayPod, ov gw = v4gw ginkgo.By("checking subnet's using ips of underlay subnet " + subnet.Name + " " + protocolStr) if subnet.Spec.U2OInterconnection { - framework.ExpectEqual(int(subnet.Status.V4UsingIPs), 2) + framework.ExpectTrue(subnet.Status.V4UsingIPs.EqualInt64(2)) } else { - framework.ExpectEqual(int(subnet.Status.V4UsingIPs), 1) + framework.ExpectTrue(subnet.Status.V4UsingIPs.EqualInt64(1)) } } else { protocolStr = "ip6" gw = v6gw ginkgo.By("checking subnet's using ips of underlay subnet " + subnet.Name + " " + protocolStr) if subnet.Spec.U2OInterconnection { - framework.ExpectEqual(int(subnet.Status.V6UsingIPs), 2) + framework.ExpectTrue(subnet.Status.V6UsingIPs.EqualInt64(2)) } else { - framework.ExpectEqual(int(subnet.Status.V6UsingIPs), 1) + framework.ExpectTrue(subnet.Status.V6UsingIPs.EqualInt64(1)) } } diff --git a/test/e2e/ovn-vpc-nat-gw/e2e_test.go b/test/e2e/ovn-vpc-nat-gw/e2e_test.go index ab78f9b5200..bd2ca06b532 100644 --- a/test/e2e/ovn-vpc-nat-gw/e2e_test.go +++ b/test/e2e/ovn-vpc-nat-gw/e2e_test.go @@ -10,6 +10,7 @@ import ( "time" dockernetwork "github.com/docker/docker/api/types/network" + "github.com/onsi/ginkgo/v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" @@ -19,10 +20,9 @@ import ( "k8s.io/kubernetes/test/e2e/framework/config" e2enode "k8s.io/kubernetes/test/e2e/framework/node" - "github.com/onsi/ginkgo/v2" - kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" "github.com/kubeovn/kube-ovn/pkg/ovs" + kotypes "github.com/kubeovn/kube-ovn/pkg/types" "github.com/kubeovn/kube-ovn/pkg/util" "github.com/kubeovn/kube-ovn/test/e2e/framework" "github.com/kubeovn/kube-ovn/test/e2e/framework/docker" @@ -522,13 +522,13 @@ var _ = framework.Describe("[group:ovn-vpc-nat-gw]", func() { newUnerlayExternalSubnet := subnetClient.Get(underlaySubnetName) ginkgo.By("Check status using ovn eip for subnet " + underlaySubnetName) if newUnerlayExternalSubnet.Spec.Protocol == kubeovnv1.ProtocolIPv4 { - framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V4AvailableIPs-1, newUnerlayExternalSubnet.Status.V4AvailableIPs) - framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V4UsingIPs+1, newUnerlayExternalSubnet.Status.V4UsingIPs) + framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V4AvailableIPs.Sub(kotypes.NewBigInt(1)), newUnerlayExternalSubnet.Status.V4AvailableIPs) + framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V4UsingIPs.Add(kotypes.NewBigInt(1)), newUnerlayExternalSubnet.Status.V4UsingIPs) framework.ExpectNotEqual(oldUnderlayExternalSubnet.Status.V4AvailableIPRange, newUnerlayExternalSubnet.Status.V4AvailableIPRange) framework.ExpectNotEqual(oldUnderlayExternalSubnet.Status.V4UsingIPRange, newUnerlayExternalSubnet.Status.V4UsingIPRange) } else { - framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V6AvailableIPs-1, newUnerlayExternalSubnet.Status.V6AvailableIPs) - framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V6UsingIPs+1, newUnerlayExternalSubnet.Status.V6UsingIPs) + framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V6AvailableIPs.Sub(kotypes.NewBigInt(1)), newUnerlayExternalSubnet.Status.V6AvailableIPs) + framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V6UsingIPs.Add(kotypes.NewBigInt(1)), newUnerlayExternalSubnet.Status.V6UsingIPs) framework.ExpectNotEqual(oldUnderlayExternalSubnet.Status.V6AvailableIPRange, newUnerlayExternalSubnet.Status.V6AvailableIPRange) framework.ExpectNotEqual(oldUnderlayExternalSubnet.Status.V6UsingIPRange, newUnerlayExternalSubnet.Status.V6UsingIPRange) } @@ -538,13 +538,13 @@ var _ = framework.Describe("[group:ovn-vpc-nat-gw]", func() { time.Sleep(3 * time.Second) newUnerlayExternalSubnet = subnetClient.Get(underlaySubnetName) if newUnerlayExternalSubnet.Spec.Protocol == kubeovnv1.ProtocolIPv4 { - framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V4AvailableIPs+1, newUnerlayExternalSubnet.Status.V4AvailableIPs) - framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V4UsingIPs-1, newUnerlayExternalSubnet.Status.V4UsingIPs) + framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V4AvailableIPs.Add(kotypes.NewBigInt(1)), newUnerlayExternalSubnet.Status.V4AvailableIPs) + framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V4UsingIPs.Sub(kotypes.NewBigInt(1)), newUnerlayExternalSubnet.Status.V4UsingIPs) framework.ExpectNotEqual(oldUnderlayExternalSubnet.Status.V4AvailableIPRange, newUnerlayExternalSubnet.Status.V4AvailableIPRange) framework.ExpectNotEqual(oldUnderlayExternalSubnet.Status.V4UsingIPRange, newUnerlayExternalSubnet.Status.V4UsingIPRange) } else { - framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V6AvailableIPs+1, newUnerlayExternalSubnet.Status.V6AvailableIPs) - framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V6UsingIPs-1, newUnerlayExternalSubnet.Status.V6UsingIPs) + framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V6AvailableIPs.Add(kotypes.NewBigInt(1)), newUnerlayExternalSubnet.Status.V6AvailableIPs) + framework.ExpectEqual(oldUnderlayExternalSubnet.Status.V6UsingIPs.Sub(kotypes.NewBigInt(1)), newUnerlayExternalSubnet.Status.V6UsingIPs) framework.ExpectNotEqual(oldUnderlayExternalSubnet.Status.V6AvailableIPRange, newUnerlayExternalSubnet.Status.V6AvailableIPRange) framework.ExpectNotEqual(oldUnderlayExternalSubnet.Status.V6UsingIPRange, newUnerlayExternalSubnet.Status.V6UsingIPRange) } diff --git a/test/e2e/vip/e2e_test.go b/test/e2e/vip/e2e_test.go index 580cd78e765..181418b1b7b 100644 --- a/test/e2e/vip/e2e_test.go +++ b/test/e2e/vip/e2e_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/kubernetes/test/e2e/framework/config" apiv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + kotypes "github.com/kubeovn/kube-ovn/pkg/types" "github.com/kubeovn/kube-ovn/pkg/util" "github.com/kubeovn/kube-ovn/test/e2e/framework" ) @@ -208,13 +209,13 @@ var _ = framework.Describe("[group:vip]", func() { time.Sleep(3 * time.Second) newSubnet := subnetClient.Get(subnetName) if newSubnet.Spec.Protocol == apiv1.ProtocolIPv4 { - framework.ExpectEqual(oldSubnet.Status.V4AvailableIPs-1, newSubnet.Status.V4AvailableIPs) - framework.ExpectEqual(oldSubnet.Status.V4UsingIPs+1, newSubnet.Status.V4UsingIPs) + framework.ExpectTrue(oldSubnet.Status.V4AvailableIPs.Sub(kotypes.NewBigInt(1)).Equal(newSubnet.Status.V4AvailableIPs)) + framework.ExpectTrue(oldSubnet.Status.V4UsingIPs.Add(kotypes.NewBigInt(1)).Equal(newSubnet.Status.V4UsingIPs)) framework.ExpectNotEqual(oldSubnet.Status.V4AvailableIPRange, newSubnet.Status.V4AvailableIPRange) framework.ExpectNotEqual(oldSubnet.Status.V4UsingIPRange, newSubnet.Status.V4UsingIPRange) } else { - framework.ExpectEqual(oldSubnet.Status.V6AvailableIPs-1, newSubnet.Status.V6AvailableIPs) - framework.ExpectEqual(oldSubnet.Status.V6UsingIPs+1, newSubnet.Status.V6UsingIPs) + framework.ExpectTrue(oldSubnet.Status.V6AvailableIPs.Sub(kotypes.NewBigInt(1)).Equal(newSubnet.Status.V6AvailableIPs)) + framework.ExpectTrue(oldSubnet.Status.V6UsingIPs.Add(kotypes.NewBigInt(1)).Equal(newSubnet.Status.V6UsingIPs)) framework.ExpectNotEqual(oldSubnet.Status.V6AvailableIPRange, newSubnet.Status.V6AvailableIPRange) framework.ExpectNotEqual(oldSubnet.Status.V6UsingIPRange, newSubnet.Status.V6UsingIPRange) } @@ -224,13 +225,13 @@ var _ = framework.Describe("[group:vip]", func() { time.Sleep(3 * time.Second) newSubnet = subnetClient.Get(subnetName) if newSubnet.Spec.Protocol == apiv1.ProtocolIPv4 { - framework.ExpectEqual(oldSubnet.Status.V4AvailableIPs+1, newSubnet.Status.V4AvailableIPs) - framework.ExpectEqual(oldSubnet.Status.V4UsingIPs-1, newSubnet.Status.V4UsingIPs) + framework.ExpectTrue(oldSubnet.Status.V4AvailableIPs.Add(kotypes.NewBigInt(1)).Equal(newSubnet.Status.V4AvailableIPs)) + framework.ExpectTrue(oldSubnet.Status.V4UsingIPs.Sub(kotypes.NewBigInt(1)).Equal(newSubnet.Status.V4UsingIPs)) framework.ExpectNotEqual(oldSubnet.Status.V4AvailableIPRange, newSubnet.Status.V4AvailableIPRange) framework.ExpectNotEqual(oldSubnet.Status.V4UsingIPRange, newSubnet.Status.V4UsingIPRange) } else { - framework.ExpectEqual(oldSubnet.Status.V6AvailableIPs+1, newSubnet.Status.V6AvailableIPs) - framework.ExpectEqual(oldSubnet.Status.V6UsingIPs-1, newSubnet.Status.V6UsingIPs) + framework.ExpectTrue(oldSubnet.Status.V6AvailableIPs.Add(kotypes.NewBigInt(1)).Equal(newSubnet.Status.V6AvailableIPs)) + framework.ExpectTrue(oldSubnet.Status.V6UsingIPs.Sub(kotypes.NewBigInt(1)).Equal(newSubnet.Status.V6UsingIPs)) framework.ExpectNotEqual(oldSubnet.Status.V6AvailableIPRange, newSubnet.Status.V6AvailableIPRange) framework.ExpectNotEqual(oldSubnet.Status.V6UsingIPRange, newSubnet.Status.V6UsingIPRange) } diff --git a/test/unittest/util/net.go b/test/unittest/util/net.go index e5d838907f2..d6e1a6cf6df 100644 --- a/test/unittest/util/net.go +++ b/test/unittest/util/net.go @@ -6,6 +6,7 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + kotypes "github.com/kubeovn/kube-ovn/pkg/types" "github.com/kubeovn/kube-ovn/pkg/util" ) @@ -17,7 +18,7 @@ var _ = ginkgo.Describe("[Net]", func() { {IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(30, 32)}, {IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(24, 32)}, } - wants := []float64{ + wants := []int64{ 1, 2, 2, @@ -27,7 +28,7 @@ var _ = ginkgo.Describe("[Net]", func() { for i := range args { gomega.Expect(args[i].IP).NotTo(gomega.BeNil()) - gomega.Expect(util.AddressCount(args[i])).To(gomega.Equal(wants[i])) + gomega.Expect(util.AddressCount(args[i])).To(gomega.Equal(kotypes.NewBigInt(wants[i]))) } }) @@ -37,7 +38,7 @@ var _ = ginkgo.Describe("[Net]", func() { {"10.0.0.101..10.0.0.105"}, {"10.0.0.101..10.0.0.105", "10.0.0.111..10.0.0.120"}, } - wants := []float64{ + wants := []int64{ 1, 5, 15, @@ -45,7 +46,7 @@ var _ = ginkgo.Describe("[Net]", func() { gomega.Expect(args).To(gomega.HaveLen(len(wants))) for i := range args { - gomega.Expect(util.CountIPNums(args[i])).To(gomega.Equal(wants[i])) + gomega.Expect(util.CountIPNums(args[i])).To(gomega.Equal(kotypes.NewBigInt(wants[i]))) } })