istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/echo/kube/workload_manager.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kube 16 17 import ( 18 "errors" 19 "fmt" 20 "io" 21 "sync" 22 23 "github.com/hashicorp/go-multierror" 24 corev1 "k8s.io/api/core/v1" 25 26 "istio.io/istio/pkg/config/protocol" 27 echoCommon "istio.io/istio/pkg/test/echo/common" 28 "istio.io/istio/pkg/test/framework/components/echo" 29 "istio.io/istio/pkg/test/framework/resource" 30 "istio.io/istio/pkg/test/util/retry" 31 ) 32 33 var ( 34 _ echo.Instance = &instance{} 35 _ io.Closer = &instance{} 36 ) 37 38 type workloadHandler interface { 39 WorkloadReady(w *workload) 40 WorkloadNotReady(w *workload) 41 } 42 43 type workloadManager struct { 44 workloads []*workload 45 mutex sync.Mutex 46 podController *podController 47 cfg echo.Config 48 ctx resource.Context 49 grpcPort uint16 50 tls *echoCommon.TLSSettings 51 closing bool 52 stopCh chan struct{} 53 handler workloadHandler 54 } 55 56 func newWorkloadManager(ctx resource.Context, cfg echo.Config, handler workloadHandler) (*workloadManager, error) { 57 // Get the gRPC port and TLS settings. 58 var grpcInstancePort int 59 var tls *echoCommon.TLSSettings 60 if cfg.IsProxylessGRPC() { 61 grpcInstancePort = grpcMagicPort 62 } 63 if grpcInstancePort == 0 { 64 if grpcPort, found := cfg.Ports.ForProtocol(protocol.GRPC); found { 65 if grpcPort.TLS { 66 tls = cfg.TLSSettings 67 } 68 grpcInstancePort = grpcPort.WorkloadPort 69 } 70 } 71 if grpcInstancePort == 0 { 72 return nil, errors.New("unable to find GRPC command port") 73 } 74 75 m := &workloadManager{ 76 cfg: cfg, 77 ctx: ctx, 78 handler: handler, 79 grpcPort: uint16(grpcInstancePort), 80 tls: tls, 81 stopCh: make(chan struct{}, 1), 82 } 83 m.podController = newPodController(cfg, podHandlers{ 84 added: m.onPodAddOrUpdate, 85 updated: m.onPodAddOrUpdate, 86 deleted: m.onPodDeleted, 87 }) 88 89 return m, nil 90 } 91 92 // WaitForReadyWorkloads waits until all known workloads are ready. 93 func (m *workloadManager) WaitForReadyWorkloads() (out echo.Workloads, err error) { 94 err = retry.UntilSuccess(func() error { 95 m.mutex.Lock() 96 out, err = m.readyWorkloads() 97 if err == nil && len(out) != len(m.workloads) { 98 err = fmt.Errorf("failed waiting for workloads for echo %s/%s to be ready", 99 m.cfg.Namespace.Name(), 100 m.cfg.Service) 101 } 102 m.mutex.Unlock() 103 return err 104 }, retry.Timeout(m.cfg.ReadinessTimeout), startDelay) 105 return 106 } 107 108 func (m *workloadManager) readyWorkloads() (echo.Workloads, error) { 109 out := make(echo.Workloads, 0, len(m.workloads)) 110 var connErrs error 111 for _, w := range m.workloads { 112 if w.IsReady() { 113 out = append(out, w) 114 } else if w.connectErr != nil { 115 connErrs = multierror.Append(connErrs, w.connectErr) 116 } 117 } 118 if len(out) == 0 { 119 err := fmt.Errorf("no workloads ready for echo %s/%s", m.cfg.Namespace.Name(), m.cfg.Service) 120 if connErrs != nil { 121 err = fmt.Errorf("%v: failed connecting: %v", err, connErrs) 122 } 123 return nil, err 124 } 125 return out, nil 126 } 127 128 // ReadyWorkloads returns all ready workloads in ascending order by pod name. 129 func (m *workloadManager) ReadyWorkloads() (echo.Workloads, error) { 130 m.mutex.Lock() 131 out, err := m.readyWorkloads() 132 m.mutex.Unlock() 133 return out, err 134 } 135 136 func (m *workloadManager) Start() error { 137 // Run the pod controller. 138 go m.podController.Run(m.stopCh) 139 140 // Wait for the cache to sync. 141 if !m.podController.WaitForSync(m.stopCh) { 142 return fmt.Errorf( 143 "failed syncing cache for echo %s/%s: controller stopping", 144 m.cfg.Namespace.Name(), 145 m.cfg.Service) 146 } 147 148 // Wait until all pods are ready. 149 _, err := m.WaitForReadyWorkloads() 150 return err 151 } 152 153 func (m *workloadManager) onPodAddOrUpdate(pod *corev1.Pod) error { 154 m.mutex.Lock() 155 156 // After the method returns, notify the handler the ready state of the workload changed. 157 var workloadReady *workload 158 var workloadNotReady *workload 159 defer func() { 160 m.mutex.Unlock() 161 162 if workloadReady != nil { 163 m.handler.WorkloadReady(workloadReady) 164 } 165 if workloadNotReady != nil { 166 m.handler.WorkloadNotReady(workloadNotReady) 167 } 168 }() 169 170 // First, check to see if we already have a workload for the pod. If we do, just update it. 171 for _, w := range m.workloads { 172 if w.pod.Name == pod.Name { 173 prevReady := w.IsReady() 174 if err := w.Update(*pod); err != nil { 175 return err 176 } 177 178 // Defer notifying the handler until after we release the mutex. 179 if !prevReady && w.IsReady() { 180 workloadReady = w 181 } else if prevReady && !w.IsReady() { 182 workloadNotReady = w 183 } 184 return nil 185 } 186 } 187 188 // Add the pod to the end of the workload list. 189 newWorkload, err := newWorkload(workloadConfig{ 190 pod: *pod, 191 hasSidecar: workloadHasSidecar(pod), 192 cluster: m.cfg.Cluster, 193 grpcPort: m.grpcPort, 194 tls: m.tls, 195 stop: m.stopCh, 196 }, m.ctx) 197 if err != nil { 198 return err 199 } 200 m.workloads = append(m.workloads, newWorkload) 201 202 if newWorkload.IsReady() { 203 workloadReady = newWorkload 204 } 205 206 return nil 207 } 208 209 func (m *workloadManager) onPodDeleted(pod *corev1.Pod) (err error) { 210 m.mutex.Lock() 211 212 // After the method returns, notify the handler the ready state of the workload changed. 213 var workloadNotReady *workload 214 defer func() { 215 m.mutex.Unlock() 216 217 if workloadNotReady != nil { 218 m.handler.WorkloadNotReady(workloadNotReady) 219 } 220 }() 221 222 newWorkloads := make([]*workload, 0, len(m.workloads)) 223 for _, w := range m.workloads { 224 if w.pod.Name == pod.Name { 225 // Close the workload and remove it from the list. If an 226 // error occurs, just continue. 227 if w.IsReady() { 228 workloadNotReady = w 229 } 230 err = w.Close() 231 } else { 232 // Just add all other workloads. 233 newWorkloads = append(newWorkloads, w) 234 } 235 } 236 237 m.workloads = newWorkloads 238 return err 239 } 240 241 func (m *workloadManager) Close() (err error) { 242 m.mutex.Lock() 243 244 // Indicate we're closing. 245 m.closing = true 246 247 // Stop the controller and queue. 248 close(m.stopCh) 249 250 // Clear out the workloads array 251 workloads := m.workloads 252 m.workloads = nil 253 254 m.mutex.Unlock() 255 256 // Close the workloads. 257 for _, w := range workloads { 258 err = multierror.Append(err, w.Close()).ErrorOrNil() 259 } 260 return 261 }