Add Ingress host, path and annotations support (#1325)

For each ingress controller, the format of ingress host,
path and annotations are different. To support different
kinds of controller, this PR adds new ingress config field
to http trigger spec. A user can set annotations, host and
path based on the type of underlying ingress controller
with CLI.

Command example:

fission route create --name foo \
    --url /foo/{bar} --function foofn --createingress \
    --ingressannotation "nginx.ingress.kubernetes.io/ssl-redirect=false" \
    --ingressannotation "nginx.ingress.kubernetes.io/use-regex=true" \
    --ingressrule "*=/foo/*"
This commit is contained in:
Ta-Ching Chen
2019-09-26 16:54:16 +08:00
committed by GitHub
parent 82a7acf408
commit 49d60b19f3
14 changed files with 1421 additions and 138 deletions
-1
View File
@@ -74,7 +74,6 @@ require (
google.golang.org/appengine v1.6.1 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.2
k8s.io/api v0.0.0-20190620084959-7cf5895f2711
k8s.io/apiextensions-apiserver v0.0.0-20190620085554-14e95df34f1f
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719
+26 -5
View File
@@ -547,20 +547,41 @@ type (
// HTTPTriggerSpec is for router to expose user functions at the given URL path.
HTTPTriggerSpec struct {
// NOT USED NOW
Host string `json:"-"` //`json:"host"`
// TODO: remove this field since we have IngressConfig already
// Deprecated: the original idea of this field is not for setting Ingress.
// Since we have IngressConfig now, remove Host after couple releases.
Host string `json:"host"`
// RelativeURL is the exposed URL for external client to access a function with.
RelativeURL string `json:"relativeurl"`
// If CreateIngress is true, router will create a ingress definition.
CreateIngress bool `json:"createingress"`
// HTTP method to access a function.
Method string `json:"method"`
// FunctionReference is a reference to the target function.
FunctionReference FunctionReference `json:"functionref"`
// If CreateIngress is true, router will create a ingress definition.
CreateIngress bool `json:"createingress"`
// TODO: make IngressConfig a independent Fission resource
// IngressConfig for router to set up Ingress.
IngressConfig IngressConfig `json:"ingressconfig"`
}
// IngressConfig is for router to set up Ingress.
IngressConfig struct {
// Annotations will be add to metadata when creating Ingress.
Annotations map[string]string `json:"annotations"`
// Path is for path matching. The format of path
// depends on what ingress controller you used.
Path string `json:"path"`
// Host is for ingress controller to apply rules. If
// host is empty or "*", the rule applies to all
// inbound HTTP traffic.
Host string `json:"host"`
}
// KubernetesWatchTriggerSpec
+54
View File
@@ -32,6 +32,8 @@ const (
ErrorUnsupportedType = iota
ErrorInvalidValue
ErrorInvalidObject
totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
)
var (
@@ -444,6 +446,58 @@ func (spec HTTPTriggerSpec) Validate() error {
}
}
result = multierror.Append(result, spec.IngressConfig.Validate())
return result.ErrorOrNil()
}
func (config IngressConfig) Validate() error {
result := &multierror.Error{}
// Details for how to validate Ingress host rule,
// see https://github.com/kubernetes/kubernetes/blob/release-1.16/pkg/apis/networking/validation/validation.go
if len(config.Path) > 0 {
if !strings.HasPrefix(config.Path, "/") {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "HTTPTriggerSpec.IngressConfig.IngressRule.Path", config.Path, "must be an absolute path"))
}
_, err := regexp.CompilePOSIX(config.Path)
if err != nil {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "HTTPTriggerSpec.IngressConfig.IngressRule.Path", config.Path, "must be a valid regex"))
}
}
// In Ingress, to accept requests from all host, the host field will
// be an empty string instead of "*" shown in kubectl. The router replaces
// the asterisk with "" when creating/updateing the Ingress, so here we
// skip the check if the Host is equal to "*".
if len(config.Host) > 0 && config.Host != "*" {
if strings.Contains(config.Host, "*") {
for _, msg := range validation.IsWildcardDNS1123Subdomain(config.Host) {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "HTTPTriggerSpec.IngressConfig.IngressRule.Host", config.Host, msg))
}
}
for _, msg := range validation.IsDNS1123Subdomain(config.Host) {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "HTTPTriggerSpec.IngressConfig.IngressRule.Host", config.Host, msg))
}
}
// Details for how to validate annotations,
// see https://github.com/kubernetes/kubernetes/blob/512eccac1f1d72d6d1cb304bc565c50d1f2e295e/staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go#L46
var totalSize int64
for k, v := range config.Annotations {
for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "HTTPTriggerSpec.IngressConfig.Annotations.key", k, msg))
}
totalSize += (int64)(len(k)) + (int64)(len(v))
}
if totalSize > (int64)(totalAnnotationSizeLimitB) {
msg := fmt.Sprintf("must have at most %v characters", totalSize)
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "HTTPTriggerSpec.IngressConfig.Annotations.value", totalAnnotationSizeLimitB, msg))
}
return result.ErrorOrNil()
}
+118
View File
@@ -0,0 +1,118 @@
/*
Copyright 2019 The Fission Authors.
Licensed 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 httptrigger
import (
"fmt"
"strings"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
)
// GetIngressConfig returns an IngressConfig based on user inputs; return error if any.
func GetIngressConfig(annotations []string, rule string, fallbackRelativeURL string, oldIngressConfig *fv1.IngressConfig) (*fv1.IngressConfig, error) {
removeAnns, anns, err := getIngressAnnotations(annotations)
if err != nil {
return nil, err
}
isEmptyRule, host, path, err := getIngressHostRule(rule, fallbackRelativeURL)
if err != nil {
return nil, err
}
if oldIngressConfig == nil {
if isEmptyRule { // assign default value
host = "*"
path = fallbackRelativeURL
}
return &fv1.IngressConfig{
Annotations: anns,
Host: host,
Path: path,
}, nil
}
if removeAnns {
oldIngressConfig.Annotations = nil
} else if len(anns) > 0 {
if oldIngressConfig.Annotations == nil {
oldIngressConfig.Annotations = make(map[string]string, len(anns))
}
for k, v := range anns {
oldIngressConfig.Annotations[k] = v
}
}
if isEmptyRule {
// an empty rule means no new rule was given,
// leave host and path intact except when host
// or path is empty.
if len(oldIngressConfig.Host) == 0 {
oldIngressConfig.Host = "*"
}
if len(oldIngressConfig.Path) == 0 {
oldIngressConfig.Path = fallbackRelativeURL
}
} else {
oldIngressConfig.Host = host
oldIngressConfig.Path = path
}
return oldIngressConfig, nil
}
func getIngressAnnotations(annotations []string) (remove bool, anns map[string]string, err error) {
if len(annotations) == 0 {
return false, nil, nil
}
anns = make(map[string]string)
for _, ann := range annotations {
if ann == "-" {
// remove all annotations
return true, nil, nil
}
v := strings.Split(ann, "=")
if len(v) != 2 {
return false, nil, fmt.Errorf("illegal ingress annotation: %v", ann)
}
key, val := v[0], v[1]
anns[key] = val
}
return false, anns, nil
}
func getIngressHostRule(rule string, fallbackPath string) (empty bool, host string, path string, err error) {
if len(fallbackPath) == 0 {
return false, "", "", fmt.Errorf("fallback url cannot be empty")
}
if len(rule) == 0 {
return true, "", "", nil
}
if rule == "-" {
return false, "*", fallbackPath, nil
}
v := strings.Split(rule, "=")
if len(v) != 2 {
return false, "", "", fmt.Errorf("illegal ingress rule: %v", rule)
}
if len(v[0]) == 0 || len(v[1]) == 0 {
return false, "", "", fmt.Errorf("host (%v) or path (%v) cannot be empty", v[0], v[1])
}
return false, v[0], v[1], nil
}
@@ -0,0 +1,442 @@
/*
Copyright 2019 The Fission Authors.
Licensed 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 httptrigger
import (
"reflect"
"testing"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
)
func Test_GetIngressConfig(t *testing.T) {
type args struct {
ingressConfig *fv1.IngressConfig
annotations []string
rule string
fallbackRelativeURL string
}
tests := []struct {
name string
args args
want *fv1.IngressConfig
wantErr bool
}{
{
name: "pass-nil-ingressconfig-pointer",
args: args{
ingressConfig: nil,
annotations: []string{"foo=bar", "bar=foo"},
rule: "test.com=/foo/bar",
fallbackRelativeURL: "/test",
},
want: &fv1.IngressConfig{
Annotations: map[string]string{
"foo": "bar",
"bar": "foo",
},
Host: "test.com",
Path: "/foo/bar",
},
wantErr: false,
},
{
name: "pass-non-nil-ingressconfig-pointer",
args: args{
ingressConfig: &fv1.IngressConfig{
Annotations: map[string]string{
"hello": "world",
},
Host: "foo",
Path: "bar",
},
annotations: []string{"foo=bar", "bar=foo"},
rule: "test.com=/foo/bar",
fallbackRelativeURL: "/test",
},
want: &fv1.IngressConfig{
Annotations: map[string]string{
"foo": "bar",
"bar": "foo",
"hello": "world",
},
Host: "test.com",
Path: "/foo/bar",
},
wantErr: false,
},
{
name: "ingressconfig-with-nil-annotations",
args: args{
ingressConfig: &fv1.IngressConfig{
Annotations: nil,
Host: "foo",
Path: "bar",
},
annotations: []string{"foo=bar", "bar=foo"},
rule: "test.com=/foo/bar",
fallbackRelativeURL: "/test",
},
want: &fv1.IngressConfig{
Annotations: map[string]string{
"foo": "bar",
"bar": "foo",
},
Host: "test.com",
Path: "/foo/bar",
},
wantErr: false,
},
{
name: "remove-annotations-from-ingressconfig",
args: args{
ingressConfig: &fv1.IngressConfig{
Annotations: map[string]string{
"hello": "world",
},
},
annotations: []string{"-"},
rule: "test.com=/foo/bar",
fallbackRelativeURL: "/test",
},
want: &fv1.IngressConfig{
Annotations: nil,
Host: "test.com",
Path: "/foo/bar",
},
wantErr: false,
},
{
name: "remove-rule-from-ingressconfig",
args: args{
ingressConfig: &fv1.IngressConfig{
Annotations: map[string]string{
"hello": "world",
},
},
annotations: []string{"-"},
rule: "-",
fallbackRelativeURL: "/test",
},
want: &fv1.IngressConfig{
Annotations: nil,
Host: "*",
Path: "/test",
},
wantErr: false,
},
{
name: "wrong-annotations-value-1",
args: args{
ingressConfig: nil,
annotations: []string{"a"},
rule: "-",
fallbackRelativeURL: "/test",
},
want: nil,
wantErr: true,
},
{
name: "wrong-annotations-value-2",
args: args{
ingressConfig: nil,
annotations: []string{"a=b=c"},
rule: "-",
fallbackRelativeURL: "/test",
},
want: nil,
wantErr: true,
},
{
name: "wrong-rule-value-1",
args: args{
ingressConfig: &fv1.IngressConfig{
Annotations: map[string]string{
"hello": "world",
},
},
annotations: []string{"a=b"},
rule: "a",
fallbackRelativeURL: "/test",
},
want: nil,
wantErr: true,
},
{
name: "wrong-rule-value-2",
args: args{
ingressConfig: &fv1.IngressConfig{
Annotations: map[string]string{
"hello": "world",
},
},
annotations: []string{"a=b"},
rule: "a=b=c",
fallbackRelativeURL: "/test",
},
want: nil,
wantErr: true,
},
{
name: "ingressconfog-with-only-fallback-rul",
args: args{
ingressConfig: nil,
annotations: nil,
rule: "",
fallbackRelativeURL: "/test",
},
want: &fv1.IngressConfig{
Annotations: nil,
Host: "*",
Path: "/test",
},
wantErr: false,
},
{
name: "backward-compatibility-test",
args: args{
ingressConfig: &fv1.IngressConfig{
Annotations: nil,
Host: "",
Path: "",
},
annotations: nil,
rule: "",
fallbackRelativeURL: "/test",
},
want: &fv1.IngressConfig{
Annotations: nil,
Host: "*",
Path: "/test",
},
wantErr: false,
},
{
name: "preserve-annotation-if-nothing-change",
args: args{
ingressConfig: &fv1.IngressConfig{
Annotations: map[string]string{
"a": "b",
},
Host: "test.com",
Path: "/foo/bar",
},
annotations: nil,
rule: "",
fallbackRelativeURL: "/test",
},
want: &fv1.IngressConfig{
Annotations: map[string]string{
"a": "b",
},
Host: "test.com",
Path: "/foo/bar",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetIngressConfig(tt.args.annotations, tt.args.rule, tt.args.fallbackRelativeURL, tt.args.ingressConfig)
if (err != nil) != tt.wantErr {
t.Errorf("getIngressConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("%v %v %v %v", got.Annotations == nil, tt.want.Annotations == nil, got.Path == tt.want.Path, got.Host == tt.want.Host)
t.Errorf("getIngressConfig() got = %v, want %v", got, tt.want)
}
})
}
}
func Test_getIngressAnnotations(t *testing.T) {
type args struct {
annotations []string
}
tests := []struct {
name string
args args
wantRemove bool
wantAnns map[string]string
wantErr bool
}{
{
name: "get-annotations",
args: args{
annotations: []string{"a=b", "c=d"},
},
wantRemove: false,
wantAnns: map[string]string{
"a": "b",
"c": "d",
},
wantErr: false,
},
{
name: "remove-all-annotations",
args: args{
annotations: []string{"-", "c=d"},
},
wantRemove: true,
wantAnns: nil,
wantErr: false,
},
{
name: "incorrect-annotation",
args: args{
annotations: []string{"a==b"},
},
wantRemove: false,
wantAnns: nil,
wantErr: true,
},
{
name: "zero-annotations-1",
args: args{
annotations: []string{},
},
wantRemove: false,
wantAnns: nil,
wantErr: false,
},
{
name: "zero-annotations-2",
args: args{
annotations: nil,
},
wantRemove: false,
wantAnns: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotRemove, gotAnns, err := getIngressAnnotations(tt.args.annotations)
if (err != nil) != tt.wantErr {
t.Errorf("getIngressAnnotations() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotRemove != tt.wantRemove {
t.Errorf("getIngressAnnotations() gotRemove = %v, want %v", gotRemove, tt.wantRemove)
}
if !reflect.DeepEqual(gotAnns, tt.wantAnns) {
t.Errorf("getIngressAnnotations() gotAnns = %v, want %v", gotAnns, tt.wantAnns)
}
})
}
}
func Test_getIngressHostRule(t *testing.T) {
type args struct {
rule string
fallbackPath string
}
tests := []struct {
name string
args args
wantEmpty bool
wantHost string
wantPath string
wantErr bool
}{
{
name: "get-rule",
args: args{
rule: "a=b",
fallbackPath: "/foo",
},
wantEmpty: false,
wantHost: "a",
wantPath: "b",
wantErr: false,
},
{
name: "remove-rule",
args: args{
rule: "-",
fallbackPath: "/foo",
},
wantEmpty: false,
wantHost: "*",
wantPath: "/foo",
wantErr: false,
},
{
name: "empty-rule",
args: args{
rule: "",
fallbackPath: "/foo",
},
wantEmpty: true,
wantHost: "",
wantPath: "",
wantErr: false,
},
{
name: "empty-host",
args: args{
rule: "=/aasd",
fallbackPath: "/foo",
},
wantEmpty: false,
wantHost: "",
wantPath: "",
wantErr: true,
},
{
name: "empty-path",
args: args{
rule: "test.com=",
fallbackPath: "/foo",
},
wantEmpty: false,
wantHost: "",
wantPath: "",
wantErr: true,
},
{
name: "empty-fallback-url",
args: args{
rule: "test.com=",
fallbackPath: "",
},
wantEmpty: false,
wantHost: "",
wantPath: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotEmpty, gotHost, gotPath, err := getIngressHostRule(tt.args.rule, tt.args.fallbackPath)
if (err != nil) != tt.wantErr {
t.Errorf("getIngressHostRule() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotEmpty != tt.wantEmpty {
t.Errorf("getIngressHostRule() gotEmpty = %v, want %v", gotEmpty, tt.wantEmpty)
}
if gotHost != tt.wantHost {
t.Errorf("getIngressHostRule() gotHost = %v, want %v", gotHost, tt.wantHost)
}
if gotPath != tt.wantPath {
t.Errorf("getIngressHostRule() gotPath = %v, want %v", gotPath, tt.wantPath)
}
})
}
}
+5
View File
@@ -441,6 +441,11 @@ func (fr *FissionResources) Validate(c *cli.Context) error {
if err != nil {
result = multierror.Append(result, err)
}
if len(t.Spec.Host) > 0 {
log.Warn(fmt.Sprintf("Host in HTTPTrigger spec.Host is now marked as deprecated, see 'help' for details"))
}
result = multierror.Append(result, t.Validate())
}
for _, t := range fr.KubernetesWatchTriggers {
+14 -4
View File
@@ -29,6 +29,7 @@ import (
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
ferror "github.com/fission/fission/pkg/error"
"github.com/fission/fission/pkg/fission-cli/cmd/httptrigger"
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
"github.com/fission/fission/pkg/fission-cli/log"
"github.com/fission/fission/pkg/fission-cli/util"
@@ -143,12 +144,14 @@ func htCreate(c *cli.Context) error {
}
}
createIngress := false
if c.IsSet("createingress") {
createIngress = c.Bool("createingress")
}
createIngress := c.Bool("createingress")
ingressConfig, err := httptrigger.GetIngressConfig(c.StringSlice("ingressannotation"), c.String("ingressrule"), triggerUrl, nil)
util.CheckErr(err, "parse ingress configuration")
host := c.String("host")
if c.IsSet("host") {
log.Warn(fmt.Sprintf("--host is now marked as deprecated, see 'help' for details"))
}
// just name triggers by uuid.
if triggerName == "" {
@@ -166,6 +169,7 @@ func htCreate(c *cli.Context) error {
Method: getMethod(method),
FunctionReference: *functionRef,
CreateIngress: createIngress,
IngressConfig: *ingressConfig,
},
}
@@ -264,6 +268,12 @@ func htUpdate(c *cli.Context) error {
if c.IsSet("host") {
ht.Spec.Host = c.String("host")
log.Warn(fmt.Sprintf("--host is now marked as deprecated, see 'help' for details"))
}
if c.IsSet("ingressrule") || c.IsSet("ingressannotation") {
_, err = httptrigger.GetIngressConfig(c.StringSlice("ingressannotation"), c.String("ingressrule"), ht.Spec.RelativeURL, &ht.Spec.IngressConfig)
util.CheckErr(err, "parse ingress configuration")
}
_, err = client.HTTPTriggerUpdate(ht)
+5 -4
View File
@@ -143,15 +143,16 @@ func NewCliApp() *cli.App {
// httptriggers
htNameFlag := cli.StringFlag{Name: "name", Usage: "HTTP Trigger name"}
htHostFlag := cli.StringFlag{Name: "host", Usage: "FQDN of the network host for route"}
htHostFlag := cli.StringFlag{Name: "host", Usage: "(DEPRECATED) Use --ingressrule instead"}
htIngressFlag := cli.BoolFlag{Name: "createingress", Usage: "Creates ingress with same URL, defaults to false"}
htIngressRuleFlag := cli.StringFlag{Name: "ingressrule", Usage: "Host for Ingress rule: --ingressrule host=path (the format of host/path depends on what ingress controller you used)"}
htIngressAnnotationFlag := cli.StringSliceFlag{Name: "ingressannotation", Usage: "Annotation for Ingress: --ingressannotation key=value (the format of annotation depends on what ingress controller you used)"}
htFnNameFlag := cli.StringSliceFlag{Name: "function", Usage: "Name(s) of the function for this trigger. If 2 functions are supplied with this flag, traffic gets routed to them based on weights supplied with --weight flag."}
htFnWeightFlag := cli.IntSliceFlag{Name: "weight", Usage: "Weight for each function supplied with --function flag, in the same order. Used for canary deployment"}
htSubcommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Create HTTP trigger", Flags: []cli.Flag{htNameFlag, htMethodFlag, htUrlFlag, htFnNameFlag, htHostFlag, htIngressFlag, fnNamespaceFlag, specSaveFlag, htFnWeightFlag}, Action: htCreate},
{Name: "create", Aliases: []string{"add"}, Usage: "Create HTTP trigger", Flags: []cli.Flag{htNameFlag, htMethodFlag, htUrlFlag, htFnNameFlag, htIngressRuleFlag, htIngressAnnotationFlag, htIngressFlag, fnNamespaceFlag, specSaveFlag, htFnWeightFlag, htHostFlag}, Action: htCreate},
{Name: "get", Usage: "Get HTTP trigger", Flags: []cli.Flag{htNameFlag}, Action: htGet},
{Name: "update", Usage: "Update HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag, htFnNameFlag, htHostFlag, htIngressFlag, htFnWeightFlag}, Action: htUpdate},
{Name: "update", Usage: "Update HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag, htFnNameFlag, htIngressRuleFlag, htIngressAnnotationFlag, htIngressFlag, htFnWeightFlag, htHostFlag}, Action: htUpdate},
{Name: "delete", Usage: "Delete HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag}, Action: htDelete},
{Name: "list", Usage: "List HTTP triggers", Flags: []cli.Flag{triggerNamespaceFlag}, Action: htList},
}
+43 -70
View File
@@ -18,16 +18,15 @@ package router
import (
"os"
"reflect"
"go.uber.org/zap"
"k8s.io/api/extensions/v1beta1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
"github.com/fission/fission/pkg/router/util"
)
var podNamespace string
@@ -43,61 +42,14 @@ func createIngress(logger *zap.Logger, trigger *fv1.HTTPTrigger, kubeClient *kub
if !trigger.Spec.CreateIngress {
return
}
_, err := kubeClient.ExtensionsV1beta1().Ingresses(podNamespace).Get(trigger.Metadata.Name, v1.GetOptions{})
if err == nil {
return
}
ing := &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: getDeployLabels(trigger),
Name: trigger.Metadata.Name,
// The Ingress NS MUST be same as Router NS, check long discussion:
// https://github.com/kubernetes/kubernetes/issues/17088
// We need to revisit this in future, once Kubernetes supports cross namespace ingress
Namespace: podNamespace,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: trigger.Spec.Host,
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: trigger.Spec.RelativeURL,
},
},
},
},
},
},
},
}
_, err = kubeClient.ExtensionsV1beta1().Ingresses(podNamespace).Create(ing)
if err != nil {
_, err := kubeClient.ExtensionsV1beta1().Ingresses(podNamespace).Create(util.GetIngressSpec(podNamespace, trigger))
if err != nil && !k8serrors.IsAlreadyExists(err) {
logger.Error("failed to create ingress", zap.Error(err))
return
}
logger.Debug("created ingress successfully for trigger", zap.String("trigger", trigger.Metadata.Name))
}
func getDeployLabels(trigger *fv1.HTTPTrigger) map[string]string {
return map[string]string{
"triggerName": trigger.Metadata.Name,
"functionName": trigger.Spec.FunctionReference.Name,
"triggerNamespace": trigger.Metadata.Namespace,
}
}
func deleteIngress(logger *zap.Logger, trigger *fv1.HTTPTrigger, kubeClient *kubernetes.Clientset) {
if !trigger.Spec.CreateIngress {
return
@@ -130,25 +82,46 @@ func updateIngress(logger *zap.Logger, oldT *fv1.HTTPTrigger, newT *fv1.HTTPTrig
return
}
if newT.Spec.Host != oldT.Spec.Host || newT.Spec.RelativeURL != oldT.Spec.RelativeURL {
ingress, err := kubeClient.ExtensionsV1beta1().Ingresses(podNamespace).Get(oldT.Metadata.Name, v1.GetOptions{})
oldIngress, err := kubeClient.ExtensionsV1beta1().Ingresses(podNamespace).Get(oldT.Metadata.Name, v1.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
createIngress(logger, newT, kubeClient)
}
logger.Error("failed to get ingress when updating trigger",
zap.Error(err),
zap.String("trigger", oldT.Metadata.Name))
return
}
newIngress := util.GetIngressSpec(podNamespace, newT)
changes := false
if !reflect.DeepEqual(oldIngress.Annotations, newIngress.Annotations) {
logger.Debug("ingress annotation",
zap.Any("old_trigger", oldIngress.Annotations), zap.Any("new_trigger", newIngress.Annotations))
if oldIngress.Annotations == nil || newIngress.Annotations == nil {
oldIngress.Annotations = newIngress.Annotations
} else {
for k, v := range newIngress.Annotations {
oldIngress.Annotations[k] = v
}
}
changes = true
}
if !reflect.DeepEqual(oldIngress.Spec, newIngress.Spec) {
logger.Debug("ingress spec",
zap.Any("old_trigger", oldIngress.Spec), zap.Any("new_trigger", newIngress.Spec))
oldIngress.Spec = newIngress.Spec
changes = true
}
if changes {
_, err = kubeClient.ExtensionsV1beta1().Ingresses(podNamespace).Update(oldIngress)
if err != nil {
logger.Error("failed to get ingress when updating trigger",
zap.Error(err),
zap.String("trigger", oldT.Metadata.Name))
}
if newT.Spec.Host != oldT.Spec.Host {
ingress.Spec.Rules[0].Host = newT.Spec.Host
}
if newT.Spec.RelativeURL != oldT.Spec.RelativeURL {
ingress.Spec.Rules[0].HTTP.Paths[0].Path = newT.Spec.RelativeURL
}
_, err = kubeClient.ExtensionsV1beta1().Ingresses(podNamespace).Update(ingress)
if err != nil {
logger.Error("failed to update ingress for trigger", zap.String("trigger", oldT.Metadata.Name))
logger.Error("failed to update ingress for trigger", zap.Error(err), zap.String("trigger", oldT.Metadata.Name))
return
}
+85
View File
@@ -0,0 +1,85 @@
/*
Copyright 2019 The Fission Authors.
Licensed 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 util
import (
"k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
)
func GetIngressSpec(namespace string, trigger *fv1.HTTPTrigger) *v1beta1.Ingress {
// TODO: remove backward compatibility
host, path := trigger.Spec.Host, trigger.Spec.RelativeURL
if len(trigger.Spec.IngressConfig.Host) > 0 && len(trigger.Spec.IngressConfig.Path) > 0 {
host, path = trigger.Spec.IngressConfig.Host, trigger.Spec.IngressConfig.Path
}
// In Ingress, to accept requests from all host, the host field will
// be an empty string instead of "*" shown in kubectl. So replace it
// with empty string
if host == "*" {
host = "" // wildcard Ingress host
}
ing := &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: GetDeployLabels(trigger),
Name: trigger.Metadata.Name,
// The Ingress NS MUST be same as Router NS, check long discussion:
// https://github.com/kubernetes/kubernetes/issues/17088
// We need to revisit this in future, once Kubernetes supports cross namespace ingress
Namespace: namespace,
Annotations: trigger.Spec.IngressConfig.Annotations,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: host,
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: path,
},
},
},
},
},
},
},
}
return ing
}
func GetDeployLabels(trigger *fv1.HTTPTrigger) map[string]string {
// TODO: support function weight
return map[string]string{
"triggerName": trigger.Metadata.Name,
"functionName": trigger.Spec.FunctionReference.Name,
"triggerNamespace": trigger.Metadata.Namespace,
}
}
+499
View File
@@ -0,0 +1,499 @@
/*
Copyright 2019 The Fission Authors.
Licensed 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 util
import (
"reflect"
"testing"
"k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
)
func TestGetIngressSpec(t *testing.T) {
type args struct {
ingressNS string
trigger *fv1.HTTPTrigger
}
tests := []struct {
name string
args args
want *v1beta1.Ingress
}{
{
name: "host-backward-compatibility",
args: args{
ingressNS: "foobarNS",
trigger: &fv1.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: fv1.HTTPTriggerSpec{
Host: "test.com",
RelativeURL: "/foo/bar",
FunctionReference: fv1.FunctionReference{
Name: "foofunc",
},
IngressConfig: fv1.IngressConfig{
Annotations: nil,
},
},
},
},
want: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"triggerName": "foo",
"functionName": "foofunc",
"triggerNamespace": "bar",
},
Name: "foo",
Namespace: "foobarNS",
Annotations: nil,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "test.com",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: "/foo/bar",
},
},
},
},
},
},
},
},
},
{
name: "create-ingress-with-only-annotations",
args: args{
ingressNS: "foobarNS",
trigger: &fv1.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: fv1.HTTPTriggerSpec{
RelativeURL: "/foo/bar",
FunctionReference: fv1.FunctionReference{
Name: "foofunc",
},
IngressConfig: fv1.IngressConfig{
Annotations: map[string]string{
"key": "value",
},
},
},
},
},
want: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"triggerName": "foo",
"functionName": "foofunc",
"triggerNamespace": "bar",
},
Name: "foo",
Namespace: "foobarNS",
Annotations: map[string]string{
"key": "value",
},
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: "/foo/bar",
},
},
},
},
},
},
},
},
},
{
name: "create-ingress-with-only-rule",
args: args{
ingressNS: "foobarNS",
trigger: &fv1.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: fv1.HTTPTriggerSpec{
RelativeURL: "/foo/{bar}",
FunctionReference: fv1.FunctionReference{
Name: "foofunc",
},
IngressConfig: fv1.IngressConfig{
Annotations: nil,
Path: "/foo/bar",
Host: "test.com",
},
},
},
},
want: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"triggerName": "foo",
"functionName": "foofunc",
"triggerNamespace": "bar",
},
Name: "foo",
Namespace: "foobarNS",
Annotations: nil,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "test.com",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: "/foo/bar",
},
},
},
},
},
},
},
},
},
{
name: "create-ingress-with-empty-rule-host",
args: args{
ingressNS: "foobarNS",
trigger: &fv1.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: fv1.HTTPTriggerSpec{
RelativeURL: "/foo/{bar}",
FunctionReference: fv1.FunctionReference{
Name: "foofunc",
},
IngressConfig: fv1.IngressConfig{
Annotations: nil,
Path: "/foo/bar",
Host: "",
},
},
},
},
want: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"triggerName": "foo",
"functionName": "foofunc",
"triggerNamespace": "bar",
},
Name: "foo",
Namespace: "foobarNS",
Annotations: nil,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: "/foo/{bar}",
},
},
},
},
},
},
},
},
},
{
name: "create-ingress-with-empty-rule-path",
args: args{
ingressNS: "foobarNS",
trigger: &fv1.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: fv1.HTTPTriggerSpec{
RelativeURL: "/foo/{bar}",
FunctionReference: fv1.FunctionReference{
Name: "foofunc",
},
IngressConfig: fv1.IngressConfig{
Annotations: nil,
Path: "",
Host: "test.com",
},
},
},
},
want: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"triggerName": "foo",
"functionName": "foofunc",
"triggerNamespace": "bar",
},
Name: "foo",
Namespace: "foobarNS",
Annotations: nil,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: "/foo/{bar}",
},
},
},
},
},
},
},
},
},
{
name: "create-ingress-with-host-and-rule",
args: args{
ingressNS: "foobarNS",
trigger: &fv1.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: fv1.HTTPTriggerSpec{
Host: "example.com",
RelativeURL: "/foo/{bar}",
FunctionReference: fv1.FunctionReference{
Name: "foofunc",
},
IngressConfig: fv1.IngressConfig{
Annotations: nil,
Path: "/foo/bar",
Host: "test.com",
},
},
},
},
want: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"triggerName": "foo",
"functionName": "foofunc",
"triggerNamespace": "bar",
},
Name: "foo",
Namespace: "foobarNS",
Annotations: nil,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "test.com",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: "/foo/bar",
},
},
},
},
},
},
},
},
},
{
name: "create-ingress-with-wildecard-rule-host",
args: args{
ingressNS: "foobarNS",
trigger: &fv1.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: fv1.HTTPTriggerSpec{
RelativeURL: "/foo/{bar}",
FunctionReference: fv1.FunctionReference{
Name: "foofunc",
},
IngressConfig: fv1.IngressConfig{
Annotations: nil,
Path: "/foo/bar",
Host: "*",
},
},
},
},
want: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"triggerName": "foo",
"functionName": "foofunc",
"triggerNamespace": "bar",
},
Name: "foo",
Namespace: "foobarNS",
Annotations: nil,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "router",
ServicePort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 80,
},
},
Path: "/foo/bar",
},
},
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetIngressSpec(tt.args.ingressNS, tt.args.trigger); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetIngressSpec() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetDeployLabels(t *testing.T) {
type args struct {
trigger *fv1.HTTPTrigger
}
// TODO: support function weight
tests := []struct {
name string
args args
want map[string]string
}{
{
name: "getdeploylabels",
args: args{
trigger: &fv1.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: "foo",
Namespace: "bar",
},
Spec: fv1.HTTPTriggerSpec{
FunctionReference: fv1.FunctionReference{
Type: "name",
Name: "foobar",
FunctionWeights: nil,
},
},
},
},
want: map[string]string{
"triggerName": "foo",
"functionName": "foobar",
"triggerNamespace": "bar",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetDeployLabels(tt.args.trigger); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetDeployLabels() = %v, want %v", got, tt.want)
}
})
}
}
+33 -28
View File
@@ -65,6 +65,20 @@ setupCIBuildEnv() {
export PRE_UPGRADE_CHECK_IMAGE=$REPO/pre-upgrade-checks
}
setupIngressController() {
# set up NGINX ingress controller
kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user $(gcloud config get-value account) || true
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.25.1/deploy/static/mandatory.yaml || true
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.25.1/deploy/static/provider/cloud-generic.yaml || true
}
removeIngressController() {
# set up NGINX ingress controller
kubectl delete clusterrolebinding cluster-admin-binding || true
kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.25.1/deploy/static/mandatory.yaml || true
kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.25.1/deploy/static/provider/cloud-generic.yaml || true
}
build_and_push_go_mod_cache_image() {
image_tag=$1
travis_fold_start go_mod_cache_image $image_tag
@@ -195,9 +209,13 @@ set_environment() {
id=$1
ns=f-$id
# fission env
export FISSION_URL=http://$(kubectl -n $ns get svc controller -o jsonpath='{...ip}')
export FISSION_ROUTER=$(kubectl -n $ns get svc router -o jsonpath='{...ip}')
export FISSION_NATS_STREAMING_URL="http://defaultFissionAuthToken@$(kubectl -n $ns get svc nats-streaming -o jsonpath='{...ip}:{.spec.ports[0].port}')"
# ingress controller env
export INGRESS_CONTROLLER=$(kubectl -n ingress-nginx get svc ingress-nginx -o jsonpath='{...ip}')
}
generate_test_id() {
@@ -273,10 +291,9 @@ dump_tiller_logs() {
export -f dump_tiller_logs
wait_for_service() {
id=$1
ns=$1
svc=$2
ns=f-$id
while true
do
ip=$(kubectl -n $ns get svc $svc -o jsonpath='{...ip}')
@@ -291,9 +308,11 @@ export -f wait_for_service
wait_for_services() {
id=$1
ns=f-$id
wait_for_service $id controller
wait_for_service $id router
wait_for_service $ns controller
wait_for_service $ns router
wait_for_service "ingress-nginx" ingress-nginx
echo Waiting for service is routable...
sleep 30
@@ -339,23 +358,6 @@ port_forward_services() {
xargs -I{} kubectl port-forward {} $port:$port -n $ns &
}
wait_for_service() {
id=$1
svc=$2
ns=f-$id
while true
do
ip=$(kubectl -n $ns get svc $svc -o jsonpath='{...ip}')
if [ ! -z $ip ]
then
break
fi
echo Waiting for service $svc...
sleep 1
done
}
dump_builder_pod_logs() {
bns=$1
builderPods=$(kubectl -n $bns get pod -o name)
@@ -600,6 +602,8 @@ install_and_test() {
exit 1
fi
setupIngressController
timeout 150 bash -c "wait_for_services $id"
timeout 120 bash -c "check_gitcommit_version"
set_environment $id
@@ -607,14 +611,15 @@ install_and_test() {
run_all_tests $id $imageTag
dump_logs $id
removeIngressController
# Commented out due to Travis-CI log length limit
# if [ $FAILURES -ne 0 ]
# then
# # describe each pod in fission ns and function namespace
# describe_all_pods $id
# exit 1
# fi
if [ $FAILURES -ne 0 ]
then
# Commented out due to Travis-CI log length limit
# describe each pod in fission ns and function namespace
# describe_all_pods $id
exit 1
fi
}
+79 -24
View File
@@ -7,50 +7,105 @@ echo "TEST_ID = $TEST_ID"
ROOT=$(dirname $0)/../..
env=nodejs-$TEST_ID
relativeUrl="/itest-$TEST_ID"
functionName="hellotest-$TEST_ID"
hostName="test-$TEST_ID.com"
routeName="ingress-$TEST_ID"
cleanup() {
clean_resource_by_id $TEST_ID
}
checkIngress() {
local route=$1
local host=$2
local path=$3
local annotations=$4
log "Ingresses matching this trigger:"
kubectl get ing -l 'functionName='$functionName',triggerName='$route --all-namespaces -o=json
log "Verifying to route value in ingress"
actual_path=$(kubectl get ing -l "functionName=$functionName,triggerName=$route" --all-namespaces -o=jsonpath='{.items[0].spec.rules[0].http.paths[0].path}')
if [ "$path" != "$actual_path" ]
then
log "Provided route ($path) and route ($actual_path) in ingress don't match"
exit 1
fi
actual_host=$(kubectl get ing -l "functionName=$functionName,triggerName=$route" --all-namespaces -o=jsonpath='{.items[0].spec.rules[0].host}')
if [ "$host" != "$actual_host" ]
then
log "Provided host ($host) and host ($actual_host) in ingress don't match"
exit 1
fi
actual_ann=$(kubectl get ing -l "functionName=$functionName,triggerName=$route" --all-namespaces -o jsonpath="{.items[0].metadata.annotations}")
if [ "$annotations" != "$actual_ann" ]
then
log "Provided annotations ($annotations) and annotations ($actual_ann) in ingress don't match"
exit 1
fi
}
createFn() {
# Create a hello world function in nodejs, test it with an http trigger
log "Creating nodejs env"
fission env create --name $env --image $NODE_RUNTIME_IMAGE
log "Creating function"
fission fn create --name $functionName --env $env --code $ROOT/examples/nodejs/hello.js
log "Doing an HTTP GET on the function's route"
response=$(fission fn test --name $functionName)
log "Checking for valid response"
echo $response | grep -i hello
}
if [ -z "${TEST_NOCLEANUP:-}" ]; then
trap cleanup EXIT
else
log "TEST_NOCLEANUP is set; not cleaning up test artifacts afterwards."
fi
createFn
log "Creating route for URL $relativeUrl"
route_name=$(fission route create --url $relativeUrl --function $functionName --createingress| grep trigger| cut -d" " -f 2|cut -d"'" -f 2)
fission route create --name $routeName --url $relativeUrl --function $functionName --createingress
log "Route $route_name created"
sleep 5
log "Ingresses matching this trigger:"
kubectl get ing -l 'functionName='$functionName',triggerName='$route_name --all-namespaces -o=json
log "Verifying to route value in ingress"
actual_route=$(kubectl -n fission get ing -l 'functionName='$functionName',triggerName='$route_name --all-namespaces -o=jsonpath='{.items[0].spec.rules[0].http.paths[0].path}')
if [ $actual_route != $relativeUrl ]
then
log "Provided route and route in ingress don't match"
exit 1
fi
sleep 3
checkIngress $routeName "" $relativeUrl ""
log "Modifying the route by adding host"
fission route update --name $route_name --host $hostName --function $functionName
fission route update --name $routeName --function $functionName --ingressannotation "foo=bar" --ingressrule "$hostName=/foo/bar"
sleep 2
sleep 3
checkIngress $routeName $hostName "/foo/bar" "map[foo:bar]"
actual_host=$(kubectl get ing -l 'functionName='$functionName',triggerName='$route_name --all-namespaces -o=jsonpath='{.items[0].spec.rules[0].host}')
log "Remove ingress annotations, host and rule"
fission route update --name $routeName --function $functionName --ingressannotation "-" --ingressrule "-"
if [ $hostName != $actual_host ]
then
log "Provided host and host in ingress don't match"
exit 1
fi
sleep 3
checkIngress $routeName "" $relativeUrl ""
fission route delete --name $routeName
relativeUrl="/itest-$TEST_ID/{url}"
wildcardPath="/itest-$TEST_ID/*"
realPath="itest-$TEST_ID/test"
log "Creating route for wildcard URL $relativeUrl"
fission route create --name $routeName --url $relativeUrl --function $functionName --createingress \
--ingressannotation "nginx.ingress.kubernetes.io/ssl-redirect=false" \
--ingressannotation "nginx.ingress.kubernetes.io/use-regex=true" \
--ingressrule "*=$wildcardPath"
sleep 3
checkIngress $routeName "" $wildcardPath "map[nginx.ingress.kubernetes.io/ssl-redirect:false nginx.ingress.kubernetes.io/use-regex:true]"
timeout 10 bash -c "test_ingress $realPath 'hello, world!'"
log "Test PASSED"
+18 -2
View File
@@ -44,9 +44,25 @@ clean_resource_by_id() {
}
test_fn() {
url="http://$FISSION_ROUTER/$1"
expect=$2
test_response $url $expect
}
export -f test_fn
test_ingress() {
url="http://$INGRESS_CONTROLLER/$1"
expect=$2
echo $url
test_response $url $expect
}
export -f test_ingress
test_response() {
# Doing an HTTP GET on the function's route
# Checking for valid response
url="http://$FISSION_ROUTER/$1"
url=$1
expect=$2
set +e
@@ -69,7 +85,7 @@ test_fn() {
done
set -e
}
export -f test_fn
export -f test_response
test_post_route() {
# Doing an HTTP POST on the function's route