Skip to content

Commit 94c7d88

Browse files
authored
Integration Testing Framework: Add retries for artifacts API call (#4348)
* Add retries for artifacts API call * Add newline * Adding logging * Pass logger in constructor * Add missing arg * Fix logic for happy path * Adding comment * Do not keep retrying if request context was cancelled or timed out
1 parent f9310c7 commit 94c7d88

11 files changed

+58
-22
lines changed

pkg/testing/tools/artifacts_api.go

+40-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"net/url"
1515
"runtime"
1616
"sort"
17+
"time"
1718

1819
"github.com/elastic/elastic-agent/pkg/testing"
1920
"github.com/elastic/elastic-agent/pkg/version"
@@ -29,6 +30,9 @@ const (
2930

3031
artifactElasticAgentProject = "elastic-agent-package"
3132
artifactReleaseCDN = "https://artifacts.elastic.co/downloads/beats/elastic-agent"
33+
34+
maxAttemptsForArtifactsAPICall = 6
35+
retryIntervalForArtifactsAPICall = 5 * time.Second
3236
)
3337

3438
var (
@@ -137,14 +141,17 @@ type ArtifactAPIClient struct {
137141
c httpDoer
138142
url string
139143
cdnURL string
144+
145+
logger logger
140146
}
141147

142148
// NewArtifactAPIClient creates a new Artifact API client
143-
func NewArtifactAPIClient(opts ...ArtifactAPIClientOpt) *ArtifactAPIClient {
149+
func NewArtifactAPIClient(logger logger, opts ...ArtifactAPIClientOpt) *ArtifactAPIClient {
144150
c := &ArtifactAPIClient{
145151
url: defaultArtifactAPIURL,
146152
cdnURL: artifactReleaseCDN,
147153
c: new(http.Client),
154+
logger: logger,
148155
}
149156

150157
for _, opt := range opts {
@@ -310,9 +317,37 @@ func (aac ArtifactAPIClient) createAndPerformRequest(ctx context.Context, URL st
310317
return nil, err
311318
}
312319

313-
resp, err := aac.c.Do(req)
320+
// Make the request with retries as the artifacts API can sometimes be flaky.
321+
var resp *http.Response
322+
// TODO (once we're on Go 1.22): replace with for numAttempts := range maxAttemptsForArtifactsAPICall {
323+
for numAttempts := 0; numAttempts < maxAttemptsForArtifactsAPICall; numAttempts++ {
324+
resp, err = aac.c.Do(req)
325+
326+
// If there is no error, no need to retry the request.
327+
if err == nil {
328+
break
329+
}
330+
331+
// If the context was cancelled or timed out, return early
332+
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
333+
return nil, fmt.Errorf(
334+
"executing http request %s %s (attempt %d of %d) cancelled or timed out: %w",
335+
req.Method, req.URL, numAttempts+1, maxAttemptsForArtifactsAPICall, err,
336+
)
337+
}
338+
339+
aac.logger.Logf(
340+
"failed attempt %d of %d executing http request %s %s: %s; retrying after %v...",
341+
numAttempts+1, maxAttemptsForArtifactsAPICall, req.Method, req.URL, err.Error(), retryIntervalForArtifactsAPICall,
342+
)
343+
time.Sleep(retryIntervalForArtifactsAPICall)
344+
}
345+
314346
if err != nil {
315-
return nil, fmt.Errorf("executing http request %v: %w", req, err)
347+
return nil, fmt.Errorf(
348+
"failed executing http request %s %s after %d attempts: %w",
349+
req.Method, req.URL, maxAttemptsForArtifactsAPICall, err,
350+
)
316351
}
317352

318353
return resp, nil
@@ -341,7 +376,7 @@ type logger interface {
341376
Logf(format string, args ...any)
342377
}
343378

344-
func (aac ArtifactAPIClient) GetLatestSnapshotVersion(ctx context.Context, log logger) (*version.ParsedSemVer, error) {
379+
func (aac ArtifactAPIClient) GetLatestSnapshotVersion(ctx context.Context) (*version.ParsedSemVer, error) {
345380
vList, err := aac.GetVersions(ctx)
346381
if err != nil {
347382
return nil, err
@@ -355,7 +390,7 @@ func (aac ArtifactAPIClient) GetLatestSnapshotVersion(ctx context.Context, log l
355390
for _, v := range vList.Versions {
356391
pv, err := version.ParseVersion(v)
357392
if err != nil {
358-
log.Logf("invalid version retrieved from artifact API: %q", v)
393+
aac.logger.Logf("invalid version retrieved from artifact API: %q", v)
359394
return nil, ErrInvalidVersionRetrieved
360395
}
361396
sortedParsedVersions = append(sortedParsedVersions, pv)

pkg/testing/tools/artifacts_api_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func TestDefaultArtifactAPIClientErrorHttpStatus(t *testing.T) {
171171
testSrv := httptest.NewServer(errorHandler)
172172
defer testSrv.Close()
173173

174-
aac := NewArtifactAPIClient(WithUrl(testSrv.URL))
174+
aac := NewArtifactAPIClient(t, WithUrl(testSrv.URL))
175175
_, err := aac.GetVersions(context.Background())
176176
assert.ErrorIs(t, err, ErrBadHTTPStatusCode, "Expected ErrBadHTTPStatusCode for status code %d", httpErrorCode)
177177
_, err = aac.GetBuildsForVersion(context.Background(), "1.2.3-SNAPSHOT")
@@ -201,7 +201,7 @@ func TestDefaultArtifactAPIClient(t *testing.T) {
201201
testSrv := httptest.NewServer(cannedRespHandler)
202202
defer testSrv.Close()
203203

204-
aac := NewArtifactAPIClient(WithUrl(testSrv.URL))
204+
aac := NewArtifactAPIClient(t, WithUrl(testSrv.URL))
205205
versions, err := aac.GetVersions(context.Background())
206206
assert.NoError(t, err)
207207
assert.NotNil(t, versions)
@@ -245,7 +245,7 @@ func TestRemoveUnreleasedVersions(t *testing.T) {
245245
resp.WriteHeader(http.StatusOK)
246246
}))
247247

248-
client := NewArtifactAPIClient(WithCDNUrl(server.URL))
248+
client := NewArtifactAPIClient(t, WithCDNUrl(server.URL))
249249

250250
t.Run("removes unreleased versions", func(t *testing.T) {
251251
unreleasedVersions = map[string]struct{}{

testing/integration/upgrade_broken_package_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestUpgradeBrokenPackageVersion(t *testing.T) {
4141
require.NoError(t, err)
4242

4343
// Upgrade to an old build.
44-
upgradeToVersion, err := upgradetest.PreviousMinor(ctx, define.Version())
44+
upgradeToVersion, err := upgradetest.PreviousMinor(ctx, define.Version(), t)
4545
require.NoError(t, err)
4646
endFixture, err := atesting.NewFixture(
4747
t,

testing/integration/upgrade_downgrade_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ func TestStandaloneDowngradeToSpecificSnapshotBuild(t *testing.T) {
4242
ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute))
4343
defer cancel()
4444

45-
aac := tools.NewArtifactAPIClient()
46-
latestSnapshotVersion, err := aac.GetLatestSnapshotVersion(ctx, t)
45+
aac := tools.NewArtifactAPIClient(t)
46+
latestSnapshotVersion, err := aac.GetLatestSnapshotVersion(ctx)
4747
require.NoError(t, err)
4848

4949
// start at the build version as we want to test the retry

testing/integration/upgrade_gpg_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func TestStandaloneUpgradeWithGPGFallbackOneRemoteFailing(t *testing.T) {
114114
require.NoError(t, err)
115115

116116
// Upgrade to an old build.
117-
upgradeToVersion, err := upgradetest.PreviousMinor(ctx, define.Version())
117+
upgradeToVersion, err := upgradetest.PreviousMinor(ctx, define.Version(), t)
118118
require.NoError(t, err)
119119
endFixture, err := atesting.NewFixture(
120120
t,

testing/integration/upgrade_rollback_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestStandaloneUpgradeRollback(t *testing.T) {
5151

5252
// Upgrade from an old build because the new watcher from the new build will
5353
// be ran. Otherwise the test will run the old watcher from the old build.
54-
upgradeFromVersion, err := upgradetest.PreviousMinor(ctx, define.Version())
54+
upgradeFromVersion, err := upgradetest.PreviousMinor(ctx, define.Version(), t)
5555
require.NoError(t, err)
5656
startFixture, err := atesting.NewFixture(
5757
t,
@@ -166,7 +166,7 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) {
166166

167167
// Upgrade from an old build because the new watcher from the new build will
168168
// be ran. Otherwise the test will run the old watcher from the old build.
169-
upgradeFromVersion, err := upgradetest.PreviousMinor(ctx, define.Version())
169+
upgradeFromVersion, err := upgradetest.PreviousMinor(ctx, define.Version(), t)
170170
require.NoError(t, err)
171171
startFixture, err := atesting.NewFixture(
172172
t,

testing/integration/upgrade_standalone_inprogress_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func TestStandaloneUpgradeFailsWhenUpgradeIsInProgress(t *testing.T) {
3939
// than the current version and upgrade to the current version. Then we attempt
4040
// upgrading to the current version again, expecting Elastic Agent to disallow
4141
// this second upgrade.
42-
upgradeFromVersion, err := upgradetest.PreviousMinor(ctx, define.Version())
42+
upgradeFromVersion, err := upgradetest.PreviousMinor(ctx, define.Version(), t)
4343
require.NoError(t, err)
4444
startFixture, err := atesting.NewFixture(
4545
t,

testing/integration/upgrade_standalone_retry_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func TestStandaloneUpgradeRetryDownload(t *testing.T) {
4949
require.NoError(t, err, "failed to get end agent build version info")
5050

5151
// Upgrade to an older snapshot build of same version but with a different commit hash
52-
aac := tools.NewArtifactAPIClient()
52+
aac := tools.NewArtifactAPIClient(t)
5353
buildInfo, err := aac.FindBuild(ctx, startVersion.VersionWithPrerelease(), startVersionInfo.Binary.Commit, 0)
5454
if errors.Is(err, tools.ErrBuildNotFound) {
5555
t.Skipf("there is no other build with a non-matching commit hash in the given version %s", define.Version())

testing/integration/upgrade_standalone_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func TestStandaloneUpgrade(t *testing.T) {
3333
// test 2 current 8.x version and 1 previous 7.x version
3434
ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(1*time.Minute))
3535
defer cancel()
36-
versionList, err := upgradetest.GetUpgradableVersions(ctx, define.Version(), 2, 1)
36+
versionList, err := upgradetest.GetUpgradableVersions(ctx, define.Version(), 2, 1, t)
3737
require.NoError(t, err)
3838
endVersion, err := version.ParseVersion(define.Version())
3939
require.NoError(t, err)

testing/integration/upgrade_uninstall_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestStandaloneUpgradeUninstallKillWatcher(t *testing.T) {
5151
// Start on a snapshot build, we want this test to upgrade to our
5252
// build to ensure that the uninstall will kill the watcher.
5353
// We need a snapshot with a non-matching commit hash to perform the upgrade
54-
aac := tools.NewArtifactAPIClient()
54+
aac := tools.NewArtifactAPIClient(t)
5555
buildInfo, err := aac.FindBuild(ctx, endVersion.VersionWithPrerelease(), endVersionInfo.Binary.Commit, 0)
5656
if errors.Is(err, tools.ErrBuildNotFound) {
5757
t.Skipf("there is no other build with a non-matching commit hash in the given version %s", endVersion.VersionWithPrerelease())

testing/upgradetest/versions.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"sort"
1212
"strings"
13+
"testing"
1314

1415
"github.com/elastic/elastic-agent/pkg/testing/tools"
1516
"github.com/elastic/elastic-agent/pkg/version"
@@ -37,8 +38,8 @@ var (
3738
)
3839

3940
// GetUpgradableVersions returns the version that the upgradeToVersion can upgrade from.
40-
func GetUpgradableVersions(ctx context.Context, upgradeToVersion string, currentMajorVersions int, previousMajorVersions int) ([]*version.ParsedSemVer, error) {
41-
aac := tools.NewArtifactAPIClient()
41+
func GetUpgradableVersions(ctx context.Context, upgradeToVersion string, currentMajorVersions int, previousMajorVersions int, t *testing.T) ([]*version.ParsedSemVer, error) {
42+
aac := tools.NewArtifactAPIClient(t)
4243
vList, err := aac.GetVersions(ctx)
4344
if err != nil {
4445
return nil, fmt.Errorf("error retrieving versions from Artifact API: %w", err)
@@ -134,8 +135,8 @@ func getUpgradableVersions(ctx context.Context, vList *tools.VersionList, upgrad
134135
// PreviousMinor gets the previous minor version of the provided version.
135136
//
136137
// This checks with the artifact API to ensure to only return version that have actual builds.
137-
func PreviousMinor(ctx context.Context, version string) (string, error) {
138-
versions, err := GetUpgradableVersions(ctx, version, 1, 0)
138+
func PreviousMinor(ctx context.Context, version string, t *testing.T) (string, error) {
139+
versions, err := GetUpgradableVersions(ctx, version, 1, 0, t)
139140
if err != nil {
140141
return "", err
141142
}

0 commit comments

Comments
 (0)