@@ -462,6 +462,15 @@ public static function successDataProvider(): array
462462 ]);
463463 $ rejectionRawConnectException = Promise \Create::rejectionFor ($ connectException );
464464
465+ $ tooManyRequestsException = self ::createRequestException (
466+ '429 Too Many Requests ' ,
467+ new Psr7 \Request ('GET ' , '/latest ' ),
468+ new Psr7 \Response (429 )
469+ );
470+ $ rejectionTooManyRequests = Promise \Create::rejectionFor ([
471+ 'exception ' => $ tooManyRequestsException ,
472+ ]);
473+
465474 $ promiseCreds = Promise \Create::promiseFor (
466475 new Response (200 , [], Psr7 \Utils::streamFor (
467476 json_encode (call_user_func_array (
@@ -524,6 +533,135 @@ public static function successDataProvider(): array
524533 ];
525534 }
526535
536+ public function testRetriesOptedInErrorCode ()
537+ {
538+ $ expiry = time () + 1000 ;
539+ $ creds = ['foo_key ' , 'baz_secret ' , 'qux_token ' , "@ {$ expiry }" ];
540+
541+ $ rejectionTooManyRequests = Promise \Create::rejectionFor ([
542+ 'exception ' => self ::createRequestException (
543+ '429 Too Many Requests ' ,
544+ new Psr7 \Request ('GET ' , '/latest ' ),
545+ new Psr7 \Response (429 )
546+ ),
547+ ]);
548+ $ promiseCreds = Promise \Create::promiseFor (
549+ new Response (200 , [], Psr7 \Utils::streamFor (
550+ json_encode (call_user_func_array (
551+ [self ::class, 'getCredentialArray ' ],
552+ $ creds
553+ )))
554+ )
555+ );
556+
557+ $ provider = new EcsCredentialProvider ([
558+ 'client ' => $ this ->getTestClient ([
559+ $ rejectionTooManyRequests ,
560+ $ promiseCreds ,
561+ ], $ creds ),
562+ 'retries ' => 2 ,
563+ 'retryable_error_codes ' => [429 ],
564+ ]);
565+
566+ $ credentials = $ provider ()->wait ();
567+ $ this ->assertSame ('foo_key ' , $ credentials ->getAccessKeyId ());
568+ $ this ->assertSame ('baz_secret ' , $ credentials ->getSecretKey ());
569+ }
570+
571+ public function testDoesNotRetry429ByDefault ()
572+ {
573+ $ rejectionTooManyRequests = Promise \Create::rejectionFor ([
574+ 'exception ' => self ::createRequestException (
575+ '429 Too Many Requests ' ,
576+ new Psr7 \Request ('GET ' , '/latest ' ),
577+ new Psr7 \Response (429 )
578+ ),
579+ ]);
580+
581+ $ provider = new EcsCredentialProvider ([
582+ 'client ' => $ this ->getTestClient ([
583+ $ rejectionTooManyRequests ,
584+ ]),
585+ 'retries ' => 3 ,
586+ ]);
587+
588+ try {
589+ $ provider ()->wait ();
590+ $ this ->fail ('Provider should have thrown an exception. ' );
591+ } catch (CredentialsException $ e ) {
592+ $ this ->assertStringContainsString (
593+ 'attempt 0/3 ' ,
594+ $ e ->getMessage ()
595+ );
596+ $ this ->assertStringContainsString ('429 Too Many Requests ' , $ e ->getMessage ());
597+ }
598+
599+ $ this ->assertSame (0 , $ provider ->getAttempts ());
600+ }
601+
602+ public function testRetriesOptedInExceptionClass ()
603+ {
604+ $ expiry = time () + 1000 ;
605+ $ creds = ['foo_key ' , 'baz_secret ' , 'qux_token ' , "@ {$ expiry }" ];
606+
607+ $ rejectionRequest = Promise \Create::rejectionFor ([
608+ 'exception ' => new \DomainException ('Boom ' ),
609+ ]);
610+ $ promiseCreds = Promise \Create::promiseFor (
611+ new Response (200 , [], Psr7 \Utils::streamFor (
612+ json_encode (call_user_func_array (
613+ [self ::class, 'getCredentialArray ' ],
614+ $ creds
615+ )))
616+ )
617+ );
618+
619+ $ provider = new EcsCredentialProvider ([
620+ 'client ' => $ this ->getTestClient ([
621+ $ rejectionRequest ,
622+ $ promiseCreds ,
623+ ], $ creds ),
624+ 'retries ' => 2 ,
625+ 'retryable_exceptions ' => [\DomainException::class],
626+ ]);
627+
628+ $ credentials = $ provider ()->wait ();
629+ $ this ->assertSame ('foo_key ' , $ credentials ->getAccessKeyId ());
630+ }
631+
632+ public function testCustomRetryableExceptionsAreAddedToDefaults ()
633+ {
634+ $ expiry = time () + 1000 ;
635+ $ creds = ['foo_key ' , 'baz_secret ' , 'qux_token ' , "@ {$ expiry }" ];
636+
637+ $ rejectionConnection = Promise \Create::rejectionFor ([
638+ 'exception ' => new ConnectException (
639+ 'cURL error 28: Connection timed out after 1000 milliseconds ' ,
640+ new Psr7 \Request ('GET ' , '/latest ' )
641+ ),
642+ ]);
643+ $ promiseCreds = Promise \Create::promiseFor (
644+ new Response (200 , [], Psr7 \Utils::streamFor (
645+ json_encode (call_user_func_array (
646+ [self ::class, 'getCredentialArray ' ],
647+ $ creds
648+ )))
649+ )
650+ );
651+
652+ $ provider = new EcsCredentialProvider ([
653+ 'client ' => $ this ->getTestClient ([
654+ $ rejectionConnection ,
655+ $ promiseCreds ,
656+ ], $ creds ),
657+ 'retries ' => 2 ,
658+ 'retryable_exceptions ' => [\DomainException::class],
659+ ]);
660+
661+ $ credentials = $ provider ()->wait ();
662+ $ this ->assertSame ('foo_key ' , $ credentials ->getAccessKeyId ());
663+ }
664+
527665 /**
528666 * @param $client
529667 * @param \Exception $expected
@@ -566,6 +704,13 @@ public static function failureDataProvider(): array
566704 'connection_error ' => true ,
567705 'exception ' => new \Exception ('cURL error 28: Connection timed out after 1000 milliseconds ' ),
568706 ]);
707+ $ rejectionTooManyRequests = Promise \Create::rejectionFor ([
708+ 'exception ' => self ::createRequestException (
709+ '429 Too Many Requests ' ,
710+ $ getRequest ,
711+ new Psr7 \Response (429 )
712+ )
713+ ]);
569714
570715 return [
571716 'Non-retryable error ' => [
@@ -585,9 +730,47 @@ public static function failureDataProvider(): array
585730 'Error retrieving credentials from container metadata after attempt 1/1 (cURL error 28: Connection timed out after 1000 milliseconds) '
586731 )
587732 ],
733+ 'Non-retryable HTTP 429 by default ' => [
734+ [
735+ $ rejectionTooManyRequests ,
736+ ],
737+ new CredentialsException (
738+ 'Error retrieving credentials from container metadata after attempt 0/1 (429 Too Many Requests) '
739+ )
740+ ],
588741 ];
589742 }
590743
744+ public function testOptedInHTTP429RetryExhaustsAttempts ()
745+ {
746+ $ rejectionTooManyRequests = Promise \Create::rejectionFor ([
747+ 'exception ' => self ::createRequestException (
748+ '429 Too Many Requests ' ,
749+ new Psr7 \Request ('GET ' , '/latest ' ),
750+ new Psr7 \Response (429 )
751+ ),
752+ ]);
753+
754+ $ provider = new EcsCredentialProvider ([
755+ 'client ' => $ this ->getTestClient ([
756+ $ rejectionTooManyRequests ,
757+ $ rejectionTooManyRequests ,
758+ ]),
759+ 'retries ' => 1 ,
760+ 'retryable_error_codes ' => [429 ],
761+ ]);
762+
763+ try {
764+ $ provider ()->wait ();
765+ $ this ->fail ('Provider should have thrown an exception. ' );
766+ } catch (CredentialsException $ e ) {
767+ $ this ->assertSame (
768+ 'Error retrieving credentials from container metadata after attempt 1/1 (429 Too Many Requests) ' ,
769+ $ e ->getMessage ()
770+ );
771+ }
772+ }
773+
591774 public function testReadsRetriesFromEnvironment ()
592775 {
593776 putenv ('AWS_METADATA_SERVICE_NUM_ATTEMPTS=1 ' );
0 commit comments