2121import java .io .IOException ;
2222import java .io .InputStream ;
2323import java .util .ArrayList ;
24- import java .util .Base64 ;
2524import java .util .List ;
2625import org .apache .commons .lang3 .RandomStringUtils ;
27- import org .apache .http .client .methods .CloseableHttpResponse ;
28- import org .apache .http .client .methods .HttpGet ;
29- import org .apache .http .impl .client .CloseableHttpClient ;
30- import org .apache .http .impl .client .HttpClientBuilder ;
31- import org .apache .http .util .EntityUtils ;
3226import org .datatransferproject .api .launcher .Monitor ;
3327import org .datatransferproject .datatransfer .backblaze .exception .BackblazeCredentialsException ;
34- import org .json .simple .JSONObject ;
35- import org .json .simple .parser .JSONParser ;
36- import org .json .simple .parser .ParseException ;
3728import software .amazon .awssdk .awscore .exception .AwsServiceException ;
3829import software .amazon .awssdk .core .exception .SdkClientException ;
3930import software .amazon .awssdk .core .sync .RequestBody ;
7566public class BackblazeDataTransferClient {
7667 private static final String DATA_TRANSFER_BUCKET_PREFIX_FORMAT_STRING = "%s-data-transfer" ;
7768 private static final int MAX_BUCKET_CREATION_ATTEMPTS = 10 ;
69+ // Backblaze B2 Native API only supports IPv4; until it supports IPv6 we cannot use
70+ // the b2_authorize_account endpoint to look up what region an account is in. For now
71+ // a hard-coded list will be maintained and updated if the cluster list changes.
72+ private static final List <String > REGIONS = List .of (
73+ "us-west-000" ,
74+ "us-west-001" ,
75+ "us-west-002" ,
76+ "us-west-004" ,
77+ "us-east-005" ,
78+ "eu-central-003" ,
79+ "ca-east-006"
80+ );
7881
7982 private final long sizeThresholdForMultipartUpload ;
8083 private final long partSizeForMultiPartUpload ;
@@ -97,21 +100,28 @@ public BackblazeDataTransferClient(
97100 this .partSizeForMultiPartUpload = partSizeForMultiPartUpload ;
98101 }
99102
100- public void init (
101- String keyId , String applicationKey , String exportService , CloseableHttpClient httpClient )
103+ public void init (String keyId , String applicationKey , String exportService )
102104 throws BackblazeCredentialsException , IOException {
103105 // Fetch all the available buckets and use that to find which region the user is in
104106 ListBucketsResponse listBucketsResponse = null ;
107+ String successfulRegion = null ;
105108
106109 Throwable s3Exception = null ;
107- String userRegion = getAccountRegion (httpClient , keyId , applicationKey );
108- s3Client = backblazeS3ClientFactory .createS3Client (keyId , applicationKey , userRegion );
109- try {
110- listBucketsResponse = s3Client .listBuckets ();
111- } catch (S3Exception e ) {
112- s3Exception = e ;
113- if (s3Client != null ) {
114- s3Client .close ();
110+ for (String region : REGIONS ) {
111+ S3Client potentialS3Client =
112+ backblazeS3ClientFactory .createS3Client (keyId , applicationKey , region );
113+ try {
114+ listBucketsResponse = potentialS3Client .listBuckets ();
115+ s3Client = potentialS3Client ;
116+ successfulRegion = region ;
117+ monitor .info (() -> "Successfully connected to Backblaze region: " + region );
118+ break ;
119+ } catch (S3Exception e ) {
120+ monitor .info (() -> "Failed to connect to Backblaze region: " + region );
121+ s3Exception = e ;
122+ if (potentialS3Client != null ) {
123+ potentialS3Client .close ();
124+ }
115125 }
116126 }
117127
@@ -120,7 +130,7 @@ public void init(
120130 "User's credentials or permissions are not valid for any regions available" , s3Exception );
121131 }
122132
123- bucketName = getOrCreateBucket (s3Client , listBucketsResponse , userRegion , exportService );
133+ bucketName = getOrCreateBucket (s3Client , listBucketsResponse , successfulRegion , exportService );
124134 }
125135
126136 public String uploadFile (String fileKey , File file ) throws IOException {
@@ -154,42 +164,6 @@ public String uploadFile(String fileKey, File file) throws IOException {
154164 }
155165 }
156166
157- private String getAccountRegion (
158- CloseableHttpClient httpClient , String keyId , String applicationKey )
159- throws BackblazeCredentialsException {
160-
161- String auth = keyId + ":" + applicationKey ;
162- byte [] encodedAuth = Base64 .getEncoder ().encode (auth .getBytes ());
163- String authHeaderValue = "Basic " + new String (encodedAuth );
164-
165- HttpGet request = new HttpGet ("https://api.backblazeb2.com/b2api/v2/b2_authorize_account" );
166- request .addHeader ("Authorization" , authHeaderValue );
167-
168- try {
169- CloseableHttpResponse response = httpClient .execute (request );
170- try (response ) {
171- int statusCode = response .getStatusLine ().getStatusCode ();
172-
173- if (statusCode == 200 ) {
174- String responseBody = EntityUtils .toString (response .getEntity ());
175- JSONParser parser = new JSONParser ();
176- JSONObject jsonResponse = (JSONObject ) parser .parse (responseBody );
177- String s3ApiUrl = (String ) jsonResponse .get ("s3ApiUrl" );
178- String region = s3ApiUrl .split ("s3." )[1 ].split ("\\ ." )[0 ];
179- monitor .info (() -> "Region extracted from s3ApiUrl: " + region );
180- return region ;
181- } else if (statusCode >= 400 && statusCode < 500 ) {
182- // Don't retry on client errors (4xx)
183- throw new BackblazeCredentialsException (
184- "Failed to retrieve account's region. Status code: " + statusCode , null );
185- } else {
186- throw new IOException ("Server returned status code: " + statusCode );
187- }
188- }
189- } catch (IOException | ParseException e ) {
190- throw new BackblazeCredentialsException ("Failed to retrieve account's region" , e );
191- }
192- }
193167
194168 private String uploadFileUsingMultipartUpload (String fileKey , File file , long contentLength )
195169 throws IOException , AwsServiceException , SdkClientException {
0 commit comments