Skip to content

Commit

Permalink
Add a retryWaitTime option when enabling retry mechanism (#494)
Browse files Browse the repository at this point in the history
Adding a new 'retryWaitTime' field that sets the number of seconds/milli-seconds to sleep between retries for upload, download, move, copy, delete and set/delete properties commands.
  • Loading branch information
gailazar300 authored Dec 29, 2021
1 parent cf6d660 commit 7d6d021
Show file tree
Hide file tree
Showing 14 changed files with 97 additions and 58 deletions.
1 change: 1 addition & 0 deletions access/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func New(config config.Config) (*AccessServicesManager, error) {
AppendPreRequestInterceptor(details.RunPreRequestFunctions).
SetContext(config.GetContext()).
SetRetries(config.GetHttpRetries()).
SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()).
Build()

return manager, err
Expand Down
1 change: 1 addition & 0 deletions artifactory/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func NewWithProgress(config config.Config, progress ioutils.ProgressMgr) (Artifa
AppendPreRequestInterceptor(artDetails.RunPreRequestFunctions).
SetContext(config.GetContext()).
SetRetries(config.GetHttpRetries()).
SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()).
SetHttpClient(config.GetHttpClient()).
Build()
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions artifactory/services/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,10 +431,10 @@ func (us *UploadService) uploadFileFromReader(getReaderFunc func() (io.Reader, e

if !checksumDeployed {
retryExecutor := clientutils.RetryExecutor{
MaxRetries: us.client.GetHttpClient().GetRetries(),
RetriesInterval: 0,
ErrorMessage: fmt.Sprintf("Failure occurred while uploading to %s", targetUrlWithProps),
LogMsgPrefix: logMsgPrefix,
MaxRetries: us.client.GetHttpClient().GetRetries(),
RetriesIntervalMilliSecs: us.client.GetHttpClient().GetRetryWaitTime(),
ErrorMessage: fmt.Sprintf("Failure occurred while uploading to %s", targetUrlWithProps),
LogMsgPrefix: logMsgPrefix,
ExecutionHandler: func() (bool, error) {
uploadZipReader, e := getReaderFunc()
if e != nil {
Expand Down
24 changes: 15 additions & 9 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,22 @@ type Config interface {
GetContext() context.Context
GetHttpTimeout() time.Duration
GetHttpRetries() int
GetHttpRetryWaitMilliSecs() int
GetHttpClient() *http.Client
}

type servicesConfig struct {
auth.ServiceDetails
certificatesPath string
dryRun bool
threads int
logger log.Log
insecureTls bool
ctx context.Context
httpTimeout time.Duration
httpRetries int
httpClient *http.Client
certificatesPath string
dryRun bool
threads int
logger log.Log
insecureTls bool
ctx context.Context
httpTimeout time.Duration
httpRetries int
httpRetryWaitMilliSecs int
httpClient *http.Client
}

func (config *servicesConfig) IsDryRun() bool {
Expand Down Expand Up @@ -70,6 +72,10 @@ func (config *servicesConfig) GetHttpRetries() int {
return config.httpRetries
}

func (config *servicesConfig) GetHttpRetryWaitMilliSecs() int {
return config.httpRetryWaitMilliSecs
}

func (config *servicesConfig) GetHttpClient() *http.Client {
return config.httpClient
}
24 changes: 16 additions & 8 deletions config/configbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ func NewConfigBuilder() *servicesConfigBuilder {
configBuilder.threads = 3
configBuilder.httpTimeout = httpclient.DefaultHttpTimeout
configBuilder.httpRetries = 3
configBuilder.httpRetryWaitMilliSecs = 0
return configBuilder
}

type servicesConfigBuilder struct {
auth.ServiceDetails
certificatesPath string
threads int
isDryRun bool
insecureTls bool
ctx context.Context
httpTimeout time.Duration
httpRetries int
httpClient *http.Client
certificatesPath string
threads int
isDryRun bool
insecureTls bool
ctx context.Context
httpTimeout time.Duration
httpRetries int
httpRetryWaitMilliSecs int
httpClient *http.Client
}

func (builder *servicesConfigBuilder) SetServiceDetails(artDetails auth.ServiceDetails) *servicesConfigBuilder {
Expand Down Expand Up @@ -68,6 +70,11 @@ func (builder *servicesConfigBuilder) SetHttpRetries(httpRetries int) *servicesC
return builder
}

func (builder *servicesConfigBuilder) SetHttpRetryWaitMilliSecs(httpRetryWaitMilliSecs int) *servicesConfigBuilder {
builder.httpRetryWaitMilliSecs = httpRetryWaitMilliSecs
return builder
}

func (builder *servicesConfigBuilder) SetHttpClient(httpClient *http.Client) *servicesConfigBuilder {
builder.httpClient = httpClient
return builder
Expand All @@ -83,6 +90,7 @@ func (builder *servicesConfigBuilder) Build() (Config, error) {
c.ctx = builder.ctx
c.httpTimeout = builder.httpTimeout
c.httpRetries = builder.httpRetries
c.httpRetryWaitMilliSecs = builder.httpRetryWaitMilliSecs
c.httpClient = builder.httpClient
return c, nil
}
1 change: 1 addition & 0 deletions distribution/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func New(config config.Config) (*DistributionServicesManager, error) {
AppendPreRequestInterceptor(details.RunPreRequestFunctions).
SetContext(config.GetContext()).
SetRetries(config.GetHttpRetries()).
SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()).
Build()
return manager, err
}
Expand Down
43 changes: 24 additions & 19 deletions http/httpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ import (
)

type HttpClient struct {
client *http.Client
ctx context.Context
retries int
client *http.Client
ctx context.Context
retries int
retryWaitMilliSecs int
}

func (jc *HttpClient) GetRetries() int {
return jc.retries
}

func (jc *HttpClient) GetRetryWaitTime() int {
return jc.retryWaitMilliSecs
}

func (jc *HttpClient) sendGetLeaveBodyOpen(url string, followRedirect bool, httpClientsDetails httputils.HttpClientDetails, logMsgPrefix string) (resp *http.Response, respBody []byte, redirectUrl string, err error) {
return jc.Send("GET", url, nil, followRedirect, false, httpClientsDetails, logMsgPrefix)
}
Expand Down Expand Up @@ -92,10 +97,10 @@ func (jc *HttpClient) newRequest(method, url string, body io.Reader) (req *http.

func (jc *HttpClient) Send(method, url string, content []byte, followRedirect, closeBody bool, httpClientsDetails httputils.HttpClientDetails, logMsgPrefix string) (resp *http.Response, respBody []byte, redirectUrl string, err error) {
retryExecutor := utils.RetryExecutor{
MaxRetries: jc.retries,
RetriesInterval: 0,
LogMsgPrefix: logMsgPrefix,
ErrorMessage: fmt.Sprintf("Failure occurred while sending %s request to %s", method, url),
MaxRetries: jc.retries,
RetriesIntervalMilliSecs: jc.retryWaitMilliSecs,
LogMsgPrefix: logMsgPrefix,
ErrorMessage: fmt.Sprintf("Failure occurred while sending %s request to %s", method, url),
ExecutionHandler: func() (bool, error) {
req, err := jc.createReq(method, url, content)
if err != nil {
Expand Down Expand Up @@ -191,10 +196,10 @@ func setRequestHeaders(httpClientsDetails httputils.HttpClientDetails, size int6
func (jc *HttpClient) UploadFile(localPath, url, logMsgPrefix string, httpClientsDetails httputils.HttpClientDetails,
progress ioutils.ProgressMgr) (resp *http.Response, body []byte, err error) {
retryExecutor := utils.RetryExecutor{
MaxRetries: jc.retries,
RetriesInterval: 0,
ErrorMessage: fmt.Sprintf("Failure occurred while uploading to %s", url),
LogMsgPrefix: logMsgPrefix,
MaxRetries: jc.retries,
RetriesIntervalMilliSecs: jc.retryWaitMilliSecs,
ErrorMessage: fmt.Sprintf("Failure occurred while uploading to %s", url),
LogMsgPrefix: logMsgPrefix,
ExecutionHandler: func() (bool, error) {
resp, body, err = jc.doUploadFile(localPath, url, httpClientsDetails, progress)
if err != nil {
Expand Down Expand Up @@ -309,10 +314,10 @@ func (jc *HttpClient) DownloadFileNoRedirect(downloadPath, localPath, fileName s
func (jc *HttpClient) downloadFile(downloadFileDetails *DownloadFileDetails, logMsgPrefix string, followRedirect bool,
httpClientsDetails httputils.HttpClientDetails, isExplode bool, progress ioutils.ProgressMgr) (resp *http.Response, redirectUrl string, err error) {
retryExecutor := utils.RetryExecutor{
MaxRetries: jc.retries,
RetriesInterval: 0,
ErrorMessage: fmt.Sprintf("Failure occurred while downloading %s", downloadFileDetails.DownloadPath),
LogMsgPrefix: logMsgPrefix,
MaxRetries: jc.retries,
RetriesIntervalMilliSecs: jc.retryWaitMilliSecs,
ErrorMessage: fmt.Sprintf("Failure occurred while downloading %s", downloadFileDetails.DownloadPath),
LogMsgPrefix: logMsgPrefix,
ExecutionHandler: func() (bool, error) {
resp, redirectUrl, err = jc.doDownloadFile(downloadFileDetails, logMsgPrefix, followRedirect, httpClientsDetails, isExplode, progress)
// In case followRedirect is 'false' and doDownloadFile did redirect, an error is returned and redirectUrl
Expand Down Expand Up @@ -598,10 +603,10 @@ func mergeChunks(chunksPaths []string, flags ConcurrentDownloadFlags) error {
func (jc *HttpClient) downloadFileRange(flags ConcurrentDownloadFlags, start, end int64, currentSplit int, logMsgPrefix, chunkDownloadPath string,
httpClientsDetails httputils.HttpClientDetails, progress ioutils.ProgressMgr, progressId int) (fileName string, resp *http.Response, err error) {
retryExecutor := utils.RetryExecutor{
MaxRetries: jc.retries,
RetriesInterval: 0,
ErrorMessage: fmt.Sprintf("Failure occurred while downloading part %d of %s", currentSplit, flags.DownloadPath),
LogMsgPrefix: fmt.Sprintf("%s[%s]: ", logMsgPrefix, strconv.Itoa(currentSplit)),
MaxRetries: jc.retries,
RetriesIntervalMilliSecs: jc.retryWaitMilliSecs,
ErrorMessage: fmt.Sprintf("Failure occurred while downloading part %d of %s", currentSplit, flags.DownloadPath),
LogMsgPrefix: fmt.Sprintf("%s[%s]: ", logMsgPrefix, strconv.Itoa(currentSplit)),
ExecutionHandler: func() (bool, error) {
fileName, resp, err = jc.doDownloadFileRange(flags, start, end, currentSplit, logMsgPrefix, chunkDownloadPath, httpClientsDetails, progress, progressId)
if err != nil {
Expand Down
10 changes: 8 additions & 2 deletions http/httpclient/clientBuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type httpClientBuilder struct {
ctx context.Context
timeout time.Duration
retries int
retryWaitMilliSecs int
httpClient *http.Client
}

Expand Down Expand Up @@ -70,6 +71,11 @@ func (builder *httpClientBuilder) SetRetries(retries int) *httpClientBuilder {
return builder
}

func (builder *httpClientBuilder) SetRetryWaitMilliSecs(retryWaitMilliSecs int) *httpClientBuilder {
builder.retryWaitMilliSecs = retryWaitMilliSecs
return builder
}

func (builder *httpClientBuilder) AddClientCertToTransport(transport *http.Transport) error {
if builder.clientCertPath != "" {
certificate, err := tls.LoadX509KeyPair(builder.clientCertPath, builder.clientCertKeyPath)
Expand All @@ -85,7 +91,7 @@ func (builder *httpClientBuilder) AddClientCertToTransport(transport *http.Trans
func (builder *httpClientBuilder) Build() (*HttpClient, error) {
if builder.httpClient != nil {
// Using a custom http.Client, pass-though.
return &HttpClient{client: builder.httpClient, ctx: builder.ctx, retries: builder.retries}, nil
return &HttpClient{client: builder.httpClient, ctx: builder.ctx, retries: builder.retries, retryWaitMilliSecs: builder.retryWaitMilliSecs}, nil
}

var err error
Expand All @@ -101,7 +107,7 @@ func (builder *httpClientBuilder) Build() (*HttpClient, error) {
}
}
err = builder.AddClientCertToTransport(transport)
return &HttpClient{client: &http.Client{Transport: transport}, ctx: builder.ctx, retries: builder.retries}, err
return &HttpClient{client: &http.Client{Transport: transport}, ctx: builder.ctx, retries: builder.retries, retryWaitMilliSecs: builder.retryWaitMilliSecs}, err
}

func (builder *httpClientBuilder) createDefaultHttpTransport() *http.Transport {
Expand Down
7 changes: 7 additions & 0 deletions http/jfroghttpclient/clientbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type jfrogHttpClientBuilder struct {
insecureTls bool
ctx context.Context
retries int
retryWaitTimMilliSecs int
preRequestInterceptors []PreRequestInterceptorFunc
clientCertPath string
clientCertKeyPath string
Expand Down Expand Up @@ -55,6 +56,11 @@ func (builder *jfrogHttpClientBuilder) SetRetries(retries int) *jfrogHttpClientB
return builder
}

func (builder *jfrogHttpClientBuilder) SetRetryWaitMilliSecs(retryWaitMilliSecs int) *jfrogHttpClientBuilder {
builder.retryWaitTimMilliSecs = retryWaitMilliSecs
return builder
}

func (builder *jfrogHttpClientBuilder) AppendPreRequestInterceptor(interceptor PreRequestInterceptorFunc) *jfrogHttpClientBuilder {
builder.preRequestInterceptors = append(builder.preRequestInterceptors, interceptor)
return builder
Expand All @@ -80,6 +86,7 @@ func (builder *jfrogHttpClientBuilder) Build() (rtHttpClient *JfrogHttpClient, e
SetContext(builder.ctx).
SetTimeout(builder.timeout).
SetRetries(builder.retries).
SetRetryWaitMilliSecs(builder.retryWaitTimMilliSecs).
SetHttpClient(builder.httpClient).
Build()
return
Expand Down
1 change: 1 addition & 0 deletions pipelines/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func New(config config.Config) (*PipelinesServicesManager, error) {
AppendPreRequestInterceptor(details.RunPreRequestFunctions).
SetContext(config.GetContext()).
SetRetries(config.GetHttpRetries()).
SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()).
Build()
return manager, err
}
Expand Down
9 changes: 5 additions & 4 deletions tests/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,10 +821,11 @@ func createRepoConfigValidationFunc(repoKey string, expectedConfig interface{})

func validateRepoConfig(t *testing.T, repoKey string, params interface{}) {
retryExecutor := &clientutils.RetryExecutor{
MaxRetries: 12,
RetriesInterval: 10,
ErrorMessage: "Waiting for Artifactory to evaluate repository operation...",
ExecutionHandler: createRepoConfigValidationFunc(repoKey, params),
MaxRetries: 12,
// RetriesIntervalMilliSecs in milliseconds
RetriesIntervalMilliSecs: 10 * 1000,
ErrorMessage: "Waiting for Artifactory to evaluate repository operation...",
ExecutionHandler: createRepoConfigValidationFunc(repoKey, params),
}
err := retryExecutor.Execute()
assert.NoError(t, err)
Expand Down
12 changes: 7 additions & 5 deletions utils/retryexecutor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package utils
import (
"fmt"
"github.com/jfrog/jfrog-client-go/utils/log"
"strconv"
"time"
)

Expand All @@ -12,8 +13,8 @@ type RetryExecutor struct {
// The amount of retries to perform.
MaxRetries int

// Number of seconds to sleep between retries.
RetriesInterval int
// Number of milliseconds to sleep between retries.
RetriesIntervalMilliSecs int

// Message to display when retrying.
ErrorMessage string
Expand All @@ -38,9 +39,10 @@ func (runner *RetryExecutor) Execute() error {
}

log.Warn(runner.getLogRetryMessage(i, err))
// Going to sleep for RetryInterval seconds
if runner.RetriesInterval > 0 && i < runner.MaxRetries {
time.Sleep(time.Second * time.Duration(runner.RetriesInterval))
// Going to sleep for RetryInterval milliseconds
if runner.RetriesIntervalMilliSecs > 0 && i < runner.MaxRetries {
log.Info("Waiting ", strconv.Itoa(runner.RetriesIntervalMilliSecs), "ms before trying again")
time.Sleep(time.Millisecond * time.Duration(runner.RetriesIntervalMilliSecs))
}
}

Expand Down
13 changes: 6 additions & 7 deletions utils/retryexecutor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ func TestRetryExecutorSuccess(t *testing.T) {
retriesToPerform := 10
breakRetriesAt := 4
runCount := 0

executor := RetryExecutor{
MaxRetries: retriesToPerform,
RetriesInterval: 0,
ErrorMessage: "Testing RetryExecutor",
MaxRetries: retriesToPerform,
RetriesIntervalMilliSecs: 0,
ErrorMessage: "Testing RetryExecutor",
ExecutionHandler: func() (bool, error) {
runCount++
if runCount == breakRetriesAt {
Expand All @@ -37,9 +36,9 @@ func TestRetryExecutorFail(t *testing.T) {
runCount := 0

executor := RetryExecutor{
MaxRetries: retriesToPerform,
RetriesInterval: 0,
ErrorMessage: "Testing RetryExecutor",
MaxRetries: retriesToPerform,
RetriesIntervalMilliSecs: 0,
ErrorMessage: "Testing RetryExecutor",
ExecutionHandler: func() (bool, error) {
runCount++
return true, nil
Expand Down
1 change: 1 addition & 0 deletions xray/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func New(config config.Config) (*XrayServicesManager, error) {
SetClientCertKeyPath(details.GetClientCertKeyPath()).
AppendPreRequestInterceptor(details.RunPreRequestFunctions).
SetRetries(config.GetHttpRetries()).
SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()).
Build()
return manager, err
}
Expand Down

0 comments on commit 7d6d021

Please sign in to comment.