istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/server/instance_test.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 server_test
    16  
    17  import (
    18  	"errors"
    19  	"testing"
    20  	"time"
    21  
    22  	. "github.com/onsi/gomega"
    23  	"go.uber.org/atomic"
    24  
    25  	"istio.io/istio/pilot/pkg/server"
    26  	"istio.io/istio/pkg/test"
    27  )
    28  
    29  func TestStartWithError(t *testing.T) {
    30  	g := NewWithT(t)
    31  
    32  	inst := server.New()
    33  	expected := errors.New("fake")
    34  	inst.RunComponent("fake", func(stop <-chan struct{}) error {
    35  		return expected
    36  	})
    37  
    38  	stop := newReclosableChannel()
    39  	t.Cleanup(stop.Close)
    40  	g.Expect(inst.Start(stop.c)).To(Equal(expected))
    41  }
    42  
    43  func TestStartWithNoError(t *testing.T) {
    44  	g := NewWithT(t)
    45  
    46  	inst := server.New()
    47  	c := newFakeComponent(0, test.NewStop(t))
    48  	inst.RunComponent("fake", c.Run)
    49  
    50  	stop := newReclosableChannel()
    51  	t.Cleanup(stop.Close)
    52  	g.Expect(inst.Start(stop.c)).To(BeNil())
    53  	g.Expect(c.started.Load()).To(BeTrue())
    54  }
    55  
    56  func TestRunComponentsAfterStart(t *testing.T) {
    57  	longDuration := 10 * time.Second
    58  	shortDuration := 10 * time.Millisecond
    59  	// Just used to make sure we do not leak goroutines
    60  	stop := test.NewStop(t)
    61  	cases := []struct {
    62  		name  string
    63  		c     *fakeComponent
    64  		async bool
    65  		wait  bool
    66  	}{
    67  		{
    68  			name: "RunComponent",
    69  			// Use a large duration - it will not complete before the end of the test.
    70  			// This is used to verify that we don't wait for it while shutting down.
    71  			c:     newFakeComponent(longDuration, stop),
    72  			async: false,
    73  			wait:  false,
    74  		},
    75  		{
    76  			name: "RunComponentAsync",
    77  			// Use a large duration - it will not complete before the end of the test.
    78  			// This is used to verify that we don't wait for it while shutting down.
    79  			c:     newFakeComponent(longDuration, stop),
    80  			async: true,
    81  			wait:  false,
    82  		},
    83  		{
    84  			name:  "RunComponentAsyncAndWait",
    85  			c:     newFakeComponent(shortDuration, stop),
    86  			async: true,
    87  			wait:  true,
    88  		},
    89  	}
    90  	for _, c := range cases {
    91  		c := c
    92  		t.Run(c.name, func(t *testing.T) {
    93  			g := NewWithT(t)
    94  
    95  			stop := newReclosableChannel()
    96  			t.Cleanup(stop.Close)
    97  			inst := server.New()
    98  			g.Expect(inst.Start(stop.c)).To(BeNil())
    99  
   100  			component := c.c.Run
   101  			go func() {
   102  				if c.async {
   103  					if c.wait {
   104  						inst.RunComponentAsyncAndWait("test", component)
   105  					} else {
   106  						inst.RunComponentAsync("test", component)
   107  					}
   108  				} else {
   109  					inst.RunComponent("test", component)
   110  				}
   111  			}()
   112  
   113  			// Ensure that the component is started.
   114  			g.Eventually(func() bool {
   115  				return c.c.started.Load()
   116  			}).Should(BeTrue())
   117  
   118  			// Stop before the tasks end.
   119  			stop.Close()
   120  			if c.wait {
   121  				// Add a little buffer to the task duration.
   122  				totalWaitTime := shortDuration + (1 * time.Second)
   123  				g.Eventually(func() bool {
   124  					return c.c.completed.Load()
   125  				}, totalWaitTime).Should(BeTrue())
   126  			} else {
   127  				g.Expect(c.c.completed.Load()).Should(BeFalse())
   128  			}
   129  		})
   130  	}
   131  }
   132  
   133  type reclosableChannel struct {
   134  	c      chan struct{}
   135  	closed bool
   136  }
   137  
   138  func newReclosableChannel() *reclosableChannel {
   139  	return &reclosableChannel{
   140  		c: make(chan struct{}),
   141  	}
   142  }
   143  
   144  func (c *reclosableChannel) Close() {
   145  	if !c.closed {
   146  		c.closed = true
   147  		close(c.c)
   148  	}
   149  }
   150  
   151  type fakeComponent struct {
   152  	started   *atomic.Bool
   153  	completed *atomic.Bool
   154  	d         time.Duration
   155  	stop      chan struct{}
   156  }
   157  
   158  func newFakeComponent(d time.Duration, stop chan struct{}) *fakeComponent {
   159  	return &fakeComponent{
   160  		started:   atomic.NewBool(false),
   161  		completed: atomic.NewBool(false),
   162  		d:         d,
   163  		stop:      stop,
   164  	}
   165  }
   166  
   167  func (c *fakeComponent) Run(stop <-chan struct{}) error {
   168  	c.started.Store(true)
   169  	select {
   170  	case <-time.After(c.d):
   171  	case <-c.stop: // ignore incoming stop; for test purposes we use our own stop
   172  	}
   173  	c.completed.Store(true)
   174  	return nil
   175  }