istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/scope.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 framework
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"reflect"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/hashicorp/go-multierror"
    25  
    26  	"istio.io/istio/pkg/test/framework/resource"
    27  	"istio.io/istio/pkg/test/scopes"
    28  )
    29  
    30  // scope hold resources in a particular scope.
    31  type scope struct {
    32  	// friendly name for the scope for debugging purposes.
    33  	id string
    34  
    35  	parent *scope
    36  
    37  	resources []resource.Resource
    38  
    39  	closers []io.Closer
    40  
    41  	children []*scope
    42  
    43  	closeChan chan struct{}
    44  
    45  	topLevel bool
    46  
    47  	skipDump bool
    48  
    49  	// Mutex to lock changes to resources, children, and closers that can be done concurrently
    50  	mu sync.Mutex
    51  }
    52  
    53  func newScope(id string, p *scope) *scope {
    54  	s := &scope{
    55  		id:        id,
    56  		parent:    p,
    57  		closeChan: make(chan struct{}, 1),
    58  	}
    59  
    60  	if p != nil {
    61  		p.children = append(p.children, s)
    62  	}
    63  
    64  	return s
    65  }
    66  
    67  func (s *scope) add(r resource.Resource, id *resourceID) {
    68  	scopes.Framework.Debugf("Adding resource for tracking: %v", id)
    69  	s.mu.Lock()
    70  	defer s.mu.Unlock()
    71  	s.resources = append(s.resources, r)
    72  
    73  	if c, ok := r.(io.Closer); ok {
    74  		s.closers = append(s.closers, c)
    75  	}
    76  }
    77  
    78  func (s *scope) get(ref any) error {
    79  	s.mu.Lock()
    80  	defer s.mu.Unlock()
    81  
    82  	refVal := reflect.ValueOf(ref)
    83  	if refVal.Kind() != reflect.Ptr {
    84  		return fmt.Errorf("ref must be a pointer instead got: %T", ref)
    85  	}
    86  	// work with the underlying value rather than the pointer
    87  	refVal = refVal.Elem()
    88  
    89  	targetT := refVal.Type()
    90  	if refVal.Kind() == reflect.Slice {
    91  		// for slices look at the element type
    92  		targetT = targetT.Elem()
    93  	}
    94  
    95  	for _, res := range s.resources {
    96  		if res == nil {
    97  			continue
    98  		}
    99  		resVal := reflect.ValueOf(res)
   100  		if resVal.Type().AssignableTo(targetT) {
   101  			if refVal.Kind() == reflect.Slice {
   102  				refVal.Set(reflect.Append(refVal, resVal))
   103  			} else {
   104  				refVal.Set(resVal)
   105  				return nil
   106  			}
   107  		}
   108  	}
   109  
   110  	if s.parent != nil {
   111  		// either didn't find the value or need to continue filling the slice
   112  		return s.parent.get(ref)
   113  	}
   114  
   115  	if refVal.Kind() != reflect.Slice {
   116  		// didn't find the non-slice value
   117  		return fmt.Errorf("no %v in context", targetT)
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  func (s *scope) addCloser(c io.Closer) {
   124  	s.mu.Lock()
   125  	defer s.mu.Unlock()
   126  	s.closers = append(s.closers, c)
   127  }
   128  
   129  func (s *scope) done(nocleanup bool) error {
   130  	scopes.Framework.Debugf("Begin cleaning up scope: %v", s.id)
   131  
   132  	// First, wait for all of the children to be done.
   133  	for _, c := range s.children {
   134  		c.waitForDone()
   135  	}
   136  
   137  	// Upon returning, notify the parent that we're done.
   138  	defer func() {
   139  		close(s.closeChan)
   140  	}()
   141  
   142  	var err error
   143  	// Do reverse walk for cleanup.
   144  	for i := len(s.closers) - 1; i >= 0; i-- {
   145  		c := s.closers[i]
   146  
   147  		if nocleanup {
   148  			if cc, ok := c.(*closer); ok && cc.noskip {
   149  				continue
   150  			} else if !ok {
   151  				continue
   152  			}
   153  		}
   154  
   155  		name := "lambda"
   156  		if r, ok := c.(resource.Resource); ok {
   157  			name = fmt.Sprintf("resource %v", r.ID())
   158  		}
   159  
   160  		scopes.Framework.Debugf("Begin cleaning up %s", name)
   161  		if e := c.Close(); e != nil {
   162  			scopes.Framework.Debugf("Error cleaning up %s: %v", name, e)
   163  			err = multierror.Append(err, e).ErrorOrNil()
   164  		}
   165  		scopes.Framework.Debugf("Cleanup complete for %s", name)
   166  	}
   167  
   168  	s.mu.Lock()
   169  	s.resources = nil
   170  	s.closers = nil
   171  	s.mu.Unlock()
   172  
   173  	scopes.Framework.Debugf("Done cleaning up scope: %v", s.id)
   174  	return err
   175  }
   176  
   177  func (s *scope) waitForDone() {
   178  	<-s.closeChan
   179  }
   180  
   181  func (s *scope) skipDumping() {
   182  	s.mu.Lock()
   183  	defer s.mu.Unlock()
   184  	s.skipDump = true
   185  }
   186  
   187  func (s *scope) markTopLevel() {
   188  	s.mu.Lock()
   189  	defer s.mu.Unlock()
   190  	s.topLevel = true
   191  }
   192  
   193  func (s *scope) dump(ctx resource.Context, recursive bool) {
   194  	s.mu.Lock()
   195  	skip := s.skipDump
   196  	s.mu.Unlock()
   197  	if skip {
   198  		return
   199  	}
   200  	st := time.Now()
   201  	defer func() {
   202  		l := scopes.Framework.Debugf
   203  		if time.Since(st) > time.Second*10 {
   204  			// Log slow dumps at higher level
   205  			l = scopes.Framework.Infof
   206  		}
   207  		l("Done dumping: %s for %s (%v)", s.id, ctx.ID(), time.Since(st))
   208  	}()
   209  	s.mu.Lock()
   210  	defer s.mu.Unlock()
   211  	if recursive {
   212  		for _, c := range s.children {
   213  			c.dump(ctx, recursive)
   214  		}
   215  	}
   216  	wg := sync.WaitGroup{}
   217  	for _, c := range s.resources {
   218  		if d, ok := c.(resource.Dumper); ok {
   219  			d := d
   220  			wg.Add(1)
   221  			go func() {
   222  				d.Dump(ctx)
   223  				wg.Done()
   224  			}()
   225  		}
   226  	}
   227  	wg.Wait()
   228  }