Skip to content

Commit 77d020e

Browse files
authored
feat: support multi backends for httproute (#48)
1 parent adad8ad commit 77d020e

File tree

11 files changed

+234
-92
lines changed

11 files changed

+234
-92
lines changed

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ ENVTEST_K8S_VERSION = 1.30.0
1212
KIND_NAME ?= api7-ingress-cluster
1313
GATEAY_API_VERSION ?= v1.1.0
1414

15-
DASHBOARD_VERSION ?= v3.2.14.2
15+
DASHBOARD_VERSION ?= v3.2.14.6
16+
TEST_TIMEOUT ?= 30m
1617

1718
# go
1819
VERSYM="github.com/api7/api7-ingress-controller/internal/version._buildVersion"
@@ -86,11 +87,11 @@ kind-e2e-test: kind-up build-image kind-load-images e2e-test
8687
# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
8788
.PHONY: e2e-test
8889
e2e-test:
89-
DASHBOARD_VERSION=$(DASHBOARD_VERSION) go test ./test/e2e/ -v -ginkgo.v
90+
DASHBOARD_VERSION=$(DASHBOARD_VERSION) go test ./test/e2e/ -test.timeout=$(TEST_TIMEOUT) -v -ginkgo.v
9091

9192
.PHONY: conformance-test
9293
conformance-test:
93-
DASHBOARD_VERSION=v3.2.14.2 go test -v ./test/conformance -tags=conformance
94+
DASHBOARD_VERSION=$(DASHBOARD_VERSION) go test -v ./test/conformance -tags=conformance
9495

9596
.PHONY: lint
9697
lint: golangci-lint ## Run golangci-lint linter

api/dashboard/v1/plugin_types.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ type TrafficSplitConfigRule struct {
4141
// the traffic split plugin rule.
4242
// +k8s:deepcopy-gen=true
4343
type TrafficSplitConfigRuleWeightedUpstream struct {
44-
UpstreamID string `json:"upstream_id,omitempty"`
45-
Weight int `json:"weight"`
44+
UpstreamID string `json:"upstream_id,omitempty"`
45+
Upstream *Upstream `json:"upstream,omitempty"`
46+
Weight int `json:"weight"`
4647
}
4748

4849
// IPRestrictConfig is the rule config for ip-restriction plugin.

api/dashboard/v1/types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ func NewDefaultService() *Service {
557557
"managed-by": "api7-ingress-controller",
558558
},
559559
},
560+
Plugins: make(Plugins),
560561
}
561562
}
562563

@@ -665,6 +666,23 @@ func ComposeServiceNameWithRule(namespace, name string, rule string) string {
665666
return buf.String()
666667
}
667668

669+
func ComposeUpstreamNameWithRule(namespace, name string, rule string) string {
670+
// FIXME Use sync.Pool to reuse this buffer if the upstream
671+
// name composing code path is hot.
672+
var p []byte
673+
plen := len(namespace) + len(name) + 2
674+
675+
p = make([]byte, 0, plen)
676+
buf := bytes.NewBuffer(p)
677+
buf.WriteString(namespace)
678+
buf.WriteByte('_')
679+
buf.WriteString(name)
680+
buf.WriteByte('_')
681+
buf.WriteString(rule)
682+
683+
return buf.String()
684+
}
685+
668686
// ComposeExternalUpstreamName uses ApisixUpstream namespace, name to compose the upstream name.
669687
func ComposeExternalUpstreamName(namespace, name string) string {
670688
return namespace + "_" + name

api/dashboard/v1/zz_generated.deepcopy.go

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/controller/config/config.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func (c *Config) validateGatewayConfig(gc *GatewayConfig) error {
108108
var gatewayNameMap map[string]*GatewayConfig
109109
var gatewayNameList []string
110110

111-
func init_gatewayNameMap() {
111+
func initGatewayNameMap() {
112112
if gatewayNameMap == nil {
113113
gatewayNameMap = make(map[string]*GatewayConfig)
114114
for _, gc := range ControllerConfig.GatewayConfigs {
@@ -118,15 +118,15 @@ func init_gatewayNameMap() {
118118
}
119119

120120
func GetControlPlaneConfigByGatewatName(gatewatName string) *ControlPlaneConfig {
121-
init_gatewayNameMap()
121+
initGatewayNameMap()
122122
if gc, ok := gatewayNameMap[gatewatName]; ok {
123123
return gc.ControlPlane
124124
}
125125
return nil
126126
}
127127

128128
func GetGatewayConfig(gatewayName string) *GatewayConfig {
129-
init_gatewayNameMap()
129+
initGatewayNameMap()
130130
if gc, ok := gatewayNameMap[gatewayName]; ok {
131131
return gc
132132
}
@@ -141,7 +141,7 @@ func GetFirstGatewayConfig() *GatewayConfig {
141141
}
142142

143143
func GetGatewayAddresses(gatewayName string) []string {
144-
init_gatewayNameMap()
144+
initGatewayNameMap()
145145
if gc, ok := gatewayNameMap[gatewayName]; ok {
146146
return gc.Addresses
147147
}

internal/controlplane/translator/httproute.go

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ const (
1818
RequestMirror = "proxy-mirror"
1919
)
2020

21-
func (t *Translator) generatePluginsFromHTTPRouteFilters(namespace string, filters []gatewayv1.HTTPRouteFilter) v1.Plugins {
22-
plugins := v1.Plugins{}
21+
func (t *Translator) fillPluginsFromHTTPRouteFilters(plugins v1.Plugins, namespace string, filters []gatewayv1.HTTPRouteFilter) {
2322
for _, filter := range filters {
2423
switch filter.Type {
2524
case gatewayv1.HTTPRouteFilterRequestHeaderModifier:
@@ -34,7 +33,6 @@ func (t *Translator) generatePluginsFromHTTPRouteFilters(namespace string, filte
3433
t.fillPluginFromHTTPResponseHeaderFilter(plugins, filter.ResponseHeaderModifier)
3534
}
3635
}
37-
return plugins
3836
}
3937

4038
func (t *Translator) fillPluginFromHTTPRequestHeaderFilter(plugins v1.Plugins, reqHeaderModifier *gatewayv1.HTTPHeaderFilter) {
@@ -158,7 +156,7 @@ func (t *Translator) fillPluginFromHTTPRequestRedirectFilter(plugins v1.Plugins,
158156
plugin.URI = uri
159157
}
160158

161-
func (t *Translator) translateEndpointSlice(endpointSlices []discoveryv1.EndpointSlice, weight int) v1.UpstreamNodes {
159+
func (t *Translator) translateEndpointSlice(endpointSlices []discoveryv1.EndpointSlice) v1.UpstreamNodes {
162160
var nodes v1.UpstreamNodes
163161
if len(endpointSlices) == 0 {
164162
return nodes
@@ -170,7 +168,7 @@ func (t *Translator) translateEndpointSlice(endpointSlices []discoveryv1.Endpoin
170168
node := v1.UpstreamNode{
171169
Host: addr,
172170
Port: int(*port.Port),
173-
Weight: weight,
171+
Weight: 1,
174172
}
175173
nodes = append(nodes, node)
176174
}
@@ -188,11 +186,7 @@ func (t *Translator) translateBackendRef(tctx *TranslateContext, ref gatewayv1.B
188186
Name: string(ref.Name),
189187
}]
190188

191-
weight := 100
192-
if ref.Weight != nil {
193-
weight = int(*ref.Weight)
194-
}
195-
upstream.Nodes = t.translateEndpointSlice(endpointSlices, weight)
189+
upstream.Nodes = t.translateEndpointSlice(endpointSlices)
196190
return upstream
197191
}
198192

@@ -207,6 +201,8 @@ func (t *Translator) TranslateGatewayHTTPRoute(tctx *TranslateContext, httpRoute
207201
rules := httpRoute.Spec.Rules
208202

209203
for i, rule := range rules {
204+
205+
var weightedUpstreams []v1.TrafficSplitConfigRuleWeightedUpstream
210206
upstreams := []*v1.Upstream{}
211207
for _, backend := range rule.BackendRefs {
212208
if backend.Namespace == nil {
@@ -226,27 +222,47 @@ func (t *Translator) TranslateGatewayHTTPRoute(tctx *TranslateContext, httpRoute
226222
},
227223
}
228224
}
225+
226+
weight := 100
227+
if backend.Weight != nil {
228+
weight = int(*backend.Weight)
229+
}
230+
weightedUpstreams = append(weightedUpstreams, v1.TrafficSplitConfigRuleWeightedUpstream{
231+
Upstream: upstream,
232+
Weight: weight,
233+
})
229234
}
230235

231-
service := v1.NewDefaultService()
232-
if len(upstreams) > 0 {
233-
service.Upstream = upstreams[0]
234-
// TODO: support multiple upstreams
235-
} else if len(upstreams) == 0 {
236-
service.Upstream = v1.NewDefaultUpstream()
237-
service.Upstream.Nodes = v1.UpstreamNodes{
236+
if len(upstreams) == 0 {
237+
upstream := v1.NewDefaultUpstream()
238+
upstream.Nodes = v1.UpstreamNodes{
238239
{
239240
Host: "0.0.0.0",
240241
Port: 80,
241242
Weight: 100,
242243
},
243244
}
245+
upstreams = append(upstreams, upstream)
244246
}
247+
248+
service := v1.NewDefaultService()
249+
service.Upstream = upstreams[0]
250+
if len(weightedUpstreams) > 1 {
251+
weightedUpstreams[0].Upstream = nil
252+
service.Plugins["traffic-split"] = &v1.TrafficSplitConfig{
253+
Rules: []v1.TrafficSplitConfigRule{
254+
{
255+
WeightedUpstreams: weightedUpstreams,
256+
},
257+
},
258+
}
259+
}
260+
245261
service.Name = v1.ComposeServiceNameWithRule(httpRoute.Namespace, httpRoute.Name, fmt.Sprintf("%d", i))
246262
service.ID = id.GenID(service.Name)
247263
service.Labels = label.GenLabel(httpRoute)
248264
service.Hosts = hosts
249-
service.Plugins = t.generatePluginsFromHTTPRouteFilters(httpRoute.GetNamespace(), rule.Filters)
265+
t.fillPluginsFromHTTPRouteFilters(service.Plugins, httpRoute.GetNamespace(), rule.Filters)
250266

251267
result.Services = append(result.Services, service)
252268

pkg/dashboard/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ func (u *serviceClient) Delete(ctx context.Context, obj *v1.Service) error {
165165

166166
func (u *serviceClient) Update(ctx context.Context, obj *v1.Service) (*v1.Service, error) {
167167
url := u.url + "/" + obj.ID
168+
log.Debugw("try to update service", zap.Any("service", obj), zap.String("url", url))
168169
return updateResource(
169170
ctx,
170171
obj,

test/e2e/framework/manifests/nginx.yaml

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,42 +19,8 @@ data:
1919
return 200 'Hello, World!';
2020
}
2121
}
22-
23-
server {
24-
listen 8080 default_server;
25-
26-
location / {
27-
return 500 'Internal Server Error!';
28-
}
29-
30-
}
3122
}
3223
33-
34-
stream {
35-
server {
36-
listen 443 reuseport ssl;
37-
ssl_certificate /etc/nginx/ssl/tls.crt;
38-
ssl_certificate_key /etc/nginx/ssl/tls.key;
39-
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
40-
ssl_ciphers HIGH:!aNULL:!MD5;
41-
42-
proxy_pass 127.0.0.1:80;
43-
}
44-
45-
server {
46-
listen 8443 reuseport ssl;
47-
ssl_certificate /etc/nginx/ssl/tls.crt;
48-
ssl_certificate_key /etc/nginx/ssl/tls.key;
49-
ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
50-
ssl_verify_client on;
51-
ssl_client_certificate /etc/nginx/ssl/tls.crt;
52-
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
53-
ssl_ciphers HIGH:!aNULL:!MD5;
54-
55-
proxy_pass 127.0.0.1:80;
56-
}
57-
}
5824
---
5925
apiVersion: apps/v1
6026
kind: Deployment
@@ -71,9 +37,6 @@ spec:
7137
app: nginx
7238
spec:
7339
volumes:
74-
- name: nginx-ssl
75-
secret:
76-
secretName: nginx-ssl
7740
- name: nginx-config
7841
configMap:
7942
name: nginx-config
@@ -91,21 +54,10 @@ spec:
9154
imagePullPolicy: IfNotPresent
9255
name: nginx
9356
ports:
94-
- containerPort: 443
95-
name: "https"
96-
protocol: "TCP"
97-
- containerPort: 8443
98-
name: "mtls"
99-
protocol: "TCP"
100-
- containerPort: 8080
101-
name: "http1"
102-
protocol: "TCP"
10357
- containerPort: 80
10458
name: "http"
10559
protocol: "TCP"
10660
volumeMounts:
107-
- mountPath: /etc/nginx/ssl
108-
name: nginx-ssl
10961
- mountPath: /etc/nginx/nginx.conf
11062
name: nginx-config
11163
subPath: nginx.conf
@@ -122,16 +74,4 @@ spec:
12274
port: 80
12375
protocol: TCP
12476
targetPort: 80
125-
- name: http1
126-
port: 8080
127-
protocol: TCP
128-
targetPort: 8080
129-
- name: https
130-
port: 443
131-
protocol: TCP
132-
targetPort: 443
133-
- name: mtls
134-
port: 8443
135-
protocol: TCP
136-
targetPort: 8443
13777
type: ClusterIP

test/e2e/framework/nginx.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package framework
2+
3+
import (
4+
"bytes"
5+
_ "embed"
6+
"text/template"
7+
8+
"github.com/Masterminds/sprig/v3"
9+
"github.com/gruntwork-io/terratest/modules/k8s"
10+
. "github.com/onsi/gomega"
11+
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
)
14+
15+
var (
16+
//go:embed manifests/nginx.yaml
17+
_ngxSpec string
18+
ngxSpecTpl *template.Template
19+
)
20+
21+
type NginxOptions struct {
22+
Namespace string
23+
}
24+
25+
func init() {
26+
tpl, err := template.New("ngx").Funcs(sprig.TxtFuncMap()).Parse(_ngxSpec)
27+
if err != nil {
28+
panic(err)
29+
}
30+
ngxSpecTpl = tpl
31+
}
32+
33+
func (f *Framework) DeployNginx(opts NginxOptions) *corev1.Service {
34+
buf := bytes.NewBuffer(nil)
35+
36+
err := ngxSpecTpl.Execute(buf, opts)
37+
f.GomegaT.Expect(err).ToNot(HaveOccurred(), "rendering nginx spec")
38+
39+
kubectlOpts := k8s.NewKubectlOptions("", "", opts.Namespace)
40+
41+
k8s.KubectlApplyFromString(f.GinkgoT, kubectlOpts, buf.String())
42+
43+
err = WaitPodsAvailable(f.GinkgoT, kubectlOpts, metav1.ListOptions{
44+
LabelSelector: "app=nginx",
45+
})
46+
Expect(err).ToNot(HaveOccurred(), "waiting for nginx pod ready")
47+
48+
return k8s.GetService(f.GinkgoT, kubectlOpts, "nginx")
49+
}

0 commit comments

Comments
 (0)