github.com/imran-kn/cilium-fork@v1.6.9/pkg/envoy/xds/ack_test.go (about)

     1  // Copyright 2018 Authors of Cilium
     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  // +build !privileged_tests
    16  
    17  package xds
    18  
    19  import (
    20  	"context"
    21  	"time"
    22  
    23  	"github.com/cilium/cilium/pkg/checker"
    24  	"github.com/cilium/cilium/pkg/completion"
    25  
    26  	. "gopkg.in/check.v1"
    27  )
    28  
    29  type AckSuite struct{}
    30  
    31  var _ = Suite(&AckSuite{})
    32  
    33  const (
    34  	node0 = "10.0.0.0"
    35  	node1 = "10.0.0.1"
    36  	node2 = "10.0.0.2"
    37  )
    38  
    39  type compCheck struct {
    40  	err error
    41  	ch  chan error
    42  }
    43  
    44  func newCompCheck() *compCheck {
    45  	return &compCheck{
    46  		ch: make(chan error, 1),
    47  	}
    48  }
    49  
    50  func (c *compCheck) Err() error {
    51  	return c.err
    52  }
    53  
    54  // Return a new completion callback that will write the completion error to a channel
    55  func newCompCallback() (func(error), *compCheck) {
    56  	comp := newCompCheck()
    57  	callback := func(err error) {
    58  		log.WithError(err).Debug("callback called")
    59  		comp.ch <- err
    60  		close(comp.ch)
    61  	}
    62  	return callback, comp
    63  }
    64  
    65  // IsCompletedChecker checks that a Completion is completed without errors.
    66  type IsCompletedChecker struct {
    67  	*CheckerInfo
    68  }
    69  
    70  func (c *IsCompletedChecker) Check(params []interface{}, names []string) (result bool, err string) {
    71  	comp, ok := params[0].(*compCheck)
    72  	if !ok {
    73  		return false, "completion must be a *compCheck"
    74  	}
    75  	if comp == nil {
    76  		return false, "completion is nil"
    77  	}
    78  
    79  	// receive from a closed channel returns nil, so test for a previous error before trying again
    80  	if comp.err != nil {
    81  		return false, string(err)
    82  	}
    83  
    84  	select {
    85  	case comp.err = <-comp.ch:
    86  		return comp.err == nil, string(err)
    87  	default:
    88  		return false, "not completed yet"
    89  	}
    90  }
    91  
    92  // IsCompleted checks that a Completion is completed.
    93  var IsCompleted Checker = &IsCompletedChecker{
    94  	&CheckerInfo{Name: "IsCompleted", Params: []string{
    95  		"completion"}},
    96  }
    97  
    98  func (s *AckSuite) TestUpsertSingleNode(c *C) {
    99  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   100  	defer cancel()
   101  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   102  	wg := completion.NewWaitGroup(ctx)
   103  
   104  	// Empty cache is the version 1
   105  	cache := NewCache()
   106  	acker := NewAckingResourceMutatorWrapper(cache)
   107  	c.Assert(acker.ackedVersions, HasLen, 0)
   108  
   109  	// Create version 2 with resource 0.
   110  	callback, comp := newCompCallback()
   111  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback)
   112  	c.Assert(comp, Not(IsCompleted))
   113  	c.Assert(acker.ackedVersions, HasLen, 0)
   114  
   115  	// Ack the right version, for the right resource, from another node.
   116  	acker.HandleResourceVersionAck(2, 2, node1, []string{resources[0].Name}, typeURL, "")
   117  	c.Assert(comp, Not(IsCompleted))
   118  	c.Assert(acker.ackedVersions, HasLen, 1)
   119  	c.Assert(acker.ackedVersions[node1], Equals, uint64(2))
   120  
   121  	// Ack the right version, for another resource, from the right node.
   122  	acker.HandleResourceVersionAck(2, 2, node0, []string{resources[1].Name}, typeURL, "")
   123  	c.Assert(comp, Not(IsCompleted))
   124  	c.Assert(acker.ackedVersions, HasLen, 2)
   125  	c.Assert(acker.ackedVersions[node0], Equals, uint64(2))
   126  
   127  	// Ack an older version, for the right resource, from the right node.
   128  	acker.HandleResourceVersionAck(1, 1, node0, []string{resources[0].Name}, typeURL, "")
   129  	c.Assert(comp, Not(IsCompleted))
   130  	c.Assert(acker.ackedVersions, HasLen, 2)
   131  	c.Assert(acker.ackedVersions[node0], Equals, uint64(2))
   132  
   133  	// Ack the right version, for the right resource, from the right node.
   134  	acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "")
   135  	c.Assert(comp, IsCompleted)
   136  	c.Assert(acker.ackedVersions, HasLen, 2)
   137  	c.Assert(acker.ackedVersions[node0], Equals, uint64(2))
   138  }
   139  
   140  func (s *AckSuite) TestUseCurrent(c *C) {
   141  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   142  	defer cancel()
   143  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   144  	wg := completion.NewWaitGroup(ctx)
   145  
   146  	// Empty cache is the version 1
   147  	cache := NewCache()
   148  	acker := NewAckingResourceMutatorWrapper(cache)
   149  	c.Assert(acker.ackedVersions, HasLen, 0)
   150  
   151  	// Create version 2 with resource 0.
   152  	callback, comp := newCompCallback()
   153  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback)
   154  	c.Assert(comp, Not(IsCompleted))
   155  	c.Assert(acker.ackedVersions, HasLen, 0)
   156  	c.Assert(acker.pendingCompletions, HasLen, 1)
   157  
   158  	// Ack the right version, for the right resource, from another node.
   159  	acker.HandleResourceVersionAck(2, 2, node1, []string{resources[0].Name}, typeURL, "")
   160  	c.Assert(comp, Not(IsCompleted))
   161  	c.Assert(acker.ackedVersions, HasLen, 1)
   162  	c.Assert(acker.ackedVersions[node1], Equals, uint64(2))
   163  	c.Assert(acker.pendingCompletions, HasLen, 1)
   164  
   165  	// Use current version, not yet acked
   166  	acker.UseCurrent(typeURL, []string{node0}, wg)
   167  	c.Assert(acker.pendingCompletions, HasLen, 2)
   168  
   169  	// Ack the right version, for another resource, from the right node.
   170  	acker.HandleResourceVersionAck(2, 2, node0, []string{resources[1].Name}, typeURL, "")
   171  	c.Assert(comp, Not(IsCompleted))
   172  	c.Assert(acker.ackedVersions, HasLen, 2)
   173  	c.Assert(acker.ackedVersions[node0], Equals, uint64(2))
   174  	// UseCurrent ignores resource names, so an ack of the same or later version from the right node will complete it
   175  	c.Assert(acker.pendingCompletions, HasLen, 1)
   176  
   177  	// Ack an older version, for the right resource, from the right node.
   178  	acker.HandleResourceVersionAck(1, 1, node0, []string{resources[0].Name}, typeURL, "")
   179  	c.Assert(comp, Not(IsCompleted))
   180  	c.Assert(acker.ackedVersions, HasLen, 2)
   181  	c.Assert(acker.ackedVersions[node0], Equals, uint64(2))
   182  	c.Assert(acker.pendingCompletions, HasLen, 1)
   183  
   184  	// Ack the right version, for the right resource, from the right node.
   185  	acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "")
   186  	c.Assert(comp, IsCompleted)
   187  	c.Assert(acker.ackedVersions, HasLen, 2)
   188  	c.Assert(acker.ackedVersions[node0], Equals, uint64(2))
   189  	c.Assert(acker.pendingCompletions, HasLen, 0)
   190  }
   191  
   192  func (s *AckSuite) TestUpsertMultipleNodes(c *C) {
   193  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   194  	defer cancel()
   195  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   196  	wg := completion.NewWaitGroup(ctx)
   197  
   198  	// Empty cache is the version 1
   199  	cache := NewCache()
   200  	acker := NewAckingResourceMutatorWrapper(cache)
   201  	c.Assert(acker.ackedVersions, HasLen, 0)
   202  
   203  	// Create version 2 with resource 0.
   204  	callback, comp := newCompCallback()
   205  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0, node1}, wg, callback)
   206  	c.Assert(comp, Not(IsCompleted))
   207  	c.Assert(acker.currentVersionAcked([]string{node0}), Equals, false)
   208  	c.Assert(acker.currentVersionAcked([]string{node1}), Equals, false)
   209  	c.Assert(acker.currentVersionAcked([]string{node2}), Equals, false)
   210  
   211  	// Ack the right version, for the right resource, from another node.
   212  	acker.HandleResourceVersionAck(2, 2, node2, []string{resources[0].Name}, typeURL, "")
   213  	c.Assert(comp, Not(IsCompleted))
   214  	c.Assert(acker.currentVersionAcked([]string{node0}), Equals, false)
   215  	c.Assert(acker.currentVersionAcked([]string{node1}), Equals, false)
   216  	c.Assert(acker.currentVersionAcked([]string{node2}), Equals, true)
   217  
   218  	// Ack the right version, for the right resource, from one of the nodes (node0).
   219  	// One of the nodes (node1) still needs to ACK.
   220  	acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "")
   221  	c.Assert(comp, Not(IsCompleted))
   222  	c.Assert(acker.currentVersionAcked([]string{node0}), Equals, true)
   223  	c.Assert(acker.currentVersionAcked([]string{node1}), Equals, false)
   224  	c.Assert(acker.currentVersionAcked([]string{node2}), Equals, true)
   225  	c.Assert(acker.currentVersionAcked([]string{node0, node1}), Equals, false)
   226  	c.Assert(acker.currentVersionAcked([]string{node0, node2}), Equals, true)
   227  
   228  	// Ack the right version, for the right resource, from the last remaining node (node1).
   229  	acker.HandleResourceVersionAck(2, 2, node1, []string{resources[0].Name}, typeURL, "")
   230  	c.Assert(comp, IsCompleted)
   231  	c.Assert(acker.currentVersionAcked([]string{node0}), Equals, true)
   232  	c.Assert(acker.currentVersionAcked([]string{node1}), Equals, true)
   233  	c.Assert(acker.currentVersionAcked([]string{node2}), Equals, true)
   234  	c.Assert(acker.currentVersionAcked([]string{node0, node1, node2}), Equals, true)
   235  }
   236  
   237  func (s *AckSuite) TestUpsertMoreRecentVersion(c *C) {
   238  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   239  	defer cancel()
   240  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   241  	wg := completion.NewWaitGroup(ctx)
   242  
   243  	// Empty cache is the version 1
   244  	cache := NewCache()
   245  	acker := NewAckingResourceMutatorWrapper(cache)
   246  
   247  	// Create version 2 with resource 0.
   248  	callback, comp := newCompCallback()
   249  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback)
   250  	c.Assert(comp, Not(IsCompleted))
   251  
   252  	// Ack an older version, for the right resource, from the right node.
   253  	acker.HandleResourceVersionAck(1, 1, node0, []string{resources[0].Name}, typeURL, "")
   254  	c.Assert(comp, Not(IsCompleted))
   255  
   256  	// Ack a more recent version, for the right resource, from the right node.
   257  	acker.HandleResourceVersionAck(123, 123, node0, []string{resources[0].Name}, typeURL, "")
   258  	c.Assert(comp, IsCompleted)
   259  }
   260  
   261  func (s *AckSuite) TestUpsertMoreRecentVersionNack(c *C) {
   262  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   263  	defer cancel()
   264  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   265  	wg := completion.NewWaitGroup(ctx)
   266  
   267  	// Empty cache is the version 1
   268  	cache := NewCache()
   269  	acker := NewAckingResourceMutatorWrapper(cache)
   270  
   271  	// Create version 2 with resource 0.
   272  	callback, comp := newCompCallback()
   273  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback)
   274  	c.Assert(comp, Not(IsCompleted))
   275  
   276  	// Ack an older version, for the right resource, from the right node.
   277  	acker.HandleResourceVersionAck(1, 1, node0, []string{resources[0].Name}, typeURL, "")
   278  	c.Assert(comp, Not(IsCompleted))
   279  
   280  	// NAck a more recent version, for the right resource, from the right node.
   281  	acker.HandleResourceVersionAck(1, 2, node0, []string{resources[0].Name}, typeURL, "Detail")
   282  	// IsCompleted is true only for completions without error
   283  	c.Assert(comp, Not(IsCompleted))
   284  	c.Assert(comp.Err(), Not(Equals), nil)
   285  	c.Assert(comp.Err(), checker.DeepEquals, &ProxyError{Err: ErrNackReceived, Detail: "Detail"})
   286  }
   287  
   288  func (s *AckSuite) TestDeleteSingleNode(c *C) {
   289  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   290  	defer cancel()
   291  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   292  	wg := completion.NewWaitGroup(ctx)
   293  
   294  	// Empty cache is the version 1
   295  	cache := NewCache()
   296  	acker := NewAckingResourceMutatorWrapper(cache)
   297  
   298  	// Create version 2 with resource 0.
   299  	callback, comp := newCompCallback()
   300  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback)
   301  	c.Assert(comp, Not(IsCompleted))
   302  
   303  	// Ack the right version, for the right resource, from the right node.
   304  	acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "")
   305  	c.Assert(comp, IsCompleted)
   306  
   307  	// Create version 3 with no resources.
   308  	callback, comp = newCompCallback()
   309  	acker.Delete(typeURL, resources[0].Name, []string{node0}, wg, callback)
   310  	c.Assert(comp, Not(IsCompleted))
   311  
   312  	// Ack the right version, for another resource, from another node.
   313  	acker.HandleResourceVersionAck(3, 3, node1, []string{resources[2].Name}, typeURL, "")
   314  	c.Assert(comp, Not(IsCompleted))
   315  
   316  	// Ack the right version, for another resource, from the right node.
   317  	acker.HandleResourceVersionAck(3, 3, node0, []string{resources[2].Name}, typeURL, "")
   318  	// The resource name is ignored. For delete, we only consider the version.
   319  	c.Assert(comp, IsCompleted)
   320  }
   321  
   322  func (s *AckSuite) TestDeleteMultipleNodes(c *C) {
   323  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   324  	defer cancel()
   325  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   326  	wg := completion.NewWaitGroup(ctx)
   327  
   328  	// Empty cache is the version 1
   329  	cache := NewCache()
   330  	acker := NewAckingResourceMutatorWrapper(cache)
   331  
   332  	// Create version 2 with resource 0.
   333  	callback, comp := newCompCallback()
   334  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, wg, callback)
   335  	c.Assert(comp, Not(IsCompleted))
   336  
   337  	// Ack the right version, for the right resource, from the right node.
   338  	acker.HandleResourceVersionAck(2, 2, node0, []string{resources[0].Name}, typeURL, "")
   339  	c.Assert(comp, IsCompleted)
   340  
   341  	// Create version 3 with no resources.
   342  	callback, comp = newCompCallback()
   343  	acker.Delete(typeURL, resources[0].Name, []string{node0, node1}, wg, callback)
   344  	c.Assert(comp, Not(IsCompleted))
   345  
   346  	// Ack the right version, for another resource, from one of the nodes.
   347  	acker.HandleResourceVersionAck(3, 3, node1, []string{resources[2].Name}, typeURL, "")
   348  	c.Assert(comp, Not(IsCompleted))
   349  
   350  	// Ack the right version, for another resource, from the remaining node.
   351  	acker.HandleResourceVersionAck(3, 3, node0, []string{resources[2].Name}, typeURL, "")
   352  	// The resource name is ignored. For delete, we only consider the version.
   353  	c.Assert(comp, IsCompleted)
   354  }
   355  
   356  func (s *AckSuite) TestRevertInsert(c *C) {
   357  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   358  	defer cancel()
   359  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   360  	wg := completion.NewWaitGroup(ctx)
   361  
   362  	cache := NewCache()
   363  	acker := NewAckingResourceMutatorWrapper(cache)
   364  
   365  	// Create version 1 with resource 0.
   366  	// Insert.
   367  	revert := acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, nil, nil)
   368  
   369  	// Insert another resource.
   370  	_ = acker.Upsert(typeURL, resources[2].Name, resources[2], []string{node0}, nil, nil)
   371  
   372  	res, err := cache.Lookup(typeURL, resources[0].Name)
   373  	c.Assert(err, IsNil)
   374  	c.Assert(res, Equals, resources[0])
   375  
   376  	res, err = cache.Lookup(typeURL, resources[2].Name)
   377  	c.Assert(err, IsNil)
   378  	c.Assert(res, Equals, resources[2])
   379  
   380  	comp := wg.AddCompletion()
   381  	defer comp.Complete(nil)
   382  	revert(comp)
   383  
   384  	res, err = cache.Lookup(typeURL, resources[0].Name)
   385  	c.Assert(err, IsNil)
   386  	c.Assert(res, IsNil)
   387  
   388  	res, err = cache.Lookup(typeURL, resources[2].Name)
   389  	c.Assert(err, IsNil)
   390  	c.Assert(res, Equals, resources[2])
   391  }
   392  
   393  func (s *AckSuite) TestRevertUpdate(c *C) {
   394  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   395  	defer cancel()
   396  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   397  	wg := completion.NewWaitGroup(ctx)
   398  
   399  	cache := NewCache()
   400  	acker := NewAckingResourceMutatorWrapper(cache)
   401  
   402  	// Create version 1 with resource 0.
   403  	// Insert.
   404  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, nil, nil)
   405  
   406  	// Insert another resource.
   407  	_ = acker.Upsert(typeURL, resources[2].Name, resources[2], []string{node0}, nil, nil)
   408  
   409  	res, err := cache.Lookup(typeURL, resources[0].Name)
   410  	c.Assert(err, IsNil)
   411  	c.Assert(res, Equals, resources[0])
   412  
   413  	res, err = cache.Lookup(typeURL, resources[2].Name)
   414  	c.Assert(err, IsNil)
   415  	c.Assert(res, Equals, resources[2])
   416  
   417  	// Update.
   418  	revert := acker.Upsert(typeURL, resources[0].Name, resources[1], []string{node0}, nil, nil)
   419  
   420  	res, err = cache.Lookup(typeURL, resources[0].Name)
   421  	c.Assert(err, IsNil)
   422  	c.Assert(res, Equals, resources[1])
   423  
   424  	comp := wg.AddCompletion()
   425  	defer comp.Complete(nil)
   426  	revert(comp)
   427  
   428  	res, err = cache.Lookup(typeURL, resources[0].Name)
   429  	c.Assert(err, IsNil)
   430  	c.Assert(res, Equals, resources[0])
   431  
   432  	res, err = cache.Lookup(typeURL, resources[2].Name)
   433  	c.Assert(err, IsNil)
   434  	c.Assert(res, Equals, resources[2])
   435  }
   436  
   437  func (s *AckSuite) TestRevertDelete(c *C) {
   438  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   439  	defer cancel()
   440  	typeURL := "type.googleapis.com/envoy.api.v2.DummyConfiguration"
   441  	wg := completion.NewWaitGroup(ctx)
   442  
   443  	cache := NewCache()
   444  	acker := NewAckingResourceMutatorWrapper(cache)
   445  
   446  	// Create version 1 with resource 0.
   447  	// Insert.
   448  	acker.Upsert(typeURL, resources[0].Name, resources[0], []string{node0}, nil, nil)
   449  
   450  	// Insert another resource.
   451  	_ = acker.Upsert(typeURL, resources[2].Name, resources[2], []string{node0}, nil, nil)
   452  
   453  	res, err := cache.Lookup(typeURL, resources[0].Name)
   454  	c.Assert(err, IsNil)
   455  	c.Assert(res, Equals, resources[0])
   456  
   457  	res, err = cache.Lookup(typeURL, resources[2].Name)
   458  	c.Assert(err, IsNil)
   459  	c.Assert(res, Equals, resources[2])
   460  
   461  	// Delete.
   462  	revert := acker.Delete(typeURL, resources[0].Name, []string{node0}, nil, nil)
   463  
   464  	res, err = cache.Lookup(typeURL, resources[0].Name)
   465  	c.Assert(err, IsNil)
   466  	c.Assert(res, IsNil)
   467  
   468  	res, err = cache.Lookup(typeURL, resources[2].Name)
   469  	c.Assert(err, IsNil)
   470  	c.Assert(res, Equals, resources[2])
   471  
   472  	comp := wg.AddCompletion()
   473  	defer comp.Complete(nil)
   474  	revert(comp)
   475  
   476  	res, err = cache.Lookup(typeURL, resources[0].Name)
   477  	c.Assert(err, IsNil)
   478  	c.Assert(res, Equals, resources[0])
   479  
   480  	res, err = cache.Lookup(typeURL, resources[2].Name)
   481  	c.Assert(err, IsNil)
   482  	c.Assert(res, Equals, resources[2])
   483  }