Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions cli/cmd/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ func DashboardCmd(ctx cli.Context) *cobra.Command {
}
if dashboardHasTracing(files) {
_, err = fmt.Fprintf(cmd.OutOrStdout(), "tracing: kubectl -n %s port-forward svc/tracing 16686:16686\n", dashboardNamespace)
if err != nil {
return err
}
}
if dashboardHasOpenTelemetry(files) {
_, err = fmt.Fprintf(cmd.OutOrStdout(), "opentelemetry: kubectl -n %s port-forward svc/opentelemetry-collector 4317:4317\n", dashboardNamespace)
}
return err
},
Expand Down Expand Up @@ -137,6 +143,10 @@ func dashboardManifestFiles(path string) ([]string, error) {
if _, err := os.Stat(tracing); err == nil {
files = append(files, tracing)
}
opentelemetry := filepath.Join(path, "opentelemetry.yaml")
if _, err := os.Stat(opentelemetry); err == nil {
files = append(files, opentelemetry)
}
return files, nil
}

Expand All @@ -149,11 +159,23 @@ func dashboardHasTracing(files []string) bool {
return false
}

func dashboardHasOpenTelemetry(files []string) bool {
for _, file := range files {
if filepath.Base(file) == "opentelemetry.yaml" {
return true
}
}
return false
}

func dashboardWaitDeploymentNames(files []string) []string {
names := []string{"prometheus", "grafana"}
if dashboardHasTracing(files) {
names = append(names, "tracing")
}
if dashboardHasOpenTelemetry(files) {
names = append(names, "opentelemetry-collector")
}
return names
}

Expand Down
29 changes: 29 additions & 0 deletions cli/cmd/dashboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,35 @@ func TestDashboardManifestFilesIncludesTracingWhenPresent(t *testing.T) {
}
}

func TestDashboardManifestFilesIncludesOpenTelemetryWhenPresent(t *testing.T) {
dir := t.TempDir()
for _, name := range []string{"prometheus.yaml", "grafana.yaml", "tracing.yaml", "opentelemetry.yaml"} {
if err := os.WriteFile(filepath.Join(dir, name), []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: "+name+"\n"), 0o600); err != nil {
t.Fatal(err)
}
}

got, err := dashboardManifestFiles(dir)
if err != nil {
t.Fatalf("dashboardManifestFiles() returned error: %v", err)
}
want := []string{
filepath.Join(dir, "prometheus.yaml"),
filepath.Join(dir, "grafana.yaml"),
filepath.Join(dir, "tracing.yaml"),
filepath.Join(dir, "opentelemetry.yaml"),
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("dashboardManifestFiles() = %v, want %v", got, want)
}
if !dashboardHasOpenTelemetry(got) {
t.Fatalf("dashboardHasOpenTelemetry() = false, want true")
}
if names := dashboardWaitDeploymentNames(got); !reflect.DeepEqual(names, []string{"prometheus", "grafana", "tracing", "opentelemetry-collector"}) {
t.Fatalf("dashboardWaitDeploymentNames() = %v", names)
}
}

func TestReadManifestObjects(t *testing.T) {
file := filepath.Join(t.TempDir(), "manifest.yaml")
manifest := strings.Join([]string{
Expand Down
76 changes: 71 additions & 5 deletions dubbod/discovery/pkg/config/kube/gateway/deployment_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"math"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -80,6 +81,10 @@ const (
serviceNodePortAnnotation = "gateway.dubbo.apache.org/node-port"
xdsAddressAnnotation = "gateway.dubbo.apache.org/xds-address"
otelEndpointAnnotation = "gateway.dubbo.apache.org/otel-endpoint"
otelSamplingAnnotation = "gateway.dubbo.apache.org/otel-sampling-percentage"
otelServiceNameAnnotation = "gateway.dubbo.apache.org/otel-service-name"
accessLogAnnotation = "gateway.dubbo.apache.org/access-log"
accessLogFormatAnnotation = "gateway.dubbo.apache.org/access-log-format"
)

var builtinClasses = getBuiltinClasses()
Expand Down Expand Up @@ -387,6 +392,7 @@ func (d *DeploymentController) configureGateway(log *dubbolog.Logger, gw gateway
return err
}

observability := observabilityConfigForGateway(gw)
input := TemplateInput{
Gateway: &gw,
GatewayClass: string(gw.Spec.GatewayClassName),
Expand All @@ -402,7 +408,11 @@ func (d *DeploymentController) configureGateway(log *dubbolog.Logger, gw gateway
SystemNamespace: d.systemNamespace,
ClusterID: string(d.clusterID),
DomainSuffix: d.domainSuffix(),
OtelEndpoint: strings.TrimSpace(gw.Annotations[otelEndpointAnnotation]),
OtelEndpoint: observability.OtelEndpoint,
OtelServiceName: observability.OtelServiceName,
OtelSampling: observability.OtelSampling,
AccessLog: observability.AccessLog,
AccessLogFormat: observability.AccessLogFormat,
}

log.Infof("desired dxgate deployment=%s/%s gatewayClass=%s serviceType=%s ports=%s image=%s",
Expand Down Expand Up @@ -456,6 +466,65 @@ type TemplateInput struct {
ClusterID string
DomainSuffix string
OtelEndpoint string
OtelServiceName string
OtelSampling string
AccessLog string
AccessLogFormat string
}

type gatewayObservabilityConfig struct {
OtelEndpoint string
OtelServiceName string
OtelSampling string
AccessLog string
AccessLogFormat string
}

func observabilityConfigForGateway(gw gateway.Gateway) gatewayObservabilityConfig {
return gatewayObservabilityConfig{
OtelEndpoint: strings.TrimSpace(gw.Annotations[otelEndpointAnnotation]),
OtelServiceName: gatewayOtelServiceName(gw),
OtelSampling: gatewayOtelSampling(gw),
AccessLog: gatewayAccessLog(gw),
AccessLogFormat: gatewayAccessLogFormat(gw),
}
}

func gatewayOtelServiceName(gw gateway.Gateway) string {
if value := strings.TrimSpace(gw.Annotations[otelServiceNameAnnotation]); value != "" {
return value
}
return fmt.Sprintf("dxgate.%s.%s", gw.Namespace, gw.Name)
}

func gatewayOtelSampling(gw gateway.Gateway) string {
value := strings.TrimSpace(gw.Annotations[otelSamplingAnnotation])
if value == "" {
return "100"
}
parsed, err := strconv.ParseFloat(value, 64)
if err != nil || math.IsNaN(parsed) || math.IsInf(parsed, 0) || parsed < 0 || parsed > 100 {
return "100"
}
return strconv.FormatFloat(parsed, 'f', -1, 64)
}

func gatewayAccessLog(gw gateway.Gateway) string {
switch strings.ToLower(strings.TrimSpace(gw.Annotations[accessLogAnnotation])) {
case "false", "0", "no", "off":
return "false"
default:
return "true"
}
}

func gatewayAccessLogFormat(gw gateway.Gateway) string {
switch strings.ToLower(strings.TrimSpace(gw.Annotations[accessLogFormatAnnotation])) {
case "json":
return "json"
default:
return "text"
}
}

type dxgateBootstrapConfig struct {
Expand Down Expand Up @@ -1366,10 +1435,7 @@ func gatewayServiceTargetPort(gw gateway.Gateway) intstr.IntOrString {
if port, ok := positiveIntAnnotation(gw, serviceTargetPortAnnotation); ok {
return intstr.FromInt(port)
}
if gw.Annotations[eastWestGatewayAnnotation] == "true" {
return intstr.FromInt(inject.ProxylessGRPCInboundPort)
}
return intstr.FromString("http")
return intstr.FromInt(inject.ProxylessGRPCInboundPort)
}

func gatewayServiceNodePort(gw gateway.Gateway) int32 {
Expand Down
108 changes: 103 additions & 5 deletions dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ func TestDeploymentControllerBuildDxgateBootstrapConfigUsesXDSAddressAnnotation(
}
}

func TestExtractServicePortsTargetsDxgateContainerPorts(t *testing.T) {
func TestExtractServicePortsTargetsGRPCInbound(t *testing.T) {
gw := gatewayv1.Gateway{
Spec: gatewayv1.GatewaySpec{
Listeners: []gatewayv1.Listener{
Expand All @@ -454,7 +454,7 @@ func TestExtractServicePortsTargetsDxgateContainerPorts(t *testing.T) {
if len(ports) != 1 {
t.Fatalf("expected only the http listener port, got %#v", ports)
}
if ports[0].Name != "http" || ports[0].Port != 8080 || ports[0].TargetPort.String() != "http" {
if ports[0].Name != "http" || ports[0].Port != 8080 || ports[0].TargetPort.IntValue() != 15080 {
t.Fatalf("unexpected http service port: %#v", ports[0])
}
}
Expand Down Expand Up @@ -498,6 +498,85 @@ func TestServiceTypeForGatewayUsesAnnotation(t *testing.T) {
}
}

func TestObservabilityConfigForGatewayDefaults(t *testing.T) {
cfg := observabilityConfigForGateway(gatewayv1.Gateway{
ObjectMeta: metav1.ObjectMeta{Name: "public", Namespace: "app"},
})

if cfg.OtelEndpoint != "" {
t.Fatalf("otel endpoint = %q, want empty", cfg.OtelEndpoint)
}
if cfg.OtelServiceName != "dxgate.app.public" {
t.Fatalf("otel service name = %q", cfg.OtelServiceName)
}
if cfg.OtelSampling != "100" {
t.Fatalf("otel sampling = %q", cfg.OtelSampling)
}
if cfg.AccessLog != "true" {
t.Fatalf("access log = %q", cfg.AccessLog)
}
if cfg.AccessLogFormat != "text" {
t.Fatalf("access log format = %q", cfg.AccessLogFormat)
}
}

func TestObservabilityConfigForGatewayAnnotations(t *testing.T) {
cfg := observabilityConfigForGateway(gatewayv1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "public",
Namespace: "app",
Annotations: map[string]string{
otelEndpointAnnotation: " http://otel-collector.dubbo-system.svc:4317 ",
otelServiceNameAnnotation: "edge-gateway",
otelSamplingAnnotation: "12.5",
accessLogAnnotation: "false",
accessLogFormatAnnotation: "json",
},
},
})

if cfg.OtelEndpoint != "http://otel-collector.dubbo-system.svc:4317" {
t.Fatalf("otel endpoint = %q", cfg.OtelEndpoint)
}
if cfg.OtelServiceName != "edge-gateway" {
t.Fatalf("otel service name = %q", cfg.OtelServiceName)
}
if cfg.OtelSampling != "12.5" {
t.Fatalf("otel sampling = %q", cfg.OtelSampling)
}
if cfg.AccessLog != "false" {
t.Fatalf("access log = %q", cfg.AccessLog)
}
if cfg.AccessLogFormat != "json" {
t.Fatalf("access log format = %q", cfg.AccessLogFormat)
}
}

func TestObservabilityConfigForGatewayInvalidSamplingFallsBack(t *testing.T) {
for _, value := range []string{"abc", "-1", "101", "NaN", "+Inf"} {
cfg := observabilityConfigForGateway(gatewayv1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "public",
Namespace: "app",
Annotations: map[string]string{
otelSamplingAnnotation: value,
accessLogAnnotation: "maybe",
accessLogFormatAnnotation: "yaml",
},
},
})
if cfg.OtelSampling != "100" {
t.Fatalf("otel sampling for %q = %q, want 100", value, cfg.OtelSampling)
}
if cfg.AccessLog != "true" {
t.Fatalf("access log for %q = %q, want true", value, cfg.AccessLog)
}
if cfg.AccessLogFormat != "text" {
t.Fatalf("access log format for %q = %q, want text", value, cfg.AccessLogFormat)
}
}
}

func TestGetDefaultNameUsesFixedDxgateGatewayName(t *testing.T) {
spec := &gatewayv1.GatewaySpec{GatewayClassName: "dubbo"}
if got := getDefaultName("httpbin-gateway", spec, false); got != "dxgate-gateway" {
Expand Down Expand Up @@ -537,7 +616,7 @@ func TestKubeGatewayTemplateRendersDxgateResources(t *testing.T) {
},
DeploymentName: "public-dubbo",
ServiceAccount: "public-dubbo",
Ports: []corev1.ServicePort{{Name: "http", Port: 80, TargetPort: intstr.FromString("http")}},
Ports: []corev1.ServicePort{{Name: "http", Port: 80, TargetPort: intstr.FromInt(15080)}},
ServiceType: corev1.ServiceTypeLoadBalancer,
Revision: "default",
BootstrapConfig: "{\n \"xds_address\": \"http://dubbod.dubbo-system.svc:26010\",\n \"listener_names\": [\n \"public-dubbo.app.svc.cluster.local:80\"\n ],\n \"cluster_id\": \"Kubernetes\",\n \"dns_domain\": \"cluster.local\"\n}\n",
Expand All @@ -546,6 +625,10 @@ func TestKubeGatewayTemplateRendersDxgateResources(t *testing.T) {
SystemNamespace: "dubbo-system",
ClusterID: "Kubernetes",
DomainSuffix: "cluster.local",
OtelServiceName: "dxgate.app.public",
OtelSampling: "100",
AccessLog: "true",
AccessLogFormat: "text",
}
rendered, err := controller.render("gateway", input)
if err != nil {
Expand Down Expand Up @@ -586,11 +669,26 @@ func TestKubeGatewayTemplateRendersDxgateResources(t *testing.T) {
if strings.Contains(rendered[2], "DXGATE_OTEL_ENDPOINT") {
t.Fatalf("deployment rendered OTEL endpoint without annotation:\n%s", rendered[2])
}
for _, want := range []string{
"DXGATE_GATEWAY_NAME",
`value: "public"`,
"DXGATE_OTEL_SERVICE_NAME",
`value: "dxgate.app.public"`,
"DXGATE_OTEL_SAMPLING_PERCENTAGE",
`value: "100"`,
"DXGATE_ACCESS_LOG",
"DXGATE_ACCESS_LOG_FORMAT",
`value: "text"`,
} {
if !strings.Contains(rendered[2], want) {
t.Fatalf("deployment did not render observability env %q:\n%s", want, rendered[2])
}
}
if !strings.Contains(rendered[2], "app.kubernetes.io/instance: public-dubbo") {
t.Fatalf("deployment did not render stable dxgate instance label:\n%s", rendered[2])
}
if !strings.Contains(rendered[3], "targetPort: http") {
t.Fatalf("service did not target dxgate http port:\n%s", rendered[3])
if !strings.Contains(rendered[3], "targetPort: 15080") {
t.Fatalf("service did not target grpc-inbound port:\n%s", rendered[3])
}
if !strings.Contains(rendered[3], `proxyless.dubbo.apache.org/inject: "false"`) {
t.Fatalf("service did not opt out of proxyless targetPort rewriting:\n%s", rendered[3])
Expand Down
10 changes: 10 additions & 0 deletions manifests/charts/dubbod/files/kube-gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,20 @@ spec:
value: 0.0.0.0:8080
- name: DXGATE_ADMIN_ADDR
value: 0.0.0.0:26021
- name: DXGATE_GATEWAY_NAME
value: {{ .Gateway.Name | quote }}
- name: DXGATE_OTEL_SERVICE_NAME
value: {{ .OtelServiceName | quote }}
- name: DXGATE_OTEL_SAMPLING_PERCENTAGE
value: {{ .OtelSampling | quote }}
{{- if .OtelEndpoint }}
- name: DXGATE_OTEL_ENDPOINT
value: {{ .OtelEndpoint | quote }}
{{- end }}
- name: DXGATE_ACCESS_LOG
value: {{ .AccessLog | quote }}
- name: DXGATE_ACCESS_LOG_FORMAT
value: {{ .AccessLogFormat | quote }}
- name: POD_NAME
valueFrom:
fieldRef:
Expand Down
1 change: 1 addition & 0 deletions manifests/charts/dubbod/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ rules:
- gateways
- gatewayclasses
- httproutes
- backendtlspolicies
verbs:
- get
- list
Expand Down
Loading