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  }