Skip to content

Commit 2cb2685

Browse files
committed
mysql/awsmysql: otel test
1 parent 00de07a commit 2cb2685

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed

mysql/awsmysql/awsmysql.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ var Set = wire.NewSet(
6666
// - aws_profile: the AWS shared config profile to use
6767
// - aws_role_arn: the ARN of the role to assume
6868
type URLOpener struct {
69+
// Client is the HTTP client used for AWS requests. If nil, then
70+
// http.DefaultClient is used.
71+
Client config.HTTPClient
6972
// CertSource specifies how the opener will obtain the RDS Certificate
7073
// Authority. If nil, it will use the default *rds.CertFetcher.
7174
CertSource rds.CertPoolProvider
@@ -100,7 +103,9 @@ func (uo *URLOpener) OpenMySQLURL(ctx context.Context, u *url.URL) (*sql.DB, err
100103
)
101104
q.Del("aws_profile")
102105
cfg, err := config.LoadDefaultConfig(ctx,
103-
config.WithSharedConfigProfile(profile)) // Ignored if empty.
106+
config.WithSharedConfigProfile(profile), // Ignored if empty.
107+
config.WithHTTPClient(uo.Client), // Ignored if nil.
108+
)
104109
if err != nil {
105110
return nil, fmt.Errorf("open OpenMySQLURL: load AWS config: %v", err)
106111
}

mysql/awsmysql/otel_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2019-2025 The Go Cloud Development Kit Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package awsmysql_test
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"log/slog"
21+
"net/url"
22+
"testing"
23+
"time"
24+
25+
"github.com/google/go-cmp/cmp"
26+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
27+
"go.opentelemetry.io/otel/attribute"
28+
"gocloud.dev/internal/testing/oteltest"
29+
"gocloud.dev/internal/testing/terraform"
30+
"gocloud.dev/mysql/awsmysql"
31+
)
32+
33+
func TestOpenTelemetry(t *testing.T) {
34+
// This test will be skipped unless the project is set up with Terraform.
35+
// Before running go test, run in this directory:
36+
//
37+
// terraform init
38+
// terraform apply
39+
tfOut, err := terraform.ReadOutput(".")
40+
if err != nil || len(tfOut) == 0 {
41+
t.Skipf("Could not obtain harness info: %v", err)
42+
}
43+
endpoint, _ := tfOut["endpoint"].Value.(string)
44+
username, _ := tfOut["iam_db_username"].Value.(string)
45+
roleARN, _ := tfOut["iam_role_arn"].Value.(string)
46+
databaseName, _ := tfOut["database"].Value.(string)
47+
if endpoint == "" || username == "" || databaseName == "" {
48+
t.Fatalf("Missing one or more required Terraform outputs; got endpoint=%q iam_db_username=%q database=%q", endpoint, username, databaseName)
49+
}
50+
ctx := context.Background()
51+
52+
// Setup the test exporter for both trace and metrics.
53+
te := oteltest.NewTestExporter(t, nil)
54+
defer te.Shutdown(ctx)
55+
56+
// Open the database with otelsql.
57+
urlstr := fmt.Sprintf("awsmysql://%s@%s/%s?parseTime=true&aws_role_arn=%s",
58+
username, endpoint, databaseName, roleARN)
59+
t.Log("Connecting to:", urlstr)
60+
u, err := url.Parse(urlstr)
61+
if err != nil {
62+
t.Fatal(err)
63+
}
64+
o := awsmysql.URLOpener{Client: otelhttp.DefaultClient}
65+
db, err := o.OpenMySQLURL(ctx, u)
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
defer db.Close()
70+
71+
query := func() error {
72+
rows, err := db.QueryContext(ctx, `SELECT CURRENT_TIMESTAMP`)
73+
if err != nil {
74+
return err
75+
}
76+
defer func() { _ = rows.Close() }()
77+
78+
var currentTime time.Time
79+
for rows.Next() {
80+
err = rows.Scan(&currentTime)
81+
if err != nil {
82+
return err
83+
}
84+
}
85+
// Check for errors from iterating over rows
86+
if err = rows.Err(); err != nil {
87+
return err
88+
}
89+
slog.Info("Current time", "time", currentTime)
90+
return nil
91+
}
92+
if err = query(); err != nil {
93+
t.Error("QueryContext:", err)
94+
}
95+
96+
spans := te.GetSpans().Snapshots()
97+
if !cmp.Equal(4, len(spans)) {
98+
t.Errorf("expected 3 spans, got %d: %v", len(spans), spans)
99+
}
100+
if !cmp.Equal("HTTP POST", spans[0].Name()) {
101+
t.Errorf("expected first span name to be HTTP POST, got %q", spans[0].Name())
102+
}
103+
if !cmp.Equal("sql.connector.connect", spans[1].Name()) {
104+
t.Errorf("expected first span name to be sql.connector.connect, got %q", spans[1].Name())
105+
}
106+
if !cmp.Equal("sql.conn.query", spans[2].Name()) {
107+
t.Errorf("expected second span name to be sql.conn.query, got %q", spans[2].Name())
108+
} else {
109+
attrs := spans[2].Attributes()
110+
slog.Info("Span Attributes", "attributes", attrs)
111+
if !cmp.Equal(1, len(attrs)) {
112+
t.Errorf("expected 1 attribute, got %d: %v", len(attrs), attrs)
113+
}
114+
if !cmp.Equal(attribute.Key("db.statement"), attrs[0].Key) {
115+
t.Errorf("expected attribute key to be db.statement, got %q", attrs[0].Key)
116+
}
117+
if !cmp.Equal("SELECT CURRENT_TIMESTAMP", attrs[0].Value.AsString()) {
118+
t.Errorf("expected attribute value to be 'SELECT CURRENT_TIMESTAMP', got %q", attrs[0].Value.AsString())
119+
}
120+
}
121+
if !cmp.Equal("sql.rows", spans[3].Name()) {
122+
t.Errorf("expected second span name to be sql.rows, got %q", spans[3].Name())
123+
} else {
124+
attrs := spans[3].Attributes()
125+
slog.Info("Span Attributes", "attributes", attrs)
126+
if !cmp.Equal(0, len(attrs)) {
127+
t.Errorf("expected 0 attribute, got %d: %v", len(attrs), attrs)
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)