2121import com .google .bigtable .repackaged .com .google .gson .Gson ;
2222import com .google .cloud .hadoop .util .AccessTokenProvider ;
2323import com .google .cloud .hadoop .util .CredentialFactory ;
24- import io .cdap .cdap .api .exception .ErrorCategory ;
25- import io .cdap .cdap .api .exception .ErrorCategory .ErrorCategoryEnum ;
24+ import com .google .common .annotations .VisibleForTesting ;
25+ import dev .failsafe .Failsafe ;
26+ import dev .failsafe .FailsafeException ;
27+ import dev .failsafe .RetryPolicy ;
2628import io .cdap .cdap .api .exception .ErrorType ;
27- import io .cdap .cdap . api . exception . ErrorUtils ;
29+ import io .cdap .plugin . gcp . common . GCPErrorDetailsProviderUtil ;
2830import io .cdap .plugin .gcp .common .GCPUtils ;
31+ import io .cdap .plugin .gcp .common .ServerErrorException ;
2932import org .apache .hadoop .conf .Configuration ;
33+ import org .apache .http .HttpStatus ;
34+ import org .slf4j .Logger ;
35+ import org .slf4j .LoggerFactory ;
3036
3137import java .io .IOException ;
38+ import java .time .Duration ;
3239import java .time .Instant ;
3340import java .util .Date ;
41+ import java .util .regex .Pattern ;
3442import java .util .stream .Collectors ;
3543import java .util .stream .Stream ;
3644
@@ -43,19 +51,65 @@ public class ServiceAccountAccessTokenProvider implements AccessTokenProvider {
4351 private Configuration conf ;
4452 private GoogleCredentials credentials ;
4553 private static final Gson GSON = new Gson ();
54+ private static final Logger LOG = LoggerFactory .getLogger (ServiceAccountAccessTokenProvider .class );
55+ public static final int DEFAULT_INITIAL_RETRY_DURATION_SECONDS = 5 ;
56+ public static final int DEFAULT_MAX_RETRY_COUNT = 5 ;
57+ public static final int DEFAULT_MAX_RETRY_DURATION_SECONDS = 80 ;
58+ private static final RetryPolicy <Object > RETRY_POLICY = createRetryPolicy ();
59+ private static final Pattern SERVER_ERROR_PATTERN = Pattern .compile ("Unexpected Error code 5\\ d{2} trying to get " +
60+ "security access token from Compute Engine metadata for the default service account.*" );
4661
62+ @ VisibleForTesting
4763 @ Override
4864 public AccessToken getAccessToken () {
49- try {
50- com .google .auth .oauth2 .AccessToken token = getCredentials ().getAccessToken ();
51- if (token == null || token .getExpirationTime ().before (Date .from (Instant .now ()))) {
52- refresh ();
53- token = getCredentials ().getAccessToken ();
65+ try {
66+ return Failsafe .with (RETRY_POLICY ).get (() -> {
67+ com .google .auth .oauth2 .AccessToken token = retrieveAccessToken ();
68+ if (token == null || token .getExpirationTime ().before (Date .from (Instant .now ()))) {
69+ refresh ();
70+ token = retrieveAccessToken ();
71+ }
72+ return new AccessToken (token .getTokenValue (), token .getExpirationTime ().getTime ());
73+ });
74+ } catch (FailsafeException e ) {
75+ Throwable t = e .getCause () != null ? e .getCause () : e ;
76+ ErrorType errorType = (t instanceof ServerErrorException ) ? ErrorType .SYSTEM : ErrorType .UNKNOWN ;
77+ throw GCPErrorDetailsProviderUtil .getHttpResponseExceptionDetailsFromChain (
78+ e , "Unable to get service account access token after retries." , errorType , true ,
79+ GCPUtils .GCE_METADATA_SERVER_ERROR_SUPPORTED_DOC_URL
80+ );
5481 }
55- return new AccessToken (token .getTokenValue (), token .getExpirationTime ().getTime ());
82+ }
83+
84+ private static RetryPolicy <Object > createRetryPolicy () {
85+ return RetryPolicy .builder ()
86+ .handle (ServerErrorException .class )
87+ .withBackoff (Duration .ofSeconds (DEFAULT_INITIAL_RETRY_DURATION_SECONDS ),
88+ Duration .ofSeconds (DEFAULT_MAX_RETRY_DURATION_SECONDS ))
89+ .withMaxRetries (DEFAULT_MAX_RETRY_COUNT )
90+ .onRetry (event -> LOG .debug ("Retry attempt {} due to {}" , event .getAttemptCount (), event .getLastException ().
91+ getMessage ()))
92+ .onSuccess (event -> LOG .debug ("Access Token Fetched Successfully." ))
93+ .onRetriesExceeded (
94+ event -> LOG .error ("Unable to get service account access token after {} retries." , event .getAttemptCount () - 1 ))
95+ .build ();
96+ }
97+
98+ @ VisibleForTesting
99+ static boolean isServerError (IOException e ) {
100+ String msg = e .getMessage ();
101+ return msg != null && SERVER_ERROR_PATTERN .matcher (msg ).matches ();
102+ }
103+
104+ com .google .auth .oauth2 .AccessToken retrieveAccessToken () throws IOException {
105+ try {
106+ return getCredentials ().getAccessToken ();
56107 } catch (IOException e ) {
57- throw ErrorUtils .getProgramFailureException (new ErrorCategory (ErrorCategoryEnum .PLUGIN ),
58- "Unable to get service account access token." , e .getMessage (), ErrorType .UNKNOWN , true , e );
108+ if (isServerError (e )) {
109+ throw new ServerErrorException (HttpStatus .SC_SERVICE_UNAVAILABLE , "Server error while fetching access token: "
110+ + e .getMessage (), e );
111+ }
112+ throw e ;
59113 }
60114 }
61115
@@ -64,9 +118,13 @@ public void refresh() throws IOException {
64118 try {
65119 getCredentials ().refresh ();
66120 } catch (IOException e ) {
67- throw ErrorUtils .getProgramFailureException (new ErrorCategory (ErrorCategoryEnum .PLUGIN ),
68- "Unable to refresh service account access token." , e .getMessage (),
69- ErrorType .UNKNOWN , true , e );
121+ if (isServerError (e )) {
122+ throw new ServerErrorException (HttpStatus .SC_SERVICE_UNAVAILABLE , "Server error during refresh: " +
123+ e .getMessage (), e );
124+ }
125+ throw GCPErrorDetailsProviderUtil .getHttpResponseExceptionDetailsFromChain (
126+ e , "Unable to refresh service account access token." , ErrorType .UNKNOWN , true ,
127+ GCPUtils .GCE_METADATA_SERVER_ERROR_SUPPORTED_DOC_URL );
70128 }
71129 }
72130
0 commit comments