ingress.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package network
  14. import (
  15. "context"
  16. "encoding/json"
  17. "fmt"
  18. "net/http"
  19. "path/filepath"
  20. "strings"
  21. "time"
  22. compute "google.golang.org/api/compute/v1"
  23. v1 "k8s.io/api/core/v1"
  24. rbacv1 "k8s.io/api/rbac/v1"
  25. apierrors "k8s.io/apimachinery/pkg/api/errors"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/runtime/schema"
  28. "k8s.io/apimachinery/pkg/util/uuid"
  29. "k8s.io/apimachinery/pkg/util/wait"
  30. "k8s.io/apiserver/pkg/authentication/serviceaccount"
  31. "k8s.io/kubernetes/test/e2e/framework"
  32. "k8s.io/kubernetes/test/e2e/framework/auth"
  33. "k8s.io/kubernetes/test/e2e/framework/ingress"
  34. "k8s.io/kubernetes/test/e2e/framework/providers/gce"
  35. e2eservice "k8s.io/kubernetes/test/e2e/framework/service"
  36. e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
  37. "github.com/onsi/ginkgo"
  38. )
  39. const (
  40. negUpdateTimeout = 2 * time.Minute
  41. instanceGroupAnnotation = "ingress.gcp.kubernetes.io/instance-groups"
  42. )
  43. var _ = SIGDescribe("Loadbalancing: L7", func() {
  44. defer ginkgo.GinkgoRecover()
  45. var (
  46. ns string
  47. jig *ingress.TestJig
  48. conformanceTests []ingress.ConformanceTests
  49. )
  50. f := framework.NewDefaultFramework("ingress")
  51. ginkgo.BeforeEach(func() {
  52. jig = ingress.NewIngressTestJig(f.ClientSet)
  53. ns = f.Namespace.Name
  54. // this test wants powerful permissions. Since the namespace names are unique, we can leave this
  55. // lying around so we don't have to race any caches
  56. err := auth.BindClusterRole(jig.Client.RbacV1(), "cluster-admin", f.Namespace.Name,
  57. rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Namespace: f.Namespace.Name, Name: "default"})
  58. framework.ExpectNoError(err)
  59. err = auth.WaitForAuthorizationUpdate(jig.Client.AuthorizationV1(),
  60. serviceaccount.MakeUsername(f.Namespace.Name, "default"),
  61. "", "create", schema.GroupResource{Resource: "pods"}, true)
  62. framework.ExpectNoError(err)
  63. })
  64. // Before enabling this loadbalancer test in any other test list you must
  65. // make sure the associated project has enough quota. At the time of this
  66. // writing a GCE project is allowed 3 backend services by default. This
  67. // test requires at least 5.
  68. //
  69. // Slow by design ~10m for each "It" block dominated by loadbalancer setup time
  70. // TODO: write similar tests for nginx, haproxy and AWS Ingress.
  71. ginkgo.Describe("GCE [Slow] [Feature:Ingress]", func() {
  72. var gceController *gce.IngressController
  73. // Platform specific setup
  74. ginkgo.BeforeEach(func() {
  75. e2eskipper.SkipUnlessProviderIs("gce", "gke")
  76. ginkgo.By("Initializing gce controller")
  77. gceController = &gce.IngressController{
  78. Ns: ns,
  79. Client: jig.Client,
  80. Cloud: framework.TestContext.CloudConfig,
  81. }
  82. err := gceController.Init()
  83. framework.ExpectNoError(err)
  84. })
  85. // Platform specific cleanup
  86. ginkgo.AfterEach(func() {
  87. if ginkgo.CurrentGinkgoTestDescription().Failed {
  88. framework.DescribeIng(ns)
  89. }
  90. if jig.Ingress == nil {
  91. ginkgo.By("No ingress created, no cleanup necessary")
  92. return
  93. }
  94. ginkgo.By("Deleting ingress")
  95. jig.TryDeleteIngress()
  96. ginkgo.By("Cleaning up cloud resources")
  97. err := gceController.CleanupIngressController()
  98. framework.ExpectNoError(err)
  99. })
  100. ginkgo.It("should conform to Ingress spec", func() {
  101. conformanceTests = ingress.CreateIngressComformanceTests(jig, ns, map[string]string{})
  102. for _, t := range conformanceTests {
  103. ginkgo.By(t.EntryLog)
  104. t.Execute()
  105. ginkgo.By(t.ExitLog)
  106. jig.WaitForIngress(true)
  107. }
  108. })
  109. ginkgo.It("should create ingress with pre-shared certificate", func() {
  110. executePresharedCertTest(f, jig, "")
  111. })
  112. ginkgo.It("should support multiple TLS certs", func() {
  113. ginkgo.By("Creating an ingress with no certs.")
  114. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "multiple-certs"), ns, map[string]string{
  115. ingress.IngressStaticIPKey: ns,
  116. }, map[string]string{})
  117. ginkgo.By("Adding multiple certs to the ingress.")
  118. hosts := []string{"test1.ingress.com", "test2.ingress.com", "test3.ingress.com", "test4.ingress.com"}
  119. secrets := []string{"tls-secret-1", "tls-secret-2", "tls-secret-3", "tls-secret-4"}
  120. certs := [][]byte{}
  121. for i, host := range hosts {
  122. jig.AddHTTPS(secrets[i], host)
  123. certs = append(certs, jig.GetRootCA(secrets[i]))
  124. }
  125. for i, host := range hosts {
  126. err := jig.WaitForIngressWithCert(true, []string{host}, certs[i])
  127. framework.ExpectNoError(err, fmt.Sprintf("Unexpected error while waiting for ingress: %v", err))
  128. }
  129. ginkgo.By("Remove all but one of the certs on the ingress.")
  130. jig.RemoveHTTPS(secrets[1])
  131. jig.RemoveHTTPS(secrets[2])
  132. jig.RemoveHTTPS(secrets[3])
  133. ginkgo.By("Test that the remaining cert is properly served.")
  134. err := jig.WaitForIngressWithCert(true, []string{hosts[0]}, certs[0])
  135. framework.ExpectNoError(err, fmt.Sprintf("Unexpected error while waiting for ingress: %v", err))
  136. ginkgo.By("Add back one of the certs that was removed and check that all certs are served.")
  137. jig.AddHTTPS(secrets[1], hosts[1])
  138. for i, host := range hosts[:2] {
  139. err := jig.WaitForIngressWithCert(true, []string{host}, certs[i])
  140. framework.ExpectNoError(err, fmt.Sprintf("Unexpected error while waiting for ingress: %v", err))
  141. }
  142. })
  143. ginkgo.It("multicluster ingress should get instance group annotation", func() {
  144. name := "echomap"
  145. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "http"), ns, map[string]string{
  146. ingress.IngressClassKey: ingress.MulticlusterIngressClassValue,
  147. }, map[string]string{})
  148. ginkgo.By(fmt.Sprintf("waiting for Ingress %s to get instance group annotation", name))
  149. pollErr := wait.Poll(2*time.Second, e2eservice.LoadBalancerPollTimeout, func() (bool, error) {
  150. ing, err := f.ClientSet.NetworkingV1beta1().Ingresses(ns).Get(context.TODO(), name, metav1.GetOptions{})
  151. framework.ExpectNoError(err)
  152. annotations := ing.Annotations
  153. if annotations == nil || annotations[instanceGroupAnnotation] == "" {
  154. framework.Logf("Waiting for ingress to get %s annotation. Found annotations: %v", instanceGroupAnnotation, annotations)
  155. return false, nil
  156. }
  157. return true, nil
  158. })
  159. if pollErr != nil {
  160. framework.ExpectNoError(fmt.Errorf("timed out waiting for ingress %s to get %s annotation", name, instanceGroupAnnotation))
  161. }
  162. // Verify that the ingress does not get other annotations like url-map, target-proxy, backends, etc.
  163. // Note: All resources except the firewall rule have an annotation.
  164. umKey := ingress.StatusPrefix + "/url-map"
  165. fwKey := ingress.StatusPrefix + "/forwarding-rule"
  166. tpKey := ingress.StatusPrefix + "/target-proxy"
  167. fwsKey := ingress.StatusPrefix + "/https-forwarding-rule"
  168. tpsKey := ingress.StatusPrefix + "/https-target-proxy"
  169. scKey := ingress.StatusPrefix + "/ssl-cert"
  170. beKey := ingress.StatusPrefix + "/backends"
  171. wait.Poll(2*time.Second, time.Minute, func() (bool, error) {
  172. ing, err := f.ClientSet.NetworkingV1beta1().Ingresses(ns).Get(context.TODO(), name, metav1.GetOptions{})
  173. framework.ExpectNoError(err)
  174. annotations := ing.Annotations
  175. if annotations != nil && (annotations[umKey] != "" || annotations[fwKey] != "" ||
  176. annotations[tpKey] != "" || annotations[fwsKey] != "" || annotations[tpsKey] != "" ||
  177. annotations[scKey] != "" || annotations[beKey] != "") {
  178. framework.Failf("unexpected annotations. Expected to not have annotations for urlmap, forwarding rule, target proxy, ssl cert and backends, got: %v", annotations)
  179. return true, nil
  180. }
  181. return false, nil
  182. })
  183. // Verify that the controller does not create any other resource except instance group.
  184. // TODO(59778): Check GCE resources specific to this ingress instead of listing all resources.
  185. if len(gceController.ListURLMaps()) != 0 {
  186. framework.Failf("unexpected url maps, expected none, got: %v", gceController.ListURLMaps())
  187. }
  188. if len(gceController.ListGlobalForwardingRules()) != 0 {
  189. framework.Failf("unexpected forwarding rules, expected none, got: %v", gceController.ListGlobalForwardingRules())
  190. }
  191. if len(gceController.ListTargetHTTPProxies()) != 0 {
  192. framework.Failf("unexpected target http proxies, expected none, got: %v", gceController.ListTargetHTTPProxies())
  193. }
  194. if len(gceController.ListTargetHTTPSProxies()) != 0 {
  195. framework.Failf("unexpected target https proxies, expected none, got: %v", gceController.ListTargetHTTPSProxies())
  196. }
  197. if len(gceController.ListSslCertificates()) != 0 {
  198. framework.Failf("unexpected ssl certificates, expected none, got: %v", gceController.ListSslCertificates())
  199. }
  200. if len(gceController.ListGlobalBackendServices()) != 0 {
  201. framework.Failf("unexpected backend service, expected none, got: %v", gceController.ListGlobalBackendServices())
  202. }
  203. // Controller does not have a list command for firewall rule. We use get instead.
  204. if fw, err := gceController.GetFirewallRule(); err == nil {
  205. framework.Failf("unexpected nil error in getting firewall rule, expected firewall NotFound, got firewall: %v", fw)
  206. }
  207. // TODO(nikhiljindal): Check the instance group annotation value and verify with a multizone cluster.
  208. })
  209. // TODO: Implement a multizone e2e that verifies traffic reaches each
  210. // zone based on pod labels.
  211. })
  212. ginkgo.Describe("GCE [Slow] [Feature:NEG]", func() {
  213. var gceController *gce.IngressController
  214. // Platform specific setup
  215. ginkgo.BeforeEach(func() {
  216. e2eskipper.SkipUnlessProviderIs("gce", "gke")
  217. ginkgo.By("Initializing gce controller")
  218. gceController = &gce.IngressController{
  219. Ns: ns,
  220. Client: jig.Client,
  221. Cloud: framework.TestContext.CloudConfig,
  222. }
  223. err := gceController.Init()
  224. framework.ExpectNoError(err)
  225. })
  226. // Platform specific cleanup
  227. ginkgo.AfterEach(func() {
  228. if ginkgo.CurrentGinkgoTestDescription().Failed {
  229. framework.DescribeIng(ns)
  230. }
  231. if jig.Ingress == nil {
  232. ginkgo.By("No ingress created, no cleanup necessary")
  233. return
  234. }
  235. ginkgo.By("Deleting ingress")
  236. jig.TryDeleteIngress()
  237. ginkgo.By("Cleaning up cloud resources")
  238. err := gceController.CleanupIngressController()
  239. framework.ExpectNoError(err)
  240. })
  241. ginkgo.It("should conform to Ingress spec", func() {
  242. jig.PollInterval = 5 * time.Second
  243. conformanceTests = ingress.CreateIngressComformanceTests(jig, ns, map[string]string{
  244. ingress.NEGAnnotation: `{"ingress": true}`,
  245. })
  246. for _, t := range conformanceTests {
  247. ginkgo.By(t.EntryLog)
  248. t.Execute()
  249. ginkgo.By(t.ExitLog)
  250. jig.WaitForIngress(true)
  251. err := gceController.WaitForNegBackendService(jig.GetServicePorts(false))
  252. framework.ExpectNoError(err)
  253. }
  254. })
  255. ginkgo.It("should be able to switch between IG and NEG modes", func() {
  256. var err error
  257. ginkgo.By("Create a basic HTTP ingress using NEG")
  258. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg"), ns, map[string]string{}, map[string]string{})
  259. jig.WaitForIngress(true)
  260. err = gceController.WaitForNegBackendService(jig.GetServicePorts(false))
  261. framework.ExpectNoError(err)
  262. ginkgo.By("Switch backend service to use IG")
  263. svcList, err := f.ClientSet.CoreV1().Services(ns).List(context.TODO(), metav1.ListOptions{})
  264. framework.ExpectNoError(err)
  265. for _, svc := range svcList.Items {
  266. svc.Annotations[ingress.NEGAnnotation] = `{"ingress": false}`
  267. _, err = f.ClientSet.CoreV1().Services(ns).Update(context.TODO(), &svc, metav1.UpdateOptions{})
  268. framework.ExpectNoError(err)
  269. }
  270. err = wait.Poll(5*time.Second, e2eservice.LoadBalancerPollTimeout, func() (bool, error) {
  271. if err := gceController.BackendServiceUsingIG(jig.GetServicePorts(false)); err != nil {
  272. framework.Logf("ginkgo.Failed to verify IG backend service: %v", err)
  273. return false, nil
  274. }
  275. return true, nil
  276. })
  277. framework.ExpectNoError(err, "Expect backend service to target IG, but failed to observe")
  278. jig.WaitForIngress(true)
  279. ginkgo.By("Switch backend service to use NEG")
  280. svcList, err = f.ClientSet.CoreV1().Services(ns).List(context.TODO(), metav1.ListOptions{})
  281. framework.ExpectNoError(err)
  282. for _, svc := range svcList.Items {
  283. svc.Annotations[ingress.NEGAnnotation] = `{"ingress": true}`
  284. _, err = f.ClientSet.CoreV1().Services(ns).Update(context.TODO(), &svc, metav1.UpdateOptions{})
  285. framework.ExpectNoError(err)
  286. }
  287. err = wait.Poll(5*time.Second, e2eservice.LoadBalancerPollTimeout, func() (bool, error) {
  288. if err := gceController.BackendServiceUsingNEG(jig.GetServicePorts(false)); err != nil {
  289. framework.Logf("ginkgo.Failed to verify NEG backend service: %v", err)
  290. return false, nil
  291. }
  292. return true, nil
  293. })
  294. framework.ExpectNoError(err, "Expect backend service to target NEG, but failed to observe")
  295. jig.WaitForIngress(true)
  296. })
  297. ginkgo.It("should be able to create a ClusterIP service", func() {
  298. ginkgo.By("Create a basic HTTP ingress using NEG")
  299. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg-clusterip"), ns, map[string]string{}, map[string]string{})
  300. jig.WaitForIngress(true)
  301. svcPorts := jig.GetServicePorts(false)
  302. err := gceController.WaitForNegBackendService(svcPorts)
  303. framework.ExpectNoError(err)
  304. // ClusterIP ServicePorts have no NodePort
  305. for _, sp := range svcPorts {
  306. framework.ExpectEqual(sp.NodePort, int32(0))
  307. }
  308. })
  309. ginkgo.It("should sync endpoints to NEG", func() {
  310. name := "hostname"
  311. scaleAndValidateNEG := func(num int) {
  312. scale, err := f.ClientSet.AppsV1().Deployments(ns).GetScale(context.TODO(), name, metav1.GetOptions{})
  313. framework.ExpectNoError(err)
  314. if scale.Spec.Replicas != int32(num) {
  315. scale.ResourceVersion = "" // indicate the scale update should be unconditional
  316. scale.Spec.Replicas = int32(num)
  317. _, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(context.TODO(), name, scale, metav1.UpdateOptions{})
  318. framework.ExpectNoError(err)
  319. }
  320. err = wait.Poll(10*time.Second, negUpdateTimeout, func() (bool, error) {
  321. res, err := jig.GetDistinctResponseFromIngress()
  322. if err != nil {
  323. return false, nil
  324. }
  325. framework.Logf("Expecting %d backends, got %d", num, res.Len())
  326. return res.Len() == num, nil
  327. })
  328. framework.ExpectNoError(err)
  329. }
  330. ginkgo.By("Create a basic HTTP ingress using NEG")
  331. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg"), ns, map[string]string{}, map[string]string{})
  332. jig.WaitForIngress(true)
  333. jig.WaitForIngressToStable()
  334. err := gceController.WaitForNegBackendService(jig.GetServicePorts(false))
  335. framework.ExpectNoError(err)
  336. // initial replicas number is 1
  337. scaleAndValidateNEG(1)
  338. ginkgo.By("Scale up number of backends to 5")
  339. scaleAndValidateNEG(5)
  340. ginkgo.By("Scale down number of backends to 3")
  341. scaleAndValidateNEG(3)
  342. ginkgo.By("Scale up number of backends to 6")
  343. scaleAndValidateNEG(6)
  344. ginkgo.By("Scale down number of backends to 2")
  345. scaleAndValidateNEG(3)
  346. })
  347. ginkgo.It("rolling update backend pods should not cause service disruption", func() {
  348. name := "hostname"
  349. replicas := 8
  350. ginkgo.By("Create a basic HTTP ingress using NEG")
  351. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg"), ns, map[string]string{}, map[string]string{})
  352. jig.WaitForIngress(true)
  353. jig.WaitForIngressToStable()
  354. err := gceController.WaitForNegBackendService(jig.GetServicePorts(false))
  355. framework.ExpectNoError(err)
  356. ginkgo.By(fmt.Sprintf("Scale backend replicas to %d", replicas))
  357. scale, err := f.ClientSet.AppsV1().Deployments(ns).GetScale(context.TODO(), name, metav1.GetOptions{})
  358. framework.ExpectNoError(err)
  359. scale.ResourceVersion = "" // indicate the scale update should be unconditional
  360. scale.Spec.Replicas = int32(replicas)
  361. _, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(context.TODO(), name, scale, metav1.UpdateOptions{})
  362. framework.ExpectNoError(err)
  363. err = wait.Poll(10*time.Second, e2eservice.LoadBalancerPollTimeout, func() (bool, error) {
  364. res, err := jig.GetDistinctResponseFromIngress()
  365. if err != nil {
  366. return false, nil
  367. }
  368. return res.Len() == replicas, nil
  369. })
  370. framework.ExpectNoError(err)
  371. ginkgo.By("Trigger rolling update and observe service disruption")
  372. deploy, err := f.ClientSet.AppsV1().Deployments(ns).Get(context.TODO(), name, metav1.GetOptions{})
  373. framework.ExpectNoError(err)
  374. // trigger by changing graceful termination period to 60 seconds
  375. gracePeriod := int64(60)
  376. deploy.Spec.Template.Spec.TerminationGracePeriodSeconds = &gracePeriod
  377. _, err = f.ClientSet.AppsV1().Deployments(ns).Update(context.TODO(), deploy, metav1.UpdateOptions{})
  378. framework.ExpectNoError(err)
  379. err = wait.Poll(10*time.Second, e2eservice.LoadBalancerPollTimeout, func() (bool, error) {
  380. res, err := jig.GetDistinctResponseFromIngress()
  381. framework.ExpectNoError(err)
  382. deploy, err := f.ClientSet.AppsV1().Deployments(ns).Get(context.TODO(), name, metav1.GetOptions{})
  383. framework.ExpectNoError(err)
  384. if int(deploy.Status.UpdatedReplicas) == replicas {
  385. if res.Len() == replicas {
  386. return true, nil
  387. }
  388. framework.Logf("Expecting %d different responses, but got %d.", replicas, res.Len())
  389. return false, nil
  390. }
  391. framework.Logf("Waiting for rolling update to finished. Keep sending traffic.")
  392. return false, nil
  393. })
  394. framework.ExpectNoError(err)
  395. })
  396. ginkgo.It("should sync endpoints for both Ingress-referenced NEG and standalone NEG", func() {
  397. name := "hostname"
  398. expectedKeys := []int32{80, 443}
  399. scaleAndValidateExposedNEG := func(num int) {
  400. scale, err := f.ClientSet.AppsV1().Deployments(ns).GetScale(context.TODO(), name, metav1.GetOptions{})
  401. framework.ExpectNoError(err)
  402. if scale.Spec.Replicas != int32(num) {
  403. scale.ResourceVersion = "" // indicate the scale update should be unconditional
  404. scale.Spec.Replicas = int32(num)
  405. _, err = f.ClientSet.AppsV1().Deployments(ns).UpdateScale(context.TODO(), name, scale, metav1.UpdateOptions{})
  406. framework.ExpectNoError(err)
  407. }
  408. err = wait.Poll(10*time.Second, negUpdateTimeout, func() (bool, error) {
  409. svc, err := f.ClientSet.CoreV1().Services(ns).Get(context.TODO(), name, metav1.GetOptions{})
  410. framework.ExpectNoError(err)
  411. var status ingress.NegStatus
  412. v, ok := svc.Annotations[ingress.NEGStatusAnnotation]
  413. if !ok {
  414. // Wait for NEG sync loop to find NEGs
  415. framework.Logf("Waiting for %v, got: %+v", ingress.NEGStatusAnnotation, svc.Annotations)
  416. return false, nil
  417. }
  418. err = json.Unmarshal([]byte(v), &status)
  419. if err != nil {
  420. framework.Logf("Error in parsing Expose NEG annotation: %v", err)
  421. return false, nil
  422. }
  423. framework.Logf("Got %v: %v", ingress.NEGStatusAnnotation, v)
  424. // Expect 2 NEGs to be created based on the test setup (neg-exposed)
  425. if len(status.NetworkEndpointGroups) != 2 {
  426. framework.Logf("Expected 2 NEGs, got %d", len(status.NetworkEndpointGroups))
  427. return false, nil
  428. }
  429. for _, port := range expectedKeys {
  430. if _, ok := status.NetworkEndpointGroups[port]; !ok {
  431. framework.Logf("Expected ServicePort key %v, but does not exist", port)
  432. }
  433. }
  434. if len(status.NetworkEndpointGroups) != len(expectedKeys) {
  435. framework.Logf("Expected length of %+v to equal length of %+v, but does not", status.NetworkEndpointGroups, expectedKeys)
  436. }
  437. gceCloud, err := gce.GetGCECloud()
  438. framework.ExpectNoError(err)
  439. for _, neg := range status.NetworkEndpointGroups {
  440. networkEndpoints, err := gceCloud.ListNetworkEndpoints(neg, gceController.Cloud.Zone, false)
  441. framework.ExpectNoError(err)
  442. if len(networkEndpoints) != num {
  443. framework.Logf("Expect number of endpoints to be %d, but got %d", num, len(networkEndpoints))
  444. return false, nil
  445. }
  446. }
  447. return true, nil
  448. })
  449. framework.ExpectNoError(err)
  450. }
  451. ginkgo.By("Create a basic HTTP ingress using NEG")
  452. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg-exposed"), ns, map[string]string{}, map[string]string{})
  453. jig.WaitForIngress(true)
  454. err := gceController.WaitForNegBackendService(jig.GetServicePorts(false))
  455. framework.ExpectNoError(err)
  456. // initial replicas number is 1
  457. scaleAndValidateExposedNEG(1)
  458. ginkgo.By("Scale up number of backends to 5")
  459. scaleAndValidateExposedNEG(5)
  460. ginkgo.By("Scale down number of backends to 3")
  461. scaleAndValidateExposedNEG(3)
  462. ginkgo.By("Scale up number of backends to 6")
  463. scaleAndValidateExposedNEG(6)
  464. ginkgo.By("Scale down number of backends to 2")
  465. scaleAndValidateExposedNEG(3)
  466. })
  467. ginkgo.It("should create NEGs for all ports with the Ingress annotation, and NEGs for the standalone annotation otherwise", func() {
  468. ginkgo.By("Create a basic HTTP ingress using standalone NEG")
  469. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "neg-exposed"), ns, map[string]string{}, map[string]string{})
  470. jig.WaitForIngress(true)
  471. name := "hostname"
  472. detectNegAnnotation(f, jig, gceController, ns, name, 2)
  473. // Add Ingress annotation - NEGs should stay the same.
  474. ginkgo.By("Adding NEG Ingress annotation")
  475. svcList, err := f.ClientSet.CoreV1().Services(ns).List(context.TODO(), metav1.ListOptions{})
  476. framework.ExpectNoError(err)
  477. for _, svc := range svcList.Items {
  478. svc.Annotations[ingress.NEGAnnotation] = `{"ingress":true,"exposed_ports":{"80":{},"443":{}}}`
  479. _, err = f.ClientSet.CoreV1().Services(ns).Update(context.TODO(), &svc, metav1.UpdateOptions{})
  480. framework.ExpectNoError(err)
  481. }
  482. detectNegAnnotation(f, jig, gceController, ns, name, 2)
  483. // Modify exposed NEG annotation, but keep ingress annotation
  484. ginkgo.By("Modifying exposed NEG annotation, but keep Ingress annotation")
  485. svcList, err = f.ClientSet.CoreV1().Services(ns).List(context.TODO(), metav1.ListOptions{})
  486. framework.ExpectNoError(err)
  487. for _, svc := range svcList.Items {
  488. svc.Annotations[ingress.NEGAnnotation] = `{"ingress":true,"exposed_ports":{"443":{}}}`
  489. _, err = f.ClientSet.CoreV1().Services(ns).Update(context.TODO(), &svc, metav1.UpdateOptions{})
  490. framework.ExpectNoError(err)
  491. }
  492. detectNegAnnotation(f, jig, gceController, ns, name, 2)
  493. // Remove Ingress annotation. Expect 1 NEG
  494. ginkgo.By("Disabling Ingress annotation, but keeping one standalone NEG")
  495. svcList, err = f.ClientSet.CoreV1().Services(ns).List(context.TODO(), metav1.ListOptions{})
  496. framework.ExpectNoError(err)
  497. for _, svc := range svcList.Items {
  498. svc.Annotations[ingress.NEGAnnotation] = `{"ingress":false,"exposed_ports":{"443":{}}}`
  499. _, err = f.ClientSet.CoreV1().Services(ns).Update(context.TODO(), &svc, metav1.UpdateOptions{})
  500. framework.ExpectNoError(err)
  501. }
  502. detectNegAnnotation(f, jig, gceController, ns, name, 1)
  503. // Remove NEG annotation entirely. Expect 0 NEGs.
  504. ginkgo.By("Removing NEG annotation")
  505. svcList, err = f.ClientSet.CoreV1().Services(ns).List(context.TODO(), metav1.ListOptions{})
  506. framework.ExpectNoError(err)
  507. for _, svc := range svcList.Items {
  508. delete(svc.Annotations, ingress.NEGAnnotation)
  509. // Service cannot be ClusterIP if it's using Instance Groups.
  510. svc.Spec.Type = v1.ServiceTypeNodePort
  511. _, err = f.ClientSet.CoreV1().Services(ns).Update(context.TODO(), &svc, metav1.UpdateOptions{})
  512. framework.ExpectNoError(err)
  513. }
  514. detectNegAnnotation(f, jig, gceController, ns, name, 0)
  515. })
  516. })
  517. ginkgo.Describe("GCE [Slow] [Feature:kubemci]", func() {
  518. var gceController *gce.IngressController
  519. var ipName, ipAddress string
  520. // Platform specific setup
  521. ginkgo.BeforeEach(func() {
  522. e2eskipper.SkipUnlessProviderIs("gce", "gke")
  523. jig.Class = ingress.MulticlusterIngressClassValue
  524. jig.PollInterval = 5 * time.Second
  525. ginkgo.By("Initializing gce controller")
  526. gceController = &gce.IngressController{
  527. Ns: ns,
  528. Client: jig.Client,
  529. Cloud: framework.TestContext.CloudConfig,
  530. }
  531. err := gceController.Init()
  532. framework.ExpectNoError(err)
  533. // TODO(https://github.com/GoogleCloudPlatform/k8s-multicluster-ingress/issues/19):
  534. // Kubemci should reserve a static ip if user has not specified one.
  535. ipName = "kubemci-" + string(uuid.NewUUID())
  536. // ip released when the rest of lb resources are deleted in CleanupIngressController
  537. ipAddress = gceController.CreateStaticIP(ipName)
  538. ginkgo.By(fmt.Sprintf("allocated static ip %v: %v through the GCE cloud provider", ipName, ipAddress))
  539. })
  540. // Platform specific cleanup
  541. ginkgo.AfterEach(func() {
  542. if ginkgo.CurrentGinkgoTestDescription().Failed {
  543. framework.DescribeIng(ns)
  544. }
  545. if jig.Ingress == nil {
  546. ginkgo.By("No ingress created, no cleanup necessary")
  547. } else {
  548. ginkgo.By("Deleting ingress")
  549. jig.TryDeleteIngress()
  550. }
  551. ginkgo.By("Cleaning up cloud resources")
  552. err := gceController.CleanupIngressController()
  553. framework.ExpectNoError(err)
  554. })
  555. ginkgo.It("should conform to Ingress spec", func() {
  556. conformanceTests = ingress.CreateIngressComformanceTests(jig, ns, map[string]string{
  557. ingress.IngressStaticIPKey: ipName,
  558. })
  559. for _, t := range conformanceTests {
  560. ginkgo.By(t.EntryLog)
  561. t.Execute()
  562. ginkgo.By(t.ExitLog)
  563. jig.WaitForIngress(false /*waitForNodePort*/)
  564. }
  565. })
  566. ginkgo.It("should create ingress with pre-shared certificate", func() {
  567. executePresharedCertTest(f, jig, ipName)
  568. })
  569. ginkgo.It("should create ingress with backend HTTPS", func() {
  570. executeBacksideBacksideHTTPSTest(f, jig, ipName)
  571. })
  572. ginkgo.It("should support https-only annotation", func() {
  573. executeStaticIPHttpsOnlyTest(f, jig, ipName, ipAddress)
  574. })
  575. ginkgo.It("should remove clusters as expected", func() {
  576. ingAnnotations := map[string]string{
  577. ingress.IngressStaticIPKey: ipName,
  578. }
  579. ingFilePath := filepath.Join(ingress.IngressManifestPath, "http")
  580. jig.CreateIngress(ingFilePath, ns, ingAnnotations, map[string]string{})
  581. jig.WaitForIngress(false /*waitForNodePort*/)
  582. name := jig.Ingress.Name
  583. // Verify that the ingress is spread to 1 cluster as expected.
  584. verifyKubemciStatusHas(name, "is spread across 1 cluster")
  585. // Validate that removing the ingress from all clusters throws an error.
  586. // Reuse the ingress file created while creating the ingress.
  587. filePath := filepath.Join(framework.TestContext.OutputDir, "mci.yaml")
  588. output, err := framework.RunKubemciWithKubeconfig("remove-clusters", name, "--ingress="+filePath)
  589. if err != nil {
  590. framework.Failf("unexpected error in running kubemci remove-clusters command to remove from all clusters: %s", err)
  591. }
  592. if !strings.Contains(output, "You should use kubemci delete to delete the ingress completely") {
  593. framework.Failf("unexpected output in removing an ingress from all clusters, expected the output to include: You should use kubemci delete to delete the ingress completely, actual output: %s", output)
  594. }
  595. // Verify that the ingress is still spread to 1 cluster as expected.
  596. verifyKubemciStatusHas(name, "is spread across 1 cluster")
  597. // remove-clusters should succeed with --force=true
  598. if _, err := framework.RunKubemciWithKubeconfig("remove-clusters", name, "--ingress="+filePath, "--force=true"); err != nil {
  599. framework.Failf("unexpected error in running kubemci remove-clusters to remove from all clusters with --force=true: %s", err)
  600. }
  601. verifyKubemciStatusHas(name, "is spread across 0 cluster")
  602. })
  603. ginkgo.It("single and multi-cluster ingresses should be able to exist together", func() {
  604. ginkgo.By("Creating a single cluster ingress first")
  605. jig.Class = ""
  606. singleIngFilePath := filepath.Join(ingress.GCEIngressManifestPath, "static-ip-2")
  607. jig.CreateIngress(singleIngFilePath, ns, map[string]string{}, map[string]string{})
  608. jig.WaitForIngress(false /*waitForNodePort*/)
  609. // jig.Ingress will be overwritten when we create MCI, so keep a reference.
  610. singleIng := jig.Ingress
  611. // Create the multi-cluster ingress next.
  612. ginkgo.By("Creating a multi-cluster ingress next")
  613. jig.Class = ingress.MulticlusterIngressClassValue
  614. ingAnnotations := map[string]string{
  615. ingress.IngressStaticIPKey: ipName,
  616. }
  617. multiIngFilePath := filepath.Join(ingress.IngressManifestPath, "http")
  618. jig.CreateIngress(multiIngFilePath, ns, ingAnnotations, map[string]string{})
  619. jig.WaitForIngress(false /*waitForNodePort*/)
  620. mciIngress := jig.Ingress
  621. ginkgo.By("Deleting the single cluster ingress and verifying that multi-cluster ingress continues to work")
  622. jig.Ingress = singleIng
  623. jig.Class = ""
  624. jig.TryDeleteIngress()
  625. jig.Ingress = mciIngress
  626. jig.Class = ingress.MulticlusterIngressClassValue
  627. jig.WaitForIngress(false /*waitForNodePort*/)
  628. ginkgo.By("Cleanup: Deleting the multi-cluster ingress")
  629. jig.TryDeleteIngress()
  630. })
  631. })
  632. // Time: borderline 5m, slow by design
  633. ginkgo.Describe("[Slow] Nginx", func() {
  634. var nginxController *ingress.NginxIngressController
  635. ginkgo.BeforeEach(func() {
  636. e2eskipper.SkipUnlessProviderIs("gce", "gke")
  637. ginkgo.By("Initializing nginx controller")
  638. jig.Class = "nginx"
  639. nginxController = &ingress.NginxIngressController{Ns: ns, Client: jig.Client}
  640. // TODO: This test may fail on other platforms. We can simply skip it
  641. // but we want to allow easy testing where a user might've hand
  642. // configured firewalls.
  643. if framework.ProviderIs("gce", "gke") {
  644. framework.ExpectNoError(gce.GcloudComputeResourceCreate("firewall-rules", fmt.Sprintf("ingress-80-443-%v", ns), framework.TestContext.CloudConfig.ProjectID, "--allow", "tcp:80,tcp:443", "--network", framework.TestContext.CloudConfig.Network))
  645. } else {
  646. framework.Logf("WARNING: Not running on GCE/GKE, cannot create firewall rules for :80, :443. Assuming traffic can reach the external ips of all nodes in cluster on those ports.")
  647. }
  648. nginxController.Init()
  649. })
  650. ginkgo.AfterEach(func() {
  651. if framework.ProviderIs("gce", "gke") {
  652. framework.ExpectNoError(gce.GcloudComputeResourceDelete("firewall-rules", fmt.Sprintf("ingress-80-443-%v", ns), framework.TestContext.CloudConfig.ProjectID))
  653. }
  654. if ginkgo.CurrentGinkgoTestDescription().Failed {
  655. framework.DescribeIng(ns)
  656. }
  657. defer nginxController.TearDown()
  658. if jig.Ingress == nil {
  659. ginkgo.By("No ingress created, no cleanup necessary")
  660. return
  661. }
  662. ginkgo.By("Deleting ingress")
  663. jig.TryDeleteIngress()
  664. })
  665. ginkgo.It("should conform to Ingress spec", func() {
  666. // Poll more frequently to reduce e2e completion time.
  667. // This test runs in presubmit.
  668. jig.PollInterval = 5 * time.Second
  669. conformanceTests = ingress.CreateIngressComformanceTests(jig, ns, map[string]string{})
  670. for _, t := range conformanceTests {
  671. ginkgo.By(t.EntryLog)
  672. t.Execute()
  673. ginkgo.By(t.ExitLog)
  674. jig.WaitForIngress(false)
  675. }
  676. })
  677. })
  678. })
  679. // verifyKubemciStatusHas fails if kubemci get-status output for the given mci does not have the given expectedSubStr.
  680. func verifyKubemciStatusHas(name, expectedSubStr string) {
  681. statusStr, err := framework.RunKubemciCmd("get-status", name)
  682. if err != nil {
  683. framework.Failf("unexpected error in running kubemci get-status %s: %s", name, err)
  684. }
  685. if !strings.Contains(statusStr, expectedSubStr) {
  686. framework.Failf("expected status to have sub string %s, actual status: %s", expectedSubStr, statusStr)
  687. }
  688. }
  689. func executePresharedCertTest(f *framework.Framework, jig *ingress.TestJig, staticIPName string) {
  690. preSharedCertName := "test-pre-shared-cert"
  691. ginkgo.By(fmt.Sprintf("Creating ssl certificate %q on GCE", preSharedCertName))
  692. testHostname := "test.ingress.com"
  693. cert, key, err := ingress.GenerateRSACerts(testHostname, true)
  694. framework.ExpectNoError(err)
  695. gceCloud, err := gce.GetGCECloud()
  696. framework.ExpectNoError(err)
  697. defer func() {
  698. // We would not be able to delete the cert until ingress controller
  699. // cleans up the target proxy that references it.
  700. ginkgo.By("Deleting ingress before deleting ssl certificate")
  701. if jig.Ingress != nil {
  702. jig.TryDeleteIngress()
  703. }
  704. ginkgo.By(fmt.Sprintf("Deleting ssl certificate %q on GCE", preSharedCertName))
  705. err := wait.Poll(e2eservice.LoadBalancerPollInterval, e2eservice.LoadBalancerCleanupTimeout, func() (bool, error) {
  706. if err := gceCloud.DeleteSslCertificate(preSharedCertName); err != nil && !apierrors.IsNotFound(err) {
  707. framework.Logf("ginkgo.Failed to delete ssl certificate %q: %v. Retrying...", preSharedCertName, err)
  708. return false, nil
  709. }
  710. return true, nil
  711. })
  712. framework.ExpectNoError(err, fmt.Sprintf("ginkgo.Failed to delete ssl certificate %q: %v", preSharedCertName, err))
  713. }()
  714. _, err = gceCloud.CreateSslCertificate(&compute.SslCertificate{
  715. Name: preSharedCertName,
  716. Certificate: string(cert),
  717. PrivateKey: string(key),
  718. Description: "pre-shared cert for ingress testing",
  719. })
  720. framework.ExpectNoError(err, fmt.Sprintf("ginkgo.Failed to create ssl certificate %q: %v", preSharedCertName, err))
  721. ginkgo.By("Creating an ingress referencing the pre-shared certificate")
  722. // Create an ingress referencing this cert using pre-shared-cert annotation.
  723. ingAnnotations := map[string]string{
  724. ingress.IngressPreSharedCertKey: preSharedCertName,
  725. // Disallow HTTP to save resources. This is irrelevant to the
  726. // pre-shared cert test.
  727. ingress.IngressAllowHTTPKey: "false",
  728. }
  729. if staticIPName != "" {
  730. ingAnnotations[ingress.IngressStaticIPKey] = staticIPName
  731. }
  732. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "pre-shared-cert"), f.Namespace.Name, ingAnnotations, map[string]string{})
  733. ginkgo.By("Test that ingress works with the pre-shared certificate")
  734. err = jig.WaitForIngressWithCert(true, []string{testHostname}, cert)
  735. framework.ExpectNoError(err, fmt.Sprintf("Unexpected error while waiting for ingress: %v", err))
  736. }
  737. func executeStaticIPHttpsOnlyTest(f *framework.Framework, jig *ingress.TestJig, ipName, ip string) {
  738. jig.CreateIngress(filepath.Join(ingress.IngressManifestPath, "static-ip"), f.Namespace.Name, map[string]string{
  739. ingress.IngressStaticIPKey: ipName,
  740. ingress.IngressAllowHTTPKey: "false",
  741. }, map[string]string{})
  742. ginkgo.By("waiting for Ingress to come up with ip: " + ip)
  743. httpClient := ingress.BuildInsecureClient(ingress.IngressReqTimeout)
  744. framework.ExpectNoError(ingress.PollURL(fmt.Sprintf("https://%s/", ip), "", e2eservice.LoadBalancerPollTimeout, jig.PollInterval, httpClient, false))
  745. ginkgo.By("should reject HTTP traffic")
  746. framework.ExpectNoError(ingress.PollURL(fmt.Sprintf("http://%s/", ip), "", e2eservice.LoadBalancerPollTimeout, jig.PollInterval, httpClient, true))
  747. }
  748. func executeBacksideBacksideHTTPSTest(f *framework.Framework, jig *ingress.TestJig, staticIPName string) {
  749. ginkgo.By("Creating a set of ingress, service and deployment that have backside re-encryption configured")
  750. deployCreated, svcCreated, ingCreated, err := jig.SetUpBacksideHTTPSIngress(f.ClientSet, f.Namespace.Name, staticIPName)
  751. defer func() {
  752. ginkgo.By("Cleaning up re-encryption ingress, service and deployment")
  753. if errs := jig.DeleteTestResource(f.ClientSet, deployCreated, svcCreated, ingCreated); len(errs) > 0 {
  754. framework.Failf("ginkgo.Failed to cleanup re-encryption ingress: %v", errs)
  755. }
  756. }()
  757. framework.ExpectNoError(err, "ginkgo.Failed to create re-encryption ingress")
  758. ginkgo.By(fmt.Sprintf("Waiting for ingress %s to come up", ingCreated.Name))
  759. ingIP, err := jig.WaitForIngressAddress(f.ClientSet, f.Namespace.Name, ingCreated.Name, e2eservice.LoadBalancerPollTimeout)
  760. framework.ExpectNoError(err, "ginkgo.Failed to wait for ingress IP")
  761. ginkgo.By(fmt.Sprintf("Polling on address %s and verify the backend is serving HTTPS", ingIP))
  762. timeoutClient := &http.Client{Timeout: ingress.IngressReqTimeout}
  763. err = wait.PollImmediate(e2eservice.LoadBalancerPollInterval, e2eservice.LoadBalancerPollTimeout, func() (bool, error) {
  764. resp, err := ingress.SimpleGET(timeoutClient, fmt.Sprintf("http://%s", ingIP), "")
  765. if err != nil {
  766. framework.Logf("SimpleGET failed: %v", err)
  767. return false, nil
  768. }
  769. if !strings.Contains(resp, "request_scheme=https") {
  770. return false, fmt.Errorf("request wasn't served by HTTPS, response body: %s", resp)
  771. }
  772. framework.Logf("Poll succeeded, request was served by HTTPS")
  773. return true, nil
  774. })
  775. framework.ExpectNoError(err, "ginkgo.Failed to verify backside re-encryption ingress")
  776. }
  777. func detectNegAnnotation(f *framework.Framework, jig *ingress.TestJig, gceController *gce.IngressController, ns, name string, negs int) {
  778. if err := wait.Poll(5*time.Second, negUpdateTimeout, func() (bool, error) {
  779. svc, err := f.ClientSet.CoreV1().Services(ns).Get(context.TODO(), name, metav1.GetOptions{})
  780. if err != nil {
  781. return false, nil
  782. }
  783. // if we expect no NEGs, then we should be using IGs
  784. if negs == 0 {
  785. err := gceController.BackendServiceUsingIG(jig.GetServicePorts(false))
  786. if err != nil {
  787. framework.Logf("ginkgo.Failed to validate IG backend service: %v", err)
  788. return false, nil
  789. }
  790. return true, nil
  791. }
  792. var status ingress.NegStatus
  793. v, ok := svc.Annotations[ingress.NEGStatusAnnotation]
  794. if !ok {
  795. framework.Logf("Waiting for %v, got: %+v", ingress.NEGStatusAnnotation, svc.Annotations)
  796. return false, nil
  797. }
  798. err = json.Unmarshal([]byte(v), &status)
  799. if err != nil {
  800. framework.Logf("Error in parsing Expose NEG annotation: %v", err)
  801. return false, nil
  802. }
  803. framework.Logf("Got %v: %v", ingress.NEGStatusAnnotation, v)
  804. if len(status.NetworkEndpointGroups) != negs {
  805. framework.Logf("Expected %d NEGs, got %d", negs, len(status.NetworkEndpointGroups))
  806. return false, nil
  807. }
  808. gceCloud, err := gce.GetGCECloud()
  809. framework.ExpectNoError(err)
  810. for _, neg := range status.NetworkEndpointGroups {
  811. networkEndpoints, err := gceCloud.ListNetworkEndpoints(neg, gceController.Cloud.Zone, false)
  812. framework.ExpectNoError(err)
  813. if len(networkEndpoints) != 1 {
  814. framework.Logf("Expect NEG %s to exist, but got %d", neg, len(networkEndpoints))
  815. return false, nil
  816. }
  817. }
  818. err = gceController.BackendServiceUsingNEG(jig.GetServicePorts(false))
  819. if err != nil {
  820. framework.Logf("ginkgo.Failed to validate NEG backend service: %v", err)
  821. return false, nil
  822. }
  823. return true, nil
  824. }); err != nil {
  825. framework.ExpectNoError(err)
  826. }
  827. }