ingress_utils.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  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 e2e
  14. import (
  15. "bytes"
  16. "crypto/rand"
  17. "crypto/rsa"
  18. "crypto/tls"
  19. "crypto/x509"
  20. "crypto/x509/pkix"
  21. "encoding/json"
  22. "encoding/pem"
  23. "fmt"
  24. "io"
  25. "io/ioutil"
  26. "math/big"
  27. "net"
  28. "net/http"
  29. "os"
  30. "os/exec"
  31. "path/filepath"
  32. "strings"
  33. "time"
  34. "k8s.io/kubernetes/pkg/api"
  35. compute "google.golang.org/api/compute/v1"
  36. apierrs "k8s.io/kubernetes/pkg/api/errors"
  37. "k8s.io/kubernetes/pkg/apis/extensions"
  38. client "k8s.io/kubernetes/pkg/client/unversioned"
  39. "k8s.io/kubernetes/pkg/labels"
  40. "k8s.io/kubernetes/pkg/runtime"
  41. utilexec "k8s.io/kubernetes/pkg/util/exec"
  42. utilnet "k8s.io/kubernetes/pkg/util/net"
  43. "k8s.io/kubernetes/pkg/util/sets"
  44. "k8s.io/kubernetes/pkg/util/wait"
  45. utilyaml "k8s.io/kubernetes/pkg/util/yaml"
  46. "k8s.io/kubernetes/test/e2e/framework"
  47. . "github.com/onsi/ginkgo"
  48. . "github.com/onsi/gomega"
  49. )
  50. const (
  51. rsaBits = 2048
  52. validFor = 365 * 24 * time.Hour
  53. // Ingress class annotation defined in ingress repository.
  54. ingressClass = "kubernetes.io/ingress.class"
  55. )
  56. type testJig struct {
  57. client *client.Client
  58. rootCAs map[string][]byte
  59. address string
  60. ing *extensions.Ingress
  61. // class is the value of the annotation keyed under
  62. // `kubernetes.io/ingress.class`. It's added to all ingresses created by
  63. // this jig.
  64. class string
  65. }
  66. type conformanceTests struct {
  67. entryLog string
  68. execute func()
  69. exitLog string
  70. }
  71. func createComformanceTests(jig *testJig, ns string) []conformanceTests {
  72. manifestPath := filepath.Join(ingressManifestPath, "http")
  73. // These constants match the manifests used in ingressManifestPath
  74. tlsHost := "foo.bar.com"
  75. tlsSecretName := "foo"
  76. updatedTLSHost := "foobar.com"
  77. updateURLMapHost := "bar.baz.com"
  78. updateURLMapPath := "/testurl"
  79. // Platform agnostic list of tests that must be satisfied by all controllers
  80. return []conformanceTests{
  81. {
  82. fmt.Sprintf("should create a basic HTTP ingress"),
  83. func() { jig.createIngress(manifestPath, ns, map[string]string{}) },
  84. fmt.Sprintf("waiting for urls on basic HTTP ingress"),
  85. },
  86. {
  87. fmt.Sprintf("should terminate TLS for host %v", tlsHost),
  88. func() { jig.addHTTPS(tlsSecretName, tlsHost) },
  89. fmt.Sprintf("waiting for HTTPS updates to reflect in ingress"),
  90. },
  91. {
  92. fmt.Sprintf("should update SSL certificated with modified hostname %v", updatedTLSHost),
  93. func() {
  94. jig.update(func(ing *extensions.Ingress) {
  95. newRules := []extensions.IngressRule{}
  96. for _, rule := range ing.Spec.Rules {
  97. if rule.Host != tlsHost {
  98. newRules = append(newRules, rule)
  99. continue
  100. }
  101. newRules = append(newRules, extensions.IngressRule{
  102. Host: updatedTLSHost,
  103. IngressRuleValue: rule.IngressRuleValue,
  104. })
  105. }
  106. ing.Spec.Rules = newRules
  107. })
  108. jig.addHTTPS(tlsSecretName, updatedTLSHost)
  109. },
  110. fmt.Sprintf("Waiting for updated certificates to accept requests for host %v", updatedTLSHost),
  111. },
  112. {
  113. fmt.Sprintf("should update url map for host %v to expose a single url: %v", updateURLMapHost, updateURLMapPath),
  114. func() {
  115. var pathToFail string
  116. jig.update(func(ing *extensions.Ingress) {
  117. newRules := []extensions.IngressRule{}
  118. for _, rule := range ing.Spec.Rules {
  119. if rule.Host != updateURLMapHost {
  120. newRules = append(newRules, rule)
  121. continue
  122. }
  123. existingPath := rule.IngressRuleValue.HTTP.Paths[0]
  124. pathToFail = existingPath.Path
  125. newRules = append(newRules, extensions.IngressRule{
  126. Host: updateURLMapHost,
  127. IngressRuleValue: extensions.IngressRuleValue{
  128. HTTP: &extensions.HTTPIngressRuleValue{
  129. Paths: []extensions.HTTPIngressPath{
  130. {
  131. Path: updateURLMapPath,
  132. Backend: existingPath.Backend,
  133. },
  134. },
  135. },
  136. },
  137. })
  138. }
  139. ing.Spec.Rules = newRules
  140. })
  141. By("Checking that " + pathToFail + " is not exposed by polling for failure")
  142. route := fmt.Sprintf("http://%v%v", jig.address, pathToFail)
  143. ExpectNoError(pollURL(route, updateURLMapHost, lbCleanupTimeout, &http.Client{Timeout: reqTimeout}, true))
  144. },
  145. fmt.Sprintf("Waiting for path updates to reflect in L7"),
  146. },
  147. }
  148. }
  149. // pollURL polls till the url responds with a healthy http code. If
  150. // expectUnreachable is true, it breaks on first non-healthy http code instead.
  151. func pollURL(route, host string, timeout time.Duration, httpClient *http.Client, expectUnreachable bool) error {
  152. var lastBody string
  153. pollErr := wait.PollImmediate(lbPollInterval, timeout, func() (bool, error) {
  154. var err error
  155. lastBody, err = simpleGET(httpClient, route, host)
  156. if err != nil {
  157. framework.Logf("host %v path %v: %v unreachable", host, route, err)
  158. return expectUnreachable, nil
  159. }
  160. return !expectUnreachable, nil
  161. })
  162. if pollErr != nil {
  163. return fmt.Errorf("Failed to execute a successful GET within %v, Last response body for %v, host %v:\n%v\n\n%v\n",
  164. timeout, route, host, lastBody, pollErr)
  165. }
  166. return nil
  167. }
  168. // generateRSACerts generates a basic self signed certificate using a key length
  169. // of rsaBits, valid for validFor time.
  170. func generateRSACerts(host string, isCA bool, keyOut, certOut io.Writer) error {
  171. if len(host) == 0 {
  172. return fmt.Errorf("Require a non-empty host for client hello")
  173. }
  174. priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
  175. if err != nil {
  176. return fmt.Errorf("Failed to generate key: %v", err)
  177. }
  178. notBefore := time.Now()
  179. notAfter := notBefore.Add(validFor)
  180. serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
  181. serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
  182. if err != nil {
  183. return fmt.Errorf("failed to generate serial number: %s", err)
  184. }
  185. template := x509.Certificate{
  186. SerialNumber: serialNumber,
  187. Subject: pkix.Name{
  188. CommonName: "default",
  189. Organization: []string{"Acme Co"},
  190. },
  191. NotBefore: notBefore,
  192. NotAfter: notAfter,
  193. KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
  194. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  195. BasicConstraintsValid: true,
  196. }
  197. hosts := strings.Split(host, ",")
  198. for _, h := range hosts {
  199. if ip := net.ParseIP(h); ip != nil {
  200. template.IPAddresses = append(template.IPAddresses, ip)
  201. } else {
  202. template.DNSNames = append(template.DNSNames, h)
  203. }
  204. }
  205. if isCA {
  206. template.IsCA = true
  207. template.KeyUsage |= x509.KeyUsageCertSign
  208. }
  209. derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
  210. if err != nil {
  211. return fmt.Errorf("Failed to create certificate: %s", err)
  212. }
  213. if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
  214. return fmt.Errorf("Failed creating cert: %v", err)
  215. }
  216. if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
  217. return fmt.Errorf("Failed creating keay: %v", err)
  218. }
  219. return nil
  220. }
  221. // buildTransport creates a transport for use in executing HTTPS requests with
  222. // the given certs. Note that the given rootCA must be configured with isCA=true.
  223. func buildTransport(serverName string, rootCA []byte) (*http.Transport, error) {
  224. pool := x509.NewCertPool()
  225. ok := pool.AppendCertsFromPEM(rootCA)
  226. if !ok {
  227. return nil, fmt.Errorf("Unable to load serverCA.")
  228. }
  229. return utilnet.SetTransportDefaults(&http.Transport{
  230. TLSClientConfig: &tls.Config{
  231. InsecureSkipVerify: false,
  232. ServerName: serverName,
  233. RootCAs: pool,
  234. },
  235. }), nil
  236. }
  237. // buildInsecureClient returns an insecure http client. Can be used for "curl -k".
  238. func buildInsecureClient(timeout time.Duration) *http.Client {
  239. t := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
  240. return &http.Client{Timeout: timeout, Transport: utilnet.SetTransportDefaults(t)}
  241. }
  242. // createSecret creates a secret containing TLS certificates for the given Ingress.
  243. // If a secret with the same name already exists in the namespace of the
  244. // Ingress, it's updated.
  245. func createSecret(kubeClient *client.Client, ing *extensions.Ingress) (host string, rootCA, privKey []byte, err error) {
  246. var k, c bytes.Buffer
  247. tls := ing.Spec.TLS[0]
  248. host = strings.Join(tls.Hosts, ",")
  249. framework.Logf("Generating RSA cert for host %v", host)
  250. if err = generateRSACerts(host, true, &k, &c); err != nil {
  251. return
  252. }
  253. cert := c.Bytes()
  254. key := k.Bytes()
  255. secret := &api.Secret{
  256. ObjectMeta: api.ObjectMeta{
  257. Name: tls.SecretName,
  258. },
  259. Data: map[string][]byte{
  260. api.TLSCertKey: cert,
  261. api.TLSPrivateKeyKey: key,
  262. },
  263. }
  264. var s *api.Secret
  265. if s, err = kubeClient.Secrets(ing.Namespace).Get(tls.SecretName); err == nil {
  266. // TODO: Retry the update. We don't really expect anything to conflict though.
  267. framework.Logf("Updating secret %v in ns %v with hosts %v for ingress %v", secret.Name, secret.Namespace, host, ing.Name)
  268. s.Data = secret.Data
  269. _, err = kubeClient.Secrets(ing.Namespace).Update(s)
  270. } else {
  271. framework.Logf("Creating secret %v in ns %v with hosts %v for ingress %v", secret.Name, secret.Namespace, host, ing.Name)
  272. _, err = kubeClient.Secrets(ing.Namespace).Create(secret)
  273. }
  274. return host, cert, key, err
  275. }
  276. func describeIng(ns string) {
  277. framework.Logf("\nOutput of kubectl describe ing:\n")
  278. desc, _ := framework.RunKubectl(
  279. "describe", "ing", fmt.Sprintf("--namespace=%v", ns))
  280. framework.Logf(desc)
  281. }
  282. func cleanupGCE(gceController *GCEIngressController) {
  283. if pollErr := wait.Poll(5*time.Second, lbCleanupTimeout, func() (bool, error) {
  284. if err := gceController.Cleanup(false); err != nil {
  285. framework.Logf("Still waiting for glbc to cleanup: %v", err)
  286. return false, nil
  287. }
  288. return true, nil
  289. }); pollErr != nil {
  290. if cleanupErr := gceController.Cleanup(true); cleanupErr != nil {
  291. framework.Logf("WARNING: Failed to cleanup resources %v", cleanupErr)
  292. }
  293. framework.Failf("Failed to cleanup GCE L7 resources.")
  294. }
  295. }
  296. func (cont *GCEIngressController) deleteForwardingRule(del bool) string {
  297. msg := ""
  298. fwList := []compute.ForwardingRule{}
  299. for _, regex := range []string{fmt.Sprintf("k8s-fw-.*--%v", cont.UID), fmt.Sprintf("k8s-fws-.*--%v", cont.UID)} {
  300. gcloudList("forwarding-rules", regex, cont.Project, &fwList)
  301. if len(fwList) != 0 {
  302. for _, f := range fwList {
  303. msg += fmt.Sprintf("%v\n", f.Name)
  304. if del {
  305. gcloudDelete("forwarding-rules", f.Name, cont.Project, "--global")
  306. }
  307. }
  308. msg += fmt.Sprintf("\nFound forwarding rules:\n%v", msg)
  309. }
  310. }
  311. return msg
  312. }
  313. func (cont *GCEIngressController) deleteAddresses(del bool) string {
  314. msg := ""
  315. ipList := []compute.Address{}
  316. gcloudList("addresses", fmt.Sprintf("k8s-fw-.*--%v", cont.UID), cont.Project, &ipList)
  317. if len(ipList) != 0 {
  318. msg := ""
  319. for _, ip := range ipList {
  320. msg += fmt.Sprintf("%v\n", ip.Name)
  321. if del {
  322. gcloudDelete("addresses", ip.Name, cont.Project)
  323. }
  324. }
  325. msg += fmt.Sprintf("Found addresses:\n%v", msg)
  326. }
  327. // If the test allocated a static ip, delete that regardless
  328. if cont.staticIPName != "" {
  329. if err := gcloudDelete("addresses", cont.staticIPName, cont.Project, "--global"); err == nil {
  330. cont.staticIPName = ""
  331. }
  332. }
  333. return msg
  334. }
  335. func (cont *GCEIngressController) deleteTargetProxy(del bool) string {
  336. msg := ""
  337. tpList := []compute.TargetHttpProxy{}
  338. gcloudList("target-http-proxies", fmt.Sprintf("k8s-tp-.*--%v", cont.UID), cont.Project, &tpList)
  339. if len(tpList) != 0 {
  340. msg := ""
  341. for _, t := range tpList {
  342. msg += fmt.Sprintf("%v\n", t.Name)
  343. if del {
  344. gcloudDelete("target-http-proxies", t.Name, cont.Project)
  345. }
  346. }
  347. msg += fmt.Sprintf("Found target proxies:\n%v", msg)
  348. }
  349. tpsList := []compute.TargetHttpsProxy{}
  350. gcloudList("target-https-proxies", fmt.Sprintf("k8s-tps-.*--%v", cont.UID), cont.Project, &tpsList)
  351. if len(tpsList) != 0 {
  352. msg := ""
  353. for _, t := range tpsList {
  354. msg += fmt.Sprintf("%v\n", t.Name)
  355. if del {
  356. gcloudDelete("target-https-proxies", t.Name, cont.Project)
  357. }
  358. }
  359. msg += fmt.Sprintf("Found target HTTPS proxies:\n%v", msg)
  360. }
  361. return msg
  362. }
  363. func (cont *GCEIngressController) deleteUrlMap(del bool) string {
  364. msg := ""
  365. umList := []compute.UrlMap{}
  366. gcloudList("url-maps", fmt.Sprintf("k8s-um-.*--%v", cont.UID), cont.Project, &umList)
  367. if len(umList) != 0 {
  368. msg := ""
  369. for _, u := range umList {
  370. msg += fmt.Sprintf("%v\n", u.Name)
  371. if del {
  372. gcloudDelete("url-maps", u.Name, cont.Project)
  373. }
  374. }
  375. msg += fmt.Sprintf("Found url maps:\n%v", msg)
  376. }
  377. return msg
  378. }
  379. func (cont *GCEIngressController) deleteBackendService(del bool) string {
  380. msg := ""
  381. beList := []compute.BackendService{}
  382. gcloudList("backend-services", fmt.Sprintf("k8s-be-[0-9]+--%v", cont.UID), cont.Project, &beList)
  383. if len(beList) != 0 {
  384. msg := ""
  385. for _, b := range beList {
  386. msg += fmt.Sprintf("%v\n", b.Name)
  387. if del {
  388. gcloudDelete("backend-services", b.Name, cont.Project)
  389. }
  390. }
  391. msg += fmt.Sprintf("Found backend services:\n%v", msg)
  392. }
  393. return msg
  394. }
  395. func (cont *GCEIngressController) deleteHttpHealthCheck(del bool) string {
  396. msg := ""
  397. hcList := []compute.HttpHealthCheck{}
  398. gcloudList("http-health-checks", fmt.Sprintf("k8s-be-[0-9]+--%v", cont.UID), cont.Project, &hcList)
  399. if len(hcList) != 0 {
  400. msg := ""
  401. for _, h := range hcList {
  402. msg += fmt.Sprintf("%v\n", h.Name)
  403. if del {
  404. gcloudDelete("http-health-checks", h.Name, cont.Project)
  405. }
  406. }
  407. msg += fmt.Sprintf("Found health check:\n%v", msg)
  408. }
  409. return msg
  410. }
  411. // Cleanup cleans up cloud resources.
  412. // If del is false, it simply reports existing resources without deleting them.
  413. // It always deletes resources created through it's methods, like staticIP, even
  414. // if del is false.
  415. func (cont *GCEIngressController) Cleanup(del bool) error {
  416. // Ordering is important here because we cannot delete resources that other
  417. // resources hold references to.
  418. errMsg := cont.deleteForwardingRule(del)
  419. // Static IPs are named after forwarding rules.
  420. errMsg += cont.deleteAddresses(del)
  421. // TODO: Check for leaked ssl certs.
  422. errMsg += cont.deleteTargetProxy(del)
  423. errMsg += cont.deleteUrlMap(del)
  424. errMsg += cont.deleteBackendService(del)
  425. errMsg += cont.deleteHttpHealthCheck(del)
  426. // TODO: Verify instance-groups, issue #16636. Gcloud mysteriously barfs when told
  427. // to unmarshal instance groups into the current vendored gce-client's understanding
  428. // of the struct.
  429. if errMsg == "" {
  430. return nil
  431. }
  432. return fmt.Errorf(errMsg)
  433. }
  434. func (cont *GCEIngressController) init() {
  435. uid, err := cont.getL7AddonUID()
  436. Expect(err).NotTo(HaveOccurred())
  437. cont.UID = uid
  438. // There's a name limit imposed by GCE. The controller will truncate.
  439. testName := fmt.Sprintf("k8s-fw-foo-app-X-%v--%v", cont.ns, cont.UID)
  440. if len(testName) > nameLenLimit {
  441. framework.Logf("WARNING: test name including cluster UID: %v is over the GCE limit of %v", testName, nameLenLimit)
  442. } else {
  443. framework.Logf("Deteced cluster UID %v", cont.UID)
  444. }
  445. }
  446. func (cont *GCEIngressController) staticIP(name string) string {
  447. ExpectNoError(gcloudCreate("addresses", name, cont.Project, "--global"))
  448. cont.staticIPName = name
  449. ipList := []compute.Address{}
  450. if pollErr := wait.PollImmediate(5*time.Second, cloudResourcePollTimeout, func() (bool, error) {
  451. gcloudList("addresses", name, cont.Project, &ipList)
  452. if len(ipList) != 1 {
  453. framework.Logf("Failed to find static ip %v even though create call succeeded, found ips %+v", name, ipList)
  454. return false, nil
  455. }
  456. return true, nil
  457. }); pollErr != nil {
  458. if err := gcloudDelete("addresses", name, cont.Project, "--global"); err == nil {
  459. framework.Logf("Failed to get AND delete address %v even though create call succeeded", name)
  460. }
  461. framework.Failf("Failed to find static ip %v even though create call succeeded, found ips %+v", name, ipList)
  462. }
  463. return ipList[0].Address
  464. }
  465. // gcloudList unmarshals json output of gcloud into given out interface.
  466. func gcloudList(resource, regex, project string, out interface{}) {
  467. // gcloud prints a message to stderr if it has an available update
  468. // so we only look at stdout.
  469. command := []string{
  470. "compute", resource, "list",
  471. fmt.Sprintf("--regex=%v", regex),
  472. fmt.Sprintf("--project=%v", project),
  473. "-q", "--format=json",
  474. }
  475. output, err := exec.Command("gcloud", command...).Output()
  476. if err != nil {
  477. errCode := -1
  478. if exitErr, ok := err.(utilexec.ExitError); ok {
  479. errCode = exitErr.ExitStatus()
  480. }
  481. framework.Logf("Error running gcloud command 'gcloud %s': err: %v, output: %v, status: %d", strings.Join(command, " "), err, string(output), errCode)
  482. }
  483. if err := json.Unmarshal([]byte(output), out); err != nil {
  484. framework.Logf("Error unmarshalling gcloud output for %v: %v, output: %v", resource, err, string(output))
  485. }
  486. }
  487. func gcloudDelete(resource, name, project string, args ...string) error {
  488. framework.Logf("Deleting %v: %v", resource, name)
  489. argList := append([]string{"compute", resource, "delete", name, fmt.Sprintf("--project=%v", project), "-q"}, args...)
  490. output, err := exec.Command("gcloud", argList...).CombinedOutput()
  491. if err != nil {
  492. framework.Logf("Error deleting %v, output: %v\nerror: %+v", resource, string(output), err)
  493. }
  494. return err
  495. }
  496. func gcloudCreate(resource, name, project string, args ...string) error {
  497. framework.Logf("Creating %v in project %v: %v", resource, project, name)
  498. argsList := append([]string{"compute", resource, "create", name, fmt.Sprintf("--project=%v", project)}, args...)
  499. framework.Logf("Running command: gcloud %+v", strings.Join(argsList, " "))
  500. output, err := exec.Command("gcloud", argsList...).CombinedOutput()
  501. if err != nil {
  502. framework.Logf("Error creating %v, output: %v\nerror: %+v", resource, string(output), err)
  503. }
  504. return err
  505. }
  506. // createIngress creates the Ingress and associated service/rc.
  507. // Required: ing.yaml, rc.yaml, svc.yaml must exist in manifestPath
  508. // Optional: secret.yaml, ingAnnotations
  509. // If ingAnnotations is specified it will overwrite any annotations in ing.yaml
  510. func (j *testJig) createIngress(manifestPath, ns string, ingAnnotations map[string]string) {
  511. mkpath := func(file string) string {
  512. return filepath.Join(framework.TestContext.RepoRoot, manifestPath, file)
  513. }
  514. framework.Logf("creating replication controller")
  515. framework.RunKubectlOrDie("create", "-f", mkpath("rc.yaml"), fmt.Sprintf("--namespace=%v", ns))
  516. framework.Logf("creating service")
  517. framework.RunKubectlOrDie("create", "-f", mkpath("svc.yaml"), fmt.Sprintf("--namespace=%v", ns))
  518. if exists(mkpath("secret.yaml")) {
  519. framework.Logf("creating secret")
  520. framework.RunKubectlOrDie("create", "-f", mkpath("secret.yaml"), fmt.Sprintf("--namespace=%v", ns))
  521. }
  522. j.ing = ingFromManifest(mkpath("ing.yaml"))
  523. j.ing.Namespace = ns
  524. j.ing.Annotations = map[string]string{ingressClass: j.class}
  525. for k, v := range ingAnnotations {
  526. j.ing.Annotations[k] = v
  527. }
  528. framework.Logf(fmt.Sprintf("creating" + j.ing.Name + " ingress"))
  529. var err error
  530. j.ing, err = j.client.Extensions().Ingress(ns).Create(j.ing)
  531. ExpectNoError(err)
  532. }
  533. func (j *testJig) update(update func(ing *extensions.Ingress)) {
  534. var err error
  535. ns, name := j.ing.Namespace, j.ing.Name
  536. for i := 0; i < 3; i++ {
  537. j.ing, err = j.client.Extensions().Ingress(ns).Get(name)
  538. if err != nil {
  539. framework.Failf("failed to get ingress %q: %v", name, err)
  540. }
  541. update(j.ing)
  542. j.ing, err = j.client.Extensions().Ingress(ns).Update(j.ing)
  543. if err == nil {
  544. describeIng(j.ing.Namespace)
  545. return
  546. }
  547. if !apierrs.IsConflict(err) && !apierrs.IsServerTimeout(err) {
  548. framework.Failf("failed to update ingress %q: %v", name, err)
  549. }
  550. }
  551. framework.Failf("too many retries updating ingress %q", name)
  552. }
  553. func (j *testJig) addHTTPS(secretName string, hosts ...string) {
  554. j.ing.Spec.TLS = []extensions.IngressTLS{{Hosts: hosts, SecretName: secretName}}
  555. // TODO: Just create the secret in getRootCAs once we're watching secrets in
  556. // the ingress controller.
  557. _, cert, _, err := createSecret(j.client, j.ing)
  558. ExpectNoError(err)
  559. framework.Logf("Updating ingress %v to use secret %v for TLS termination", j.ing.Name, secretName)
  560. j.update(func(ing *extensions.Ingress) {
  561. ing.Spec.TLS = []extensions.IngressTLS{{Hosts: hosts, SecretName: secretName}}
  562. })
  563. j.rootCAs[secretName] = cert
  564. }
  565. func (j *testJig) getRootCA(secretName string) (rootCA []byte) {
  566. var ok bool
  567. rootCA, ok = j.rootCAs[secretName]
  568. if !ok {
  569. framework.Failf("Failed to retrieve rootCAs, no recorded secret by name %v", secretName)
  570. }
  571. return
  572. }
  573. func (j *testJig) deleteIngress() {
  574. ExpectNoError(j.client.Extensions().Ingress(j.ing.Namespace).Delete(j.ing.Name, nil))
  575. }
  576. func (j *testJig) waitForIngress() {
  577. // Wait for the loadbalancer IP.
  578. address, err := framework.WaitForIngressAddress(j.client, j.ing.Namespace, j.ing.Name, lbPollTimeout)
  579. if err != nil {
  580. framework.Failf("Ingress failed to acquire an IP address within %v", lbPollTimeout)
  581. }
  582. j.address = address
  583. framework.Logf("Found address %v for ingress %v", j.address, j.ing.Name)
  584. timeoutClient := &http.Client{Timeout: reqTimeout}
  585. // Check that all rules respond to a simple GET.
  586. for _, rules := range j.ing.Spec.Rules {
  587. proto := "http"
  588. if len(j.ing.Spec.TLS) > 0 {
  589. knownHosts := sets.NewString(j.ing.Spec.TLS[0].Hosts...)
  590. if knownHosts.Has(rules.Host) {
  591. timeoutClient.Transport, err = buildTransport(rules.Host, j.getRootCA(j.ing.Spec.TLS[0].SecretName))
  592. ExpectNoError(err)
  593. proto = "https"
  594. }
  595. }
  596. for _, p := range rules.IngressRuleValue.HTTP.Paths {
  597. j.curlServiceNodePort(j.ing.Namespace, p.Backend.ServiceName, int(p.Backend.ServicePort.IntVal))
  598. route := fmt.Sprintf("%v://%v%v", proto, address, p.Path)
  599. framework.Logf("Testing route %v host %v with simple GET", route, rules.Host)
  600. ExpectNoError(pollURL(route, rules.Host, lbPollTimeout, timeoutClient, false))
  601. }
  602. }
  603. }
  604. // verifyURL polls for the given iterations, in intervals, and fails if the
  605. // given url returns a non-healthy http code even once.
  606. func (j *testJig) verifyURL(route, host string, iterations int, interval time.Duration, httpClient *http.Client) error {
  607. for i := 0; i < iterations; i++ {
  608. b, err := simpleGET(httpClient, route, host)
  609. if err != nil {
  610. framework.Logf(b)
  611. return err
  612. }
  613. framework.Logf("Verfied %v with host %v %d times, sleeping for %v", route, host, i, interval)
  614. time.Sleep(interval)
  615. }
  616. return nil
  617. }
  618. func (j *testJig) curlServiceNodePort(ns, name string, port int) {
  619. // TODO: Curl all nodes?
  620. u, err := framework.GetNodePortURL(j.client, ns, name, port)
  621. ExpectNoError(err)
  622. ExpectNoError(pollURL(u, "", 30*time.Second, &http.Client{Timeout: reqTimeout}, false))
  623. }
  624. // ingFromManifest reads a .json/yaml file and returns the rc in it.
  625. func ingFromManifest(fileName string) *extensions.Ingress {
  626. var ing extensions.Ingress
  627. framework.Logf("Parsing ingress from %v", fileName)
  628. data, err := ioutil.ReadFile(fileName)
  629. ExpectNoError(err)
  630. json, err := utilyaml.ToJSON(data)
  631. ExpectNoError(err)
  632. ExpectNoError(runtime.DecodeInto(api.Codecs.UniversalDecoder(), json, &ing))
  633. return &ing
  634. }
  635. func (cont *GCEIngressController) getL7AddonUID() (string, error) {
  636. framework.Logf("Retrieving UID from config map: %v/%v", api.NamespaceSystem, uidConfigMap)
  637. cm, err := cont.c.ConfigMaps(api.NamespaceSystem).Get(uidConfigMap)
  638. if err != nil {
  639. return "", err
  640. }
  641. if uid, ok := cm.Data[uidKey]; ok {
  642. return uid, nil
  643. }
  644. return "", fmt.Errorf("Could not find cluster UID for L7 addon pod")
  645. }
  646. func exists(path string) bool {
  647. _, err := os.Stat(path)
  648. if err == nil {
  649. return true
  650. }
  651. if os.IsNotExist(err) {
  652. return false
  653. }
  654. framework.Failf("Failed to os.Stat path %v", path)
  655. return false
  656. }
  657. // GCEIngressController manages implementation details of Ingress on GCE/GKE.
  658. type GCEIngressController struct {
  659. ns string
  660. rcPath string
  661. UID string
  662. Project string
  663. staticIPName string
  664. rc *api.ReplicationController
  665. svc *api.Service
  666. c *client.Client
  667. }
  668. func newTestJig(c *client.Client) *testJig {
  669. return &testJig{client: c, rootCAs: map[string][]byte{}}
  670. }
  671. // NginxIngressController manages implementation details of Ingress on Nginx.
  672. type NginxIngressController struct {
  673. ns string
  674. rc *api.ReplicationController
  675. pod *api.Pod
  676. c *client.Client
  677. externalIP string
  678. }
  679. func (cont *NginxIngressController) init() {
  680. mkpath := func(file string) string {
  681. return filepath.Join(framework.TestContext.RepoRoot, ingressManifestPath, "nginx", file)
  682. }
  683. framework.Logf("initializing nginx ingress controller")
  684. framework.RunKubectlOrDie("create", "-f", mkpath("rc.yaml"), fmt.Sprintf("--namespace=%v", cont.ns))
  685. rc, err := cont.c.ReplicationControllers(cont.ns).Get("nginx-ingress-controller")
  686. ExpectNoError(err)
  687. cont.rc = rc
  688. framework.Logf("waiting for pods with label %v", rc.Spec.Selector)
  689. sel := labels.SelectorFromSet(labels.Set(rc.Spec.Selector))
  690. ExpectNoError(framework.WaitForPodsWithLabelRunning(cont.c, cont.ns, sel))
  691. pods, err := cont.c.Pods(cont.ns).List(api.ListOptions{LabelSelector: sel})
  692. ExpectNoError(err)
  693. if len(pods.Items) == 0 {
  694. framework.Failf("Failed to find nginx ingress controller pods with selector %v", sel)
  695. }
  696. cont.pod = &pods.Items[0]
  697. cont.externalIP, err = framework.GetHostExternalAddress(cont.c, cont.pod)
  698. ExpectNoError(err)
  699. framework.Logf("ingress controller running in pod %v on ip %v", cont.pod.Name, cont.externalIP)
  700. }