This is an automated email from the ASF dual-hosted git repository.

wenming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new d339fe5  chore: hone e2e test scaffold and run cases in CI (#122)
d339fe5 is described below

commit d339fe5fc02f6457e64de7fa0fd289b431e4fa5a
Author: Alex Zhang <[email protected]>
AuthorDate: Fri Dec 25 15:14:32 2020 +0800

    chore: hone e2e test scaffold and run cases in CI (#122)
---
 .github/workflows/e2e-test-ci.yml       |  38 ++++++++
 .github/workflows/lint-checker.yml      |   1 -
 .github/workflows/unit-test-ci.yml      |   1 -
 .gitignore                              |   1 +
 Makefile                                |  14 +++
 test/e2e/e2e.go                         |  69 +++++++++++++++
 test/e2e/{main_test.go => e2e_test.go}  |   3 +-
 test/e2e/go.mod                         |   2 +
 test/e2e/ingress/sanity.go              |  62 +++++++++++++
 test/e2e/main.go                        |  21 -----
 test/e2e/proxy/sanity.go                |  28 ------
 test/e2e/scaffold/apisix.go             |  69 +++++++++++++--
 test/e2e/scaffold/crd.go                | 150 ++++++++++++++++++++++++++++++++
 test/e2e/scaffold/etcd.go               |  38 +++++++-
 test/e2e/scaffold/ingress.go            |  39 ++++++++-
 test/e2e/scaffold/scaffold.go           |  69 ++++++++++++---
 test/e2e/testdata/apisix-gw-config.yaml |   3 +-
 17 files changed, 530 insertions(+), 78 deletions(-)

diff --git a/.github/workflows/e2e-test-ci.yml 
b/.github/workflows/e2e-test-ci.yml
new file mode 100644
index 0000000..ff4f400
--- /dev/null
+++ b/.github/workflows/e2e-test-ci.yml
@@ -0,0 +1,38 @@
+name: e2e-test-ci
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+      - test/e2e-sanity-case
+jobs:
+  e2e-test:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install minikube
+        uses: abstractj/setup-minikube@issue-12
+      - name: Output cluster info
+        run: kubectl cluster-info
+      - name: Add images
+        run: |
+          IMAGE_TAG=dev make build-image
+          minikube cache add apache/apisix:latest -v 7 --alsologtostderr
+          minikube cache add bitnami/etcd:3.4.14-debian-10-r0 -v 7 
--alsologtostderr
+          minikube cache add kennethreitz/httpbin -v 7 --alsologtostderr
+          minikube cache add apisix-ingress-controller:dev -v 7 
--alsologtostderr
+      - name: Setup Go Env
+        uses: actions/setup-go@v1
+        with:
+          go-version: '1.13'
+      - name: Run e2e test cases
+        working-directory: ./
+        run: |
+          make e2e-test
+      - name: upload coverage profile
+        working-directory: ./test/e2e
+        run: |
+          bash <(curl -s https://codecov.io/bash)
diff --git a/.github/workflows/lint-checker.yml 
b/.github/workflows/lint-checker.yml
index 31d65ab..c1f59d8 100644
--- a/.github/workflows/lint-checker.yml
+++ b/.github/workflows/lint-checker.yml
@@ -4,7 +4,6 @@ on:
   push:
     branches:
       - master
-      - chore/gofmt
   pull_request:
     branches:
       - master
diff --git a/.github/workflows/unit-test-ci.yml 
b/.github/workflows/unit-test-ci.yml
index 527ed96..62d03f5 100644
--- a/.github/workflows/unit-test-ci.yml
+++ b/.github/workflows/unit-test-ci.yml
@@ -4,7 +4,6 @@ on:
   push:
     branches:
       - master
-      - ci/go-unit-test-coverage-report
   pull_request:
     branches:
       - master
diff --git a/.gitignore b/.gitignore
index 0fdeedf..384d362 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,5 +14,6 @@
 .idea
 .DS_Store
 coverage.txt
+test/e2e/coverage.txt
 apisix-ingress-controller
 .actions/openwhisk-utilities
diff --git a/Makefile b/Makefile
index b1d54e1..2a7b3fc 100644
--- a/Makefile
+++ b/Makefile
@@ -17,9 +17,11 @@
 default: help
 
 VERSION ?= 0.0.0
+IMAGE_TAG ?= "latest"
 GITSHA ?= $(shell git rev-parse --short=7 HEAD)
 OSNAME ?= $(shell uname -s | tr A-Z a-z)
 OSARCH ?= $(shell uname -m | tr A-Z a-z)
+PWD ?= $(shell pwd)
 ifeq ($(OSARCH), x86_64)
        OSARCH = amd64
 endif
@@ -36,6 +38,10 @@ build:
                -ldflags $(GO_LDFLAGS) \
                main.go
 
+### build-image:      Build apisix-ingress-controller image
+build-image:
+       docker build -t apisix-ingress-controller:$(IMAGE_TAG) .
+
 ### lint:             Do static lint check
 lint:
        golangci-lint run
@@ -48,6 +54,14 @@ gofmt:
 unit-test:
        go test -cover -coverprofile=coverage.txt ./...
 
+### e2e-test:         Run e2e test cases
+e2e-test:
+       export 
APISIX_ROUTE_DEF=$(PWD)/samples/deploy/crd/v1beta1/ApisixRoute.yaml && \
+       export 
APISIX_UPSTREAM_DEF=$(PWD)/samples/deploy/crd/v1beta1/ApisixUpstream.yaml && \
+       export 
APISIX_SERVICE_DEF=$(PWD)/samples/deploy/crd/v1beta1/ApisixService.yaml && \
+       export APISIX_TLS_DEF=$(PWD)/samples/deploy/crd/v1beta1/ApisixTls.yaml 
&& \
+       cd test/e2e && go test -cover -coverprofile=coverage.txt 
github.com/api7/ingress-controller/test/e2e
+
 ### license-check:    Do Apache License Header check
 license-check:
 ifeq ("$(wildcard .actions/openwhisk-utilities/scancode/scanCode.py)", "")
diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go
new file mode 100644
index 0000000..54a408e
--- /dev/null
+++ b/test/e2e/e2e.go
@@ -0,0 +1,69 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package e2e
+
+import (
+       "os"
+
+       "github.com/gruntwork-io/terratest/modules/k8s"
+       "github.com/onsi/ginkgo"
+
+       _ "github.com/api7/ingress-controller/test/e2e/ingress"
+       "github.com/api7/ingress-controller/test/e2e/scaffold"
+)
+
+var (
+       _apisixRouteDef    string
+       _apisixUpstreamDef string
+       _apisixServiceDef  string
+       _apisixTLSDef      string
+)
+
+func runE2E() {
+       if v := os.Getenv("APISIX_ROUTE_DEF"); v != "" {
+               _apisixRouteDef = v
+       } else {
+               panic("no specified ApisixRoute definition file")
+       }
+       if v := os.Getenv("APISIX_UPSTREAM_DEF"); v != "" {
+               _apisixUpstreamDef = v
+       } else {
+               panic("no specified ApisixUpstream resource definition file")
+       }
+       if v := os.Getenv("APISIX_UPSTREAM_DEF"); v != "" {
+               _apisixUpstreamDef = v
+       } else {
+               panic("no specified ApisixUpstream resource definition file")
+       }
+       if v := os.Getenv("APISIX_SERVICE_DEF"); v != "" {
+               _apisixServiceDef = v
+       } else {
+               panic("no specified ApisixService resource definition file")
+       }
+       if v := os.Getenv("APISIX_TLS_DEF"); v != "" {
+               _apisixTLSDef = v
+       } else {
+               panic("no specified ApisixTls resource definition file")
+       }
+
+       kubeconfig := scaffold.GetKubeconfig()
+       opts := &k8s.KubectlOptions{
+               ConfigPath: kubeconfig,
+       }
+       k8s.KubectlApply(ginkgo.GinkgoT(), opts, _apisixRouteDef)
+       k8s.KubectlApply(ginkgo.GinkgoT(), opts, _apisixUpstreamDef)
+       k8s.KubectlApply(ginkgo.GinkgoT(), opts, _apisixServiceDef)
+       k8s.KubectlApply(ginkgo.GinkgoT(), opts, _apisixTLSDef)
+}
diff --git a/test/e2e/main_test.go b/test/e2e/e2e_test.go
similarity index 97%
rename from test/e2e/main_test.go
rename to test/e2e/e2e_test.go
index 01d4be0..989adf7 100644
--- a/test/e2e/main_test.go
+++ b/test/e2e/e2e_test.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package main
+package e2e
 
 import (
        "testing"
@@ -22,5 +22,6 @@ import (
 )
 
 func TestRunE2E(t *testing.T) {
+       runE2E()
        ginkgo.RunSpecs(t, "ingress-apisix e2e test suites")
 }
diff --git a/test/e2e/go.mod b/test/e2e/go.mod
index 01479bc..fb68a99 100644
--- a/test/e2e/go.mod
+++ b/test/e2e/go.mod
@@ -10,6 +10,8 @@ require (
        github.com/onsi/ginkgo v1.14.2
        github.com/sergi/go-diff v1.1.0 // indirect
        github.com/stretchr/testify v1.6.1
+       gopkg.in/yaml.v2 v2.3.0
        k8s.io/api v0.19.3
+       k8s.io/apimachinery v0.19.3
        k8s.io/client-go v0.19.3
 )
diff --git a/test/e2e/ingress/sanity.go b/test/e2e/ingress/sanity.go
new file mode 100644
index 0000000..3b107f9
--- /dev/null
+++ b/test/e2e/ingress/sanity.go
@@ -0,0 +1,62 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package ingress
+
+import (
+       "encoding/json"
+       "net/http"
+
+       "github.com/onsi/ginkgo"
+       "github.com/stretchr/testify/assert"
+
+       "github.com/api7/ingress-controller/test/e2e/scaffold"
+)
+
+type ip struct {
+       IP string `json:"ip"`
+}
+
+var _ = ginkgo.Describe("single-route", func() {
+       s := scaffold.NewDefaultScaffold()
+       ginkgo.It("/ip should return your ip", func() {
+               backendSvc, backendSvcPort := s.DefaultHTTPBackend()
+               s.CreateApisixRoute("httpbin-route", []scaffold.ApisixRouteRule{
+                       {
+                               Host: "httpbin.com",
+                               HTTP: scaffold.ApisixRouteRuleHTTP{
+                                       Paths: 
[]scaffold.ApisixRouteRuleHTTPPath{
+                                               {
+                                                       Path: "/ip",
+                                                       Backend: 
scaffold.ApisixRouteRuleHTTPBackend{
+                                                               ServiceName: 
backendSvc,
+                                                               ServicePort: 
backendSvcPort[0],
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               })
+               err := s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "checking number of routes")
+               err = s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "checking number of 
upstreams")
+               body := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().Status(http.StatusOK).Body().Raw()
+               var placeholder ip
+               err = json.Unmarshal([]byte(body), &placeholder)
+               assert.Nil(ginkgo.GinkgoT(), err, "unmarshalling IP")
+               // It's not our focus point to check the IP address returned by 
httpbin,
+               // so here skip the IP address validation.
+       })
+})
diff --git a/test/e2e/main.go b/test/e2e/main.go
deleted file mode 100644
index 3e5e488..0000000
--- a/test/e2e/main.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package main
-
-import (
-       _ "github.com/api7/ingress-controller/test/e2e/proxy"
-)
-
-func main() {}
diff --git a/test/e2e/proxy/sanity.go b/test/e2e/proxy/sanity.go
deleted file mode 100644
index eab3b28..0000000
--- a/test/e2e/proxy/sanity.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package proxy
-
-import (
-       "github.com/onsi/ginkgo"
-
-       "github.com/api7/ingress-controller/test/e2e/scaffold"
-)
-
-var _ = ginkgo.Describe("proxy-sanity", func() {
-       _ = scaffold.NewDefaultScaffold()
-
-       ginkgo.It("make sure the environment is OK", func() {
-       })
-})
diff --git a/test/e2e/scaffold/apisix.go b/test/e2e/scaffold/apisix.go
index 8f0f0e6..194a49e 100644
--- a/test/e2e/scaffold/apisix.go
+++ b/test/e2e/scaffold/apisix.go
@@ -17,10 +17,14 @@ package scaffold
 import (
        "errors"
        "fmt"
+       "net"
+       "strconv"
        "strings"
 
        "github.com/gruntwork-io/terratest/modules/k8s"
+       "github.com/onsi/ginkgo"
        corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 var (
@@ -59,16 +63,16 @@ spec:
       containers:
         - livenessProbe:
             failureThreshold: 3
-            initialDelaySeconds: 2
-            periodSeconds: 5
+            initialDelaySeconds: 1
+            periodSeconds: 2
             successThreshold: 1
             tcpSocket:
               port: 9080
             timeoutSeconds: 2
           readinessProbe:
             failureThreshold: 3
-            initialDelaySeconds: 2
-            periodSeconds: 5
+            initialDelaySeconds: 1
+            periodSeconds: 2
             successThreshold: 1
             tcpSocket:
               port: 9080
@@ -120,15 +124,40 @@ func (s *Scaffold) apisixServiceURL() (string, error) {
        if len(s.nodes) == 0 {
                return "", errors.New("no available node")
        }
+       var addr string
+       for _, node := range s.nodes {
+               if len(node.Status.Addresses) > 0 {
+                       addr = node.Status.Addresses[0].Address
+                       break
+               }
+       }
        for _, port := range s.apisixService.Spec.Ports {
                if port.Name == "http" {
-                       // Basically we use minikube, so just use the first 
node.
-                       return fmt.Sprintf("http://%s:%d";, s.nodes[0], 
port.NodePort), nil
+                       return net.JoinHostPort(addr, 
strconv.Itoa(int(port.NodePort))), nil
                }
        }
        return "", errors.New("no http port in apisix service")
 }
 
+func (s *Scaffold) apisixAdminServiceURL() (string, error) {
+       if len(s.nodes) == 0 {
+               return "", errors.New("no available node")
+       }
+       var addr string
+       for _, node := range s.nodes {
+               if len(node.Status.Addresses) > 0 {
+                       addr = node.Status.Addresses[0].Address
+                       break
+               }
+       }
+       for _, port := range s.apisixService.Spec.Ports {
+               if port.Name == "http-admin" {
+                       return net.JoinHostPort(addr, 
strconv.Itoa(int(port.NodePort))), nil
+               }
+       }
+       return "", errors.New("no http-admin port in apisix admin service")
+}
+
 func (s *Scaffold) newAPISIX() (*corev1.Service, error) {
        defaultData, err := s.renderConfig(s.opts.APISIXDefaultConfigPath)
        if err != nil {
@@ -165,3 +194,31 @@ func indent(data string) string {
        }
        return strings.Join(list, "\n")
 }
+
+func (s *Scaffold) waitAllAPISIXPodsAvailable() error {
+       opts := metav1.ListOptions{
+               LabelSelector: "app=apisix-deployment-e2e-test",
+       }
+       condFunc := func() (bool, error) {
+               items, err := k8s.ListPodsE(s.t, s.kubectlOptions, opts)
+               if err != nil {
+                       return false, err
+               }
+               if len(items) == 0 {
+                       ginkgo.GinkgoT().Log("no apisix pods created")
+                       return false, nil
+               }
+               for _, item := range items {
+                       for _, cond := range item.Status.Conditions {
+                               if cond.Type != corev1.PodReady {
+                                       continue
+                               }
+                               if cond.Status != "True" {
+                                       return false, nil
+                               }
+                       }
+               }
+               return true, nil
+       }
+       return waitExponentialBackoff(condFunc)
+}
diff --git a/test/e2e/scaffold/crd.go b/test/e2e/scaffold/crd.go
new file mode 100644
index 0000000..39b5014
--- /dev/null
+++ b/test/e2e/scaffold/crd.go
@@ -0,0 +1,150 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package scaffold
+
+import (
+       "encoding/json"
+       "net/http"
+       "net/url"
+       "strconv"
+       "time"
+
+       "github.com/gruntwork-io/terratest/modules/k8s"
+       "github.com/onsi/ginkgo"
+       "github.com/stretchr/testify/assert"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/util/wait"
+)
+
+type counter struct {
+       Count string `json:"count"`
+}
+
+// ApisixRoute is the ApisixRoute CRD definition.
+// We don't use the definition in apisix-ingress-controller,
+// since the k8s dependencies in terratest and
+// apisix-ingress-controller are conflicted.
+type apisixRoute struct {
+       metav1.TypeMeta   `json:",inline"`
+       metav1.ObjectMeta `json:"metadata"`
+       Spec              apisixRouteSpec `json:"spec"`
+}
+
+type apisixRouteSpec struct {
+       Rules []ApisixRouteRule `json:"rules"`
+}
+
+// ApisixRouteRule defines the route policies of ApisixRoute.
+type ApisixRouteRule struct {
+       Host string              `json:"host"`
+       HTTP ApisixRouteRuleHTTP `json:"http"`
+}
+
+// ApisixRouteRuleHTTP defines the HTTP part of route policies.
+type ApisixRouteRuleHTTP struct {
+       Paths []ApisixRouteRuleHTTPPath `json:"paths"`
+}
+
+// ApisixRouteRuleHTTP defines a route in the HTTP part of ApisixRoute.
+type ApisixRouteRuleHTTPPath struct {
+       Path    string                     `json:"path"`
+       Backend ApisixRouteRuleHTTPBackend `json:"backend"`
+}
+
+// ApisixRouteRuleHTTPBackend defines a HTTP backend.
+type ApisixRouteRuleHTTPBackend struct {
+       ServiceName string `json:"serviceName"`
+       ServicePort int32  `json:"servicePort"`
+}
+
+// CreateApisixRoute creates an ApisixRoute object.
+func (s *Scaffold) CreateApisixRoute(name string, rules []ApisixRouteRule) {
+       route := &apisixRoute{
+               TypeMeta: metav1.TypeMeta{
+                       Kind:       "ApisixRoute",
+                       APIVersion: "apisix.apache.org/v1",
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Name: name,
+               },
+               Spec: apisixRouteSpec{
+                       Rules: rules,
+               },
+       }
+       data, err := json.Marshal(route)
+       assert.Nil(s.t, err)
+       k8s.KubectlApplyFromString(s.t, s.kubectlOptions, string(data))
+}
+
+func ensureNumApisixCRDsCreated(url string, desired int) error {
+       condFunc := func() (bool, error) {
+               resp, err := http.Get(url)
+               if err != nil {
+                       ginkgo.GinkgoT().Logf("failed to get resources from 
APISIX: %s", err.Error())
+                       return false, nil
+               }
+               if resp.StatusCode != http.StatusOK {
+                       ginkgo.GinkgoT().Logf("got status code %d from APISIX", 
resp.StatusCode)
+                       return false, nil
+               }
+               var c counter
+               dec := json.NewDecoder(resp.Body)
+               if err := dec.Decode(&c); err != nil {
+                       return false, err
+               }
+               // NOTE count field is a string.
+               count, err := strconv.Atoi(c.Count)
+               if err != nil {
+                       return false, err
+               }
+               // 1 for dir.
+               if count != desired+1 {
+                       ginkgo.GinkgoT().Logf("mismatched number of items, 
expected %d but found %d", desired, count-1)
+                       return false, nil
+               }
+               return true, nil
+       }
+       return wait.Poll(3*time.Second, 15*time.Second, condFunc)
+}
+
+// EnsureNumApisixRoutesCreated waits until desired number of Routes are 
created in
+// APISIX cluster.
+func (s *Scaffold) EnsureNumApisixRoutesCreated(desired int) error {
+       host, err := s.apisixAdminServiceURL()
+       if err != nil {
+               return err
+       }
+       u := url.URL{
+               Scheme: "http",
+               Host:   host,
+               Path:   "/apisix/admin/routes",
+       }
+       return ensureNumApisixCRDsCreated(u.String(), desired)
+}
+
+// EnsureNumApisixUpstreamsCreated waits until desired number of Upstreams are 
created in
+// APISIX cluster.
+func (s *Scaffold) EnsureNumApisixUpstreamsCreated(desired int) error {
+       host, err := s.apisixAdminServiceURL()
+       if err != nil {
+               return err
+       }
+       u := url.URL{
+               Scheme: "http",
+               Host:   host,
+               Path:   "/apisix/admin/upstreams",
+       }
+       return ensureNumApisixCRDsCreated(u.String(), desired)
+}
diff --git a/test/e2e/scaffold/etcd.go b/test/e2e/scaffold/etcd.go
index c5272d6..2ad9ff7 100644
--- a/test/e2e/scaffold/etcd.go
+++ b/test/e2e/scaffold/etcd.go
@@ -18,7 +18,9 @@ import (
        "fmt"
 
        "github.com/gruntwork-io/terratest/modules/k8s"
+       "github.com/onsi/ginkgo"
        corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 var (
@@ -49,16 +51,16 @@ spec:
             value: "yes"
           livenessProbe:
             failureThreshold: 3
-            initialDelaySeconds: 2
-            periodSeconds: 5
+            initialDelaySeconds: 1
+            periodSeconds: 2
             successThreshold: 1
             tcpSocket:
               port: 2379
             timeoutSeconds: 2
           readinessProbe:
             failureThreshold: 3
-            initialDelaySeconds: 2
-            periodSeconds: 5
+            initialDelaySeconds: 1
+            periodSeconds: 2
             successThreshold: 1
             tcpSocket:
               port: 2379
@@ -103,3 +105,31 @@ func (s *Scaffold) newEtcd() (*corev1.Service, error) {
        s.EtcdServiceFQDN = 
fmt.Sprintf("etcd-service-e2e-test.%s.svc.cluster.local", svc.Namespace)
        return svc, nil
 }
+
+func (s *Scaffold) waitAllEtcdPodsAvailable() error {
+       opts := metav1.ListOptions{
+               LabelSelector: "app=etcd-deployment-e2e-test",
+       }
+       condFunc := func() (bool, error) {
+               items, err := k8s.ListPodsE(s.t, s.kubectlOptions, opts)
+               if err != nil {
+                       return false, err
+               }
+               if len(items) == 0 {
+                       ginkgo.GinkgoT().Log("no etcd pods created")
+                       return false, nil
+               }
+               for _, item := range items {
+                       for _, cond := range item.Status.Conditions {
+                               if cond.Type != corev1.PodReady {
+                                       continue
+                               }
+                               if cond.Status != "True" {
+                                       return false, nil
+                               }
+                       }
+               }
+               return true, nil
+       }
+       return waitExponentialBackoff(condFunc)
+}
diff --git a/test/e2e/scaffold/ingress.go b/test/e2e/scaffold/ingress.go
index a53b523..f0ba3dd 100644
--- a/test/e2e/scaffold/ingress.go
+++ b/test/e2e/scaffold/ingress.go
@@ -18,6 +18,9 @@ import (
        "fmt"
 
        "github.com/gruntwork-io/terratest/modules/k8s"
+       "github.com/onsi/ginkgo"
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 const (
@@ -60,16 +63,16 @@ spec:
       containers:
         - livenessProbe:
             failureThreshold: 3
-            initialDelaySeconds: 2
-            periodSeconds: 5
+            initialDelaySeconds: 1
+            periodSeconds: 2
             successThreshold: 1
             tcpSocket:
               port: 8080
             timeoutSeconds: 2
           readinessProbe:
             failureThreshold: 3
-            initialDelaySeconds: 2
-            periodSeconds: 5
+            initialDelaySeconds: 1
+            periodSeconds: 2
             successThreshold: 1
             tcpSocket:
               port: 8080
@@ -110,3 +113,31 @@ func (s *Scaffold) newIngressAPISIXController() error {
        }
        return nil
 }
+
+func (s *Scaffold) waitAllIngressControllerPodsAvailable() error {
+       opts := metav1.ListOptions{
+               LabelSelector: 
"app=ingress-apisix-controller-deployment-e2e-test",
+       }
+       condFunc := func() (bool, error) {
+               items, err := k8s.ListPodsE(s.t, s.kubectlOptions, opts)
+               if err != nil {
+                       return false, err
+               }
+               if len(items) == 0 {
+                       ginkgo.GinkgoT().Log("no ingress-apisix-controller pods 
created")
+                       return false, nil
+               }
+               for _, item := range items {
+                       for _, cond := range item.Status.Conditions {
+                               if cond.Type != corev1.PodReady {
+                                       continue
+                               }
+                               if cond.Status != "True" {
+                                       return false, nil
+                               }
+                       }
+               }
+               return true, nil
+       }
+       return waitExponentialBackoff(condFunc)
+}
diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go
index 3363636..1383f79 100644
--- a/test/e2e/scaffold/scaffold.go
+++ b/test/e2e/scaffold/scaffold.go
@@ -18,6 +18,10 @@ import (
        "fmt"
        "io/ioutil"
        "net/http"
+       "net/url"
+       "os"
+       "os/user"
+       "path/filepath"
        "strings"
        "text/template"
        "time"
@@ -29,6 +33,7 @@ import (
        "github.com/stretchr/testify/assert"
        appsv1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/util/wait"
 )
 
 type Options struct {
@@ -53,6 +58,26 @@ type Scaffold struct {
        EtcdServiceFQDN string
 }
 
+// Getkubeconfig returns the kubeconfig file path.
+// Order:
+// env KUBECONFIG;
+// ~/.kube/config;
+// "" (in case in-cluster configuration will be used).
+func GetKubeconfig() string {
+       kubeconfig := os.Getenv("KUBECONFIG")
+       if kubeconfig == "" {
+               u, err := user.Current()
+               if err != nil {
+                       panic(err)
+               }
+               kubeconfig = filepath.Join(u.HomeDir, ".kube", "config")
+               if _, err := os.Stat(kubeconfig); err != nil && 
!os.IsNotExist(err) {
+                       kubeconfig = ""
+               }
+       }
+       return kubeconfig
+}
+
 // NewScaffold creates an e2e test scaffold.
 func NewScaffold(o *Options) *Scaffold {
        defer ginkgo.GinkgoRecover()
@@ -68,12 +93,13 @@ func NewScaffold(o *Options) *Scaffold {
        return s
 }
 
+// NewDefaultScaffold creates a scaffold with some default options.
 func NewDefaultScaffold() *Scaffold {
        opts := &Options{
-               Name:                    "sample",
-               Kubeconfig:              "/Users/alex/.kube/config",
-               APISIXConfigPath:        
"/Users/alex/Workstation/tokers/apisix-ingress-controller/test/e2e/testdata/apisix-gw-config.yaml",
-               APISIXDefaultConfigPath: 
"/Users/alex/Workstation/tokers/apisix-ingress-controller/test/e2e/testdata/apisix-gw-config-default.yaml",
+               Name:                    "default",
+               Kubeconfig:              GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
        }
        return NewScaffold(opts)
 }
@@ -88,12 +114,16 @@ func (s *Scaffold) DefaultHTTPBackend() (string, []int32) {
        return s.httpbinService.Name, ports
 }
 
-// NewHTTPClient creates the default HTTP client.
-func (s *Scaffold) NewHTTPClient() *httpexpect.Expect {
-       url, err := s.apisixServiceURL()
+// NewAPISIXClient creates the default HTTP client.
+func (s *Scaffold) NewAPISIXClient() *httpexpect.Expect {
+       host, err := s.apisixServiceURL()
        assert.Nil(s.t, err, "getting apisix service url")
+       u := url.URL{
+               Scheme: "http",
+               Host:   host,
+       }
        return httpexpect.WithConfig(httpexpect.Config{
-               BaseURL: url,
+               BaseURL: u.String(),
                Client: &http.Client{
                        Transport: &http.Transport{},
                },
@@ -126,18 +156,27 @@ func (s *Scaffold) beforeEach() {
        s.etcdService, err = s.newEtcd()
        assert.Nil(s.t, err, "initializing etcd")
 
-       k8s.WaitUntilServiceAvailable(s.t, s.kubectlOptions, 
s.etcdService.Name, 3, 2*time.Second)
+       // We don't use k8s.WaitUntilServiceAvailable since it hacks for 
Minikube.
+       err = s.waitAllEtcdPodsAvailable()
+       assert.Nil(s.t, err, "waiting for etcd ready")
 
        s.apisixService, err = s.newAPISIX()
        assert.Nil(s.t, err, "initializing Apache APISIX")
 
-       k8s.WaitUntilServiceAvailable(s.t, s.kubectlOptions, 
s.apisixService.Name, 3, 2*time.Second)
+       // We don't use k8s.WaitUntilServiceAvailable since it hacks for 
Minikube.
+       err = s.waitAllAPISIXPodsAvailable()
+       assert.Nil(s.t, err, "waiting for apisix ready")
 
        s.httpbinService, err = s.newHTTPBIN()
        assert.Nil(s.t, err, "initializing httpbin")
 
+       k8s.WaitUntilServiceAvailable(s.t, s.kubectlOptions, 
s.httpbinService.Name, 3, 2*time.Second)
+
        err = s.newIngressAPISIXController()
        assert.Nil(s.t, err, "initializing ingress apisix controller")
+
+       err = s.waitAllIngressControllerPodsAvailable()
+       assert.Nil(s.t, err, "waiting for ingress apisix controller ready")
 }
 
 func (s *Scaffold) afterEach() {
@@ -159,3 +198,13 @@ func (s *Scaffold) renderConfig(path string) (string, 
error) {
        }
        return buf.String(), nil
 }
+
+func waitExponentialBackoff(condFunc func() (bool, error)) error {
+       backoff := wait.Backoff{
+               Duration: 100 * time.Millisecond,
+               Factor:   3,
+               Jitter:   0,
+               Steps:    6,
+       }
+       return wait.ExponentialBackoff(backoff, condFunc)
+}
diff --git a/test/e2e/testdata/apisix-gw-config.yaml 
b/test/e2e/testdata/apisix-gw-config.yaml
index ad68fcf..8124b17 100644
--- a/test/e2e/testdata/apisix-gw-config.yaml
+++ b/test/e2e/testdata/apisix-gw-config.yaml
@@ -45,9 +45,8 @@ apisix:
 
   port_admin: 9180              # use a separate port
   allow_admin:
-    - 172.17.0.0/16
-    - 10.0.0.0/24
     - 127.0.0.0/24
+    - 0.0.0.0/0
 
   delete_uri_tail_slash: false    # delete the '/' at the end of the URI
   router:

Reply via email to