ingress_utils.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  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 ingress
  14. import (
  15. "bytes"
  16. "crypto/rand"
  17. "crypto/rsa"
  18. "crypto/tls"
  19. "crypto/x509"
  20. "crypto/x509/pkix"
  21. "encoding/pem"
  22. "fmt"
  23. "math/big"
  24. "net"
  25. "net/http"
  26. "path/filepath"
  27. "regexp"
  28. "strconv"
  29. "strings"
  30. "time"
  31. compute "google.golang.org/api/compute/v1"
  32. "k8s.io/klog"
  33. apps "k8s.io/api/apps/v1"
  34. v1 "k8s.io/api/core/v1"
  35. networkingv1beta1 "k8s.io/api/networking/v1beta1"
  36. apierrs "k8s.io/apimachinery/pkg/api/errors"
  37. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  38. "k8s.io/apimachinery/pkg/labels"
  39. "k8s.io/apimachinery/pkg/util/intstr"
  40. utilnet "k8s.io/apimachinery/pkg/util/net"
  41. "k8s.io/apimachinery/pkg/util/sets"
  42. "k8s.io/apimachinery/pkg/util/wait"
  43. clientset "k8s.io/client-go/kubernetes"
  44. "k8s.io/kubernetes/test/e2e/framework"
  45. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  46. "k8s.io/kubernetes/test/e2e/framework/testfiles"
  47. "k8s.io/kubernetes/test/e2e/manifest"
  48. testutils "k8s.io/kubernetes/test/utils"
  49. "github.com/onsi/ginkgo"
  50. )
  51. const (
  52. rsaBits = 2048
  53. validFor = 365 * 24 * time.Hour
  54. // IngressClassKey is ingress class annotation defined in ingress repository.
  55. // TODO: All these annotations should be reused from
  56. // ingress-gce/pkg/annotations instead of duplicating them here.
  57. IngressClassKey = "kubernetes.io/ingress.class"
  58. // MulticlusterIngressClassValue is ingress class annotation value for multi cluster ingress.
  59. MulticlusterIngressClassValue = "gce-multi-cluster"
  60. // IngressStaticIPKey is static IP annotation defined in ingress repository.
  61. IngressStaticIPKey = "kubernetes.io/ingress.global-static-ip-name"
  62. // IngressAllowHTTPKey is Allow HTTP annotation defined in ingress repository.
  63. IngressAllowHTTPKey = "kubernetes.io/ingress.allow-http"
  64. // IngressPreSharedCertKey is Pre-shared-cert annotation defined in ingress repository.
  65. IngressPreSharedCertKey = "ingress.gcp.kubernetes.io/pre-shared-cert"
  66. // ServiceApplicationProtocolKey annotation defined in ingress repository.
  67. ServiceApplicationProtocolKey = "service.alpha.kubernetes.io/app-protocols"
  68. // Name of the default http backend service
  69. defaultBackendName = "default-http-backend"
  70. // IngressManifestPath is the parent path to yaml test manifests.
  71. IngressManifestPath = "test/e2e/testing-manifests/ingress"
  72. // GCEIngressManifestPath is the parent path to GCE-specific yaml test manifests.
  73. GCEIngressManifestPath = IngressManifestPath + "/gce"
  74. // IngressReqTimeout is the timeout on a single http request.
  75. IngressReqTimeout = 10 * time.Second
  76. // healthz port used to verify glbc restarted correctly on the master.
  77. glbcHealthzPort = 8086
  78. // General cloud resource poll timeout (eg: create static ip, firewall etc)
  79. cloudResourcePollTimeout = 5 * time.Minute
  80. // NEGAnnotation is NEG annotation.
  81. NEGAnnotation = "cloud.google.com/neg"
  82. // NEGStatusAnnotation is NEG status annotation.
  83. NEGStatusAnnotation = "cloud.google.com/neg-status"
  84. // StatusPrefix is prefix for annotation keys used by the ingress controller to specify the
  85. // names of GCP resources such as forwarding rules, url maps, target proxies, etc
  86. // that it created for the corresponding ingress.
  87. StatusPrefix = "ingress.kubernetes.io"
  88. )
  89. // TestLogger is an interface for log.
  90. type TestLogger interface {
  91. Infof(format string, args ...interface{})
  92. Errorf(format string, args ...interface{})
  93. }
  94. // GLogger is test logger.
  95. type GLogger struct{}
  96. // Infof outputs log with info level.
  97. func (l *GLogger) Infof(format string, args ...interface{}) {
  98. klog.Infof(format, args...)
  99. }
  100. // Errorf outputs log with error level.
  101. func (l *GLogger) Errorf(format string, args ...interface{}) {
  102. klog.Errorf(format, args...)
  103. }
  104. // E2ELogger is test logger.
  105. type E2ELogger struct{}
  106. // Infof outputs log.
  107. func (l *E2ELogger) Infof(format string, args ...interface{}) {
  108. e2elog.Logf(format, args...)
  109. }
  110. // Errorf outputs log.
  111. func (l *E2ELogger) Errorf(format string, args ...interface{}) {
  112. e2elog.Logf(format, args...)
  113. }
  114. // ConformanceTests contains a closure with an entry and exit log line.
  115. type ConformanceTests struct {
  116. EntryLog string
  117. Execute func()
  118. ExitLog string
  119. }
  120. // NegStatus contains name and zone of the Network Endpoint Group
  121. // resources associated with this service.
  122. // Needs to be consistent with the NEG internal structs in ingress-gce.
  123. type NegStatus struct {
  124. // NetworkEndpointGroups returns the mapping between service port and NEG
  125. // resource. key is service port, value is the name of the NEG resource.
  126. NetworkEndpointGroups map[int32]string `json:"network_endpoint_groups,omitempty"`
  127. Zones []string `json:"zones,omitempty"`
  128. }
  129. // CreateIngressComformanceTests generates an slice of sequential test cases:
  130. // a simple http ingress, ingress with HTTPS, ingress HTTPS with a modified hostname,
  131. // ingress https with a modified URLMap
  132. func CreateIngressComformanceTests(jig *TestJig, ns string, annotations map[string]string) []ConformanceTests {
  133. manifestPath := filepath.Join(IngressManifestPath, "http")
  134. // These constants match the manifests used in IngressManifestPath
  135. tlsHost := "foo.bar.com"
  136. tlsSecretName := "foo"
  137. updatedTLSHost := "foobar.com"
  138. updateURLMapHost := "bar.baz.com"
  139. updateURLMapPath := "/testurl"
  140. // Platform agnostic list of tests that must be satisfied by all controllers
  141. tests := []ConformanceTests{
  142. {
  143. fmt.Sprintf("should create a basic HTTP ingress"),
  144. func() { jig.CreateIngress(manifestPath, ns, annotations, annotations) },
  145. fmt.Sprintf("waiting for urls on basic HTTP ingress"),
  146. },
  147. {
  148. fmt.Sprintf("should terminate TLS for host %v", tlsHost),
  149. func() { jig.SetHTTPS(tlsSecretName, tlsHost) },
  150. fmt.Sprintf("waiting for HTTPS updates to reflect in ingress"),
  151. },
  152. {
  153. fmt.Sprintf("should update url map for host %v to expose a single url: %v", updateURLMapHost, updateURLMapPath),
  154. func() {
  155. var pathToFail string
  156. jig.Update(func(ing *networkingv1beta1.Ingress) {
  157. newRules := []networkingv1beta1.IngressRule{}
  158. for _, rule := range ing.Spec.Rules {
  159. if rule.Host != updateURLMapHost {
  160. newRules = append(newRules, rule)
  161. continue
  162. }
  163. existingPath := rule.IngressRuleValue.HTTP.Paths[0]
  164. pathToFail = existingPath.Path
  165. newRules = append(newRules, networkingv1beta1.IngressRule{
  166. Host: updateURLMapHost,
  167. IngressRuleValue: networkingv1beta1.IngressRuleValue{
  168. HTTP: &networkingv1beta1.HTTPIngressRuleValue{
  169. Paths: []networkingv1beta1.HTTPIngressPath{
  170. {
  171. Path: updateURLMapPath,
  172. Backend: existingPath.Backend,
  173. },
  174. },
  175. },
  176. },
  177. })
  178. }
  179. ing.Spec.Rules = newRules
  180. })
  181. ginkgo.By("Checking that " + pathToFail + " is not exposed by polling for failure")
  182. route := fmt.Sprintf("http://%v%v", jig.Address, pathToFail)
  183. framework.ExpectNoError(framework.PollURL(route, updateURLMapHost, framework.LoadBalancerCleanupTimeout, jig.PollInterval, &http.Client{Timeout: IngressReqTimeout}, true))
  184. },
  185. fmt.Sprintf("Waiting for path updates to reflect in L7"),
  186. },
  187. }
  188. // Skip the Update TLS cert test for kubemci: https://github.com/GoogleCloudPlatform/k8s-multicluster-ingress/issues/141.
  189. if jig.Class != MulticlusterIngressClassValue {
  190. tests = append(tests, ConformanceTests{
  191. fmt.Sprintf("should update SSL certificate with modified hostname %v", updatedTLSHost),
  192. func() {
  193. jig.Update(func(ing *networkingv1beta1.Ingress) {
  194. newRules := []networkingv1beta1.IngressRule{}
  195. for _, rule := range ing.Spec.Rules {
  196. if rule.Host != tlsHost {
  197. newRules = append(newRules, rule)
  198. continue
  199. }
  200. newRules = append(newRules, networkingv1beta1.IngressRule{
  201. Host: updatedTLSHost,
  202. IngressRuleValue: rule.IngressRuleValue,
  203. })
  204. }
  205. ing.Spec.Rules = newRules
  206. })
  207. jig.SetHTTPS(tlsSecretName, updatedTLSHost)
  208. },
  209. fmt.Sprintf("Waiting for updated certificates to accept requests for host %v", updatedTLSHost),
  210. })
  211. }
  212. return tests
  213. }
  214. // GenerateRSACerts generates a basic self signed certificate using a key length
  215. // of rsaBits, valid for validFor time.
  216. func GenerateRSACerts(host string, isCA bool) ([]byte, []byte, error) {
  217. if len(host) == 0 {
  218. return nil, nil, fmt.Errorf("Require a non-empty host for client hello")
  219. }
  220. priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
  221. if err != nil {
  222. return nil, nil, fmt.Errorf("Failed to generate key: %v", err)
  223. }
  224. notBefore := time.Now()
  225. notAfter := notBefore.Add(validFor)
  226. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
  227. serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
  228. if err != nil {
  229. return nil, nil, fmt.Errorf("failed to generate serial number: %s", err)
  230. }
  231. template := x509.Certificate{
  232. SerialNumber: serialNumber,
  233. Subject: pkix.Name{
  234. CommonName: "default",
  235. Organization: []string{"Acme Co"},
  236. },
  237. NotBefore: notBefore,
  238. NotAfter: notAfter,
  239. KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  240. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  241. BasicConstraintsValid: true,
  242. }
  243. hosts := strings.Split(host, ",")
  244. for _, h := range hosts {
  245. if ip := net.ParseIP(h); ip != nil {
  246. template.IPAddresses = append(template.IPAddresses, ip)
  247. } else {
  248. template.DNSNames = append(template.DNSNames, h)
  249. }
  250. }
  251. if isCA {
  252. template.IsCA = true
  253. template.KeyUsage |= x509.KeyUsageCertSign
  254. }
  255. var keyOut, certOut bytes.Buffer
  256. derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
  257. if err != nil {
  258. return nil, nil, fmt.Errorf("Failed to create certificate: %s", err)
  259. }
  260. if err := pem.Encode(&certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
  261. return nil, nil, fmt.Errorf("Failed creating cert: %v", err)
  262. }
  263. if err := pem.Encode(&keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
  264. return nil, nil, fmt.Errorf("Failed creating key: %v", err)
  265. }
  266. return certOut.Bytes(), keyOut.Bytes(), nil
  267. }
  268. // buildTransportWithCA creates a transport for use in executing HTTPS requests with
  269. // the given certs. Note that the given rootCA must be configured with isCA=true.
  270. func buildTransportWithCA(serverName string, rootCA []byte) (*http.Transport, error) {
  271. pool := x509.NewCertPool()
  272. ok := pool.AppendCertsFromPEM(rootCA)
  273. if !ok {
  274. return nil, fmt.Errorf("Unable to load serverCA")
  275. }
  276. return utilnet.SetTransportDefaults(&http.Transport{
  277. TLSClientConfig: &tls.Config{
  278. InsecureSkipVerify: false,
  279. ServerName: serverName,
  280. RootCAs: pool,
  281. },
  282. }), nil
  283. }
  284. // BuildInsecureClient returns an insecure http client. Can be used for "curl -k".
  285. func BuildInsecureClient(timeout time.Duration) *http.Client {
  286. t := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
  287. return &http.Client{Timeout: timeout, Transport: utilnet.SetTransportDefaults(t)}
  288. }
  289. // createTLSSecret creates a secret containing TLS certificates.
  290. // If a secret with the same name already pathExists in the namespace of the
  291. // Ingress, it's updated.
  292. func createTLSSecret(kubeClient clientset.Interface, namespace, secretName string, hosts ...string) (host string, rootCA, privKey []byte, err error) {
  293. host = strings.Join(hosts, ",")
  294. e2elog.Logf("Generating RSA cert for host %v", host)
  295. cert, key, err := GenerateRSACerts(host, true)
  296. if err != nil {
  297. return
  298. }
  299. secret := &v1.Secret{
  300. ObjectMeta: metav1.ObjectMeta{
  301. Name: secretName,
  302. },
  303. Data: map[string][]byte{
  304. v1.TLSCertKey: cert,
  305. v1.TLSPrivateKeyKey: key,
  306. },
  307. }
  308. var s *v1.Secret
  309. if s, err = kubeClient.CoreV1().Secrets(namespace).Get(secretName, metav1.GetOptions{}); err == nil {
  310. // TODO: Retry the update. We don't really expect anything to conflict though.
  311. e2elog.Logf("Updating secret %v in ns %v with hosts %v", secret.Name, namespace, host)
  312. s.Data = secret.Data
  313. _, err = kubeClient.CoreV1().Secrets(namespace).Update(s)
  314. } else {
  315. e2elog.Logf("Creating secret %v in ns %v with hosts %v", secret.Name, namespace, host)
  316. _, err = kubeClient.CoreV1().Secrets(namespace).Create(secret)
  317. }
  318. return host, cert, key, err
  319. }
  320. // TestJig holds the relevant state and parameters of the ingress test.
  321. type TestJig struct {
  322. Client clientset.Interface
  323. Logger TestLogger
  324. RootCAs map[string][]byte
  325. Address string
  326. Ingress *networkingv1beta1.Ingress
  327. // class is the value of the annotation keyed under
  328. // `kubernetes.io/ingress.class`. It's added to all ingresses created by
  329. // this jig.
  330. Class string
  331. // The interval used to poll urls
  332. PollInterval time.Duration
  333. }
  334. // NewIngressTestJig instantiates struct with client
  335. func NewIngressTestJig(c clientset.Interface) *TestJig {
  336. return &TestJig{
  337. Client: c,
  338. RootCAs: map[string][]byte{},
  339. PollInterval: framework.LoadBalancerPollInterval,
  340. Logger: &E2ELogger{},
  341. }
  342. }
  343. // CreateIngress creates the Ingress and associated service/rc.
  344. // Required: ing.yaml, rc.yaml, svc.yaml must exist in manifestPath
  345. // Optional: secret.yaml, ingAnnotations
  346. // If ingAnnotations is specified it will overwrite any annotations in ing.yaml
  347. // If svcAnnotations is specified it will overwrite any annotations in svc.yaml
  348. func (j *TestJig) CreateIngress(manifestPath, ns string, ingAnnotations map[string]string, svcAnnotations map[string]string) {
  349. var err error
  350. read := func(file string) string {
  351. return string(testfiles.ReadOrDie(filepath.Join(manifestPath, file), ginkgo.Fail))
  352. }
  353. exists := func(file string) bool {
  354. return testfiles.Exists(filepath.Join(manifestPath, file), ginkgo.Fail)
  355. }
  356. j.Logger.Infof("creating replication controller")
  357. framework.RunKubectlOrDieInput(read("rc.yaml"), "create", "-f", "-", fmt.Sprintf("--namespace=%v", ns))
  358. j.Logger.Infof("creating service")
  359. framework.RunKubectlOrDieInput(read("svc.yaml"), "create", "-f", "-", fmt.Sprintf("--namespace=%v", ns))
  360. if len(svcAnnotations) > 0 {
  361. svcList, err := j.Client.CoreV1().Services(ns).List(metav1.ListOptions{})
  362. framework.ExpectNoError(err)
  363. for _, svc := range svcList.Items {
  364. svc.Annotations = svcAnnotations
  365. _, err = j.Client.CoreV1().Services(ns).Update(&svc)
  366. framework.ExpectNoError(err)
  367. }
  368. }
  369. if exists("secret.yaml") {
  370. j.Logger.Infof("creating secret")
  371. framework.RunKubectlOrDieInput(read("secret.yaml"), "create", "-f", "-", fmt.Sprintf("--namespace=%v", ns))
  372. }
  373. j.Logger.Infof("Parsing ingress from %v", filepath.Join(manifestPath, "ing.yaml"))
  374. j.Ingress, err = manifest.IngressFromManifest(filepath.Join(manifestPath, "ing.yaml"))
  375. framework.ExpectNoError(err)
  376. j.Ingress.Namespace = ns
  377. j.Ingress.Annotations = map[string]string{IngressClassKey: j.Class}
  378. for k, v := range ingAnnotations {
  379. j.Ingress.Annotations[k] = v
  380. }
  381. j.Logger.Infof(fmt.Sprintf("creating " + j.Ingress.Name + " ingress"))
  382. j.Ingress, err = j.runCreate(j.Ingress)
  383. framework.ExpectNoError(err)
  384. }
  385. // runCreate runs the required command to create the given ingress.
  386. func (j *TestJig) runCreate(ing *networkingv1beta1.Ingress) (*networkingv1beta1.Ingress, error) {
  387. if j.Class != MulticlusterIngressClassValue {
  388. return j.Client.NetworkingV1beta1().Ingresses(ing.Namespace).Create(ing)
  389. }
  390. // Use kubemci to create a multicluster ingress.
  391. filePath := framework.TestContext.OutputDir + "/mci.yaml"
  392. if err := manifest.IngressToManifest(ing, filePath); err != nil {
  393. return nil, err
  394. }
  395. _, err := framework.RunKubemciWithKubeconfig("create", ing.Name, fmt.Sprintf("--ingress=%s", filePath))
  396. return ing, err
  397. }
  398. // runUpdate runs the required command to update the given ingress.
  399. func (j *TestJig) runUpdate(ing *networkingv1beta1.Ingress) (*networkingv1beta1.Ingress, error) {
  400. if j.Class != MulticlusterIngressClassValue {
  401. return j.Client.NetworkingV1beta1().Ingresses(ing.Namespace).Update(ing)
  402. }
  403. // Use kubemci to update a multicluster ingress.
  404. // kubemci does not have an update command. We use "create --force" to update an existing ingress.
  405. filePath := framework.TestContext.OutputDir + "/mci.yaml"
  406. if err := manifest.IngressToManifest(ing, filePath); err != nil {
  407. return nil, err
  408. }
  409. _, err := framework.RunKubemciWithKubeconfig("create", ing.Name, fmt.Sprintf("--ingress=%s", filePath), "--force")
  410. return ing, err
  411. }
  412. // Update retrieves the ingress, performs the passed function, and then updates it.
  413. func (j *TestJig) Update(update func(ing *networkingv1beta1.Ingress)) {
  414. var err error
  415. ns, name := j.Ingress.Namespace, j.Ingress.Name
  416. for i := 0; i < 3; i++ {
  417. j.Ingress, err = j.Client.NetworkingV1beta1().Ingresses(ns).Get(name, metav1.GetOptions{})
  418. if err != nil {
  419. framework.Failf("failed to get ingress %s/%s: %v", ns, name, err)
  420. }
  421. update(j.Ingress)
  422. j.Ingress, err = j.runUpdate(j.Ingress)
  423. if err == nil {
  424. framework.DescribeIng(j.Ingress.Namespace)
  425. return
  426. }
  427. if !apierrs.IsConflict(err) && !apierrs.IsServerTimeout(err) {
  428. framework.Failf("failed to update ingress %s/%s: %v", ns, name, err)
  429. }
  430. }
  431. framework.Failf("too many retries updating ingress %s/%s", ns, name)
  432. }
  433. // AddHTTPS updates the ingress to add this secret for these hosts.
  434. func (j *TestJig) AddHTTPS(secretName string, hosts ...string) {
  435. // TODO: Just create the secret in GetRootCAs once we're watching secrets in
  436. // the ingress controller.
  437. _, cert, _, err := createTLSSecret(j.Client, j.Ingress.Namespace, secretName, hosts...)
  438. framework.ExpectNoError(err)
  439. j.Logger.Infof("Updating ingress %v to also use secret %v for TLS termination", j.Ingress.Name, secretName)
  440. j.Update(func(ing *networkingv1beta1.Ingress) {
  441. ing.Spec.TLS = append(ing.Spec.TLS, networkingv1beta1.IngressTLS{Hosts: hosts, SecretName: secretName})
  442. })
  443. j.RootCAs[secretName] = cert
  444. }
  445. // SetHTTPS updates the ingress to use only this secret for these hosts.
  446. func (j *TestJig) SetHTTPS(secretName string, hosts ...string) {
  447. _, cert, _, err := createTLSSecret(j.Client, j.Ingress.Namespace, secretName, hosts...)
  448. framework.ExpectNoError(err)
  449. j.Logger.Infof("Updating ingress %v to only use secret %v for TLS termination", j.Ingress.Name, secretName)
  450. j.Update(func(ing *networkingv1beta1.Ingress) {
  451. ing.Spec.TLS = []networkingv1beta1.IngressTLS{{Hosts: hosts, SecretName: secretName}}
  452. })
  453. j.RootCAs = map[string][]byte{secretName: cert}
  454. }
  455. // RemoveHTTPS updates the ingress to not use this secret for TLS.
  456. // Note: Does not delete the secret.
  457. func (j *TestJig) RemoveHTTPS(secretName string) {
  458. newTLS := []networkingv1beta1.IngressTLS{}
  459. for _, ingressTLS := range j.Ingress.Spec.TLS {
  460. if secretName != ingressTLS.SecretName {
  461. newTLS = append(newTLS, ingressTLS)
  462. }
  463. }
  464. j.Logger.Infof("Updating ingress %v to not use secret %v for TLS termination", j.Ingress.Name, secretName)
  465. j.Update(func(ing *networkingv1beta1.Ingress) {
  466. ing.Spec.TLS = newTLS
  467. })
  468. delete(j.RootCAs, secretName)
  469. }
  470. // PrepareTLSSecret creates a TLS secret and caches the cert.
  471. func (j *TestJig) PrepareTLSSecret(namespace, secretName string, hosts ...string) error {
  472. _, cert, _, err := createTLSSecret(j.Client, namespace, secretName, hosts...)
  473. if err != nil {
  474. return err
  475. }
  476. j.RootCAs[secretName] = cert
  477. return nil
  478. }
  479. // GetRootCA returns a rootCA from the ingress test jig.
  480. func (j *TestJig) GetRootCA(secretName string) (rootCA []byte) {
  481. var ok bool
  482. rootCA, ok = j.RootCAs[secretName]
  483. if !ok {
  484. framework.Failf("Failed to retrieve rootCAs, no recorded secret by name %v", secretName)
  485. }
  486. return
  487. }
  488. // TryDeleteIngress attempts to delete the ingress resource and logs errors if they occur.
  489. func (j *TestJig) TryDeleteIngress() {
  490. j.tryDeleteGivenIngress(j.Ingress)
  491. }
  492. func (j *TestJig) tryDeleteGivenIngress(ing *networkingv1beta1.Ingress) {
  493. if err := j.runDelete(ing); err != nil {
  494. j.Logger.Infof("Error while deleting the ingress %v/%v with class %s: %v", ing.Namespace, ing.Name, j.Class, err)
  495. }
  496. }
  497. // runDelete runs the required command to delete the given ingress.
  498. func (j *TestJig) runDelete(ing *networkingv1beta1.Ingress) error {
  499. if j.Class != MulticlusterIngressClassValue {
  500. return j.Client.NetworkingV1beta1().Ingresses(ing.Namespace).Delete(ing.Name, nil)
  501. }
  502. // Use kubemci to delete a multicluster ingress.
  503. filePath := framework.TestContext.OutputDir + "/mci.yaml"
  504. if err := manifest.IngressToManifest(ing, filePath); err != nil {
  505. return err
  506. }
  507. _, err := framework.RunKubemciWithKubeconfig("delete", ing.Name, fmt.Sprintf("--ingress=%s", filePath))
  508. return err
  509. }
  510. // getIngressAddressFromKubemci returns the IP address of the given multicluster ingress using kubemci.
  511. // TODO(nikhiljindal): Update this to be able to return hostname as well.
  512. func getIngressAddressFromKubemci(name string) ([]string, error) {
  513. var addresses []string
  514. out, err := framework.RunKubemciCmd("get-status", name)
  515. if err != nil {
  516. return addresses, err
  517. }
  518. ip := findIPv4(out)
  519. if ip != "" {
  520. addresses = append(addresses, ip)
  521. }
  522. return addresses, err
  523. }
  524. // findIPv4 returns the first IPv4 address found in the given string.
  525. func findIPv4(input string) string {
  526. numBlock := "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
  527. regexPattern := numBlock + "\\." + numBlock + "\\." + numBlock + "\\." + numBlock
  528. regEx := regexp.MustCompile(regexPattern)
  529. return regEx.FindString(input)
  530. }
  531. // getIngressAddress returns the ips/hostnames associated with the Ingress.
  532. func getIngressAddress(client clientset.Interface, ns, name, class string) ([]string, error) {
  533. if class == MulticlusterIngressClassValue {
  534. return getIngressAddressFromKubemci(name)
  535. }
  536. ing, err := client.NetworkingV1beta1().Ingresses(ns).Get(name, metav1.GetOptions{})
  537. if err != nil {
  538. return nil, err
  539. }
  540. var addresses []string
  541. for _, a := range ing.Status.LoadBalancer.Ingress {
  542. if a.IP != "" {
  543. addresses = append(addresses, a.IP)
  544. }
  545. if a.Hostname != "" {
  546. addresses = append(addresses, a.Hostname)
  547. }
  548. }
  549. return addresses, nil
  550. }
  551. // WaitForIngressAddress waits for the Ingress to acquire an address.
  552. func (j *TestJig) WaitForIngressAddress(c clientset.Interface, ns, ingName string, timeout time.Duration) (string, error) {
  553. var address string
  554. err := wait.PollImmediate(10*time.Second, timeout, func() (bool, error) {
  555. ipOrNameList, err := getIngressAddress(c, ns, ingName, j.Class)
  556. if err != nil || len(ipOrNameList) == 0 {
  557. j.Logger.Errorf("Waiting for Ingress %s/%s to acquire IP, error: %v, ipOrNameList: %v", ns, ingName, err, ipOrNameList)
  558. if testutils.IsRetryableAPIError(err) {
  559. return false, nil
  560. }
  561. return false, err
  562. }
  563. address = ipOrNameList[0]
  564. j.Logger.Infof("Found address %s for ingress %s/%s", address, ns, ingName)
  565. return true, nil
  566. })
  567. return address, err
  568. }
  569. func (j *TestJig) pollIngressWithCert(ing *networkingv1beta1.Ingress, address string, knownHosts []string, cert []byte, waitForNodePort bool, timeout time.Duration) error {
  570. // Check that all rules respond to a simple GET.
  571. knownHostsSet := sets.NewString(knownHosts...)
  572. for _, rules := range ing.Spec.Rules {
  573. timeoutClient := &http.Client{Timeout: IngressReqTimeout}
  574. proto := "http"
  575. if knownHostsSet.Has(rules.Host) {
  576. var err error
  577. // Create transport with cert to verify if the server uses the correct one.
  578. timeoutClient.Transport, err = buildTransportWithCA(rules.Host, cert)
  579. if err != nil {
  580. return err
  581. }
  582. proto = "https"
  583. }
  584. for _, p := range rules.IngressRuleValue.HTTP.Paths {
  585. if waitForNodePort {
  586. nodePort := int(p.Backend.ServicePort.IntVal)
  587. if err := j.pollServiceNodePort(ing.Namespace, p.Backend.ServiceName, nodePort); err != nil {
  588. j.Logger.Infof("Error in waiting for nodeport %d on service %v/%v: %s", nodePort, ing.Namespace, p.Backend.ServiceName, err)
  589. return err
  590. }
  591. }
  592. route := fmt.Sprintf("%v://%v%v", proto, address, p.Path)
  593. j.Logger.Infof("Testing route %v host %v with simple GET", route, rules.Host)
  594. if err := framework.PollURL(route, rules.Host, timeout, j.PollInterval, timeoutClient, false); err != nil {
  595. return err
  596. }
  597. }
  598. }
  599. j.Logger.Infof("Finished polling on all rules on ingress %q", ing.Name)
  600. return nil
  601. }
  602. // WaitForIngress waits for the Ingress to get an address.
  603. // WaitForIngress returns when it gets the first 200 response
  604. func (j *TestJig) WaitForIngress(waitForNodePort bool) {
  605. if err := j.WaitForGivenIngressWithTimeout(j.Ingress, waitForNodePort, framework.LoadBalancerPollTimeout); err != nil {
  606. framework.Failf("error in waiting for ingress to get an address: %s", err)
  607. }
  608. }
  609. // WaitForIngressToStable waits for the LB return 100 consecutive 200 responses.
  610. func (j *TestJig) WaitForIngressToStable() {
  611. if err := wait.Poll(10*time.Second, framework.LoadBalancerCreateTimeoutDefault, func() (bool, error) {
  612. _, err := j.GetDistinctResponseFromIngress()
  613. if err != nil {
  614. return false, nil
  615. }
  616. return true, nil
  617. }); err != nil {
  618. framework.Failf("error in waiting for ingress to stablize: %v", err)
  619. }
  620. }
  621. // WaitForGivenIngressWithTimeout waits till the ingress acquires an IP,
  622. // then waits for its hosts/urls to respond to a protocol check (either
  623. // http or https). If waitForNodePort is true, the NodePort of the Service
  624. // is verified before verifying the Ingress. NodePort is currently a
  625. // requirement for cloudprovider Ingress.
  626. func (j *TestJig) WaitForGivenIngressWithTimeout(ing *networkingv1beta1.Ingress, waitForNodePort bool, timeout time.Duration) error {
  627. // Wait for the loadbalancer IP.
  628. address, err := j.WaitForIngressAddress(j.Client, ing.Namespace, ing.Name, timeout)
  629. if err != nil {
  630. return fmt.Errorf("Ingress failed to acquire an IP address within %v", timeout)
  631. }
  632. var knownHosts []string
  633. var cert []byte
  634. if len(ing.Spec.TLS) > 0 {
  635. knownHosts = ing.Spec.TLS[0].Hosts
  636. cert = j.GetRootCA(ing.Spec.TLS[0].SecretName)
  637. }
  638. return j.pollIngressWithCert(ing, address, knownHosts, cert, waitForNodePort, timeout)
  639. }
  640. // WaitForIngressWithCert waits till the ingress acquires an IP, then waits for its
  641. // hosts/urls to respond to a protocol check (either http or https). If
  642. // waitForNodePort is true, the NodePort of the Service is verified before
  643. // verifying the Ingress. NodePort is currently a requirement for cloudprovider
  644. // Ingress. Hostnames and certificate need to be explicitly passed in.
  645. func (j *TestJig) WaitForIngressWithCert(waitForNodePort bool, knownHosts []string, cert []byte) error {
  646. // Wait for the loadbalancer IP.
  647. address, err := j.WaitForIngressAddress(j.Client, j.Ingress.Namespace, j.Ingress.Name, framework.LoadBalancerPollTimeout)
  648. if err != nil {
  649. return fmt.Errorf("Ingress failed to acquire an IP address within %v", framework.LoadBalancerPollTimeout)
  650. }
  651. return j.pollIngressWithCert(j.Ingress, address, knownHosts, cert, waitForNodePort, framework.LoadBalancerPollTimeout)
  652. }
  653. // VerifyURL polls for the given iterations, in intervals, and fails if the
  654. // given url returns a non-healthy http code even once.
  655. func (j *TestJig) VerifyURL(route, host string, iterations int, interval time.Duration, httpClient *http.Client) error {
  656. for i := 0; i < iterations; i++ {
  657. b, err := framework.SimpleGET(httpClient, route, host)
  658. if err != nil {
  659. e2elog.Logf(b)
  660. return err
  661. }
  662. j.Logger.Infof("Verified %v with host %v %d times, sleeping for %v", route, host, i, interval)
  663. time.Sleep(interval)
  664. }
  665. return nil
  666. }
  667. func (j *TestJig) pollServiceNodePort(ns, name string, port int) error {
  668. // TODO: Curl all nodes?
  669. u, err := framework.GetNodePortURL(j.Client, ns, name, port)
  670. if err != nil {
  671. return err
  672. }
  673. return framework.PollURL(u, "", 30*time.Second, j.PollInterval, &http.Client{Timeout: IngressReqTimeout}, false)
  674. }
  675. // GetIngressNodePorts returns related backend services' nodePorts.
  676. // Current GCE ingress controller allows traffic to the default HTTP backend
  677. // by default, so retrieve its nodePort if includeDefaultBackend is true.
  678. func (j *TestJig) GetIngressNodePorts(includeDefaultBackend bool) []string {
  679. nodePorts := []string{}
  680. svcPorts := j.GetServicePorts(includeDefaultBackend)
  681. for _, svcPort := range svcPorts {
  682. nodePorts = append(nodePorts, strconv.Itoa(int(svcPort.NodePort)))
  683. }
  684. return nodePorts
  685. }
  686. // GetServicePorts returns related backend services' svcPorts.
  687. // Current GCE ingress controller allows traffic to the default HTTP backend
  688. // by default, so retrieve its nodePort if includeDefaultBackend is true.
  689. func (j *TestJig) GetServicePorts(includeDefaultBackend bool) map[string]v1.ServicePort {
  690. svcPorts := make(map[string]v1.ServicePort)
  691. if includeDefaultBackend {
  692. defaultSvc, err := j.Client.CoreV1().Services(metav1.NamespaceSystem).Get(defaultBackendName, metav1.GetOptions{})
  693. framework.ExpectNoError(err)
  694. svcPorts[defaultBackendName] = defaultSvc.Spec.Ports[0]
  695. }
  696. backendSvcs := []string{}
  697. if j.Ingress.Spec.Backend != nil {
  698. backendSvcs = append(backendSvcs, j.Ingress.Spec.Backend.ServiceName)
  699. }
  700. for _, rule := range j.Ingress.Spec.Rules {
  701. for _, ingPath := range rule.HTTP.Paths {
  702. backendSvcs = append(backendSvcs, ingPath.Backend.ServiceName)
  703. }
  704. }
  705. for _, svcName := range backendSvcs {
  706. svc, err := j.Client.CoreV1().Services(j.Ingress.Namespace).Get(svcName, metav1.GetOptions{})
  707. framework.ExpectNoError(err)
  708. svcPorts[svcName] = svc.Spec.Ports[0]
  709. }
  710. return svcPorts
  711. }
  712. // ConstructFirewallForIngress returns the expected GCE firewall rule for the ingress resource
  713. func (j *TestJig) ConstructFirewallForIngress(firewallRuleName string, nodeTags []string) *compute.Firewall {
  714. nodePorts := j.GetIngressNodePorts(true)
  715. fw := compute.Firewall{}
  716. fw.Name = firewallRuleName
  717. fw.SourceRanges = framework.TestContext.CloudConfig.Provider.LoadBalancerSrcRanges()
  718. fw.TargetTags = nodeTags
  719. fw.Allowed = []*compute.FirewallAllowed{
  720. {
  721. IPProtocol: "tcp",
  722. Ports: nodePorts,
  723. },
  724. }
  725. return &fw
  726. }
  727. // GetDistinctResponseFromIngress tries GET call to the ingress VIP and return all distinct responses.
  728. func (j *TestJig) GetDistinctResponseFromIngress() (sets.String, error) {
  729. // Wait for the loadbalancer IP.
  730. address, err := j.WaitForIngressAddress(j.Client, j.Ingress.Namespace, j.Ingress.Name, framework.LoadBalancerPollTimeout)
  731. if err != nil {
  732. framework.Failf("Ingress failed to acquire an IP address within %v", framework.LoadBalancerPollTimeout)
  733. }
  734. responses := sets.NewString()
  735. timeoutClient := &http.Client{Timeout: IngressReqTimeout}
  736. for i := 0; i < 100; i++ {
  737. url := fmt.Sprintf("http://%v", address)
  738. res, err := framework.SimpleGET(timeoutClient, url, "")
  739. if err != nil {
  740. j.Logger.Errorf("Failed to GET %q. Got responses: %q: %v", url, res, err)
  741. return responses, err
  742. }
  743. responses.Insert(res)
  744. }
  745. return responses, nil
  746. }
  747. // NginxIngressController manages implementation details of Ingress on Nginx.
  748. type NginxIngressController struct {
  749. Ns string
  750. rc *v1.ReplicationController
  751. pod *v1.Pod
  752. Client clientset.Interface
  753. externalIP string
  754. }
  755. // Init initializes the NginxIngressController
  756. func (cont *NginxIngressController) Init() {
  757. read := func(file string) string {
  758. return string(testfiles.ReadOrDie(filepath.Join(IngressManifestPath, "nginx", file), ginkgo.Fail))
  759. }
  760. e2elog.Logf("initializing nginx ingress controller")
  761. framework.RunKubectlOrDieInput(read("rc.yaml"), "create", "-f", "-", fmt.Sprintf("--namespace=%v", cont.Ns))
  762. rc, err := cont.Client.CoreV1().ReplicationControllers(cont.Ns).Get("nginx-ingress-controller", metav1.GetOptions{})
  763. framework.ExpectNoError(err)
  764. cont.rc = rc
  765. e2elog.Logf("waiting for pods with label %v", rc.Spec.Selector)
  766. sel := labels.SelectorFromSet(labels.Set(rc.Spec.Selector))
  767. framework.ExpectNoError(testutils.WaitForPodsWithLabelRunning(cont.Client, cont.Ns, sel))
  768. pods, err := cont.Client.CoreV1().Pods(cont.Ns).List(metav1.ListOptions{LabelSelector: sel.String()})
  769. framework.ExpectNoError(err)
  770. if len(pods.Items) == 0 {
  771. framework.Failf("Failed to find nginx ingress controller pods with selector %v", sel)
  772. }
  773. cont.pod = &pods.Items[0]
  774. cont.externalIP, err = framework.GetHostExternalAddress(cont.Client, cont.pod)
  775. framework.ExpectNoError(err)
  776. e2elog.Logf("ingress controller running in pod %v on ip %v", cont.pod.Name, cont.externalIP)
  777. }
  778. func generateBacksideHTTPSIngressSpec(ns string) *networkingv1beta1.Ingress {
  779. return &networkingv1beta1.Ingress{
  780. ObjectMeta: metav1.ObjectMeta{
  781. Name: "echoheaders-https",
  782. Namespace: ns,
  783. },
  784. Spec: networkingv1beta1.IngressSpec{
  785. // Note kubemci requires a default backend.
  786. Backend: &networkingv1beta1.IngressBackend{
  787. ServiceName: "echoheaders-https",
  788. ServicePort: intstr.IntOrString{
  789. Type: intstr.Int,
  790. IntVal: 443,
  791. },
  792. },
  793. },
  794. }
  795. }
  796. func generateBacksideHTTPSServiceSpec() *v1.Service {
  797. return &v1.Service{
  798. ObjectMeta: metav1.ObjectMeta{
  799. Name: "echoheaders-https",
  800. Annotations: map[string]string{
  801. ServiceApplicationProtocolKey: `{"my-https-port":"HTTPS"}`,
  802. },
  803. },
  804. Spec: v1.ServiceSpec{
  805. Ports: []v1.ServicePort{{
  806. Name: "my-https-port",
  807. Protocol: v1.ProtocolTCP,
  808. Port: 443,
  809. TargetPort: intstr.FromString("echo-443"),
  810. }},
  811. Selector: map[string]string{
  812. "app": "echoheaders-https",
  813. },
  814. Type: v1.ServiceTypeNodePort,
  815. },
  816. }
  817. }
  818. func generateBacksideHTTPSDeploymentSpec() *apps.Deployment {
  819. return &apps.Deployment{
  820. ObjectMeta: metav1.ObjectMeta{
  821. Name: "echoheaders-https",
  822. },
  823. Spec: apps.DeploymentSpec{
  824. Selector: &metav1.LabelSelector{MatchLabels: map[string]string{
  825. "app": "echoheaders-https",
  826. }},
  827. Template: v1.PodTemplateSpec{
  828. ObjectMeta: metav1.ObjectMeta{
  829. Labels: map[string]string{
  830. "app": "echoheaders-https",
  831. },
  832. },
  833. Spec: v1.PodSpec{
  834. Containers: []v1.Container{
  835. {
  836. Name: "echoheaders-https",
  837. Image: "k8s.gcr.io/echoserver:1.10",
  838. Ports: []v1.ContainerPort{{
  839. ContainerPort: 8443,
  840. Name: "echo-443",
  841. }},
  842. },
  843. },
  844. },
  845. },
  846. },
  847. }
  848. }
  849. // SetUpBacksideHTTPSIngress sets up deployment, service and ingress with backside HTTPS configured.
  850. func (j *TestJig) SetUpBacksideHTTPSIngress(cs clientset.Interface, namespace string, staticIPName string) (*apps.Deployment, *v1.Service, *networkingv1beta1.Ingress, error) {
  851. deployCreated, err := cs.AppsV1().Deployments(namespace).Create(generateBacksideHTTPSDeploymentSpec())
  852. if err != nil {
  853. return nil, nil, nil, err
  854. }
  855. svcCreated, err := cs.CoreV1().Services(namespace).Create(generateBacksideHTTPSServiceSpec())
  856. if err != nil {
  857. return nil, nil, nil, err
  858. }
  859. ingToCreate := generateBacksideHTTPSIngressSpec(namespace)
  860. if staticIPName != "" {
  861. if ingToCreate.Annotations == nil {
  862. ingToCreate.Annotations = map[string]string{}
  863. }
  864. ingToCreate.Annotations[IngressStaticIPKey] = staticIPName
  865. }
  866. ingCreated, err := j.runCreate(ingToCreate)
  867. if err != nil {
  868. return nil, nil, nil, err
  869. }
  870. return deployCreated, svcCreated, ingCreated, nil
  871. }
  872. // DeleteTestResource deletes given deployment, service and ingress.
  873. func (j *TestJig) DeleteTestResource(cs clientset.Interface, deploy *apps.Deployment, svc *v1.Service, ing *networkingv1beta1.Ingress) []error {
  874. var errs []error
  875. if ing != nil {
  876. if err := j.runDelete(ing); err != nil {
  877. errs = append(errs, fmt.Errorf("error while deleting ingress %s/%s: %v", ing.Namespace, ing.Name, err))
  878. }
  879. }
  880. if svc != nil {
  881. if err := cs.CoreV1().Services(svc.Namespace).Delete(svc.Name, nil); err != nil {
  882. errs = append(errs, fmt.Errorf("error while deleting service %s/%s: %v", svc.Namespace, svc.Name, err))
  883. }
  884. }
  885. if deploy != nil {
  886. if err := cs.AppsV1().Deployments(deploy.Namespace).Delete(deploy.Name, nil); err != nil {
  887. errs = append(errs, fmt.Errorf("error while deleting deployment %s/%s: %v", deploy.Namespace, deploy.Name, err))
  888. }
  889. }
  890. return errs
  891. }