Skip to content

Commit

Permalink
add routePatch plugin (#769)
Browse files Browse the repository at this point in the history
Signed-off-by: spacewander <[email protected]>
  • Loading branch information
spacewander authored Oct 17, 2024
1 parent 88fb116 commit 8ded9c5
Show file tree
Hide file tree
Showing 17 changed files with 323 additions and 28 deletions.
28 changes: 24 additions & 4 deletions controller/internal/istio/envoyfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,28 @@ func GenerateRouteFilter(host *model.VirtualHost, route string, config map[strin
},
}

routeConfig := map[string]interface{}{}
routeFilters, _ := config[model.CategoryRouteFilter].(map[string]*fmModel.FilterConfig)
extraRouteConfig, _ := config[model.CategoryRoute].(map[string]*fmModel.FilterConfig)
if routeFilters == nil || extraRouteConfig == nil {
// bug in the code
panic("route filter and route config must be provided")
}

if len(routeFilters) > 0 {
plainCfg := map[string]interface{}{}
for k, v := range routeFilters {
plainCfg[k] = v.Config
}
routeConfig["typed_per_filter_config"] = plainCfg
}
for _, filter := range extraRouteConfig {
fields, _ := filter.Config.(map[string]interface{})
for k, v := range fields {
routeConfig[k] = v
}
}

return &istiov1a3.EnvoyFilter{
// We don't set ObjectMeta here because this EnvoyFilter will be merged later
Spec: istioapi.EnvoyFilter{
Expand All @@ -199,9 +221,7 @@ func GenerateRouteFilter(host *model.VirtualHost, route string, config map[strin
},
Patch: &istioapi.EnvoyFilter_Patch{
Operation: istioapi.EnvoyFilter_Patch_MERGE,
Value: MustNewStruct(map[string]interface{}{
"typed_per_filter_config": config,
}),
Value: MustNewStruct(routeConfig),
},
},
},
Expand Down Expand Up @@ -334,7 +354,7 @@ func GenerateLDSFilter(key string, ldsName string, hasHCM bool, config map[strin
if cfg == nil {
cfg = map[string]interface{}{}
}
ecdsName := key + "-" + model.CategoryGolangPlugins
ecdsName := key + "-" + model.ECDSGolangPlugins
ef.Spec.ConfigPatches = append(ef.Spec.ConfigPatches,
&istioapi.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: istioapi.EnvoyFilter_HTTP_FILTER,
Expand Down
15 changes: 10 additions & 5 deletions controller/internal/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,14 @@ type VirtualHost struct {
}

const (
CategoryECDSGolang = "ecds_golang"
CategoryECDSListener = "ecds_listener"
CategoryECDSNetwork = "ecds_network"
CategoryListener = "listener"
CategoryGolangPlugins = "golang-filter"
CategoryECDSGolang = "ecds_golang"
CategoryECDSListener = "ecds_listener"
CategoryECDSNetwork = "ecds_network"
CategoryListener = "listener"

CategoryRoute = "route"
CategoryRouteFilter = "route_filter"

// This constant is used in the resource name which doesn't support '_' in the name
ECDSGolangPlugins = "golang-filter"
)
42 changes: 26 additions & 16 deletions controller/internal/translation/merged_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,11 @@ const (
func translateFilterManagerConfigToPolicyInRDS(fmc *filtermanager.FilterManagerConfig,
nsName *types.NamespacedName, virtualHost *model.VirtualHost) map[string]interface{} {

config := map[string]interface{}{}
nativeFilters := map[string]map[string]*fmModel.FilterConfig{
model.CategoryRoute: {},
model.CategoryRouteFilter: {},
}

nativeFilters := []*fmModel.FilterConfig{}
goFilterManager := &filtermanager.FilterManagerConfig{
Plugins: []*fmModel.FilterConfig{},
}
Expand Down Expand Up @@ -143,9 +145,14 @@ func translateFilterManagerConfigToPolicyInRDS(fmc *filtermanager.FilterManagerC
m = wrapper.ToRouteConfig(m)
}

m["@type"] = url
plugin.Config = m
nativeFilters = append(nativeFilters, plugin)
if url != "" {
filterName := fmt.Sprintf("htnn.filters.http.%s", plugin.Name)
m["@type"] = url
nativeFilters[model.CategoryRouteFilter][filterName] = plugin
} else {
nativeFilters[model.CategoryRoute][name] = plugin
}
}

_, ok = p.(plugins.ConsumerPlugin)
Expand Down Expand Up @@ -173,26 +180,29 @@ func translateFilterManagerConfigToPolicyInRDS(fmc *filtermanager.FilterManagerC

golangFilterName := "htnn.filters.http.golang"
if ctrlcfg.EnableLDSPluginViaECDS() {
golangFilterName = virtualHost.ECDSResourceName + "-" + model.CategoryGolangPlugins
golangFilterName = virtualHost.ECDSResourceName + "-" + model.ECDSGolangPlugins
}
config[golangFilterName] = map[string]interface{}{
"@type": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.ConfigsPerRoute",
"plugins_config": map[string]interface{}{
"fm": map[string]interface{}{
"config": map[string]interface{}{
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"value": v,
golangFilterPlugin := &fmModel.FilterConfig{
Config: map[string]interface{}{
"@type": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.ConfigsPerRoute",
"plugins_config": map[string]interface{}{
"fm": map[string]interface{}{
"config": map[string]interface{}{
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
"value": v,
},
},
},
},
}
nativeFilters[model.CategoryRouteFilter][golangFilterName] = golangFilterPlugin
}

for _, filter := range nativeFilters {
name := fmt.Sprintf("htnn.filters.http.%s", filter.Name)
config[name] = filter.Config
// satisfy the requirement of the returned type
config := map[string]interface{}{}
for k, v := range nativeFilters {
config[k] = v
}

return config
}

Expand Down
1 change: 1 addition & 0 deletions controller/plugins/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
_ "mosn.io/htnn/controller/plugins/localratelimit"
_ "mosn.io/htnn/controller/plugins/lua"
_ "mosn.io/htnn/controller/plugins/networkrbac"
_ "mosn.io/htnn/controller/plugins/routepatch"
_ "mosn.io/htnn/controller/plugins/tlsinspector"
_ "mosn.io/htnn/types/plugins"
)
32 changes: 32 additions & 0 deletions controller/plugins/routepatch/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright The HTNN 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 routepatch

import (
"mosn.io/htnn/api/pkg/plugins"
"mosn.io/htnn/types/plugins/routepatch"
)

func init() {
plugins.RegisterPlugin(routepatch.Name, &plugin{})
}

type plugin struct {
routepatch.Plugin
}

func (p *plugin) ConfigTypeURL() string {
return ""
}
15 changes: 15 additions & 0 deletions controller/plugins/testdata/http/route_patch.in.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: htnn.mosn.io/v1
kind: FilterPolicy
metadata:
name: policy
namespace: default
spec:
targetRef:
group: networking.istio.io
kind: VirtualService
name: default
filters:
routePatch:
config:
route:
cluster_header: "Cluster-Name"
26 changes: 26 additions & 0 deletions controller/plugins/testdata/http/route_patch.out.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
- metadata:
creationTimestamp: null
name: htnn-h-default.local
namespace: default
spec:
configPatches:
- applyTo: HTTP_ROUTE
match:
routeConfiguration:
vhost:
name: default.local:80
route:
name: default/default
patch:
operation: MERGE
value:
route:
cluster_header: Cluster-Name
status: {}
- metadata:
creationTimestamp: null
name: htnn-http-filter
namespace: istio-system
spec:
priority: -10
status: {}
2 changes: 2 additions & 0 deletions e2e/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ delete-cluster: kind
.PHONY: e2e-prepare-controller-image
e2e-prepare-controller-image: kind
cd ../manifests/ && CONTROLLER_IMAGE=htnn/controller:e2e CONTROLLER_BASE_IMAGE=$(DOCKER_MIRROR)docker.io/istio/pilot:$(ISTIO_VERSION) \
GO_BUILD_BASE_IMAGE=$(DOCKER_MIRROR)docker.io/golang:1.21 \
make build-controller-image
$(KIND) load docker-image -n htnn htnn/controller:e2e

.PHONY: e2e-prepare-data-plane-image
e2e-prepare-data-plane-image: kind
cd ../manifests/ && PROXY_BASE_IMAGE=$(DOCKER_MIRROR)docker.io/istio/proxyv2:$(ISTIO_VERSION) \
GO_BUILD_BASE_IMAGE=$(DOCKER_MIRROR)docker.io/golang:1.21 \
PROXY_IMAGE=htnn/gateway:e2e make build-proxy-image
$(KIND) load docker-image htnn/gateway:e2e --name htnn

Expand Down
2 changes: 1 addition & 1 deletion e2e/base/nacos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ spec:
spec:
containers:
- name: nacos
image: nacos/nacos-server:v1.4.6-slim
image: m.daocloud.io/docker.io/nacos/nacos-server:v1.4.6-slim
ports:
- containerPort: 8848
env:
Expand Down
1 change: 1 addition & 0 deletions e2e/pkg/suite/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func (suite *Suite) waitDeployments(t *testing.T) {
} {
cmdline := fmt.Sprintf("kubectl wait --timeout=5m -n %s deployment/%s --for=condition=Available",
cond.ns, cond.name)
t.Logf("start waiting for deployment %s in namespace %s, cmd: %s", cond.name, cond.ns, cmdline)
cmd := strings.Fields(cmdline)
wait := exec.Command(cmd[0], cmd[1:]...)
err := wait.Run()
Expand Down
47 changes: 47 additions & 0 deletions e2e/tests/route_patch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright The HTNN 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 tests

import (
"context"
"net"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/require"

"mosn.io/htnn/e2e/pkg/suite"
)

func init() {
suite.Register(suite.Test{
Run: func(t *testing.T, suite *suite.Suite) {
tr := &http.Transport{DialContext: func(ctx context.Context, proto, addr string) (conn net.Conn, err error) {
return net.DialTimeout("tcp", ":18000", 1*time.Second)
}}
client := &http.Client{Transport: tr, Timeout: 10 * time.Second}
rsp, err := client.Get("http://default.local:18000/echo")
require.NoError(t, err)
require.Equal(t, 403, rsp.StatusCode)
rsp, err = client.Get("http://default.local:18000/echo2")
require.NoError(t, err)
require.Equal(t, 403, rsp.StatusCode)
rsp, err = client.Get("http://default.local:18000/")
require.NoError(t, err)
require.Equal(t, 405, rsp.StatusCode)
},
})
}
69 changes: 69 additions & 0 deletions e2e/tests/route_patch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: vs
namespace: istio-system
spec:
gateways:
- default
hosts:
- "default.local"
http:
- match:
- uri:
prefix: /echo
route:
- destination:
host: backend
port:
number: 8080
- match:
- uri:
prefix: /echo2
route:
- destination:
host: backend
port:
number: 8080
- match:
- uri:
prefix: /
name: last
route:
- destination:
host: backend
port:
number: 8080
---
apiVersion: htnn.mosn.io/v1
kind: FilterPolicy
metadata:
name: policy
namespace: istio-system
spec:
targetRef:
group: networking.istio.io
kind: VirtualService
name: vs
filters:
routePatch:
config:
directResponse:
status: 403
---
apiVersion: htnn.mosn.io/v1
kind: FilterPolicy
metadata:
name: policy2
namespace: istio-system
spec:
targetRef:
group: networking.istio.io
kind: VirtualService
name: vs
sectionName: last
filters:
routePatch:
config:
directResponse:
status: 405
Loading

0 comments on commit 8ded9c5

Please sign in to comment.