@@ -11,6 +11,7 @@ import (
1111 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1212
1313 kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
14+ "github.com/kubeovn/kube-ovn/pkg/ipam"
1415 "github.com/kubeovn/kube-ovn/pkg/util"
1516)
1617
@@ -329,3 +330,202 @@ func TestGetPodKubeovnNetsNonPrimaryCNI(t *testing.T) {
329330 })
330331 }
331332}
333+
334+ func TestAcquireAddressWithSpecifiedSubnet (t * testing.T ) {
335+ tests := []struct {
336+ name string
337+ pod * corev1.Pod
338+ namespaces []* corev1.Namespace
339+ subnets []* kubeovnv1.Subnet
340+ setupIPAM func (* Controller )
341+ expectError bool
342+ expectedSubnet string
343+ description string
344+ }{
345+ {
346+ name : "User specifies subnet - should succeed" ,
347+ pod : & corev1.Pod {
348+ ObjectMeta : metav1.ObjectMeta {
349+ Name : "test-pod" ,
350+ Namespace : "default" ,
351+ Annotations : map [string ]string {
352+ util .LogicalSwitchAnnotation : "subnet1" ,
353+ util .IPAddressAnnotation : "10.0.1.10" ,
354+ },
355+ },
356+ },
357+ namespaces : []* corev1.Namespace {
358+ {
359+ ObjectMeta : metav1.ObjectMeta {
360+ Name : "default" ,
361+ Annotations : map [string ]string {
362+ util .LogicalSwitchAnnotation : "subnet1,subnet2" ,
363+ },
364+ },
365+ },
366+ },
367+ subnets : []* kubeovnv1.Subnet {
368+ {
369+ ObjectMeta : metav1.ObjectMeta {Name : "subnet1" },
370+ Spec : kubeovnv1.SubnetSpec {
371+ CIDRBlock : "10.0.1.0/24" ,
372+ Protocol : kubeovnv1 .ProtocolIPv4 ,
373+ Provider : util .OvnProvider ,
374+ },
375+ Status : kubeovnv1.SubnetStatus {V4AvailableIPs : 100 },
376+ },
377+ {
378+ ObjectMeta : metav1.ObjectMeta {Name : "subnet2" },
379+ Spec : kubeovnv1.SubnetSpec {
380+ CIDRBlock : "10.0.1.0/24" ,
381+ Protocol : kubeovnv1 .ProtocolIPv4 ,
382+ Provider : util .OvnProvider ,
383+ },
384+ Status : kubeovnv1.SubnetStatus {V4AvailableIPs : 100 },
385+ },
386+ },
387+ expectError : false ,
388+ expectedSubnet : "subnet1" ,
389+ description : "Should allocate from specified subnet" ,
390+ },
391+ {
392+ name : "User specifies subnet but IP occupied - should NOT fallback" ,
393+ pod : & corev1.Pod {
394+ ObjectMeta : metav1.ObjectMeta {
395+ Name : "test-pod" ,
396+ Namespace : "default" ,
397+ Annotations : map [string ]string {
398+ util .LogicalSwitchAnnotation : "subnet1" ,
399+ util .IPAddressAnnotation : "10.0.1.10" ,
400+ },
401+ },
402+ },
403+ namespaces : []* corev1.Namespace {
404+ {
405+ ObjectMeta : metav1.ObjectMeta {
406+ Name : "default" ,
407+ Annotations : map [string ]string {
408+ util .LogicalSwitchAnnotation : "subnet1,subnet2" ,
409+ },
410+ },
411+ },
412+ },
413+ subnets : []* kubeovnv1.Subnet {
414+ {
415+ ObjectMeta : metav1.ObjectMeta {Name : "subnet1" },
416+ Spec : kubeovnv1.SubnetSpec {
417+ CIDRBlock : "10.0.1.0/24" ,
418+ Protocol : kubeovnv1 .ProtocolIPv4 ,
419+ Provider : util .OvnProvider ,
420+ },
421+ Status : kubeovnv1.SubnetStatus {V4AvailableIPs : 100 },
422+ },
423+ {
424+ ObjectMeta : metav1.ObjectMeta {Name : "subnet2" },
425+ Spec : kubeovnv1.SubnetSpec {
426+ CIDRBlock : "10.0.1.0/24" ,
427+ Protocol : kubeovnv1 .ProtocolIPv4 ,
428+ Provider : util .OvnProvider ,
429+ },
430+ Status : kubeovnv1.SubnetStatus {V4AvailableIPs : 100 },
431+ },
432+ },
433+ setupIPAM : func (c * Controller ) {
434+ _ , _ , _ , _ = c .ipam .GetStaticAddress ("other-pod.default" , "other-pod.default" , "10.0.1.10" , nil , "subnet1" , true )
435+ },
436+ expectError : true ,
437+ description : "Should NOT fallback to subnet2 when IP is occupied in specified subnet1" ,
438+ },
439+ {
440+ name : "No subnet specified - should try all namespace subnets" ,
441+ pod : & corev1.Pod {
442+ ObjectMeta : metav1.ObjectMeta {
443+ Name : "test-pod" ,
444+ Namespace : "default" ,
445+ Annotations : map [string ]string {
446+ util .IPAddressAnnotation : "10.0.2.10" ,
447+ },
448+ },
449+ },
450+ namespaces : []* corev1.Namespace {
451+ {
452+ ObjectMeta : metav1.ObjectMeta {
453+ Name : "default" ,
454+ Annotations : map [string ]string {
455+ util .LogicalSwitchAnnotation : "subnet1,subnet2" ,
456+ },
457+ },
458+ },
459+ },
460+ subnets : []* kubeovnv1.Subnet {
461+ {
462+ ObjectMeta : metav1.ObjectMeta {Name : "subnet1" },
463+ Spec : kubeovnv1.SubnetSpec {
464+ CIDRBlock : "10.0.1.0/24" ,
465+ Protocol : kubeovnv1 .ProtocolIPv4 ,
466+ Provider : util .OvnProvider ,
467+ },
468+ Status : kubeovnv1.SubnetStatus {V4AvailableIPs : 100 },
469+ },
470+ {
471+ ObjectMeta : metav1.ObjectMeta {Name : "subnet2" },
472+ Spec : kubeovnv1.SubnetSpec {
473+ CIDRBlock : "10.0.2.0/24" ,
474+ Protocol : kubeovnv1 .ProtocolIPv4 ,
475+ Provider : util .OvnProvider ,
476+ },
477+ Status : kubeovnv1.SubnetStatus {V4AvailableIPs : 100 },
478+ },
479+ },
480+ expectError : false ,
481+ expectedSubnet : "subnet2" ,
482+ description : "Should try all subnets and find matching one when no subnet specified" ,
483+ },
484+ }
485+
486+ for _ , tt := range tests {
487+ t .Run (tt .name , func (t * testing.T ) {
488+ fakeController , err := newFakeControllerWithOptions (t , & FakeControllerOptions {
489+ Namespaces : tt .namespaces ,
490+ Subnets : tt .subnets ,
491+ Pods : []* corev1.Pod {tt .pod },
492+ })
493+ require .NoError (t , err )
494+ controller := fakeController .fakeController
495+ controller .ipam = newIPAMForTest (tt .subnets )
496+
497+ if tt .setupIPAM != nil {
498+ tt .setupIPAM (controller )
499+ }
500+
501+ podNets , err := controller .getPodKubeovnNets (tt .pod )
502+ require .NoError (t , err )
503+ require .Greater (t , len (podNets ), 0 )
504+
505+ _ , _ , _ , subnet , err := controller .acquireAddress (tt .pod , podNets [0 ])
506+
507+ if tt .expectError {
508+ assert .Error (t , err , tt .description )
509+ } else {
510+ require .NoError (t , err , tt .description )
511+ assert .Equal (t , tt .expectedSubnet , subnet .Name , tt .description )
512+ }
513+ })
514+ }
515+ }
516+
517+ func newIPAMForTest (subnets []* kubeovnv1.Subnet ) * ipam.IPAM {
518+ ipamInstance := ipam .NewIPAM ()
519+ for _ , subnet := range subnets {
520+ excludeIPs := subnet .Spec .ExcludeIps
521+ if len (excludeIPs ) == 0 {
522+ excludeIPs = []string {}
523+ }
524+ s , err := ipam .NewSubnet (subnet .Name , subnet .Spec .CIDRBlock , excludeIPs )
525+ if err != nil {
526+ panic (err )
527+ }
528+ ipamInstance .Subnets [subnet .Name ] = s
529+ }
530+ return ipamInstance
531+ }
0 commit comments