Skip to content

Commit

Permalink
feat: add plugin_config_namespace parameter to ApisixRoute (apache#2137)
Browse files Browse the repository at this point in the history
* feat: add plugin_config_namespace parameter to ApisixRoute

Add plugin_config_namespace parameter to ApisixRoute resource to allow cross namespace discovery.

* fix indentation

Signed-off-by: Ashish Tiwari <[email protected]>

* remove route.yaml

Signed-off-by: Ashish Tiwari <[email protected]>

* fix e2e test

Signed-off-by: Ashish Tiwari <[email protected]>

* update gomod gosum

* fix e2e test

Signed-off-by: Ashish Tiwari <[email protected]>

* fix e2e test

Signed-off-by: Ashish Tiwari <[email protected]>

* Update pkg/providers/apisix/apisix_route.go

Co-authored-by: Gallardot <[email protected]>

* create namespace

* refactor test

* refactor test

* fix e2e

* fix e2e

* update crd

* Add EOL

Signed-off-by: Ashish Tiwari <[email protected]>

---------

Signed-off-by: Ashish Tiwari <[email protected]>
Co-authored-by: Gallardot <[email protected]>
  • Loading branch information
Revolyssup and Gallardot authored Jan 18, 2024
1 parent e94edee commit 96510b6
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/en/latest/references/apisix_route_v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ The table below describes each of the attributes in the spec. The fields `apiVer
| http[].match.exprs[].set | array | Set to compare the subject with. Only used when the operator is `In` or `NotIn`. Can use either this or `http[].match.exprs[].value`. |
| http[].websocket | boolean | When set to `true` enables websocket proxy. |
| http[].plugin_config_name | string | Existing Plugin Config name to use in the Route. |
| http[].plugin_config_namespace | string | Namespace in which to look for `plugin_config_name` Route. |
| http[].backends | object | List of backend services. If there are more than one, a weight based traffic split policy would be applied. |
| http[].backends[].serviceName | string | Name of the backend service. The service and the `ApisixRoute` resource should be created in the same namespace. |
| http[].backends[].servicePort | integer or string | Port number or the name defined in the service object of the backend. |
Expand Down
10 changes: 6 additions & 4 deletions pkg/kube/apisix/apis/config/v2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ type ApisixRouteHTTP struct {
// Upstreams refer to ApisixUpstream CRD
Upstreams []ApisixRouteUpstreamReference `json:"upstreams,omitempty" yaml:"upstreams,omitempty"`

Websocket bool `json:"websocket" yaml:"websocket"`
PluginConfigName string `json:"plugin_config_name,omitempty" yaml:"plugin_config_name,omitempty"`
Plugins []ApisixRoutePlugin `json:"plugins,omitempty" yaml:"plugins,omitempty"`
Authentication ApisixRouteAuthentication `json:"authentication,omitempty" yaml:"authentication,omitempty"`
Websocket bool `json:"websocket" yaml:"websocket"`
PluginConfigName string `json:"plugin_config_name,omitempty" yaml:"plugin_config_name,omitempty"`
//By default, PluginConfigNamespace will be the same as the namespace of ApisixRoute
PluginConfigNamespace string `json:"plugin_config_namespace,omitempty" yaml:"plugin_config_namespace,omitempty"`
Plugins []ApisixRoutePlugin `json:"plugins,omitempty" yaml:"plugins,omitempty"`
Authentication ApisixRouteAuthentication `json:"authentication,omitempty" yaml:"authentication,omitempty"`
}

// ApisixRouteHTTPBackend represents an HTTP backend (a Kubernetes Service).
Expand Down
10 changes: 7 additions & 3 deletions pkg/providers/apisix/apisix_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,16 +397,20 @@ updateStatus:
func (c *apisixRouteController) checkPluginNameIfNotEmptyV2(ctx context.Context, in *v2.ApisixRoute) error {
for _, v := range in.Spec.HTTP {
if v.PluginConfigName != "" {
_, err := c.APISIX.Cluster(c.Config.APISIX.DefaultClusterName).PluginConfig().Get(ctx, apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName))
ns := in.Namespace
if v.PluginConfigNamespace != "" {
ns = v.PluginConfigNamespace
}
_, err := c.APISIX.Cluster(c.Config.APISIX.DefaultClusterName).PluginConfig().Get(ctx, apisixv1.ComposePluginConfigName(ns, v.PluginConfigName))
if err != nil {
if err == apisixcache.ErrNotFound {
log.Errorw("checkPluginNameIfNotEmptyV2 error: plugin_config not found",
zap.String("name", apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName)),
zap.String("name", apisixv1.ComposePluginConfigName(ns, v.PluginConfigName)),
zap.Any("obj", in),
zap.Error(err))
} else {
log.Errorw("checkPluginNameIfNotEmptyV2 PluginConfig get failed",
zap.String("name", apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName)),
zap.String("name", apisixv1.ComposePluginConfigName(ns, v.PluginConfigName)),
zap.Any("obj", in),
zap.Error(err))
}
Expand Down
12 changes: 10 additions & 2 deletions pkg/providers/apisix/translation/apisix_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,11 @@ func (t *translator) translateHTTPRouteV2(ctx *translation.TranslateContext, ar
route.FilterFunc = part.Match.FilterFunc

if part.PluginConfigName != "" {
route.PluginConfigId = id.GenID(apisixv1.ComposePluginConfigName(ar.Namespace, part.PluginConfigName))
ns := ar.Namespace
if part.PluginConfigNamespace != "" {
ns = part.PluginConfigNamespace
}
route.PluginConfigId = id.GenID(apisixv1.ComposePluginConfigName(ns, part.PluginConfigName))
}

for k, v := range ar.ObjectMeta.Labels {
Expand Down Expand Up @@ -465,7 +469,11 @@ func (t *translator) generateHTTPRouteV2DeleteMark(ctx *translation.TranslateCon
route.Name = apisixv1.ComposeRouteName(ar.Namespace, ar.Name, part.Name)
route.ID = id.GenID(route.Name)
if part.PluginConfigName != "" {
route.PluginConfigId = id.GenID(apisixv1.ComposePluginConfigName(ar.Namespace, part.PluginConfigName))
ns := ar.Namespace
if part.PluginConfigNamespace != "" {
ns = part.PluginConfigNamespace
}
route.PluginConfigId = id.GenID(apisixv1.ComposePluginConfigName(ns, part.PluginConfigName))
}

ctx.AddRoute(route)
Expand Down
40 changes: 40 additions & 0 deletions pkg/providers/apisix/translation/apisix_route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,46 @@ func TestTranslateApisixRouteV2WithEmptyPluginConfigName(t *testing.T) {
assert.Equal(t, "", res.Routes[2].PluginConfigId)
}

func TestTranslateApisixRouteV2WithPluginConfigNamespace(t *testing.T) {
tr, processCh := mockTranslatorV2(t)
<-processCh
<-processCh
pluginConfigNamespace := "test-2"
ar := &configv2.ApisixRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "ar",
Namespace: "test",
},
Spec: configv2.ApisixRouteSpec{
HTTP: []configv2.ApisixRouteHTTP{
{
Name: "rule1",
Match: configv2.ApisixRouteHTTPMatch{
Paths: []string{
"/*",
},
},
Backends: []configv2.ApisixRouteHTTPBackend{
{
ServiceName: "svc",
ServicePort: intstr.IntOrString{
IntVal: 80,
},
},
},
PluginConfigName: "test-PluginConfigName-1",
PluginConfigNamespace: pluginConfigNamespace,
},
},
},
}
res, err := tr.TranslateRouteV2(ar)
assert.NoError(t, err)
assert.Len(t, res.PluginConfigs, 0)
expectedPluginId := id.GenID(apisixv1.ComposePluginConfigName(pluginConfigNamespace, ar.Spec.HTTP[0].PluginConfigName))
assert.Equal(t, expectedPluginId, res.Routes[0].PluginConfigId)
}

func TestGenerateApisixRouteV2DeleteMark(t *testing.T) {
tr := &translator{
&TranslatorOptions{},
Expand Down
3 changes: 3 additions & 0 deletions samples/deploy/crd/v1/ApisixRoute.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ spec:
plugin_config_name:
type: string
minLength: 1
plugin_config_namespace:
type: string
minLength: 1
upstreams:
description: Upstreams refer to ApisixUpstream CRD
type: array
Expand Down
90 changes: 90 additions & 0 deletions test/e2e/suite-plugins/suite-plugins-other/plugin_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,3 +594,93 @@ spec:
resp.Status(http.StatusOK)
})
})

var _ = ginkgo.Describe("suite-plugins-other: ApisixPluginConfig cross namespace", func() {
s := scaffold.NewScaffold(&scaffold.Options{
NamespaceSelectorLabel: map[string][]string{
"apisix.ingress.watch": {"test"},
},
})
ginkgo.It("ApisixPluginConfig cross namespace", func() {
testns := `
apiVersion: v1
kind: Namespace
metadata:
name: test
labels:
apisix.ingress.watch: test
`
err := s.CreateResourceFromString(testns)
assert.Nil(ginkgo.GinkgoT(), err, "Creating test namespace")
backendSvc, backendPorts := s.DefaultHTTPBackend()
apc := fmt.Sprintf(`
apiVersion: apisix.apache.org/v2
kind: ApisixPluginConfig
metadata:
name: echo-and-cors-apc
namespace: test
spec:
plugins:
- name: echo
enable: true
config:
before_body: "This is the preface"
after_body: "This is the epilogue"
headers:
X-Foo: v1
X-Foo2: v2
- name: cors
enable: true
`)
assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromStringWithNamespace(apc, "test"))

err = s.EnsureNumApisixPluginConfigCreated(1)
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of pluginConfigs")

time.Sleep(time.Second * 3)

ar := fmt.Sprintf(`
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: httpbin-route
spec:
http:
- name: rule1
match:
hosts:
- httpbin.org
paths:
- /ip
backends:
- serviceName: %s
servicePort: %d
weight: 10
plugin_config_name: echo-and-cors-apc
plugin_config_namespace: test
`, backendSvc, backendPorts[0])
assert.Nil(ginkgo.GinkgoT(), s.CreateVersionedApisixResource(ar))

err = s.EnsureNumApisixRoutesCreated(1)
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")

time.Sleep(3 * time.Second)
pcs, err := s.ListApisixPluginConfig()
assert.Nil(ginkgo.GinkgoT(), err, nil, "listing pluginConfigs")
assert.Len(ginkgo.GinkgoT(), pcs, 1)
assert.Len(ginkgo.GinkgoT(), pcs[0].Plugins, 2)

resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", "httpbin.org").Expect()
resp.Status(http.StatusOK)
resp.Header("X-Foo").Equal("v1")
resp.Header("X-Foo2").Equal("v2")
resp.Header("Access-Control-Allow-Origin").Equal("*")
resp.Header("Access-Control-Allow-Methods").Equal("*")
resp.Header("Access-Control-Allow-Headers").Equal("*")
resp.Header("Access-Control-Expose-Headers").Equal("*")
resp.Header("Access-Control-Max-Age").Equal("5")
resp.Body().Contains("This is the preface")
resp.Body().Contains("origin")
resp.Body().Contains("This is the epilogue")
})
})

0 comments on commit 96510b6

Please sign in to comment.