github.com/webmeshproj/webmesh-cni@v0.0.27/internal/controllers/pod_controller.go (about) 1 /* 2 Copyright 2023 Avi Zimmerman <avi.zimmerman@gmail.com>. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package controllers 18 19 import ( 20 "context" 21 "fmt" 22 "net/netip" 23 "time" 24 25 v1 "github.com/webmeshproj/api/go/v1" 26 "github.com/webmeshproj/storage-provider-k8s/provider" 27 meshtypes "github.com/webmeshproj/webmesh/pkg/storage/types" 28 corev1 "k8s.io/api/core/v1" 29 ctrl "sigs.k8s.io/controller-runtime" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 "sigs.k8s.io/controller-runtime/pkg/handler" 32 "sigs.k8s.io/controller-runtime/pkg/log" 33 34 "github.com/webmeshproj/webmesh-cni/internal/host" 35 ) 36 37 //+kubebuilder:rbac:groups="",resources=pods;configmaps,verbs=get;list;watch 38 39 // PodReconciler watches for pods of interest to the outside world 40 // that have become ready and ensures their features are advertised. 41 // TODO: This reoconciler should be used to match up all pod names 42 // with containers for easier lookup. 43 type PodReconciler struct { 44 client.Client 45 Host host.Node 46 Provider *provider.Provider 47 DNSSelector map[string]string 48 DNSNamespace string 49 DNSPort string 50 } 51 52 // SetupWithManager sets up the node reconciler with the manager. 53 func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { 54 return ctrl.NewControllerManagedBy(mgr). 55 Named("pod-features"). 56 Watches( 57 &corev1.Pod{}, 58 handler.EnqueueRequestsFromMapFunc(r.enqueueIfReadyAndInNetwork), 59 ). 60 Complete(r) 61 } 62 63 // Reconcile reconciles a node. 64 func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 65 log := log.FromContext(ctx) 66 if !r.Host.Started() { 67 // Requeue until the host is started. 68 log.Info("Host not started, requeuing") 69 return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 2}, nil 70 } 71 log.Info("Reconciling available features for pod") 72 var pod corev1.Pod 73 if err := r.Get(ctx, req.NamespacedName, &pod); err != nil { 74 return ctrl.Result{}, client.IgnoreNotFound(err) 75 } 76 // Get the peer from the storage provider by their IP address. 77 db := r.Provider.Datastore() 78 var peer meshtypes.MeshNode 79 for _, ip := range pod.Status.PodIPs { 80 if ip.IP == "" { 81 continue 82 } 83 addr, err := netip.ParseAddr(ip.IP) 84 if err != nil { 85 log.Error(err, "Failed to parse pod IP") 86 continue 87 } 88 switch { 89 case addr.Is4(): 90 peer, err = db.GetPeerByIPv4Addr(ctx, netip.PrefixFrom(addr, 32)) 91 case addr.Is6(): 92 peer, err = db.GetPeerByIPv6Addr(ctx, netip.PrefixFrom(addr, 112)) 93 default: 94 log.Info("Ignoring invalid IP address", "addr", addr.String()) 95 continue 96 } 97 if err != nil { 98 log.Error(err, "Failed to lookup peer by IP address") 99 continue 100 } 101 } 102 if peer.MeshNode == nil { 103 return ctrl.Result{}, fmt.Errorf("failed to find peer for pod") 104 } 105 // Ensure the pod has the DNS feature. 106 dnsPort := func() int32 { 107 for _, container := range pod.Spec.Containers { 108 for _, port := range container.Ports { 109 if port.Name == r.DNSPort { 110 return port.ContainerPort 111 } 112 } 113 } 114 // Assume the DNS port is 53. 115 return 53 116 }() 117 if !peer.HasFeature(v1.Feature_MESH_DNS) { 118 log.Info("Ensuring pod has MeshDNS feature") 119 peer.Features = append(peer.Features, &v1.FeaturePort{ 120 Feature: v1.Feature_MESH_DNS, 121 Port: dnsPort, 122 }, &v1.FeaturePort{ 123 Feature: v1.Feature_FORWARD_MESH_DNS, 124 Port: dnsPort, 125 }) 126 if err := r.Provider.MeshDB().Peers().Put(ctx, peer); err != nil { 127 log.Error(err, "Failed to add DNS feature to peer") 128 return ctrl.Result{}, nil 129 } 130 } 131 return ctrl.Result{}, nil 132 } 133 134 func (r *PodReconciler) enqueueIfReadyAndInNetwork(ctx context.Context, o client.Object) []ctrl.Request { 135 // Fast path, if the host isn't running there isn't anything we care about 136 if !r.Host.Started() { 137 return nil 138 } 139 pod := o.(*corev1.Pod) 140 // Ignore deleted pods. 141 if pod.GetDeletionTimestamp() != nil { 142 return nil 143 } 144 // Ignore host network pods. 145 if pod.Spec.HostNetwork { 146 return nil 147 } 148 // Ignore pods that aren't running yet. 149 if pod.Status.Phase != corev1.PodRunning { 150 return nil 151 } 152 // Ignore pods that aren't in the DNS namespace. 153 if pod.GetNamespace() != r.DNSNamespace { 154 return nil 155 } 156 // Ignore pods that don't match the DNS selector. 157 for k, v := range r.DNSSelector { 158 if pod.GetLabels()[k] != v { 159 return nil 160 } 161 } 162 // Match the request if the pod has an in network IP. 163 key := client.ObjectKeyFromObject(pod) 164 req := ctrl.Request{NamespacedName: key} 165 for _, ip := range pod.Status.PodIPs { 166 if ip.IP == "" { 167 continue 168 } 169 addr, err := netip.ParseAddr(ip.IP) 170 if err != nil { 171 log.FromContext(ctx).Error(err, "Failed to parse pod IP") 172 continue 173 } 174 switch { 175 case addr.Is6() && r.Host.Node().Network().NetworkV6().Contains(addr): 176 return []ctrl.Request{req} 177 case addr.Is4() && r.Host.Node().Network().NetworkV4().Contains(addr): 178 return []ctrl.Request{req} 179 } 180 } 181 return nil 182 }