blob: a5816b5fb175ffbb733a5f34e3f7248a0af3ba60 [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 vm
import (
"fmt"
"os"
"strings"
"testing"
)
import (
"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/proto"
"google.golang.org/genproto/googleapis/devtools/cloudtrace/v1"
loggingpb "google.golang.org/genproto/googleapis/logging/v2"
monitoring "google.golang.org/genproto/googleapis/monitoring/v3"
"istio.io/api/annotation"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config/protocol"
"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/deployment"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/gcemetadata"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/istio"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/namespace"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/stackdriver"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/label"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/resource"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/tmpl"
"github.com/apache/dubbo-go-pixiu/tests/integration/telemetry"
sdtest "github.com/apache/dubbo-go-pixiu/tests/integration/telemetry/stackdriver"
)
const (
// testdata, including golden files
stackdriverBootstrapOverride = "../testdata/custom_bootstrap.yaml.tmpl"
serverRequestCount = "testdata/server_request_count.json.tmpl"
clientRequestCount = "testdata/client_request_count.json.tmpl"
serverLogEntry = "testdata/server_access_log.json.tmpl"
traceTmplFile = "testdata/trace.prototext.tmpl"
sdBootstrapConfigMap = "stackdriver-bootstrap-config"
)
var (
istioInst istio.Instance
ns namespace.Instance
sdInst stackdriver.Instance
server echo.Instance
client echo.Instance
vmEnv map[string]string
)
var (
// golden values for tests
wantServerReqs *monitoring.TimeSeries
wantClientReqs *monitoring.TimeSeries
wantLogEntry *loggingpb.LogEntry
wantTrace *cloudtrace.Trace
)
var clientBuilder, serverBuilder deployment.Builder
var (
proxyConfigAnnotation = echo.Annotation{
Name: annotation.ProxyConfig.Name,
Type: echo.WorkloadAnnotation,
}
envTagsProxyConfig = `
tracing:
stackdriver:
debug: true
sampling: 100.0`
)
const enforceMTLS = `
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: STRICT
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: send-mtls
spec:
host: "*.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
`
// Testing telemetry with VM mesh expansion on a simulated GCE instance.
// Rather than deal with the infra to get a real VM, we will use a pod
// with no Service, no DNS, no service account, etc to simulate a VM.
//
// This test setup borrows heavily from the following packages:
// - tests/integration/pilot/vm
// - tests/integration/telemetry/stackdriver
func TestMain(m *testing.M) {
// nolint: staticcheck
framework.
NewSuite(m).
// https://github.com/istio/istio/issues/35923. Since IPv6 has no external connectivity, we are "not on GCP"
// in the sense that we cannot access the metadata server
Label(label.IPv4).
SkipIf("test requires VMs", func(ctx resource.Context) bool {
return ctx.Settings().Skip(echo.VM)
}).
RequireMultiPrimary().
Setup(func(ctx resource.Context) error {
var err error
// Unlike other tests, we use GCE metadata server unconditionally for VMs because they would not have
// stable labels otherwise, unlike pods.
if sdtest.GCEInst, err = gcemetadata.New(ctx, gcemetadata.Config{}); err != nil {
return err
}
return nil
}).
Setup(istio.Setup(&istioInst, func(_ resource.Context, cfg *istio.Config) {
cfg.ControlPlaneValues = `
meshConfig:
`
cfg.Values["meshConfig.enableTracing"] = "true"
cfg.Values["meshConfig.defaultConfig.tracing.sampling"] = "100.0"
cfg.Values["global.meshID"] = "proj-test-mesh"
cfg.Values["global.proxy.tracer"] = "stackdriver"
cfg.Values["telemetry.v2.enabled"] = "true"
cfg.Values["telemetry.v2.stackdriver.enabled"] = "true"
cfg.Values["telemetry.v2.stackdriver.logging"] = "true"
// conditionally use a fake metadata server for testing off of GCP
if sdtest.GCEInst != nil {
cfg.ControlPlaneValues = strings.Join([]string{cfg.ControlPlaneValues, sdtest.FakeGCEMetadataServerValues, sdtest.GCEInst.Address()}, "")
}
})).
Setup(testSetup).
Run()
}
func testSetup(ctx resource.Context) error {
var err error
if ns, err = namespace.New(ctx, namespace.Config{
Prefix: "istio-echo",
Inject: true,
}); err != nil {
return err
}
sdtest.EchoNsInst = ns
if sdInst, err = stackdriver.New(ctx, stackdriver.Config{}); err != nil {
return err
}
sdtest.SDInst = sdInst
if err = ctx.ConfigKube().EvalFile(ns.Name(), map[string]interface{}{
"StackdriverAddress": sdInst.Address(),
"EchoNamespace": ns.Name(),
}, stackdriverBootstrapOverride).Apply(); err != nil {
return err
}
vmLabelsJSON := "{\\\"service.istio.io/canonical-name\\\":\\\"vm-server\\\",\\\"service.istio.io/canonical-revision\\\":\\\"v1\\\"}"
vmEnv = map[string]string{
"ISTIO_META_INSECURE_STACKDRIVER_ENDPOINT": sdInst.Address(),
"ISTIO_META_STACKDRIVER_MONITORING_EXPORT_INTERVAL_SECS": "10",
"ISTIO_META_MESH_ID": "proj-test-mesh",
"ISTIO_META_WORKLOAD_NAME": "vm-server-v1",
"ISTIO_METAJSON_LABELS": vmLabelsJSON,
"CANONICAL_SERVICE": "vm-server",
"CANONICAL_REVISION": "v1",
// we must supply a bootstrap override to get the test endpoint uri into the tracing configuration
"ISTIO_BOOTSTRAP_OVERRIDE": "/etc/istio-custom-bootstrap/custom_bootstrap.json",
}
if sdtest.GCEInst != nil {
vmEnv["GCE_METADATA_HOST"] = sdtest.GCEInst.Address()
}
trustDomain := telemetry.GetTrustDomain(ctx.Clusters()[0], istioInst.Settings().SystemNamespace)
// read expected values from testdata
wantClientReqs, wantServerReqs, err = goldenRequestCounts(trustDomain)
if err != nil {
return fmt.Errorf("failed to get golden metrics from file: %v", err)
}
wantLogEntry, err = goldenLogEntry(trustDomain)
if err != nil {
return fmt.Errorf("failed to get golden log entry from file: %v", err)
}
wantTrace, err = goldenTrace(trustDomain)
if err != nil {
return fmt.Errorf("failed to get golden trace from file: %v", err)
}
// set up client and server
ports := []echo.Port{
{
Name: "http",
Protocol: protocol.HTTP,
// Due to a bug in WorkloadEntry, service port must equal target port for now
WorkloadPort: 8090,
ServicePort: 8090,
},
}
// builder to build the instances iteratively
clientBuilder = deployment.New(ctx).
With(&client, echo.Config{
Service: "client",
Namespace: ns,
Ports: ports,
Subsets: []echo.SubsetConfig{
{
Annotations: echo.NewAnnotations().Set(echo.SidecarBootstrapOverride, sdBootstrapConfigMap),
},
},
})
serverBuilder = deployment.New(ctx).
With(&server, echo.Config{
Service: "server",
Namespace: ns,
Ports: ports,
DeployAsVM: true,
VMEnvironment: vmEnv,
Subsets: []echo.SubsetConfig{
{
Annotations: echo.NewAnnotations().
Set(proxyConfigAnnotation, envTagsProxyConfig).
Set(echo.SidecarBootstrapOverride, sdBootstrapConfigMap),
},
},
})
return nil
}
func goldenRequestCounts(trustDomain string) (cltRequestCount, srvRequestCount *monitoring.TimeSeries, err error) {
srvRequestCountTmpl, err := os.ReadFile(serverRequestCount)
if err != nil {
return
}
sr, err := tmpl.Evaluate(string(srvRequestCountTmpl), map[string]interface{}{
"EchoNamespace": ns.Name(),
"TrustDomain": trustDomain,
})
if err != nil {
return
}
cltRequestCount = &monitoring.TimeSeries{}
srvRequestCount = &monitoring.TimeSeries{}
if err = jsonpb.UnmarshalString(sr, srvRequestCount); err != nil {
return
}
cltRequestCountTmpl, err := os.ReadFile(clientRequestCount)
if err != nil {
return
}
cr, err := tmpl.Evaluate(string(cltRequestCountTmpl), map[string]interface{}{
"EchoNamespace": ns.Name(),
"TrustDomain": trustDomain,
})
if err != nil {
return
}
err = jsonpb.UnmarshalString(cr, cltRequestCount)
return
}
func goldenLogEntry(trustDomain string) (srvLogEntry *loggingpb.LogEntry, err error) {
srvlogEntryTmpl, err := os.ReadFile(serverLogEntry)
if err != nil {
return
}
sr, err := tmpl.Evaluate(string(srvlogEntryTmpl), map[string]interface{}{
"EchoNamespace": ns.Name(),
"TrustDomain": trustDomain,
})
if err != nil {
return
}
srvLogEntry = &loggingpb.LogEntry{}
if err = jsonpb.UnmarshalString(sr, srvLogEntry); err != nil {
return
}
return
}
func goldenTrace(trustDomain string) (*cloudtrace.Trace, error) {
traceTmpl, err := os.ReadFile(traceTmplFile)
if err != nil {
return nil, err
}
traceStr, err := tmpl.Evaluate(string(traceTmpl), map[string]interface{}{
"EchoNamespace": ns.Name(),
"TrustDomain": trustDomain,
})
if err != nil {
return nil, err
}
var trace cloudtrace.Trace
if err = proto.UnmarshalText(traceStr, &trace); err != nil {
return nil, err
}
return &trace, nil
}