1//  Copyright 2019 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
15package framework
16
17import (
18	"fmt"
19	"io"
20	"sync"
21
22	"github.com/hashicorp/go-multierror"
23
24	"istio.io/istio/pkg/test/framework/resource"
25	"istio.io/istio/pkg/test/scopes"
26)
27
28// scope hold resources in a particular scope.
29type scope struct {
30	// friendly name for the scope for debugging purposes.
31	id string
32
33	parent *scope
34
35	resources []resource.Resource
36
37	closers []io.Closer
38
39	children []*scope
40
41	closeChan chan struct{}
42
43	// Mutex to lock changes to resources, children, and closers that can be done concurrently
44	mu sync.Mutex
45}
46
47func newScope(id string, p *scope) *scope {
48	s := &scope{
49		id:        id,
50		parent:    p,
51		closeChan: make(chan struct{}, 1),
52	}
53
54	if p != nil {
55		p.children = append(p.children, s)
56	}
57
58	return s
59}
60
61func (s *scope) add(r resource.Resource, id *resourceID) {
62	scopes.Framework.Debugf("Adding resource for tracking: %v", id)
63	s.mu.Lock()
64	defer s.mu.Unlock()
65	s.resources = append(s.resources, r)
66
67	if c, ok := r.(io.Closer); ok {
68		s.addCloser(c)
69	}
70}
71
72func (s *scope) addCloser(c io.Closer) {
73	s.closers = append(s.closers, c)
74}
75
76func (s *scope) done(nocleanup bool) error {
77	scopes.Framework.Debugf("Begin cleaning up scope: %v", s.id)
78
79	// First, wait for all of the children to be done.
80	for _, c := range s.children {
81		c.waitForDone()
82	}
83
84	// Upon returning, notify the parent that we're done.
85	defer func() {
86		close(s.closeChan)
87	}()
88
89	var err error
90	if !nocleanup {
91
92		// Do reverse walk for cleanup.
93		for i := len(s.closers) - 1; i >= 0; i-- {
94			c := s.closers[i]
95
96			name := "lambda"
97			if r, ok := c.(resource.Resource); ok {
98				name = fmt.Sprintf("resource %v", r.ID())
99			}
100
101			scopes.Framework.Debugf("Begin cleaning up %s", name)
102			if e := c.Close(); e != nil {
103				scopes.Framework.Debugf("Error cleaning up %s: %v", name, e)
104				err = multierror.Append(err, e)
105			}
106			scopes.Framework.Debugf("Cleanup complete for %s", name)
107		}
108	}
109	s.mu.Lock()
110	s.resources = nil
111	s.closers = nil
112	s.mu.Unlock()
113
114	scopes.Framework.Debugf("Done cleaning up scope: %v", s.id)
115	return err
116}
117
118func (s *scope) waitForDone() {
119	<-s.closeChan
120}
121
122func (s *scope) dump() {
123	s.mu.Lock()
124	defer s.mu.Unlock()
125	for _, c := range s.children {
126		c.dump()
127	}
128	for _, c := range s.resources {
129		if d, ok := c.(resource.Dumper); ok {
130			d.Dump()
131		}
132	}
133}
134