github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/pebblepoller_test.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter_test
     5  
     6  import (
     7  	"context"
     8  	"regexp"
     9  	"sync"
    10  	"time"
    11  
    12  	pebbleclient "github.com/canonical/pebble/client"
    13  	"github.com/juju/clock/testclock"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils/v3"
    18  	"github.com/juju/worker/v3/workertest"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/testing"
    22  	"github.com/juju/juju/worker/uniter"
    23  	"github.com/juju/juju/worker/uniter/container"
    24  )
    25  
    26  type pebblePollerSuite struct{}
    27  
    28  var _ = gc.Suite(&pebblePollerSuite{})
    29  
    30  const (
    31  	pebbleSocketPathRegexpString = "/charm/containers/([^/]+)/pebble.socket"
    32  )
    33  
    34  var (
    35  	pebbleSocketPathRegexp = regexp.MustCompile(pebbleSocketPathRegexpString)
    36  )
    37  
    38  func (s *pebblePollerSuite) TestStart(c *gc.C) {
    39  	clients := map[string]*fakePebbleClient{
    40  		"a": {
    41  			sysInfo: pebbleclient.SysInfo{
    42  				BootID: "1",
    43  			},
    44  			err: errors.Errorf("not yet workin"),
    45  		},
    46  		"b": {
    47  			sysInfo: pebbleclient.SysInfo{
    48  				BootID: "1",
    49  			},
    50  			err: errors.Errorf("not yet workin"),
    51  		},
    52  		"c": {
    53  			sysInfo: pebbleclient.SysInfo{
    54  				BootID: "1",
    55  			},
    56  			err: errors.Errorf("not yet workin"),
    57  		},
    58  	}
    59  	newClient := func(cfg *pebbleclient.Config) (uniter.PebbleClient, error) {
    60  		c.Assert(cfg.Socket, gc.Matches, pebbleSocketPathRegexpString)
    61  		res := pebbleSocketPathRegexp.FindAllStringSubmatch(cfg.Socket, 1)
    62  		return clients[res[0][1]], nil
    63  	}
    64  	clock := testclock.NewClock(time.Time{})
    65  	containerNames := []string{
    66  		"a", "b", "c",
    67  	}
    68  	workloadEventChan := make(chan string)
    69  	workloadEvents := container.NewWorkloadEvents()
    70  	worker := uniter.NewPebblePoller(loggo.GetLogger("test"), clock, containerNames, workloadEventChan, workloadEvents, newClient)
    71  
    72  	doRestart := func(containerName string) {
    73  		client := clients[containerName]
    74  		c.Assert(workloadEvents.Events(), gc.HasLen, 0)
    75  		client.TriggerStart()
    76  		timeout := time.After(testing.LongWait)
    77  		for {
    78  			select {
    79  			case id := <-workloadEventChan:
    80  				c.Logf("got queued log id %s", id)
    81  				c.Assert(workloadEvents.Events(), gc.HasLen, 1)
    82  				evt, cb, err := workloadEvents.GetWorkloadEvent(id)
    83  				c.Assert(err, jc.ErrorIsNil)
    84  				c.Assert(evt, gc.DeepEquals, container.WorkloadEvent{
    85  					Type:         container.ReadyEvent,
    86  					WorkloadName: containerName,
    87  				})
    88  				c.Assert(cb, gc.NotNil)
    89  				workloadEvents.RemoveWorkloadEvent(id)
    90  				cb(nil)
    91  				c.Assert(workloadEvents.Events(), gc.HasLen, 0)
    92  				return
    93  			case <-time.After(testing.ShortWait):
    94  				clock.Advance(5 * time.Second)
    95  			case <-timeout:
    96  				c.Fatalf("timed out waiting for event id")
    97  				return
    98  			}
    99  		}
   100  	}
   101  
   102  	doRestart("a")
   103  	doRestart("b")
   104  	doRestart("c")
   105  	doRestart("a")
   106  	doRestart("a")
   107  	doRestart("a")
   108  
   109  	workertest.CleanKill(c, worker)
   110  
   111  	for k, v := range clients {
   112  		c.Assert(v.closed, jc.IsTrue, gc.Commentf("client %s not closed", k))
   113  	}
   114  }
   115  
   116  type fakePebbleClient struct {
   117  	sysInfo     pebbleclient.SysInfo
   118  	err         error
   119  	mut         sync.Mutex
   120  	closed      bool
   121  	clock       *testclock.Clock
   122  	noticeAdded chan *pebbleclient.Notice
   123  }
   124  
   125  func (c *fakePebbleClient) SysInfo() (*pebbleclient.SysInfo, error) {
   126  	c.mut.Lock()
   127  	defer c.mut.Unlock()
   128  	if c.err != nil {
   129  		return nil, c.err
   130  	}
   131  	sysInfoCopy := c.sysInfo
   132  	return &sysInfoCopy, nil
   133  }
   134  
   135  func (c *fakePebbleClient) TriggerStart() {
   136  	c.mut.Lock()
   137  	defer c.mut.Unlock()
   138  	c.err = nil
   139  	c.sysInfo.BootID = utils.MustNewUUID().String()
   140  }
   141  
   142  func (c *fakePebbleClient) CloseIdleConnections() {
   143  	c.mut.Lock()
   144  	defer c.mut.Unlock()
   145  	c.closed = true
   146  }
   147  
   148  // AddNotice adds a notice for WaitNotices to receive. To have WaitNotices
   149  // return an error, use notice.Type "error" with the message in notice.Key.
   150  func (c *fakePebbleClient) AddNotice(checkC *gc.C, notice *pebbleclient.Notice) {
   151  	select {
   152  	case c.noticeAdded <- notice:
   153  	case <-time.After(testing.LongWait):
   154  		checkC.Fatalf("timed out waiting to add notice")
   155  	}
   156  }
   157  
   158  func (c *fakePebbleClient) WaitNotices(ctx context.Context, serverTimeout time.Duration, opts *pebbleclient.NoticesOptions) ([]*pebbleclient.Notice, error) {
   159  	timeoutCh := c.clock.After(serverTimeout)
   160  	for {
   161  		select {
   162  		case notice := <-c.noticeAdded:
   163  			if notice.Type == "error" {
   164  				return nil, errors.New(notice.Key)
   165  			}
   166  			if noticeMatches(notice, opts) {
   167  				return []*pebbleclient.Notice{notice}, nil
   168  			}
   169  		case <-timeoutCh:
   170  			return nil, nil // no notices after serverTimeout is not an error
   171  		case <-ctx.Done():
   172  			return nil, ctx.Err()
   173  		}
   174  	}
   175  }
   176  
   177  func noticeMatches(notice *pebbleclient.Notice, opts *pebbleclient.NoticesOptions) bool {
   178  	if opts == nil || opts.Types != nil || opts.Keys != nil {
   179  		panic("not supported")
   180  	}
   181  	if !opts.After.IsZero() && !notice.LastRepeated.After(opts.After) {
   182  		return false
   183  	}
   184  	return true
   185  }