-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix issues with the retry logic in the OktaApiClient and OktaRetryApi classes (#61) #62
base: main
Are you sure you want to change the base?
Changes from all commits
a560d16
66d2c05
327d7a0
7e8c8ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -151,7 +151,8 @@ function Invoke-OktaApiClient { | |||||||||||||||||||||||||||||||||||||||
-ErrorAction Stop ` | ||||||||||||||||||||||||||||||||||||||||
-UseBasicParsing ` | ||||||||||||||||||||||||||||||||||||||||
-SkipCertificateCheck ` | ||||||||||||||||||||||||||||||||||||||||
-UserAgent $OktaUserAgent | ||||||||||||||||||||||||||||||||||||||||
-UserAgent $OktaUserAgent ` | ||||||||||||||||||||||||||||||||||||||||
-SkipHttpErrorCheck | ||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||
# skip certification check, use proxy | ||||||||||||||||||||||||||||||||||||||||
$RawResponse = Invoke-WebRequest -Uri $UriBuilder.Uri ` | ||||||||||||||||||||||||||||||||||||||||
|
@@ -163,7 +164,8 @@ function Invoke-OktaApiClient { | |||||||||||||||||||||||||||||||||||||||
-SkipCertificateCheck ` | ||||||||||||||||||||||||||||||||||||||||
-Proxy $Configuration["Proxy"].GetProxy($UriBuilder.Uri) ` | ||||||||||||||||||||||||||||||||||||||||
-ProxyUseDefaultCredentials ` | ||||||||||||||||||||||||||||||||||||||||
-UserAgent $OktaUserAgent | ||||||||||||||||||||||||||||||||||||||||
-UserAgent $OktaUserAgent ` | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Considering your comment about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I gave that a look and the tests pass because the calls to IWR are being Mock'ed out. For example in this block okta-powershell-cli/tests/Internal/OktaApiClient.Tests.ps1 Lines 9 to 27 in f78b2c6
Calls to I cant think of an "easy" way to unit test IWR getting an actual 429 response thats not overly contrived - eg mock an internal part of that method - so powershells IWR method gets the 429, or call an actual weburl tat forcibly returns 429 - both seem more like integration tests than unit tests though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense, thanks for clarifying! |
||||||||||||||||||||||||||||||||||||||||
-SkipHttpErrorCheck | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||
if ($null -eq $Configuration["Proxy"]) { | ||||||||||||||||||||||||||||||||||||||||
|
@@ -174,7 +176,8 @@ function Invoke-OktaApiClient { | |||||||||||||||||||||||||||||||||||||||
-Body $RequestBody ` | ||||||||||||||||||||||||||||||||||||||||
-ErrorAction Stop ` | ||||||||||||||||||||||||||||||||||||||||
-UseBasicParsing ` | ||||||||||||||||||||||||||||||||||||||||
-UserAgent $OktaUserAgent | ||||||||||||||||||||||||||||||||||||||||
-UserAgent $OktaUserAgent ` | ||||||||||||||||||||||||||||||||||||||||
-SkipHttpErrorCheck | ||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||
# perform certification check, use proxy | ||||||||||||||||||||||||||||||||||||||||
$RawResponse = Invoke-WebRequest -Uri $UriBuilder.Uri ` | ||||||||||||||||||||||||||||||||||||||||
|
@@ -185,7 +188,8 @@ function Invoke-OktaApiClient { | |||||||||||||||||||||||||||||||||||||||
-UseBasicParsing ` | ||||||||||||||||||||||||||||||||||||||||
-Proxy $Configuration["Proxy"].GetProxy($UriBuilder.Uri) ` | ||||||||||||||||||||||||||||||||||||||||
-ProxyUseDefaultCredentials ` | ||||||||||||||||||||||||||||||||||||||||
-UserAgent $OktaUserAgent | ||||||||||||||||||||||||||||||||||||||||
-UserAgent $OktaUserAgent ` | ||||||||||||||||||||||||||||||||||||||||
-SkipHttpErrorCheck | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
$Response = $null | ||||||||||||||||||||||||||||||||||||||||
|
@@ -204,7 +208,11 @@ function Invoke-OktaApiClient { | |||||||||||||||||||||||||||||||||||||||
if ($WaitInMilliseconds -gt 0) { | ||||||||||||||||||||||||||||||||||||||||
$RetryCount = $RetryCount + 1 | ||||||||||||||||||||||||||||||||||||||||
$RequestId = $Headers['X-Okta-Request-Id'][0] | ||||||||||||||||||||||||||||||||||||||||
AddRetryHeaders -Headers $HeaderParameters -RequestId $RequestId -RetryCount $RetryCount | ||||||||||||||||||||||||||||||||||||||||
# This has to be piped to null as the AddRetryHeaders function has a return that adds to the pipeline | ||||||||||||||||||||||||||||||||||||||||
# and messes up the callers that assume they are getting a response object and not an array of headers | ||||||||||||||||||||||||||||||||||||||||
# and a response if there is a retry | ||||||||||||||||||||||||||||||||||||||||
AddRetryHeaders -Headers $HeaderParameters -RequestId $RequestId -RetryCount $RetryCount | out-null | ||||||||||||||||||||||||||||||||||||||||
Write-Verbose "Hit Rate limit: Retrying request after $WaitInMilliseconds milliseconds" | ||||||||||||||||||||||||||||||||||||||||
Start-Sleep -Milliseconds $WaitInMilliseconds | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
else { | ||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,15 +5,16 @@ Context 'Invoke-OktaApiClient - 429 Responses' { | |
$Config.RequestTimeout = $null | ||
$Now = Get-Date # Used as a reference for the test. Indicates when the request was executed | ||
$ResetDate = $Now.AddSeconds(3) # Indicates when one should retry | ||
|
||
$ResetDateEpoch = $ResetDate.ToUniversalTime().Subtract((New-Object DateTime(1970, 1, 1, 0, 0, 0, 0))).TotalSeconds | ||
|
||
Mock -ModuleName Okta.PowerShell Get-OktaConfiguration { return $Config } -Verifiable | ||
|
||
$Response = [PSCustomObject]@{ | ||
Content = "[]" | ||
Headers = @{ | ||
"Date" = @($Now) | ||
"Content-Type" = @('application/json; charset=utf-8') | ||
"X-Rate-Limit-Reset" = @($ResetDate) | ||
"x-rate-limit-reset" = @($ResetDateEpoch) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we also need to fix the casing (or make the appropriate changes to handle headers in a case-insensitive way) of additional headers such as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting one, I didn't look at that one because it wasn't erroring - now im questioning my case sensitive testing for sure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is a great question and I didnt do that one because it didn't throw errors on me. Im now questioning myself about the case behavior - Ill retest it on the weekend There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies for the delay, I started in on this and it got weird, but I finally found it Heres a screenshot of the debugging and some annotations, So whats happening is that inside Invoke-OptaApiClient the $headers that come back from the Invoke are taken from the rawResponse.Headers. These are a Dictionary and the reference to the Dictionary keyvalue pairs is case insensitive as you can see in the debug checks using various casing in the box that points to 1 When this Dictionary is passed in to CalculateDelay the Dictionary is cast to a Hashtable which behaves in a case sensitive behavior - as you can see in the debug results in the box pointing to 2 So the short answer is the casing re There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for sharing your findings, and we appreciate the time you took to look into this! |
||
"X-Okta-Request-Id" = @("foo") | ||
} | ||
StatusCode = 429 | ||
|
@@ -33,6 +34,7 @@ Context 'Invoke-OktaApiClient - 429 Responses' { | |
$Config.RequestTimeout = 100000 | ||
$Now = Get-Date # Used as a reference for the test. Indicates when the request was executed | ||
$ResetDate = $Now.AddSeconds(3) # Indicates when one should retry | ||
$ResetDateEpoch = $ResetDate.ToUniversalTime().Subtract((New-Object DateTime(1970, 1, 1, 0, 0, 0, 0))).TotalSeconds | ||
|
||
Mock -ModuleName Okta.PowerShell Get-OktaConfiguration { return $Config } -Verifiable | ||
|
||
|
@@ -41,7 +43,7 @@ Context 'Invoke-OktaApiClient - 429 Responses' { | |
Headers = @{ | ||
"Date" = @($Now) | ||
"Content-Type" = @('application/json; charset=utf-8') | ||
"X-Rate-Limit-Reset" = @($ResetDate) | ||
"x-rate-limit-reset" = @($ResetDateEpoch) | ||
"X-Okta-Request-Id" = @("foo") | ||
} | ||
StatusCode = 429 | ||
|
@@ -61,6 +63,7 @@ Context 'Invoke-OktaApiClient - 429 Responses' { | |
$Config.RequestTimeout = 1000 | ||
$Now = Get-Date # Used as a reference for the test. Indicates when the request was executed | ||
$ResetDate = $Now.AddSeconds(3) # Indicates when one should retry | ||
$ResetDateEpoch = $ResetDate.ToUniversalTime().Subtract((New-Object DateTime(1970, 1, 1, 0, 0, 0, 0))).TotalSeconds | ||
|
||
Mock -ModuleName Okta.PowerShell Get-OktaConfiguration { return $Config } -Verifiable | ||
|
||
|
@@ -71,7 +74,7 @@ Context 'Invoke-OktaApiClient - 429 Responses' { | |
Headers = @{ | ||
"Date" = @($Now) | ||
"Content-Type" = @('application/json; charset=utf-8') | ||
"X-Rate-Limit-Reset" = @($ResetDate) | ||
"x-rate-limit-reset" = @($ResetDateEpoch) | ||
"X-Okta-Request-Id" = @("foo") | ||
} | ||
StatusCode = 429 | ||
|
@@ -92,6 +95,7 @@ Context 'Invoke-OktaApiClient - 429 Responses' { | |
$Config.RequestTimeout = 1000 | ||
$Now = Get-Date # Used as a reference for the test. Indicates when the request was executed | ||
$ResetDate = $Now.AddSeconds(3) # Indicates when one should retry | ||
$ResetDateEpoch = $ResetDate.ToUniversalTime().Subtract((New-Object DateTime(1970, 1, 1, 0, 0, 0, 0))).TotalSeconds | ||
|
||
Mock -ModuleName Okta.PowerShell Get-OktaConfiguration { return $Config } -Verifiable | ||
|
||
|
@@ -102,7 +106,7 @@ Context 'Invoke-OktaApiClient - 429 Responses' { | |
Headers = @{ | ||
"Date" = @($Now) | ||
"Content-Type" = @('application/json; charset=utf-8') | ||
"X-Rate-Limit-Reset" = @($ResetDate) | ||
"x-rate-limit-reset" = @($ResetDateEpoch) | ||
"X-Okta-Request-Id" = @("foo") | ||
} | ||
StatusCode = 400 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the
SkipHttpErrorCheck
param is supported by PS 7+, and the Okta PS module supports PS 6.2+, do you have any thoughts on how this should be handled in PS 6+?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, interesting one, I did look at Powershell 5.1, but didn't look at PS 6 vs 7 consideration apologies.
Off top of my head can think of three ways to handle it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with these three options. I'm inclined towards 1, but I would also consider number 3. We'll have to revisit the status of previous PS versions starting from 6.2.