mirror of
https://github.com/alibaba/higress.git
synced 2026-03-16 07:00:48 +08:00
1057 lines
26 KiB
Go
1057 lines
26 KiB
Go
// Copyright Istio 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.
|
|
|
|
// Updated based on Istio codebase by Higress
|
|
|
|
package istio
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"istio.io/istio/pilot/pkg/config/kube/crd"
|
|
credentials "istio.io/istio/pilot/pkg/credentials/kube"
|
|
"istio.io/istio/pilot/pkg/features"
|
|
"istio.io/istio/pilot/pkg/model"
|
|
"istio.io/istio/pilot/pkg/model/kstatus"
|
|
"istio.io/istio/pilot/pkg/networking/core/v1alpha3"
|
|
"istio.io/istio/pilot/test/util"
|
|
"istio.io/istio/pkg/cluster"
|
|
"istio.io/istio/pkg/config"
|
|
crdvalidation "istio.io/istio/pkg/config/crd"
|
|
"istio.io/istio/pkg/config/schema/gvk"
|
|
"istio.io/istio/pkg/kube"
|
|
"istio.io/istio/pkg/test"
|
|
"istio.io/istio/pkg/test/util/assert"
|
|
"istio.io/istio/pkg/util/sets"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
k8s "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
// Start - Updated by Higress
|
|
var ports = []corev1.ServicePort{
|
|
{
|
|
Name: "http",
|
|
Port: 80,
|
|
Protocol: "HTTP",
|
|
},
|
|
{
|
|
Name: "tcp",
|
|
Port: 34000,
|
|
Protocol: "TCP",
|
|
},
|
|
}
|
|
|
|
var defaultGatewaySelector = map[string]string{
|
|
"higress": "higress-system-higress-gateway",
|
|
}
|
|
|
|
var services = []corev1.Service{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "higress-gateway",
|
|
Namespace: "higress-system",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
ExternalIPs: []string{"1.2.3.4"},
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example.com",
|
|
Namespace: "higress-system",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-apple",
|
|
Namespace: "apple",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-banana",
|
|
Namespace: "banana",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-second",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-wildcard",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo-svc",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-other",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "echo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin",
|
|
Namespace: "cert",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "my-svc",
|
|
Namespace: "service",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "google.com",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc2",
|
|
Namespace: "allowed-1",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc2",
|
|
Namespace: "allowed-2",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc1",
|
|
Namespace: "allowed-1",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc3",
|
|
Namespace: "allowed-2",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "svc4",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin",
|
|
Namespace: "group-namespace1",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin",
|
|
Namespace: "group-namespace2",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-zero",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin",
|
|
Namespace: "higress-system",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-mirror",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-foo",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-alt",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "higress-controller",
|
|
Namespace: "higress-system",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "echo",
|
|
Namespace: "higress-system",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "httpbin-bad",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
},
|
|
},
|
|
}
|
|
|
|
var endpoints = []corev1.Endpoints{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "higress-gateway",
|
|
Namespace: "higress-system",
|
|
},
|
|
Subsets: []corev1.EndpointSubset{
|
|
{
|
|
Ports: []corev1.EndpointPort{
|
|
{
|
|
Port: 8080,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// End - Updated by Higress
|
|
|
|
var (
|
|
// https://github.com/kubernetes/kubernetes/blob/v1.25.4/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_tls_test.go#L31
|
|
rsaCertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
|
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
|
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
|
|
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
|
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
|
|
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
|
|
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
|
|
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
|
|
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
|
|
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
|
|
-----END CERTIFICATE-----
|
|
`
|
|
rsaKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
|
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
|
|
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
|
|
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
|
|
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
|
|
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
|
|
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
|
|
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
|
|
-----END RSA PRIVATE KEY-----
|
|
`
|
|
|
|
secrets = []runtime.Object{
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "my-cert-http",
|
|
Namespace: "higress-system",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(rsaCertPEM),
|
|
"tls.key": []byte(rsaKeyPEM),
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "cert",
|
|
Namespace: "cert",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(rsaCertPEM),
|
|
"tls.key": []byte(rsaKeyPEM),
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "malformed",
|
|
Namespace: "higress-system",
|
|
},
|
|
Data: map[string][]byte{
|
|
// nolint: lll
|
|
// https://github.com/kubernetes-sigs/gateway-api/blob/d7f71d6b7df7e929ae299948973a693980afc183/conformance/tests/gateway-invalid-tls-certificateref.yaml#L87-L90
|
|
// this certificate is invalid because contains an invalid pem (base64 of "Hello world"),
|
|
// and the certificate and the key are identical
|
|
"tls.crt": []byte("SGVsbG8gd29ybGQK"),
|
|
"tls.key": []byte("SGVsbG8gd29ybGQK"),
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
features.EnableAlphaGatewayAPI = true
|
|
features.EnableAmbientControllers = true
|
|
// Recompute with ambient enabled
|
|
classInfos = getClassInfos()
|
|
builtinClasses = getBuiltinClasses()
|
|
}
|
|
|
|
func TestConvertResources(t *testing.T) {
|
|
validator := crdvalidation.NewIstioValidator(t)
|
|
|
|
// Start - Updated by Higress
|
|
client := kube.NewFakeClient()
|
|
for _, svc := range services {
|
|
if _, err := client.Kube().CoreV1().Services(svc.Namespace).Create(context.TODO(), &svc, metav1.CreateOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, endpoint := range endpoints {
|
|
if _, err := client.Kube().CoreV1().Endpoints(endpoint.Namespace).Create(context.TODO(), &endpoint, metav1.CreateOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// End - Updated by Higress
|
|
|
|
cases := []struct {
|
|
name string
|
|
}{
|
|
{"http"},
|
|
{"tcp"},
|
|
{"tls"},
|
|
{"mismatch"},
|
|
{"weighted"},
|
|
{"zero"},
|
|
{"invalid"},
|
|
// 目前仅支持 type 为 Hostname 和 ServiceImport
|
|
//{"multi-gateway"},
|
|
{"delegated"},
|
|
{"route-binding"},
|
|
{"reference-policy-tls"},
|
|
{"reference-policy-service"},
|
|
//{"serviceentry"},
|
|
{"alias"},
|
|
//{"mcs"},
|
|
{"route-precedence"},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
input := readConfig(t, fmt.Sprintf("testdata/%s.yaml", tt.name), validator)
|
|
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
|
|
kr := splitInput(t, input)
|
|
kr.Context = NewGatewayContext(cg.PushContext(), client, "domain.suffix", "")
|
|
output := convertResources(kr)
|
|
output.AllowedReferences = AllowedReferences{} // Not tested here
|
|
output.ReferencedNamespaceKeys = nil // Not tested here
|
|
output.ResourceReferences = nil // Not tested here
|
|
|
|
// sort virtual services to make the order deterministic
|
|
sort.Slice(output.VirtualService, func(i, j int) bool {
|
|
return output.VirtualService[i].Namespace+"/"+output.VirtualService[i].Name < output.VirtualService[j].Namespace+"/"+output.VirtualService[j].Name
|
|
})
|
|
goldenFile := fmt.Sprintf("testdata/%s.yaml.golden", tt.name)
|
|
res := append(output.Gateway, output.VirtualService...)
|
|
util.CompareContent(t, marshalYaml(t, res), goldenFile)
|
|
golden := splitOutput(readConfig(t, goldenFile, validator))
|
|
|
|
// sort virtual services to make the order deterministic
|
|
sort.Slice(golden.VirtualService, func(i, j int) bool {
|
|
return golden.VirtualService[i].Namespace+"/"+golden.VirtualService[i].Name < golden.VirtualService[j].Namespace+"/"+golden.VirtualService[j].Name
|
|
})
|
|
|
|
assert.Equal(t, golden, output)
|
|
|
|
outputStatus := getStatus(t, kr.GatewayClass, kr.Gateway, kr.HTTPRoute, kr.TLSRoute, kr.TCPRoute)
|
|
goldenStatusFile := fmt.Sprintf("testdata/%s.status.yaml.golden", tt.name)
|
|
if util.Refresh() {
|
|
if err := os.WriteFile(goldenStatusFile, outputStatus, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
goldenStatus, err := os.ReadFile(goldenStatusFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if diff := cmp.Diff(string(goldenStatus), string(outputStatus)); diff != "" {
|
|
t.Fatalf("Diff:\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReferencePolicy(t *testing.T) {
|
|
validator := crdvalidation.NewIstioValidator(t)
|
|
type res struct {
|
|
name, namespace string
|
|
allowed bool
|
|
}
|
|
cases := []struct {
|
|
name string
|
|
config string
|
|
expectations []res
|
|
}{
|
|
{
|
|
name: "simple",
|
|
config: `apiVersion: gateway.networking.k8s.io/v1alpha2
|
|
kind: ReferenceGrant
|
|
metadata:
|
|
name: allow-gateways-to-ref-secrets
|
|
namespace: default
|
|
spec:
|
|
from:
|
|
- group: gateway.networking.k8s.io
|
|
kind: Gateway
|
|
namespace: higress-system
|
|
to:
|
|
- group: ""
|
|
kind: Secret
|
|
`,
|
|
expectations: []res{
|
|
// allow cross namespace
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "higress-system", true},
|
|
// denied same namespace. We do not implicitly allow (in this code - higher level code does)
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "default", false},
|
|
// denied namespace
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple in one",
|
|
config: `apiVersion: gateway.networking.k8s.io/v1alpha2
|
|
kind: ReferenceGrant
|
|
metadata:
|
|
name: allow-gateways-to-ref-secrets
|
|
namespace: default
|
|
spec:
|
|
from:
|
|
- group: gateway.networking.k8s.io
|
|
kind: Gateway
|
|
namespace: ns-1
|
|
- group: gateway.networking.k8s.io
|
|
kind: Gateway
|
|
namespace: ns-2
|
|
to:
|
|
- group: ""
|
|
kind: Secret
|
|
`,
|
|
expectations: []res{
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-1", true},
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-2", true},
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple",
|
|
config: `apiVersion: gateway.networking.k8s.io/v1alpha2
|
|
kind: ReferenceGrant
|
|
metadata:
|
|
name: ns1
|
|
namespace: default
|
|
spec:
|
|
from:
|
|
- group: gateway.networking.k8s.io
|
|
kind: Gateway
|
|
namespace: ns-1
|
|
to:
|
|
- group: ""
|
|
kind: Secret
|
|
---
|
|
apiVersion: gateway.networking.k8s.io/v1alpha2
|
|
kind: ReferenceGrant
|
|
metadata:
|
|
name: ns2
|
|
namespace: default
|
|
spec:
|
|
from:
|
|
- group: gateway.networking.k8s.io
|
|
kind: Gateway
|
|
namespace: ns-2
|
|
to:
|
|
- group: ""
|
|
kind: Secret
|
|
`,
|
|
expectations: []res{
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-1", true},
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-2", true},
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
|
|
},
|
|
},
|
|
{
|
|
name: "same namespace",
|
|
config: `apiVersion: gateway.networking.k8s.io/v1alpha2
|
|
kind: ReferenceGrant
|
|
metadata:
|
|
name: allow-gateways-to-ref-secrets
|
|
namespace: default
|
|
spec:
|
|
from:
|
|
- group: gateway.networking.k8s.io
|
|
kind: Gateway
|
|
namespace: default
|
|
to:
|
|
- group: ""
|
|
kind: Secret
|
|
`,
|
|
expectations: []res{
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "higress-system", false},
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "default", true},
|
|
{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
|
|
},
|
|
},
|
|
{
|
|
name: "same name",
|
|
config: `apiVersion: gateway.networking.k8s.io/v1alpha2
|
|
kind: ReferenceGrant
|
|
metadata:
|
|
name: allow-gateways-to-ref-secrets
|
|
namespace: default
|
|
spec:
|
|
from:
|
|
- group: gateway.networking.k8s.io
|
|
kind: Gateway
|
|
namespace: default
|
|
to:
|
|
- group: ""
|
|
kind: Secret
|
|
name: public
|
|
`,
|
|
expectations: []res{
|
|
{"kubernetes-gateway://default/public", "higress-system", false},
|
|
{"kubernetes-gateway://default/public", "default", true},
|
|
{"kubernetes-gateway://default/private", "default", false},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
input := readConfigString(t, tt.config, validator)
|
|
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
|
|
kr := splitInput(t, input)
|
|
kr.Context = NewGatewayContext(cg.PushContext(), nil, "", "")
|
|
output := convertResources(kr)
|
|
c := &Controller{
|
|
state: output,
|
|
}
|
|
for _, sc := range tt.expectations {
|
|
t.Run(fmt.Sprintf("%v/%v", sc.name, sc.namespace), func(t *testing.T) {
|
|
got := c.SecretAllowed(sc.name, sc.namespace)
|
|
if got != sc.allowed {
|
|
t.Fatalf("expected allowed=%v, got allowed=%v", sc.allowed, got)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func getStatus(t test.Failer, acfgs ...[]config.Config) []byte {
|
|
cfgs := []config.Config{}
|
|
for _, cl := range acfgs {
|
|
cfgs = append(cfgs, cl...)
|
|
}
|
|
for i, c := range cfgs {
|
|
c = c.DeepCopy()
|
|
c.Spec = nil
|
|
c.Labels = nil
|
|
c.Annotations = nil
|
|
if c.Status.(*kstatus.WrappedStatus) != nil {
|
|
c.Status = c.Status.(*kstatus.WrappedStatus).Status
|
|
}
|
|
cfgs[i] = c
|
|
}
|
|
return timestampRegex.ReplaceAll(marshalYaml(t, cfgs), []byte("lastTransitionTime: fake"))
|
|
}
|
|
|
|
var timestampRegex = regexp.MustCompile(`lastTransitionTime:.*`)
|
|
|
|
func splitOutput(configs []config.Config) IstioResources {
|
|
out := IstioResources{
|
|
Gateway: []config.Config{},
|
|
VirtualService: []config.Config{},
|
|
}
|
|
for _, c := range configs {
|
|
c.Domain = "domain.suffix"
|
|
switch c.GroupVersionKind {
|
|
case gvk.Gateway:
|
|
out.Gateway = append(out.Gateway, c)
|
|
case gvk.VirtualService:
|
|
out.VirtualService = append(out.VirtualService, c)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func splitInput(t test.Failer, configs []config.Config) GatewayResources {
|
|
out := GatewayResources{DefaultGatewaySelector: defaultGatewaySelector}
|
|
namespaces := sets.New[string]()
|
|
for _, c := range configs {
|
|
namespaces.Insert(c.Namespace)
|
|
switch c.GroupVersionKind {
|
|
case gvk.GatewayClass:
|
|
out.GatewayClass = append(out.GatewayClass, c)
|
|
case gvk.KubernetesGateway:
|
|
out.Gateway = append(out.Gateway, c)
|
|
case gvk.HTTPRoute:
|
|
out.HTTPRoute = append(out.HTTPRoute, c)
|
|
case gvk.TCPRoute:
|
|
out.TCPRoute = append(out.TCPRoute, c)
|
|
case gvk.TLSRoute:
|
|
out.TLSRoute = append(out.TLSRoute, c)
|
|
case gvk.ReferenceGrant:
|
|
out.ReferenceGrant = append(out.ReferenceGrant, c)
|
|
}
|
|
}
|
|
out.Namespaces = map[string]*corev1.Namespace{}
|
|
for ns := range namespaces {
|
|
out.Namespaces[ns] = &corev1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: ns,
|
|
Labels: map[string]string{
|
|
"istio.io/test-name-part": strings.Split(ns, "-")[0],
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
client := kube.NewFakeClient(secrets...)
|
|
out.Credentials = credentials.NewCredentialsController(client)
|
|
client.RunAndWait(test.NewStop(t))
|
|
|
|
out.Domain = "domain.suffix"
|
|
return out
|
|
}
|
|
|
|
func readConfig(t testing.TB, filename string, validator *crdvalidation.Validator) []config.Config {
|
|
t.Helper()
|
|
|
|
data, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
t.Fatalf("failed to read input yaml file: %v", err)
|
|
}
|
|
return readConfigString(t, string(data), validator)
|
|
}
|
|
|
|
func readConfigString(t testing.TB, data string, validator *crdvalidation.Validator) []config.Config {
|
|
if err := validator.ValidateCustomResourceYAML(data); err != nil {
|
|
t.Error(err)
|
|
}
|
|
c, _, err := crd.ParseInputs(data)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse CRD: %v", err)
|
|
}
|
|
return insertDefaults(c)
|
|
}
|
|
|
|
// insertDefaults sets default values that would be present when reading from Kubernetes but not from
|
|
// files
|
|
func insertDefaults(cfgs []config.Config) []config.Config {
|
|
res := make([]config.Config, 0, len(cfgs))
|
|
for _, c := range cfgs {
|
|
switch c.GroupVersionKind {
|
|
case gvk.GatewayClass:
|
|
c.Status = kstatus.Wrap(&k8s.GatewayClassStatus{})
|
|
case gvk.KubernetesGateway:
|
|
c.Status = kstatus.Wrap(&k8s.GatewayStatus{})
|
|
case gvk.HTTPRoute:
|
|
c.Status = kstatus.Wrap(&k8s.HTTPRouteStatus{})
|
|
case gvk.TCPRoute:
|
|
c.Status = kstatus.Wrap(&k8s.TCPRouteStatus{})
|
|
case gvk.TLSRoute:
|
|
c.Status = kstatus.Wrap(&k8s.TLSRouteStatus{})
|
|
}
|
|
res = append(res, c)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Print as YAML
|
|
func marshalYaml(t test.Failer, cl []config.Config) []byte {
|
|
t.Helper()
|
|
result := []byte{}
|
|
separator := []byte("---\n")
|
|
for _, config := range cl {
|
|
obj, err := crd.ConvertConfig(config)
|
|
if err != nil {
|
|
t.Fatalf("Could not decode %v: %v", config.Name, err)
|
|
}
|
|
bytes, err := yaml.Marshal(obj)
|
|
if err != nil {
|
|
t.Fatalf("Could not convert %v to YAML: %v", config, err)
|
|
}
|
|
result = append(result, bytes...)
|
|
result = append(result, separator...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func TestHumanReadableJoin(t *testing.T) {
|
|
tests := []struct {
|
|
input []string
|
|
want string
|
|
}{
|
|
{[]string{"a"}, "a"},
|
|
{[]string{"a", "b"}, "a and b"},
|
|
{[]string{"a", "b", "c"}, "a, b, and c"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(strings.Join(tt.input, "_"), func(t *testing.T) {
|
|
if got := humanReadableJoin(tt.input); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("got %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkBuildHTTPVirtualServices(b *testing.B) {
|
|
ports := []*model.Port{
|
|
{
|
|
Name: "http",
|
|
Port: 80,
|
|
Protocol: "HTTP",
|
|
},
|
|
{
|
|
Name: "tcp",
|
|
Port: 34000,
|
|
Protocol: "TCP",
|
|
},
|
|
}
|
|
ingressSvc := &model.Service{
|
|
Attributes: model.ServiceAttributes{
|
|
Name: "higress-gateway",
|
|
Namespace: "higress-system",
|
|
ClusterExternalAddresses: &model.AddressMap{
|
|
Addresses: map[cluster.ID][]string{
|
|
"Kubernetes": {"1.2.3.4"},
|
|
},
|
|
},
|
|
},
|
|
Ports: ports,
|
|
Hostname: "higress-gateway.higress-system.svc.domain.suffix",
|
|
}
|
|
altIngressSvc := &model.Service{
|
|
Attributes: model.ServiceAttributes{
|
|
Namespace: "higress-system",
|
|
},
|
|
Ports: ports,
|
|
Hostname: "example.com",
|
|
}
|
|
cg := v1alpha3.NewConfigGenTest(b, v1alpha3.TestOptions{
|
|
Services: []*model.Service{ingressSvc, altIngressSvc},
|
|
Instances: []*model.ServiceInstance{
|
|
{Service: ingressSvc, ServicePort: ingressSvc.Ports[0], Endpoint: &model.IstioEndpoint{EndpointPort: 8080}},
|
|
{Service: ingressSvc, ServicePort: ingressSvc.Ports[1], Endpoint: &model.IstioEndpoint{}},
|
|
{Service: altIngressSvc, ServicePort: altIngressSvc.Ports[0], Endpoint: &model.IstioEndpoint{}},
|
|
{Service: altIngressSvc, ServicePort: altIngressSvc.Ports[1], Endpoint: &model.IstioEndpoint{}},
|
|
},
|
|
})
|
|
|
|
validator := crdvalidation.NewIstioValidator(b)
|
|
input := readConfig(b, "testdata/benchmark-httproute.yaml", validator)
|
|
kr := splitInput(b, input)
|
|
kr.Context = NewGatewayContext(cg.PushContext(), nil, "", "")
|
|
ctx := configContext{
|
|
GatewayResources: kr,
|
|
AllowedReferences: convertReferencePolicies(kr),
|
|
}
|
|
_, gwMap, _ := convertGateways(ctx)
|
|
ctx.GatewayReferences = gwMap
|
|
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
// for gateway routes, build one VS per gateway+host
|
|
gatewayRoutes := make(map[string]map[string]*config.Config)
|
|
// for mesh routes, build one VS per namespace+host
|
|
meshRoutes := make(map[string]map[string]*config.Config)
|
|
for _, obj := range kr.HTTPRoute {
|
|
buildHTTPVirtualServices(ctx, obj, gatewayRoutes, meshRoutes)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start - Updated by Higress
|
|
func TestExtractGatewayServices(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
r GatewayResources
|
|
kgw *k8s.GatewaySpec
|
|
obj config.Config
|
|
gatewayServices []string
|
|
useDefaultService bool
|
|
err *ConfigError
|
|
}{
|
|
{
|
|
name: "default gateway",
|
|
r: GatewayResources{Domain: "cluster.local", DefaultGatewaySelector: defaultGatewaySelector},
|
|
kgw: &k8s.GatewaySpec{
|
|
GatewayClassName: "higress",
|
|
},
|
|
obj: config.Config{
|
|
Meta: config.Meta{
|
|
Name: "foo",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
gatewayServices: []string{"higress-gateway.higress-system.svc.cluster.local"},
|
|
useDefaultService: true,
|
|
},
|
|
{
|
|
name: "default gateway with name overridden",
|
|
r: GatewayResources{Domain: "cluster.local", DefaultGatewaySelector: defaultGatewaySelector},
|
|
kgw: &k8s.GatewaySpec{
|
|
GatewayClassName: "higress",
|
|
},
|
|
obj: config.Config{
|
|
Meta: config.Meta{
|
|
Name: "foo",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
gatewayNameOverride: "bar",
|
|
},
|
|
},
|
|
},
|
|
gatewayServices: []string{"bar.default.svc.cluster.local"},
|
|
},
|
|
{
|
|
name: "unmanaged gateway with only hostname address",
|
|
r: GatewayResources{Domain: "domain", DefaultGatewaySelector: defaultGatewaySelector},
|
|
kgw: &k8s.GatewaySpec{
|
|
GatewayClassName: "higress",
|
|
Addresses: []k8s.GatewayAddress{
|
|
{
|
|
Type: func() *k8s.AddressType {
|
|
t := k8s.HostnameAddressType
|
|
return &t
|
|
}(),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
obj: config.Config{
|
|
Meta: config.Meta{
|
|
Name: "foo",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
gatewayServices: []string{"example.com"},
|
|
},
|
|
{
|
|
name: "unmanaged gateway with other address types",
|
|
r: GatewayResources{Domain: "domain", DefaultGatewaySelector: defaultGatewaySelector},
|
|
kgw: &k8s.GatewaySpec{
|
|
GatewayClassName: "higress",
|
|
Addresses: []k8s.GatewayAddress{
|
|
{
|
|
Value: "abc",
|
|
},
|
|
{
|
|
Type: func() *k8s.AddressType {
|
|
t := k8s.HostnameAddressType
|
|
return &t
|
|
}(),
|
|
Value: "example.com",
|
|
},
|
|
{
|
|
Type: func() *k8s.AddressType {
|
|
t := k8s.IPAddressType
|
|
return &t
|
|
}(),
|
|
Value: "1.2.3.4",
|
|
},
|
|
},
|
|
},
|
|
obj: config.Config{
|
|
Meta: config.Meta{
|
|
Name: "foo",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
gatewayServices: []string{"abc.default.svc.domain", "example.com"},
|
|
err: &ConfigError{
|
|
Reason: InvalidAddress,
|
|
Message: "only Hostname is supported, ignoring [1.2.3.4]",
|
|
},
|
|
},
|
|
{
|
|
name: "unmanaged gateway with empty type address",
|
|
r: GatewayResources{Domain: "domain", DefaultGatewaySelector: defaultGatewaySelector},
|
|
kgw: &k8s.GatewaySpec{
|
|
GatewayClassName: "higress",
|
|
Addresses: []k8s.GatewayAddress{
|
|
{
|
|
Value: "abc",
|
|
},
|
|
},
|
|
},
|
|
obj: config.Config{
|
|
Meta: config.Meta{
|
|
Name: "foo",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
gatewayServices: []string{"abc.default.svc.domain"},
|
|
},
|
|
{
|
|
name: "unmanaged gateway with no hostname address",
|
|
r: GatewayResources{Domain: "domain", DefaultGatewaySelector: defaultGatewaySelector},
|
|
kgw: &k8s.GatewaySpec{
|
|
GatewayClassName: "higress",
|
|
Addresses: []k8s.GatewayAddress{
|
|
{
|
|
Type: func() *k8s.AddressType {
|
|
t := k8s.IPAddressType
|
|
return &t
|
|
}(),
|
|
Value: "1.2.3.4",
|
|
},
|
|
},
|
|
},
|
|
obj: config.Config{
|
|
Meta: config.Meta{
|
|
Name: "foo",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
gatewayServices: []string{"higress-gateway.higress-system.svc.cluster.local"},
|
|
useDefaultService: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gatewayServices, useDefaultService, err := extractGatewayServices(tt.r, tt.kgw, tt.obj)
|
|
assert.Equal(t, gatewayServices, tt.gatewayServices)
|
|
assert.Equal(t, useDefaultService, tt.useDefaultService)
|
|
assert.Equal(t, err, tt.err)
|
|
})
|
|
}
|
|
}
|
|
|
|
// End - Updated by Higress
|