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 }