github.com/fafucoder/cilium@v1.6.11/pkg/clustermesh/services_test.go (about)

     1  // Copyright 2018-2019 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 clustermesh
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path"
    24  	"time"
    25  
    26  	"github.com/cilium/cilium/pkg/defaults"
    27  	"github.com/cilium/cilium/pkg/identity"
    28  	"github.com/cilium/cilium/pkg/identity/cache"
    29  	"github.com/cilium/cilium/pkg/k8s"
    30  	"github.com/cilium/cilium/pkg/k8s/types"
    31  	"github.com/cilium/cilium/pkg/kvstore"
    32  	"github.com/cilium/cilium/pkg/loadbalancer"
    33  	"github.com/cilium/cilium/pkg/testutils"
    34  
    35  	. "gopkg.in/check.v1"
    36  	"k8s.io/api/core/v1"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  )
    39  
    40  var etcdConfig = []byte(fmt.Sprintf("endpoints:\n- %s\n", kvstore.EtcdDummyAddress()))
    41  
    42  func (s *ClusterMeshServicesTestSuite) prepareServiceUpdate(clusterSuffix, backendIP, portName, port string) (string, []byte) {
    43  	return "cilium/state/services/v1/" + s.randomName + clusterSuffix + "/default/foo",
    44  		[]byte(`{"cluster":"` + s.randomName + clusterSuffix + `","namespace":"default","name":"foo","frontends":{"172.20.0.177":{"port":{"protocol":"TCP","port":80}}},"backends":{"` + backendIP + `":{"` + portName + `":{"protocol":"TCP","port":` + port + `}}},"labels":{},"selector":{"name":"foo"}}`)
    45  
    46  }
    47  
    48  type ClusterMeshServicesTestSuite struct {
    49  	svcCache   k8s.ServiceCache
    50  	testDir    string
    51  	mesh       *ClusterMesh
    52  	randomName string
    53  }
    54  
    55  var (
    56  	_ = Suite(&ClusterMeshServicesTestSuite{})
    57  )
    58  
    59  func (s *ClusterMeshServicesTestSuite) SetUpTest(c *C) {
    60  	kvstore.SetupDummy("etcd")
    61  
    62  	s.randomName = testutils.RandomRune()
    63  
    64  	kvstore.Client().DeletePrefix("cilium/state/services/v1/" + s.randomName)
    65  	s.svcCache = k8s.NewServiceCache()
    66  	identity.InitWellKnownIdentities()
    67  	// The nils are only used by k8s CRD identities. We default to kvstore.
    68  	<-cache.InitIdentityAllocator(&identityAllocatorOwnerMock{}, nil, nil)
    69  	dir, err := ioutil.TempDir("", "multicluster")
    70  	s.testDir = dir
    71  	c.Assert(err, IsNil)
    72  
    73  	config1 := path.Join(dir, s.randomName+"1")
    74  	err = ioutil.WriteFile(config1, etcdConfig, 0644)
    75  	c.Assert(err, IsNil)
    76  
    77  	config2 := path.Join(dir, s.randomName+"2")
    78  	err = ioutil.WriteFile(config2, etcdConfig, 0644)
    79  	c.Assert(err, IsNil)
    80  
    81  	cm, err := NewClusterMesh(Configuration{
    82  		Name:            "test2",
    83  		ConfigDirectory: dir,
    84  		NodeKeyCreator:  testNodeCreator,
    85  		nodeObserver:    &testObserver{},
    86  		ServiceMerger:   &s.svcCache,
    87  	})
    88  	c.Assert(err, IsNil)
    89  	c.Assert(cm, Not(IsNil))
    90  
    91  	s.mesh = cm
    92  
    93  	// wait for both clusters to appear in the list of cm clusters
    94  	c.Assert(testutils.WaitUntil(func() bool {
    95  		return cm.NumReadyClusters() == 2
    96  	}, 10*time.Second), IsNil)
    97  }
    98  
    99  func (s *ClusterMeshServicesTestSuite) TearDownTest(c *C) {
   100  	if s.mesh != nil {
   101  		s.mesh.Close()
   102  	}
   103  
   104  	cache.Close()
   105  
   106  	os.RemoveAll(s.testDir)
   107  	kvstore.Client().DeletePrefix("cilium/state/services/v1/" + s.randomName)
   108  	kvstore.Client().Close()
   109  }
   110  
   111  func (s *ClusterMeshServicesTestSuite) expectEvent(c *C, action k8s.CacheAction, id k8s.ServiceID, fn func(event k8s.ServiceEvent) bool) {
   112  	c.Assert(testutils.WaitUntil(func() bool {
   113  		var event k8s.ServiceEvent
   114  		select {
   115  		case event = <-s.svcCache.Events:
   116  		case <-time.After(defaults.NodeDeleteDelay + time.Second*10):
   117  			c.Errorf("Timeout while waiting for event to be received")
   118  			return false
   119  		}
   120  
   121  		c.Assert(event.Action, Equals, action)
   122  		c.Assert(event.ID, Equals, id)
   123  
   124  		if fn != nil {
   125  			return fn(event)
   126  		}
   127  
   128  		return true
   129  	}, 2*time.Second), IsNil)
   130  }
   131  
   132  func (s *ClusterMeshServicesTestSuite) TestClusterMeshServicesGlobal(c *C) {
   133  	kvstore.Client().Set(s.prepareServiceUpdate("1", "10.0.185.196", "http", "80"))
   134  	kvstore.Client().Set(s.prepareServiceUpdate("2", "20.0.185.196", "http2", "90"))
   135  
   136  	k8sSvc := &types.Service{
   137  		Service: &v1.Service{
   138  			ObjectMeta: metav1.ObjectMeta{
   139  				Name:      "foo",
   140  				Namespace: "default",
   141  				Annotations: map[string]string{
   142  					"io.cilium/global-service": "true",
   143  				},
   144  			},
   145  			Spec: v1.ServiceSpec{
   146  				ClusterIP: "127.0.0.1",
   147  				Type:      v1.ServiceTypeClusterIP,
   148  			},
   149  		},
   150  	}
   151  
   152  	svcID := s.svcCache.UpdateService(k8sSvc)
   153  
   154  	s.expectEvent(c, k8s.UpdateService, svcID, func(event k8s.ServiceEvent) bool {
   155  		return event.Endpoints.Backends["10.0.185.196"] != nil &&
   156  			event.Endpoints.Backends["20.0.185.196"] != nil
   157  	})
   158  
   159  	k8sEndpoints := &types.Endpoints{
   160  		Endpoints: &v1.Endpoints{
   161  			ObjectMeta: metav1.ObjectMeta{
   162  				Name:      "foo",
   163  				Namespace: "default",
   164  			},
   165  			Subsets: []v1.EndpointSubset{
   166  				{
   167  					Addresses: []v1.EndpointAddress{{IP: "30.0.185.196"}},
   168  					Ports: []v1.EndpointPort{
   169  						{
   170  							Name:     "http",
   171  							Port:     100,
   172  							Protocol: v1.ProtocolTCP,
   173  						},
   174  					},
   175  				},
   176  			},
   177  		},
   178  	}
   179  
   180  	s.svcCache.UpdateEndpoints(k8sEndpoints)
   181  	s.expectEvent(c, k8s.UpdateService, svcID, func(event k8s.ServiceEvent) bool {
   182  		return event.Endpoints.Backends["30.0.185.196"] != nil
   183  	})
   184  
   185  	s.svcCache.DeleteEndpoints(k8sEndpoints)
   186  	s.expectEvent(c, k8s.UpdateService, svcID, func(event k8s.ServiceEvent) bool {
   187  		return event.Endpoints.Backends["30.0.185.196"] == nil
   188  	})
   189  
   190  	kvstore.Client().DeletePrefix("cilium/state/services/v1/" + s.randomName + "1")
   191  	s.expectEvent(c, k8s.UpdateService, svcID, nil)
   192  
   193  	kvstore.Client().DeletePrefix("cilium/state/services/v1/" + s.randomName + "2")
   194  	s.expectEvent(c, k8s.DeleteService, svcID, nil)
   195  }
   196  
   197  func (s *ClusterMeshServicesTestSuite) TestClusterMeshServicesUpdate(c *C) {
   198  	kvstore.Client().Set(s.prepareServiceUpdate("1", "10.0.185.196", "http", "80"))
   199  	kvstore.Client().Set(s.prepareServiceUpdate("2", "20.0.185.196", "http2", "90"))
   200  
   201  	k8sSvc := &types.Service{
   202  		Service: &v1.Service{
   203  			ObjectMeta: metav1.ObjectMeta{
   204  				Name:      "foo",
   205  				Namespace: "default",
   206  				Annotations: map[string]string{
   207  					"io.cilium/global-service": "true",
   208  				},
   209  			},
   210  			Spec: v1.ServiceSpec{
   211  				ClusterIP: "127.0.0.1",
   212  				Type:      v1.ServiceTypeClusterIP,
   213  			},
   214  		},
   215  	}
   216  
   217  	svcID := s.svcCache.UpdateService(k8sSvc)
   218  
   219  	s.expectEvent(c, k8s.UpdateService, svcID, func(event k8s.ServiceEvent) bool {
   220  		return event.Endpoints.Backends["10.0.185.196"]["http"].Equals(
   221  			loadbalancer.NewL4Addr(loadbalancer.TCP, 80)) &&
   222  			event.Endpoints.Backends["20.0.185.196"]["http2"].Equals(
   223  				loadbalancer.NewL4Addr(loadbalancer.TCP, 90))
   224  	})
   225  
   226  	kvstore.Client().Set(s.prepareServiceUpdate("1", "80.0.185.196", "http", "8080"))
   227  	s.expectEvent(c, k8s.UpdateService, svcID, func(event k8s.ServiceEvent) bool {
   228  		return event.Endpoints.Backends["80.0.185.196"] != nil &&
   229  			event.Endpoints.Backends["20.0.185.196"] != nil
   230  	})
   231  
   232  	kvstore.Client().Set(s.prepareServiceUpdate("2", "90.0.185.196", "http", "8080"))
   233  	s.expectEvent(c, k8s.UpdateService, svcID, func(event k8s.ServiceEvent) bool {
   234  		return event.Endpoints.Backends["80.0.185.196"] != nil &&
   235  			event.Endpoints.Backends["90.0.185.196"] != nil
   236  	})
   237  
   238  	kvstore.Client().DeletePrefix("cilium/state/services/v1/" + s.randomName + "1")
   239  	// The observer will have a defaults.NodeDeleteDelay time before it receives
   240  	// the event. For this reason we will trigger the delete events sequentially
   241  	// and only do the assertion in the end. This way we wait 30seconds for the
   242  	// test to complete instead of 30+30 seconds.
   243  	time.Sleep(2 * time.Second)
   244  	kvstore.Client().DeletePrefix("cilium/state/services/v1/" + s.randomName + "2")
   245  
   246  	s.expectEvent(c, k8s.UpdateService, svcID, nil)
   247  	s.expectEvent(c, k8s.DeleteService, svcID, nil)
   248  }
   249  
   250  func (s *ClusterMeshServicesTestSuite) TestClusterMeshServicesNonGlobal(c *C) {
   251  	kvstore.Client().Set(s.prepareServiceUpdate("1", "10.0.185.196", "http", "80"))
   252  	kvstore.Client().Set(s.prepareServiceUpdate("2", "20.0.185.196", "http2", "90"))
   253  
   254  	k8sSvc := &types.Service{
   255  		Service: &v1.Service{
   256  			ObjectMeta: metav1.ObjectMeta{
   257  				Name:      "foo",
   258  				Namespace: "default",
   259  				// shared annotation is NOT set
   260  			},
   261  			Spec: v1.ServiceSpec{
   262  				ClusterIP: "127.0.0.1",
   263  				Type:      v1.ServiceTypeClusterIP,
   264  			},
   265  		},
   266  	}
   267  
   268  	s.svcCache.UpdateService(k8sSvc)
   269  
   270  	time.Sleep(100 * time.Millisecond)
   271  	select {
   272  	case event := <-s.svcCache.Events:
   273  		c.Errorf("Unexpected service event received: %+v", event)
   274  	default:
   275  	}
   276  }