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 }