mirror of https://github.com/knative/func.git
CI enhancements (e2e tests) and Go template fix (#970)
* Adding timeout and additional logs on e2e http test * Better revision check on e2e http update test * ci: Adding workflow to run e2e for all runtimes * fix: server error 500 for Go cloudevents template
This commit is contained in:
parent
e502d554c8
commit
01f113969a
|
@ -0,0 +1,30 @@
|
|||
name: Func E2E Lifecycle Test
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ci-e1e-${{ github.ref }}-1
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: E2E Test
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: ["go", "python", "quarkus", "springboot", "typescript"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16.x
|
||||
- name: Install Binaries
|
||||
run: ./hack/binaries.sh
|
||||
- name: Allocate Cluster
|
||||
run: ./hack/allocate.sh
|
||||
- name: Local Registry
|
||||
run: ./hack/registry.sh
|
||||
- name: Build
|
||||
run: make
|
||||
- name: E2E runtime for ${{ matrix.runtime }}
|
||||
run: make test-e2e-runtime runtime=${{ matrix.runtime }}
|
2
Makefile
2
Makefile
|
@ -136,6 +136,8 @@ test-e2e: ## Run end-to-end tests using an available cluster.
|
|||
./test/e2e_lifecycle_tests.sh node
|
||||
./test/e2e_extended_tests.sh
|
||||
|
||||
test-e2e-runtime: ## Run end-to-end lifecycle tests using an available cluster for a single runtime.
|
||||
./test/e2e_lifecycle_tests.sh $(runtime)
|
||||
|
||||
######################
|
||||
##@ Release Artifacts
|
||||
|
|
|
@ -9,7 +9,7 @@ Develop new features by adding a test to [`handle_test.go`](handle_test.go) for
|
|||
Update the running analog of the function using the `func` CLI or client library, and it can be invoked using a manually-created CloudEvent:
|
||||
|
||||
```console
|
||||
curl -v -X POST -d 'hello' \
|
||||
curl -v -X POST -d '{"message": "hello"}' \
|
||||
-H'Content-type: application/json' \
|
||||
-H'Ce-id: 1' \
|
||||
-H'Ce-source: cloud-event-example' \
|
||||
|
|
|
@ -20,24 +20,28 @@ func Handle(ctx context.Context, event cloudevents.Event) (*event.Event, error)
|
|||
*/
|
||||
|
||||
fmt.Printf("Incoming Event: %v\n", event) // print the received event to standard output
|
||||
payload := ""
|
||||
var payload Echo
|
||||
err := json.Unmarshal(event.Data(), &payload)
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload = "echo " + payload
|
||||
payload.Message = "echo " + payload.Message
|
||||
outputEvent := cloudevents.NewEvent()
|
||||
outputEvent.SetSource("http://example.com/echo")
|
||||
outputEvent.SetType("Echo")
|
||||
outputEvent.SetData(cloudevents.ApplicationJSON, &payload)
|
||||
outputEvent.SetData(cloudevents.ApplicationJSON, payload)
|
||||
|
||||
fmt.Printf("Outgoing Event: %v\n", outputEvent)
|
||||
|
||||
return &outputEvent, nil
|
||||
}
|
||||
|
||||
type Echo struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
/*
|
||||
Other supported function signatures:
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ func TestHandle(t *testing.T) {
|
|||
event.SetType("MyEvent")
|
||||
event.SetSource("http://localhost:8080/")
|
||||
event.SetSubject("Echo")
|
||||
input := "hello"
|
||||
event.SetData(cloudevents.ApplicationJSON, &input)
|
||||
input := Echo{Message: "hello"}
|
||||
event.SetData(cloudevents.ApplicationJSON, input)
|
||||
// Invoke the defined handler.
|
||||
ce, err := Handle(context.Background(), event)
|
||||
if err != nil {
|
||||
|
@ -32,13 +32,13 @@ func TestHandle(t *testing.T) {
|
|||
if ce.Type() != "Echo" {
|
||||
t.Errorf("Wrong CloudEvent Type received: %v , expected Echo", ce.Type())
|
||||
}
|
||||
output := ""
|
||||
var output Echo
|
||||
err = json.Unmarshal(ce.Data(), &output)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if output != "echo "+input {
|
||||
t.Errorf("The expected output should be: 'echo hello and it was: %v", output)
|
||||
if expected := "echo " + input.Message; output.Message != expected {
|
||||
t.Errorf("The expected output should be: %v, and it was: %v", expected, output.Message)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,16 +4,9 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"knative.dev/kn-plugin-func/k8s"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -102,31 +95,3 @@ func TestConfigLabel(t *testing.T) {
|
|||
t.Errorf("Expected label with name %v and value %v not found", labelKey3, testEnvValue)
|
||||
}
|
||||
}
|
||||
|
||||
// ** Helpers **
|
||||
|
||||
// RetrieveKnativeServiceResource wraps the logic to query knative serving resources in current namespace
|
||||
func RetrieveKnativeServiceResource(t *testing.T, serviceName string) *unstructured.Unstructured {
|
||||
// create k8s dynamic client
|
||||
config, err := k8s.GetClientConfig().ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
dynClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
knativeServiceResource := schema.GroupVersionResource{
|
||||
Group: "serving.knative.dev",
|
||||
Version: "v1",
|
||||
Resource: "services",
|
||||
}
|
||||
namespace, _, _ := k8s.GetClientConfig().Namespace()
|
||||
resource, err := dynClient.Resource(knativeServiceResource).Namespace(namespace).Get(context.Background(), serviceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return resource
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"knative.dev/kn-plugin-func/k8s"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
// RetrieveKnativeServiceResource wraps the logic to query knative serving resources in current namespace
|
||||
func RetrieveKnativeServiceResource(t *testing.T, serviceName string) *unstructured.Unstructured {
|
||||
// create k8s dynamic client
|
||||
config, err := k8s.GetClientConfig().ClientConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
dynClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
knativeServiceResource := schema.GroupVersionResource{
|
||||
Group: "serving.knative.dev",
|
||||
Version: "v1",
|
||||
Resource: "services",
|
||||
}
|
||||
namespace, _, _ := k8s.GetClientConfig().Namespace()
|
||||
resource, err := dynClient.Resource(knativeServiceResource).Namespace(namespace).Get(context.Background(), serviceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return resource
|
||||
}
|
||||
|
||||
// GetCurrentServiceRevision retrieves current revision name for the deployed function
|
||||
func GetCurrentServiceRevision(t *testing.T, project *FunctionTestProject) string {
|
||||
resource := RetrieveKnativeServiceResource(t, project.FunctionName)
|
||||
rootMap := resource.UnstructuredContent()
|
||||
statusMap := rootMap["status"].(map[string]interface{})
|
||||
latestReadyRevision := statusMap["latestReadyRevisionName"].(string)
|
||||
return latestReadyRevision
|
||||
}
|
|
@ -20,3 +20,16 @@ func ReadyCheck(t *testing.T, knFunc *TestShellCmdRunner, project FunctionTestPr
|
|||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
// NewRevisionCheck waits for a new revision to report as ready
|
||||
func NewRevisionCheck(t *testing.T, previousRevision string, project *FunctionTestProject) (newRevision string) {
|
||||
err := wait.PollImmediate(5*time.Second, 1*time.Minute, func() (done bool, err error) {
|
||||
newRevision = GetCurrentServiceRevision(t, project)
|
||||
t.Logf("Waiting for new revision deployment (previous revision [%v], current revision [%v])", previousRevision, newRevision)
|
||||
return newRevision != "" && newRevision != previousRevision, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("Function new revision never got ready")
|
||||
}
|
||||
return newRevision
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SimpleTestEvent struct {
|
||||
|
@ -16,7 +17,7 @@ type SimpleTestEvent struct {
|
|||
}
|
||||
|
||||
func (s SimpleTestEvent) pushTo(url string, t *testing.T) (body string, statusCode int, err error) {
|
||||
client := &http.Client{}
|
||||
client := &http.Client{Timeout: time.Second * 15}
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(s.Data))
|
||||
req.Header.Add("Ce-Id", "message-1")
|
||||
req.Header.Add("Ce-Specversion", "1.0")
|
||||
|
@ -58,16 +59,30 @@ var defaultFunctionsCloudEventsValidators = map[string]FunctionCloudEventsValida
|
|||
},
|
||||
}
|
||||
|
||||
var defaultFunctionsCloudEventsMessage = map[string]SimpleTestEvent{
|
||||
"default": {
|
||||
Type: "e2e.test",
|
||||
Source: "e2e:test",
|
||||
ContentType: "text/plain",
|
||||
Data: "hello",
|
||||
},
|
||||
"go": {
|
||||
Type: "e2e.test",
|
||||
Source: "e2e:test",
|
||||
ContentType: "application/json",
|
||||
Data: `{"message": "hello"}`,
|
||||
},
|
||||
}
|
||||
|
||||
// DefaultFunctionEventsTest executes a common test (applied for all runtimes) against a deployed
|
||||
// functions that responds to CloudEvents
|
||||
func DefaultFunctionEventsTest(t *testing.T, knFunc *TestShellCmdRunner, project FunctionTestProject) {
|
||||
if project.Template == "cloudevents" && project.IsDeployed {
|
||||
|
||||
simpleEvent := SimpleTestEvent{
|
||||
Type: "e2e.test",
|
||||
Source: "e2e:test",
|
||||
ContentType: "text/plain",
|
||||
Data: "hello",
|
||||
simpleEvent, ok := defaultFunctionsCloudEventsMessage[project.Runtime]
|
||||
if !ok {
|
||||
t.Log("Using default message template")
|
||||
simpleEvent = defaultFunctionsCloudEventsMessage["default"]
|
||||
}
|
||||
targetUrl := project.FunctionURL
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTP Based Function Test Validator
|
||||
|
@ -37,7 +38,7 @@ func (f FunctionHttpResponsivenessValidator) Validate(t *testing.T, project Func
|
|||
if f.contentType != "" {
|
||||
req.Header.Add("Content-Type", f.contentType)
|
||||
}
|
||||
client := &http.Client{}
|
||||
client := &http.Client{Timeout: time.Second * 15}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
// Http Response Handling
|
||||
|
@ -57,7 +58,7 @@ func (f FunctionHttpResponsivenessValidator) Validate(t *testing.T, project Func
|
|||
t.Fatalf("Expected status code 200, received %v", resp.StatusCode)
|
||||
}
|
||||
if f.expects != "" && !strings.Contains(string(body), f.expects) {
|
||||
t.Fatalf("Body does not contains expected sentence [%v]", f.expects)
|
||||
t.Fatalf("Body does not contains expected sentence [%v]\nBody received is: %v", f.expects, string(body))
|
||||
}
|
||||
if f.responseValidator != nil {
|
||||
if err = f.responseValidator(string(body)); err != nil {
|
||||
|
|
|
@ -3,6 +3,7 @@ package e2e
|
|||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
@ -32,11 +33,13 @@ func Update(t *testing.T, knFunc *TestShellCmdRunner, project *FunctionTestProje
|
|||
t.Fatal("an error has occurred while updating project folder with new sources.", err.Error())
|
||||
}
|
||||
|
||||
previousRevision := GetCurrentServiceRevision(t, project)
|
||||
|
||||
// Redeploy function
|
||||
Deploy(t, knFunc, project)
|
||||
|
||||
// Waits to become ready
|
||||
ReadyCheck(t, knFunc, *project)
|
||||
// Waits New Revision to become ready
|
||||
NewRevisionCheck(t, previousRevision, project)
|
||||
|
||||
// Indicates new project (from update templates) is in use
|
||||
project.IsNewRevision = true
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue