github.com/rohankumardubey/cilium@v1.6.12/daemon/endpoint_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 main
    18  
    19  import (
    20  	"context"
    21  	"net"
    22  	"runtime"
    23  	"time"
    24  
    25  	"github.com/cilium/cilium/api/v1/models"
    26  	apiEndpoint "github.com/cilium/cilium/api/v1/server/restapi/endpoint"
    27  	"github.com/cilium/cilium/pkg/checker"
    28  	"github.com/cilium/cilium/pkg/endpoint"
    29  	endpointid "github.com/cilium/cilium/pkg/endpoint/id"
    30  	"github.com/cilium/cilium/pkg/endpointmanager"
    31  	"github.com/cilium/cilium/pkg/eventqueue"
    32  	"github.com/cilium/cilium/pkg/identity"
    33  	"github.com/cilium/cilium/pkg/labels"
    34  	"github.com/cilium/cilium/pkg/metrics"
    35  	"github.com/cilium/cilium/pkg/option"
    36  	"github.com/cilium/cilium/pkg/testutils"
    37  
    38  	. "gopkg.in/check.v1"
    39  )
    40  
    41  func getEPTemplate(c *C, d *Daemon) *models.EndpointChangeRequest {
    42  	ip4, ip6, err := d.ipam.AllocateNext("", "test")
    43  	c.Assert(err, Equals, nil)
    44  	c.Assert(ip4, Not(IsNil))
    45  	c.Assert(ip6, Not(IsNil))
    46  
    47  	return &models.EndpointChangeRequest{
    48  		ContainerName: "foo",
    49  		State:         models.EndpointStateWaitingForIdentity,
    50  		Addressing: &models.AddressPair{
    51  			IPV6: ip6.IP.String(),
    52  			IPV4: ip4.IP.String(),
    53  		},
    54  	}
    55  }
    56  
    57  func (ds *DaemonSuite) TestEndpointAddReservedLabel(c *C) {
    58  	assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0)
    59  
    60  	epTemplate := getEPTemplate(c, ds.d)
    61  	epTemplate.Labels = []string{"reserved:world"}
    62  	_, code, err := ds.d.createEndpoint(context.TODO(), epTemplate)
    63  	c.Assert(err, Not(IsNil))
    64  	c.Assert(code, Equals, apiEndpoint.PutEndpointIDInvalidCode)
    65  
    66  	// Endpoint was created with invalid data; should transition from
    67  	// WaitForIdentity -> Invalid.
    68  	assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0)
    69  	assertOnMetric(c, string(models.EndpointStateInvalid), 0)
    70  
    71  	// Endpoint is created with inital label as well as disallowed
    72  	// reserved:world label.
    73  	epTemplate.Labels = append(epTemplate.Labels, "reserved:init")
    74  	_, code, err = ds.d.createEndpoint(context.TODO(), epTemplate)
    75  	c.Assert(err, ErrorMatches, "not allowed to add reserved labels:.+")
    76  	c.Assert(code, Equals, apiEndpoint.PutEndpointIDInvalidCode)
    77  
    78  	// Endpoint was created with invalid data; should transition from
    79  	// WaitForIdentity -> Invalid.
    80  	assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0)
    81  	assertOnMetric(c, string(models.EndpointStateInvalid), 0)
    82  }
    83  
    84  func (ds *DaemonSuite) TestEndpointAddInvalidLabel(c *C) {
    85  	assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0)
    86  
    87  	epTemplate := getEPTemplate(c, ds.d)
    88  	epTemplate.Labels = []string{"reserved:foo"}
    89  	_, code, err := ds.d.createEndpoint(context.TODO(), epTemplate)
    90  	c.Assert(err, Not(IsNil))
    91  	c.Assert(code, Equals, apiEndpoint.PutEndpointIDInvalidCode)
    92  
    93  	// Endpoint was created with invalid data; should transition from
    94  	// WaitForIdentity -> Invalid.
    95  	assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0)
    96  	assertOnMetric(c, string(models.EndpointStateInvalid), 0)
    97  }
    98  
    99  func (ds *DaemonSuite) TestEndpointAddNoLabels(c *C) {
   100  	assertOnMetric(c, string(models.EndpointStateWaitingForIdentity), 0)
   101  
   102  	// Create the endpoint without any labels.
   103  	epTemplate := getEPTemplate(c, ds.d)
   104  	_, _, err := ds.d.createEndpoint(context.TODO(), epTemplate)
   105  	c.Assert(err, IsNil)
   106  
   107  	// Endpoint enters WaitingToRegenerate as it has its labels updated during
   108  	// creation.
   109  	assertOnMetric(c, string(models.EndpointStateWaitingToRegenerate), 1)
   110  
   111  	expectedLabels := labels.Labels{
   112  		labels.IDNameInit: labels.NewLabel(labels.IDNameInit, "", labels.LabelSourceReserved),
   113  	}
   114  	// Check that the endpoint has the reserved:init label.
   115  	ep, err := endpointmanager.Lookup(endpointid.NewIPPrefixID(net.ParseIP(epTemplate.Addressing.IPV4)))
   116  	c.Assert(err, IsNil)
   117  	c.Assert(ep.OpLabels.IdentityLabels(), checker.DeepEquals, expectedLabels)
   118  
   119  	// Check that the endpoint received the reserved identity for the
   120  	// reserved:init entities.
   121  	timeout := time.NewTimer(3 * time.Second)
   122  	defer timeout.Stop()
   123  	tick := time.NewTicker(200 * time.Millisecond)
   124  	defer tick.Stop()
   125  	var secID *identity.Identity
   126  Loop:
   127  	for {
   128  		select {
   129  		case <-timeout.C:
   130  			break Loop
   131  		case <-tick.C:
   132  			ep.UnconditionalRLock()
   133  			secID = ep.SecurityIdentity
   134  			ep.RUnlock()
   135  			if secID != nil {
   136  				break Loop
   137  			}
   138  		}
   139  	}
   140  	c.Assert(secID, Not(IsNil))
   141  	c.Assert(secID.ID, Equals, identity.ReservedIdentityInit)
   142  
   143  	// Endpoint should transition from WaitingToRegenerate -> Ready.
   144  	assertOnMetric(c, string(models.EndpointStateWaitingToRegenerate), 0)
   145  	assertOnMetric(c, string(models.EndpointStateReady), 1)
   146  }
   147  
   148  func (ds *DaemonSuite) TestUpdateSecLabels(c *C) {
   149  	lbls := labels.NewLabelsFromModel([]string{"reserved:world"})
   150  	code, err := ds.d.modifyEndpointIdentityLabelsFromAPI("1", lbls, nil)
   151  	c.Assert(err, Not(IsNil))
   152  	c.Assert(code, Equals, apiEndpoint.PatchEndpointIDLabelsUpdateFailedCode)
   153  }
   154  
   155  func (ds *DaemonSuite) TestUpdateLabelsFailed(c *C) {
   156  	cancelledContext, cancelFunc := context.WithTimeout(context.Background(), 1*time.Second)
   157  	cancelFunc() // Cancel immediatly to trigger the codepath to test.
   158  
   159  	// Create the endpoint without any labels.
   160  	epTemplate := getEPTemplate(c, ds.d)
   161  	_, _, err := ds.d.createEndpoint(cancelledContext, epTemplate)
   162  	c.Assert(err, ErrorMatches, "request cancelled while resolving identity")
   163  
   164  	assertOnMetric(c, string(models.EndpointStateReady), 0)
   165  }
   166  
   167  func getMetricValue(state string) int64 {
   168  	return int64(metrics.GetGaugeValue(metrics.EndpointStateCount.WithLabelValues(state)))
   169  }
   170  
   171  func assertOnMetric(c *C, state string, expected int64) {
   172  	_, _, line, _ := runtime.Caller(1)
   173  
   174  	obtainedValues := make(map[int64]struct{}, 0)
   175  	err := testutils.WaitUntil(func() bool {
   176  		obtained := getMetricValue(state)
   177  		obtainedValues[obtained] = struct{}{}
   178  		return obtained == expected
   179  	}, 10*time.Second)
   180  	if err != nil {
   181  		// We are printing the map here to show every unique obtained metrics
   182  		// value because these values change rapidly and it may be misleading
   183  		// to only show the last obtained value.
   184  		c.Errorf("Metrics assertion failed on line %d for Endpoint state %s: obtained %v, expected %d",
   185  			line, state, obtainedValues, expected)
   186  	}
   187  }
   188  
   189  type EndpointDeadlockEvent struct {
   190  	ep           *endpoint.Endpoint
   191  	deadlockChan chan struct{}
   192  }
   193  
   194  var (
   195  	deadlockTimeout     = 2 * time.Second
   196  	deadlockTestTimeout = 3*deadlockTimeout + 1*time.Second
   197  )
   198  
   199  func (n *EndpointDeadlockEvent) Handle(ifc chan interface{}) {
   200  	// We need to sleep here so that we are consuming an event off the queue,
   201  	// but not acquiring the lock yet.
   202  	// There isn't much of a better way to ensure that an Event is being
   203  	// processed off of the EventQueue, but hasn't acquired the Endpoint's
   204  	// lock *before* we call deleteEndpointQuiet (see below test).
   205  	close(n.deadlockChan)
   206  	time.Sleep(deadlockTimeout)
   207  	n.ep.UnconditionalLock()
   208  	n.ep.Unlock()
   209  }
   210  
   211  // This unit test is a bit weird - see
   212  // https://github.com/cilium/cilium/pull/8687 .
   213  func (ds *DaemonSuite) TestEndpointEventQueueDeadlockUponDeletion(c *C) {
   214  	// Need to modify global configuration (hooray!), change back when test is
   215  	// done.
   216  	oldQueueSize := option.Config.EndpointQueueSize
   217  	option.Config.EndpointQueueSize = 1
   218  	defer func() {
   219  		option.Config.EndpointQueueSize = oldQueueSize
   220  	}()
   221  
   222  	// Create the endpoint without any labels.
   223  	epTemplate := getEPTemplate(c, ds.d)
   224  	ep, _, err := ds.d.createEndpoint(context.TODO(), epTemplate)
   225  	c.Assert(err, IsNil)
   226  	c.Assert(ep, Not(IsNil))
   227  
   228  	// In case deadlock occurs, provide a timeout of 3 (number of events) *
   229  	// deadlockTimeout + 1 seconds to ensure that we are actually testing for
   230  	// deadlock, and not prematurely exiting, and also so the test suite doesn't
   231  	// hang forever.
   232  	ctx, cancel := context.WithTimeout(context.Background(), deadlockTestTimeout)
   233  	defer cancel()
   234  
   235  	// Create three events that go on the endpoint's EventQueue. We need three
   236  	// events because the first event enqueued immediately is consumed off of
   237  	// the queue; the second event is put onto the queue (which has length of
   238  	// one), and the third queue is waiting for the queue's buffer to not be
   239  	// full (e.g., the first event is finished processing). If the first event
   240  	// gets stuck processing forever due to deadlock, then the third event
   241  	// will never be consumed, and the endpoint's EventQueue will never be
   242  	// closed because Enqueue gets stuck.
   243  	ev1Ch := make(chan struct{})
   244  	ev2Ch := make(chan struct{})
   245  	ev3Ch := make(chan struct{})
   246  
   247  	ev := eventqueue.NewEvent(&EndpointDeadlockEvent{
   248  		ep:           ep,
   249  		deadlockChan: ev1Ch,
   250  	})
   251  
   252  	ev2 := eventqueue.NewEvent(&EndpointDeadlockEvent{
   253  		ep:           ep,
   254  		deadlockChan: ev2Ch,
   255  	})
   256  
   257  	ev3 := eventqueue.NewEvent(&EndpointDeadlockEvent{
   258  		ep:           ep,
   259  		deadlockChan: ev3Ch,
   260  	})
   261  
   262  	ev2EnqueueCh := make(chan struct{})
   263  
   264  	go func() {
   265  		_, err := ep.EventQueue.Enqueue(ev)
   266  		c.Assert(err, IsNil)
   267  		_, err = ep.EventQueue.Enqueue(ev2)
   268  		c.Assert(err, IsNil)
   269  		close(ev2EnqueueCh)
   270  		_, err = ep.EventQueue.Enqueue(ev3)
   271  		c.Assert(err, IsNil)
   272  	}()
   273  
   274  	// Ensure that the second event is enqueued before proceeding further, as
   275  	// we need to assume that at least one event is being processed, and another
   276  	// one is pushed onto the endpoint's EventQueue.
   277  	<-ev2EnqueueCh
   278  	epDelComplete := make(chan struct{})
   279  
   280  	// Launch endpoint deletion async so that we do not deadlock (which is what
   281  	// this unit test is designed to test).
   282  	go func(ch chan struct{}) {
   283  		errors := ds.d.deleteEndpointQuiet(ep, endpoint.DeleteConfig{})
   284  		c.Assert(errors, Not(IsNil))
   285  		epDelComplete <- struct{}{}
   286  	}(epDelComplete)
   287  
   288  	select {
   289  	case <-ctx.Done():
   290  		c.Log("endpoint deletion did not complete in time")
   291  		c.Fail()
   292  	case <-epDelComplete:
   293  		// Success, do nothing.
   294  	}
   295  }