Skip to content

Commit

Permalink
Merge af956c7 into 589bae6
Browse files Browse the repository at this point in the history
  • Loading branch information
Revolyssup committed Dec 11, 2023
2 parents 589bae6 + af956c7 commit aec4d5a
Show file tree
Hide file tree
Showing 24 changed files with 584 additions and 105 deletions.
1 change: 1 addition & 0 deletions .github/workflows/e2e-test-ci-v2-cron-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ jobs:
${REGISTRY}/apisix-ingress-controller:dev \
${REGISTRY}/httpbin:dev \
${REGISTRY}/test-backend:dev \
${REGISTRY}/test-timeout:dev \
${REGISTRY}/echo-server:dev \
${REGISTRY}/busybox:dev \
| pigz > docker-dev.tar.gz
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/e2e-test-ci-v2-cron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ jobs:
${REGISTRY}/apisix-ingress-controller:dev \
${REGISTRY}/httpbin:dev \
${REGISTRY}/test-backend:dev \
${REGISTRY}/test-timeout:dev \
${REGISTRY}/echo-server:dev \
${REGISTRY}/busybox:dev \
| pigz > docker-v2.tar.gz
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/e2e-test-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ jobs:
${REGISTRY}/apisix-ingress-controller:dev \
${REGISTRY}/httpbin:dev \
${REGISTRY}/test-backend:dev \
${REGISTRY}/test-timeout:dev \
${REGISTRY}/echo-server:dev \
${REGISTRY}/busybox:dev \
| pigz > docker.tar.gz
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/k8s-timer-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ jobs:
${REGISTRY}/apisix-ingress-controller:dev \
${REGISTRY}/httpbin:dev \
${REGISTRY}/test-backend:dev \
${REGISTRY}/test-timeout:dev \
${REGISTRY}/echo-server:dev \
${REGISTRY}/busybox:dev \
| pigz > docker.tar.gz
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ ifeq ($(E2E_SKIP_BUILD), 0)
docker tag kennethreitz/httpbin $(REGISTRY)/httpbin:$(IMAGE_TAG)

docker build -t test-backend:$(IMAGE_TAG) --build-arg ENABLE_PROXY=$(ENABLE_PROXY) ./test/e2e/testbackend
docker build -t test-timeout:$(IMAGE_TAG) --build-arg ENABLE_PROXY=$(ENABLE_PROXY) ./test/e2e/testtimeout
docker tag test-backend:$(IMAGE_TAG) $(REGISTRY)/test-backend:$(IMAGE_TAG)

docker tag test-timeout:$(IMAGE_TAG) $(REGISTRY)/test-timeout:$(IMAGE_TAG)
docker tag apache/apisix-ingress-controller:$(IMAGE_TAG) $(REGISTRY)/apisix-ingress-controller:$(IMAGE_TAG)

docker pull jmalloc/echo-server:latest
Expand All @@ -122,6 +123,7 @@ ifeq ($(E2E_SKIP_BUILD), 0)
docker push $(REGISTRY)/etcd:$(IMAGE_TAG)
docker push $(REGISTRY)/httpbin:$(IMAGE_TAG)
docker push $(REGISTRY)/test-backend:$(IMAGE_TAG)
docker push $(REGISTRY)/test-timeout:$(IMAGE_TAG)
docker push $(REGISTRY)/apisix-ingress-controller:$(IMAGE_TAG)
docker push $(REGISTRY)/echo-server:$(IMAGE_TAG)
docker push $(REGISTRY)/busybox:$(IMAGE_TAG)
Expand Down Expand Up @@ -305,6 +307,7 @@ kind-load-images:
$(REGISTRY)/apisix-ingress-controller:dev \
$(REGISTRY)/httpbin:dev \
$(REGISTRY)/test-backend:dev \
$(REGISTRY)/test-timeout:dev \
$(REGISTRY)/echo-server:dev \
$(REGISTRY)/busybox:dev

Expand Down
60 changes: 60 additions & 0 deletions docs/en/latest/concepts/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,63 @@ spec:
port:
number: 80
```

## Upstream retries

This annotation can be used to configure retries among multiple nodes in an upstream. You may want the proxy to retry when requests occur faults like transient network errors or service unavailable, By default the retry count is 1. You can change it by specifying the retries field.

The following configuration configures the retries to 3, which indicates there'll be at most 3 requests sent to Kubernetes service httpbin's endpoints.

One should bear in mind that passing a request to the next endpoint is only possible if nothing has been sent to a client yet. That is, if an error or timeout occurs in the middle of the transferring of a response, fixing this is impossible.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
k8s.apisix.apache.org/upstream-retries: "3"
name: ingress-ext-v1beta1
spec:
ingressClassName: apisix
rules:
- host: httpbin.org
http:
paths:
- path: /ip
pathType: Exact
backend:
service:
name: httpbin
port:
number: 80
```

## Upstream timeout

This annotation can be used to configure different types of timeout on an upstream. The default connect, read and send timeout are 60s, which might not be proper for some applications.

The below example sets the read, connect and send timeout to 5s, 10s, 10s respectively.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
k8s.apisix.apache.org/upstream-read-timeout.: "5s"
k8s.apisix.apache.org/upstream-connect-timeout: "10s"
k8s.apisix.apache.org/upstream-send-timeout: "10s"
name: ingress-ext-v1beta1
spec:
ingressClassName: apisix
rules:
- host: httpbin.org
http:
paths:
- path: /ip
pathType: Exact
backend:
service:
name: httpbin
port:
number: 80
```
6 changes: 3 additions & 3 deletions pkg/providers/ingress/translation/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations/plugins"
"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations/regex"
"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations/servicenamespace"
"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations/upstreamscheme"
"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations/upstream"
"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations/websocket"
apisix "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)
Expand All @@ -36,7 +36,7 @@ type Ingress struct {
EnableWebSocket bool
PluginConfigName string
ServiceNamespace string
UpstreamScheme string
Upstream upstream.Upstream
}

var (
Expand All @@ -46,7 +46,7 @@ var (
"EnableWebSocket": websocket.NewParser(),
"PluginConfigName": pluginconfig.NewParser(),
"ServiceNamespace": servicenamespace.NewParser(),
"UpstreamScheme": upstreamscheme.NewParser(),
"Upstream": upstream.NewParser(),
}
)

Expand Down
6 changes: 6 additions & 0 deletions pkg/providers/ingress/translation/annotations/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ const (
AnnotationsEnableWebSocket = AnnotationsPrefix + "enable-websocket"
AnnotationsPluginConfigName = AnnotationsPrefix + "plugin-config-name"
AnnotationsUpstreamScheme = AnnotationsPrefix + "upstream-scheme"

//support retries and timeouts on upstream
AnnotationsUpstreamRetry = AnnotationsPrefix + "upstream-retries"
AnnotationsUpstreamTimeoutConnect = AnnotationsPrefix + "upstream-connect-timeout"
AnnotationsUpstreamTimeoutRead = AnnotationsPrefix + "upstream-read-timeout"
AnnotationsUpstreamTimeoutSend = AnnotationsPrefix + "upstream-send-timeout"
)

const (
Expand Down
89 changes: 89 additions & 0 deletions pkg/providers/ingress/translation/annotations/upstream/upstream.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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 upstream

import (
"fmt"
"strconv"
"strings"

"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations"
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

func NewParser() annotations.IngressAnnotationsParser {
return &Upstream{}
}

type Upstream struct {
Scheme string
Retry int
TimeoutRead int
TimeoutConnect int
TimeoutSend int
}

func (u *Upstream) Parse(e annotations.Extractor) (interface{}, error) {
scheme := strings.ToLower(e.GetStringAnnotation(annotations.AnnotationsUpstreamScheme))
if scheme != "" {
_, ok := apisixv1.ValidSchemes[scheme]
if !ok {
keys := make([]string, 0, len(apisixv1.ValidSchemes))
for key := range apisixv1.ValidSchemes {
keys = append(keys, key)
}
return nil, fmt.Errorf("scheme %s is not supported, Only { %s } are supported", scheme, strings.Join(keys, ", "))
}
u.Scheme = scheme
}

retry := e.GetStringAnnotation(annotations.AnnotationsUpstreamRetry)
if retry != "" {
t, err := strconv.Atoi(retry)
if err != nil {
return nil, fmt.Errorf("could not parse retry as an integer: %s", err.Error())
}
u.Retry = t
}

timeoutConnect := strings.TrimSuffix(e.GetStringAnnotation(annotations.AnnotationsUpstreamTimeoutConnect), "s")
if timeoutConnect != "" {
t, err := strconv.Atoi(timeoutConnect)
if err != nil {
return nil, fmt.Errorf("could not parse timeout as an integer: %s", err.Error())
}
u.TimeoutConnect = t
}

timeoutRead := strings.TrimSuffix(e.GetStringAnnotation(annotations.AnnotationsUpstreamTimeoutRead), "s")
if timeoutRead != "" {
t, err := strconv.Atoi(timeoutRead)
if err != nil {
return nil, fmt.Errorf("could not parse timeout as an integer: %s", err.Error())
}
u.TimeoutRead = t
}

timeoutSend := strings.TrimSuffix(e.GetStringAnnotation(annotations.AnnotationsUpstreamTimeoutSend), "s")
if timeoutSend != "" {
t, err := strconv.Atoi(timeoutSend)
if err != nil {
return nil, fmt.Errorf("could not parse timeout as an integer: %s", err.Error())
}
u.TimeoutSend = t
}

return *u, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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 upstream_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations"
"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations/upstream"
)

func TestIPRestrictionHandler(t *testing.T) {
anno := map[string]string{
annotations.AnnotationsUpstreamScheme: "grpcs",
}
u := upstream.NewParser()

out, err := u.Parse(annotations.NewExtractor(anno))
ups, ok := out.(upstream.Upstream)
if !ok {
t.Fatalf("could not parse upstream")
}
assert.Nil(t, err, "checking given error")
assert.Equal(t, "grpcs", ups.Scheme)

anno[annotations.AnnotationsUpstreamScheme] = "gRPC"
out, err = u.Parse(annotations.NewExtractor(anno))
ups, ok = out.(upstream.Upstream)
if !ok {
t.Fatalf("could not parse upstream")
}
assert.Nil(t, err, "checking given error")
assert.Equal(t, "grpc", ups.Scheme)

anno[annotations.AnnotationsUpstreamScheme] = "nothing"
out, err = u.Parse(annotations.NewExtractor(anno))
assert.NotNil(t, err, "checking given error")
assert.Nil(t, out, "checking given output")
}

func TestRetryParsing(t *testing.T) {
anno := map[string]string{
annotations.AnnotationsUpstreamRetry: "2",
}
u := upstream.NewParser()
out, err := u.Parse(annotations.NewExtractor(anno))
if err != nil {
t.Fatalf(err.Error())
}
ups, ok := out.(upstream.Upstream)
if !ok {
t.Fatalf("could not parse upstream")
}
assert.Nil(t, err, "checking given error")
assert.Equal(t, 2, ups.Retry)

anno[annotations.AnnotationsUpstreamRetry] = "asdf"
out, err = u.Parse(annotations.NewExtractor(anno))
assert.NotNil(t, err, "checking given error")
}

func TestTimeoutParsing(t *testing.T) {
anno := map[string]string{
annotations.AnnotationsUpstreamTimeoutConnect: "2s",
annotations.AnnotationsUpstreamTimeoutRead: "3s",
annotations.AnnotationsUpstreamTimeoutSend: "4s",
}
u := upstream.NewParser()
out, err := u.Parse(annotations.NewExtractor(anno))
if err != nil {
t.Fatalf(err.Error())
}
ups, ok := out.(upstream.Upstream)
if !ok {
t.Fatalf("could not parse upstream")
}
assert.Nil(t, err, "checking given error")
assert.Equal(t, 2, ups.TimeoutConnect)
assert.Equal(t, 3, ups.TimeoutRead)
assert.Equal(t, 4, ups.TimeoutSend)
anno[annotations.AnnotationsUpstreamRetry] = "asdf"
out, err = u.Parse(annotations.NewExtractor(anno))
assert.NotNil(t, err, "checking given error")
}

This file was deleted.

Loading

0 comments on commit aec4d5a

Please sign in to comment.