github.com/cilium/cilium@v1.16.2/operator/pkg/bgpv2/cluster.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package bgpv2 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 11 "github.com/sirupsen/logrus" 12 k8s_errors "k8s.io/apimachinery/pkg/api/errors" 13 meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/util/sets" 15 16 cilium_api_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 17 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 18 "github.com/cilium/cilium/pkg/k8s/resource" 19 slim_labels "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/labels" 20 slim_meta_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 21 ) 22 23 func (b *BGPResourceManager) reconcileBGPClusterConfigs(ctx context.Context) error { 24 var err error 25 for _, config := range b.clusterConfigStore.List() { 26 rcErr := b.reconcileBGPClusterConfig(ctx, config) 27 if rcErr != nil { 28 err = errors.Join(err, rcErr) 29 } 30 } 31 return err 32 } 33 34 func (b *BGPResourceManager) reconcileBGPClusterConfig(ctx context.Context, config *v2alpha1.CiliumBGPClusterConfig) error { 35 // get nodes which match node selector for given cluster config 36 matchingNodes, err := b.getMatchingNodes(config.Spec.NodeSelector, config.Name) 37 if err != nil { 38 return err 39 } 40 41 // update node configs for matched nodes 42 for nodeRef := range matchingNodes { 43 upsertErr := b.upsertNodeConfig(ctx, config, nodeRef) 44 if upsertErr != nil { 45 err = errors.Join(err, upsertErr) 46 } 47 } 48 49 // delete node configs for this cluster that are not in the matching nodes 50 dErr := b.deleteStaleNodeConfigs(ctx, matchingNodes, config.Name) 51 if dErr != nil { 52 err = errors.Join(err, dErr) 53 } 54 55 return err 56 } 57 58 func (b *BGPResourceManager) upsertNodeConfig(ctx context.Context, config *v2alpha1.CiliumBGPClusterConfig, nodeName string) error { 59 prev, exists, err := b.nodeConfigStore.GetByKey(resource.Key{Name: nodeName}) 60 if err != nil { 61 return err 62 } 63 64 // find node overrides for given node 65 var overrideInstances []v2alpha1.CiliumBGPNodeConfigInstanceOverride 66 overrides := b.nodeConfigOverrideStore.List() 67 for _, override := range overrides { 68 if override.Name == nodeName { 69 overrideInstances = override.Spec.BGPInstances 70 break 71 } 72 } 73 74 // create new config 75 nodeConfig := &v2alpha1.CiliumBGPNodeConfig{ 76 ObjectMeta: meta_v1.ObjectMeta{ 77 Name: nodeName, 78 OwnerReferences: []meta_v1.OwnerReference{ 79 { 80 APIVersion: v2alpha1.SchemeGroupVersion.String(), 81 Kind: v2alpha1.BGPCCKindDefinition, 82 Name: config.GetName(), 83 UID: config.GetUID(), 84 }, 85 }, 86 }, 87 Spec: v2alpha1.CiliumBGPNodeSpec{ 88 BGPInstances: toNodeBGPInstance(config.Spec.BGPInstances, overrideInstances), 89 }, 90 } 91 92 switch { 93 case exists && prev.Spec.DeepEqual(&nodeConfig.Spec): 94 return nil 95 case exists: 96 // reinitialize spec and status fields 97 prev.Spec = nodeConfig.Spec 98 _, err = b.nodeConfigClient.Update(ctx, prev, meta_v1.UpdateOptions{}) 99 default: 100 _, err = b.nodeConfigClient.Create(ctx, nodeConfig, meta_v1.CreateOptions{}) 101 if err != nil && k8s_errors.IsAlreadyExists(err) { 102 // local store is not yet updated, but API server has the resource. Get resource from API server 103 // to compare spec. 104 prev, err = b.nodeConfigClient.Get(ctx, nodeConfig.Name, meta_v1.GetOptions{}) 105 if err != nil { 106 return err 107 } 108 109 // if prev already exist and spec is different, update it 110 if !prev.Spec.DeepEqual(&nodeConfig.Spec) { 111 prev.Spec = nodeConfig.Spec 112 _, err = b.nodeConfigClient.Update(ctx, prev, meta_v1.UpdateOptions{}) 113 } else { 114 // idempotent change, skip 115 return nil 116 } 117 } 118 } 119 120 b.logger.WithFields(logrus.Fields{ 121 "node config": nodeConfig.Name, 122 "cluster config": config.Name, 123 }).Debug("Upserting BGP node config") 124 125 return err 126 } 127 128 func toNodeBGPInstance(clusterBGPInstances []v2alpha1.CiliumBGPInstance, overrideBGPInstances []v2alpha1.CiliumBGPNodeConfigInstanceOverride) []v2alpha1.CiliumBGPNodeInstance { 129 var res []v2alpha1.CiliumBGPNodeInstance 130 131 for _, clusterBGPInstance := range clusterBGPInstances { 132 nodeBGPInstance := v2alpha1.CiliumBGPNodeInstance{ 133 Name: clusterBGPInstance.Name, 134 LocalASN: clusterBGPInstance.LocalASN, 135 } 136 137 // find BGPResourceManager global override for this instance 138 var override v2alpha1.CiliumBGPNodeConfigInstanceOverride 139 for _, overrideBGPInstance := range overrideBGPInstances { 140 if overrideBGPInstance.Name == clusterBGPInstance.Name { 141 nodeBGPInstance.RouterID = overrideBGPInstance.RouterID 142 nodeBGPInstance.LocalPort = overrideBGPInstance.LocalPort 143 override = overrideBGPInstance 144 break 145 } 146 } 147 148 for _, clusterBGPInstancePeer := range clusterBGPInstance.Peers { 149 nodePeer := v2alpha1.CiliumBGPNodePeer{ 150 Name: clusterBGPInstancePeer.Name, 151 PeerAddress: clusterBGPInstancePeer.PeerAddress, 152 PeerASN: clusterBGPInstancePeer.PeerASN, 153 PeerConfigRef: clusterBGPInstancePeer.PeerConfigRef, 154 } 155 156 // find BGPResourceManager Peer override for this instance 157 for _, overrideBGPPeer := range override.Peers { 158 if overrideBGPPeer.Name == clusterBGPInstancePeer.Name { 159 nodePeer.LocalAddress = overrideBGPPeer.LocalAddress 160 break 161 } 162 } 163 164 nodeBGPInstance.Peers = append(nodeBGPInstance.Peers, nodePeer) 165 } 166 167 res = append(res, nodeBGPInstance) 168 } 169 return res 170 } 171 172 // getMatchingNodes returns a map of node names that match the given cluster config's node selector. 173 func (b *BGPResourceManager) getMatchingNodes(nodeSelector *slim_meta_v1.LabelSelector, configName string) (sets.Set[string], error) { 174 labelSelector, err := slim_meta_v1.LabelSelectorAsSelector(nodeSelector) 175 if err != nil { 176 return nil, err 177 } 178 179 // find nodes that match the cluster config's node selector 180 matchingNodes := sets.New[string]() 181 182 for _, n := range b.ciliumNodeStore.List() { 183 // nil node selector means match all nodes 184 if nodeSelector == nil || labelSelector.Matches(slim_labels.Set(n.Labels)) { 185 err := b.validNodeSelection(n, configName) 186 if err != nil { 187 b.logger.WithError(err).Errorf("skipping node %s", n.Name) 188 continue 189 } 190 matchingNodes.Insert(n.Name) 191 } 192 } 193 194 return matchingNodes, nil 195 } 196 197 // deleteStaleNodeConfigs deletes node configs that are not in the expected list for given cluster. 198 // TODO there might be a race where stale node configs are not detected and deleted. Check issue #30320 for more details. 199 func (b *BGPResourceManager) deleteStaleNodeConfigs(ctx context.Context, expectedNodes sets.Set[string], clusterRef string) error { 200 var err error 201 for _, existingNode := range b.nodeConfigStore.List() { 202 if expectedNodes.Has(existingNode.Name) || !IsOwner(existingNode.GetOwnerReferences(), clusterRef) { 203 continue 204 } 205 206 dErr := b.nodeConfigClient.Delete(ctx, existingNode.Name, meta_v1.DeleteOptions{}) 207 if dErr != nil && k8s_errors.IsNotFound(dErr) { 208 continue 209 } else if dErr != nil { 210 err = errors.Join(err, dErr) 211 } else { 212 b.logger.WithFields(logrus.Fields{ 213 "node config": existingNode.Name, 214 "cluster config": clusterRef, 215 }).Debug("Deleting BGP node config") 216 } 217 } 218 return err 219 } 220 221 // validNodeSelection checks if the node is already present in another cluster config. 222 func (b *BGPResourceManager) validNodeSelection(node *cilium_api_v2.CiliumNode, expectedOwnerName string) error { 223 existingBGPNodeConfig, exists, err := b.nodeConfigStore.GetByKey(resource.Key{Name: node.Name}) 224 if err != nil { 225 return err 226 } 227 228 if exists && !IsOwner(existingBGPNodeConfig.GetOwnerReferences(), expectedOwnerName) { 229 return fmt.Errorf("BGPResourceManager node config %s already exist", existingBGPNodeConfig.Name) 230 } 231 232 return nil 233 } 234 235 // IsOwner checks if the expected is present in owners list. 236 func IsOwner(owners []meta_v1.OwnerReference, expected string) bool { 237 for _, owner := range owners { 238 if owner.Name == expected { 239 return true 240 } 241 } 242 return false 243 }