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  }