blob: 731c69c764382306084a86ecbe7fcdf69736dfee [file] [log] [blame]
//go:build integ
// +build integ
// 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.
package reachability
import (
"fmt"
"path/filepath"
"strings"
"time"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/test/echo/common/scheme"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/echo"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/echo/check"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/echo/match"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/namespace"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/resource"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/retry"
"github.com/apache/dubbo-go-pixiu/tests/integration/security/util"
"github.com/apache/dubbo-go-pixiu/tests/integration/security/util/scheck"
)
// TestCase represents reachability test cases.
type TestCase struct {
// ConfigFile is the name of the yaml contains the authentication policy and destination rule CRs
// that are needed for the test setup.
// The file is expected in the tests/integration/security/reachability/testdata folder.
ConfigFile string
Namespace namespace.Instance
// CallOpts specified the call options for destination service. If not specified, use the default
// framework provided ones.
CallOpts []echo.CallOptions
// Indicates whether a test should be created for the given configuration.
Include func(from echo.Instance, opts echo.CallOptions) bool
// Indicates whether the test should expect a successful response.
ExpectSuccess func(from echo.Instance, opts echo.CallOptions) bool
// Allows filtering the destinations we expect to reach (optional).
ExpectDestinations func(from echo.Instance, to echo.Target) echo.Instances
// Indicates whether the test should expect a MTLS response.
ExpectMTLS func(from echo.Instance, opts echo.CallOptions) bool
// Indicates whether a test should be run in the multicluster environment.
// This is a temporary flag during the converting tests into multicluster supported.
// TODO: Remove this flag when all tests support multicluster
SkippedForMulticluster bool
}
// Run runs the given reachability test cases with the context.
func Run(testCases []TestCase, t framework.TestContext, apps *util.EchoDeployments) {
callOptions := []echo.CallOptions{
{
Port: echo.Port{
Name: "http",
},
Scheme: scheme.HTTP,
},
{
Port: echo.Port{
Name: "http",
},
Scheme: scheme.WebSocket,
},
{
Port: echo.Port{
Name: "tcp",
},
Scheme: scheme.TCP,
},
{
Port: echo.Port{
Name: "grpc",
},
Scheme: scheme.GRPC,
},
{
Port: echo.Port{
Name: "https",
},
Scheme: scheme.HTTPS,
},
}
for _, c := range testCases {
// Create a copy to avoid races, as tests are run in parallel
c := c
testName := strings.TrimSuffix(c.ConfigFile, filepath.Ext(c.ConfigFile))
t.NewSubTest(testName).Run(func(t framework.TestContext) {
if c.SkippedForMulticluster && t.Clusters().IsMulticluster() {
t.Skip("https://github.com/istio/istio/issues/37307")
}
// Apply the policy.
cfg := t.ConfigIstio().File(c.Namespace.Name(), filepath.Join("./testdata", c.ConfigFile))
retry.UntilSuccessOrFail(t, func() error {
t.Logf("[%s] [%v] Apply config %s", testName, time.Now(), c.ConfigFile)
// TODO(https://github.com/istio/istio/issues/20460) We shouldn't need a retry loop
return cfg.Apply(resource.Wait)
})
for _, clients := range []echo.Instances{apps.A, match.Namespace(apps.Namespace1).GetMatches(apps.B), apps.Headless, apps.Naked, apps.HeadlessNaked} {
for _, from := range clients {
from := from
t.NewSubTest(fmt.Sprintf("%s in %s",
from.Config().Service, from.Config().Cluster.StableName())).Run(func(t framework.TestContext) {
destinationSets := []echo.Instances{
apps.A,
apps.B,
// only hit same network headless services
match.Network(from.Config().Cluster.NetworkName()).GetMatches(apps.Headless),
// only hit same cluster multiversion services
match.Cluster(from.Config().Cluster).GetMatches(apps.Multiversion),
// only hit same cluster naked services
match.Cluster(from.Config().Cluster).GetMatches(apps.Naked),
apps.VM,
}
for _, to := range destinationSets {
to := to
if c.ExpectDestinations != nil {
to = c.ExpectDestinations(from, to)
}
toClusters := to.Clusters()
if len(toClusters) == 0 {
continue
}
// grabbing the 0th assumes all echos in destinations have the same service name
if isNakedToVM(apps, from, to) {
// No need to waste time on these tests which will time out on connection instead of fail-fast
continue
}
callCount := 1
if len(toClusters) > 1 {
// so we can validate all clusters are hit
callCount = util.CallsPerCluster * to.WorkloadsOrFail(t).Len()
}
copts := &callOptions
// If test case specified service call options, use that instead.
if c.CallOpts != nil {
copts = &c.CallOpts
}
for _, opts := range *copts {
// Copy the loop variables so they won't change for the subtests.
opts := opts
// Set the target on the call options.
opts.To = to
opts.Count = callCount
// TODO(https://github.com/istio/istio/issues/37629) go back to converge
opts.Retry.Options = []retry.Option{retry.Converge(1)}
// TODO(https://github.com/istio/istio/issues/37629) go back to 5s
opts.Timeout = time.Second * 10
expectSuccess := c.ExpectSuccess(from, opts)
expectMTLS := c.ExpectMTLS(from, opts)
var tpe string
if expectSuccess {
tpe = "positive"
opts.Check = check.And(
check.OK(),
scheck.ReachedClusters(t.AllClusters(), &opts))
if expectMTLS {
opts.Check = check.And(opts.Check,
check.MTLSForHTTP())
}
} else {
tpe = "negative"
opts.Check = scheck.NotOK()
}
include := c.Include
if include == nil {
include = func(_ echo.Instance, _ echo.CallOptions) bool { return true }
}
if include(from, opts) {
subTestName := fmt.Sprintf("%s to %s:%s%s %s",
opts.Scheme,
to.Config().Service,
opts.Port.Name,
opts.HTTP.Path,
tpe)
t.NewSubTest(subTestName).
Run(func(t framework.TestContext) {
// TODO: fix Multiversion related test in multicluster
if t.Clusters().IsMulticluster() && apps.Multiversion.ContainsTarget(to) {
t.Skip("https://github.com/istio/istio/issues/37307")
}
if (apps.IsNaked(from)) && len(toClusters) > 1 {
// TODO use echotest to generate the cases that would work for multi-network + naked
t.Skip("https://github.com/istio/istio/issues/37307")
}
from.CallOrFail(t, opts)
})
}
}
}
})
}
}
})
}
}
// Exclude calls from naked->VM since naked has no Envoy
// However, no endpoint exists for VM in k8s, so calls from naked->VM will fail, regardless of mTLS
func isNakedToVM(apps *util.EchoDeployments, from echo.Instance, to echo.Target) bool {
return apps.IsNaked(from) && apps.IsVM(to)
}