github.com/cilium/cilium@v1.16.2/operator/watchers/cilium_podippool.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package watchers 5 6 import ( 7 "context" 8 "fmt" 9 "net/netip" 10 "strconv" 11 "strings" 12 13 "github.com/sirupsen/logrus" 14 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 17 operatorOption "github.com/cilium/cilium/operator/option" 18 cilium_v2alpha1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 19 "github.com/cilium/cilium/pkg/k8s/client" 20 "github.com/cilium/cilium/pkg/k8s/resource" 21 ) 22 23 // PooledAllocatorProvider defines the functions of IPAM provider front-end which additionally allow 24 // definition of IP pools at runtime. 25 // This is implemented by e.g. pkg/ipam/allocator/multipool 26 type PooledAllocatorProvider interface { 27 UpsertPool(ctx context.Context, pool *cilium_v2alpha1.CiliumPodIPPool) error 28 DeletePool(ctx context.Context, pool *cilium_v2alpha1.CiliumPodIPPool) error 29 } 30 31 const ( 32 poolKeyIPv4CIDRs = "ipv4-cidrs" 33 poolKeyIPv4MaskSize = "ipv4-mask-size" 34 poolKeyIPv6CIDRs = "ipv6-cidrs" 35 poolKeyIPv6MaskSize = "ipv6-mask-size" 36 ) 37 38 // parsePoolSpec parses a pool spec string in the form 39 // "ipv4-cidrs:172.16.0.0/16,172.17.0.0/16;ipv4-mask-size:24". 40 func parsePoolSpec(poolString string) (cilium_v2alpha1.IPPoolSpec, error) { 41 fields := strings.FieldsFunc(strings.ReplaceAll(poolString, " ", ""), func(c rune) bool { 42 return c == ';' 43 }) 44 45 var ipv4CIDRs, ipv6CIDRs []cilium_v2alpha1.PoolCIDR 46 var ipv4MaskSize, ipv6MaskSize uint8 47 48 for _, field := range fields { 49 key, value, ok := strings.Cut(field, ":") 50 if !ok { 51 return cilium_v2alpha1.IPPoolSpec{}, fmt.Errorf("invalid number of key delimiters in pool spec %s", poolString) 52 } 53 switch key { 54 case poolKeyIPv4CIDRs: 55 for _, cidr := range strings.Split(value, ",") { 56 _, err := netip.ParsePrefix(cidr) 57 if err != nil { 58 return cilium_v2alpha1.IPPoolSpec{}, fmt.Errorf("invalid value for key %q: %w", poolKeyIPv4CIDRs, err) 59 } 60 ipv4CIDRs = append(ipv4CIDRs, cilium_v2alpha1.PoolCIDR(cidr)) 61 } 62 case poolKeyIPv4MaskSize: 63 mask, err := strconv.ParseUint(value, 10, 8) 64 if err != nil { 65 return cilium_v2alpha1.IPPoolSpec{}, fmt.Errorf("invalid value for key %q: %w", poolKeyIPv4MaskSize, err) 66 } 67 ipv4MaskSize = uint8(mask) 68 case poolKeyIPv6CIDRs: 69 for _, cidr := range strings.Split(value, ",") { 70 _, err := netip.ParsePrefix(cidr) 71 if err != nil { 72 return cilium_v2alpha1.IPPoolSpec{}, fmt.Errorf("invalid value for key %q: %w", poolKeyIPv6CIDRs, err) 73 } 74 ipv6CIDRs = append(ipv6CIDRs, cilium_v2alpha1.PoolCIDR(cidr)) 75 } 76 case poolKeyIPv6MaskSize: 77 mask, err := strconv.ParseUint(value, 10, 8) 78 if err != nil { 79 return cilium_v2alpha1.IPPoolSpec{}, fmt.Errorf("invalid value for key %q: %w", poolKeyIPv6MaskSize, err) 80 } 81 ipv6MaskSize = uint8(mask) 82 } 83 } 84 85 pool := cilium_v2alpha1.IPPoolSpec{} 86 if len(ipv4CIDRs) > 0 { 87 pool.IPv4 = &cilium_v2alpha1.IPv4PoolSpec{ 88 CIDRs: ipv4CIDRs, 89 MaskSize: ipv4MaskSize, 90 } 91 } 92 if len(ipv6CIDRs) > 0 { 93 pool.IPv6 = &cilium_v2alpha1.IPv6PoolSpec{ 94 CIDRs: ipv6CIDRs, 95 MaskSize: ipv6MaskSize, 96 } 97 } 98 99 return pool, nil 100 } 101 102 func multiPoolAutoCreatePools(ctx context.Context, clientset client.Clientset, poolMap map[string]string) { 103 for poolName, poolSpecStr := range poolMap { 104 poolSpec, err := parsePoolSpec(poolSpecStr) 105 if err != nil { 106 log.WithError(err).WithFields(logrus.Fields{ 107 "poolName": poolName, 108 "poolSpec": poolSpecStr, 109 }).Fatalf("Failed to parse IP pool spec in %q flag", operatorOption.IPAMAutoCreateCiliumPodIPPools) 110 } 111 112 pool := &cilium_v2alpha1.CiliumPodIPPool{ 113 ObjectMeta: metav1.ObjectMeta{ 114 Name: poolName, 115 }, 116 Spec: poolSpec, 117 } 118 119 _, err = clientset.CiliumV2alpha1().CiliumPodIPPools().Create(ctx, pool, metav1.CreateOptions{}) 120 if err != nil { 121 if k8sErrors.IsAlreadyExists(err) { 122 // Nothing to do, we will not try to update an existing resource 123 log.WithField("poolName", poolName).Info("Found existing CiliumPodIPPool resource. Skipping creation") 124 } else { 125 log.WithError(err).WithField("poolName", poolName).WithField("obj", pool).Error("Failed to create CiliumPodIPPool resource") 126 } 127 continue 128 } 129 130 log.WithField("poolName", poolName).Info("Created CiliumPodIPPool resource") 131 } 132 } 133 134 func StartIPPoolAllocator( 135 ctx context.Context, 136 clientset client.Clientset, 137 allocator PooledAllocatorProvider, 138 ipPools resource.Resource[*cilium_v2alpha1.CiliumPodIPPool], 139 ) { 140 log.Info("Starting CiliumPodIPPool allocator watcher") 141 142 multiPoolAutoCreatePools(ctx, clientset, operatorOption.Config.IPAMAutoCreateCiliumPodIPPools) 143 144 synced := make(chan struct{}) 145 146 go func() { 147 for ev := range ipPools.Events(ctx) { 148 var err error 149 var action string 150 151 switch ev.Kind { 152 case resource.Sync: 153 close(synced) 154 case resource.Upsert: 155 err = allocator.UpsertPool(ctx, ev.Object) 156 action = "upsert" 157 case resource.Delete: 158 err = allocator.DeletePool(ctx, ev.Object) 159 action = "delete" 160 } 161 ev.Done(err) 162 if err != nil { 163 log.WithError(err).Errorf("failed to %s pool %q", action, ev.Key) 164 } 165 } 166 }() 167 168 // Block until all pools are restored, so callers can safely start node allocation 169 // right after return. 170 <-synced 171 log.Info("All CiliumPodIPPool resources synchronized") 172 }