From 85a21210385c39fd8b98f78a308d0c65d2b5e181 Mon Sep 17 00:00:00 2001 From: Yusuke Kuoka Date: Tue, 3 Sep 2024 01:29:07 +0000 Subject: [PATCH 1/3] Fix S3 metrics test to actually test the correct code path --- go.mod | 11 ++++--- go.sum | 12 +++++++ main_test.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index f93f4d5..336962a 100644 --- a/go.mod +++ b/go.mod @@ -34,11 +34,12 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - golang.org/x/sys v0.17.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + golang.org/x/sys v0.21.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 94dbbe5..70bc971 100644 --- a/go.sum +++ b/go.sum @@ -58,16 +58,24 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -75,8 +83,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/main_test.go b/main_test.go index cc36037..fa3d659 100644 --- a/main_test.go +++ b/main_test.go @@ -10,8 +10,12 @@ import ( "testing" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/cw-sakamoto/sample/localstack" + "github.com/prometheus/common/expfmt" "github.com/stretchr/testify/require" ) @@ -25,10 +29,17 @@ func TestSigint(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + awsConfig, err := config.LoadDefaultConfig(ctx) + require.NoError(t, err) + + s3EndpointResolver := localstack.S3EndpointResolver() + + setupS3BucketAndObject(t, ctx, awsConfig, s3EndpointResolver) + go func() { runErr <- Run(ContextWithSignal(ctx, sigs), func(c *checker) { // Use localstack for S3 - c.s3Opts = append(c.s3Opts, s3.WithEndpointResolverV2(localstack.S3EndpointResolver())) + c.s3Opts = append(c.s3Opts, s3.WithEndpointResolverV2(s3EndpointResolver)) }) cancel() @@ -55,7 +66,38 @@ func TestSigint(t *testing.T) { // m is the metrics in the Prometheus exposition format, // expectedly containing the aws_request_duration_seconds metric. // We can check the presence of any metric here, in any detail. - require.Contains(t, m, "promhttp_metric_handler_requests_total") + + p := expfmt.TextParser{} + mf, err := p.TextToMetricFamilies(strings.NewReader(m)) + require.NoError(t, err) + + mt, ok := mf["aws_request_duration_seconds"] + require.True(t, ok) + + type labels struct { + service, method, status string + } + + mm := make(map[labels]struct{}) + + for _, m := range mt.Metric { + t.Logf("Metric: %v", m) + var labels labels + for _, l := range m.Label { + switch *l.Name { + case "service": + labels.service = *l.Value + case "method": + labels.method = *l.Value + case "status": + labels.status = *l.Value + } + } + mm[labels] = struct{}{} + } + + require.Len(t, mm, 1) + require.Contains(t, mm, labels{"S3", "GetObject", "Success"}) case <-time.After(2 * time.Second): // We assume that the server is expected to start and expose metrics within 2 seconds. // Otherwise, we consider it as a failure, and you may need to fix the server implementation, @@ -75,6 +117,51 @@ func TestSigint(t *testing.T) { } } +// Put s3 object for testing +func setupS3BucketAndObject(t *testing.T, ctx context.Context, awsConfig aws.Config, s3EndpointResolver s3.EndpointResolverV2) { + s3Client := s3.NewFromConfig(awsConfig, s3.WithEndpointResolverV2(s3EndpointResolver)) + + // We assume that S3_BUCKET and S3_KEY are set in the environment variables + // before running the tests. + s3Bucket := os.Getenv("S3_BUCKET") + s3Key := os.Getenv("S3_KEY") + + _, err := s3Client.CreateBucket(ctx, &s3.CreateBucketInput{ + Bucket: &s3Bucket, + // LocationConstraint is required when AWS_DEFAULT_REGION is not us-east-1 + // Otherwise, you will get the following error: + // IllegalLocationConstraintException: The unspecified location constraint is incompatible for the region specific endpoint this request was sent to. + CreateBucketConfiguration: &s3types.CreateBucketConfiguration{ + LocationConstraint: s3types.BucketLocationConstraint(os.Getenv("AWS_DEFAULT_REGION")), + }, + }) + require.NoError(t, err) + t.Cleanup(func() { + _, err = s3Client.DeleteBucket(context.Background(), &s3.DeleteBucketInput{ + Bucket: &s3Bucket, + }) + if err != nil { + t.Logf("Error: %v", err) + } + }) + + _, err = s3Client.PutObject(ctx, &s3.PutObjectInput{ + Bucket: &s3Bucket, + Key: &s3Key, + Body: strings.NewReader("hello"), + }) + require.NoError(t, err) + t.Cleanup(func() { + _, err := s3Client.DeleteObject(context.Background(), &s3.DeleteObjectInput{ + Bucket: &s3Bucket, + Key: &s3Key, + }) + if err != nil { + t.Logf("Error: %v", err) + } + }) +} + func httpGetStr(t *testing.T, url string) string { resp, err := http.Get(url) if err != nil { From d223a3e2274bee36cb3e93db1d6cb5d3af0801f4 Mon Sep 17 00:00:00 2001 From: Yusuke Kuoka Date: Tue, 3 Sep 2024 01:50:59 +0000 Subject: [PATCH 2/3] Use some fake AWS creds for localstack in CI --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6f9b4d..af7cc4f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,3 +22,11 @@ jobs: install-awslocal: 'true' - name: Run tests run: go test -v ./... + env: + # localstack requires these environment variables + AWS_DEFAULT_REGION: ap-northeast-1 + # Credentials are important for localstack + # Without them, the tests will fail like: + # operation error S3: CreateBucket, get identity: get credentials: failed to refresh cached credentials, no EC2 IMDS role found, operation error ec2imds: GetMetadata, failed to get API token, operation error ec2imds: getToken, http response error StatusCode: 400, request to EC2 IMDS failed + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test From 8076a440fbe20c353eb9155457b319e0984f1a71 Mon Sep 17 00:00:00 2001 From: Yusuke Kuoka Date: Tue, 3 Sep 2024 03:32:12 +0000 Subject: [PATCH 3/3] Add aws-checker-specific and s3-related envvars for CI --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af7cc4f..13630ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,3 +30,8 @@ jobs: # operation error S3: CreateBucket, get identity: get credentials: failed to refresh cached credentials, no EC2 IMDS role found, operation error ec2imds: GetMetadata, failed to get API token, operation error ec2imds: getToken, http response error StatusCode: 400, request to EC2 IMDS failed AWS_ACCESS_KEY_ID: test AWS_SECRET_ACCESS_KEY: test + # These environment variables are required by main and main_test + # Without them, the tests will fail like: + # operation error S3: CreateBucket, exceeded maximum number of attempts, 3, https response error StatusCode: 500, RequestID: 346f5995-bbe8-41e2-be03-3a6ab0ad5edd, HostID: s9lzHYrFp76ZVxRcpX9+5cjAnEH2ROuNkd2BHfIa6UkFVdtjf5mKR3/eTPFvsiP/XV/VLi31234=, api error InternalError: exception while calling s3 with unknown operation: Unable to find operation for request to service s3: PUT / + S3_BUCKET: mybucket + S3_KEY: mykey