From d117a6bad04848fdb75e3dc73b514736204557c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Kubik?= Date: Tue, 12 Nov 2024 11:49:49 +0100 Subject: [PATCH] add old api with storage sdk v8 to prevent BCR --- parent-pom.xml | 27 +++++++- pom.xml | 12 ++++ .../net/snowflake/client/core/HttpUtil.java | 68 +++++++++++++++++++ .../storage/AzureObjectSummariesIterator.java | 36 +++++++++- .../cloud/storage/StorageObjectSummary.java | 51 ++++++++++++++ .../StorageObjectSummaryCollection.java | 19 ++++-- ...akeAzureClientHandleExceptionLatestIT.java | 66 ++++++------------ 7 files changed, 223 insertions(+), 56 deletions(-) diff --git a/parent-pom.xml b/parent-pom.xml index 5b19171e5..6c9c3705c 100644 --- a/parent-pom.xml +++ b/parent-pom.xml @@ -26,6 +26,8 @@ 4.2.0 1.12.655 12.26.1 + 8.0.0 + 1.2.29 1.78.1 1.0.2.5 1.0.7 @@ -115,6 +117,13 @@ pom import + + com.azure + azure-sdk-bom + ${azuresdk.version} + pom + import + com.fasterxml.jackson jackson-bom @@ -201,9 +210,9 @@ ${google.guava.version} - com.azure - azure-storage-blob - ${azure.storage.blob.version} + com.microsoft.azure + azure-storage + ${azure.storage.version} com.nimbusds @@ -590,6 +599,18 @@ com.azure azure-storage-blob + + com.azure + azure-core + + + com.azure + azure-storage-common + + + com.microsoft.azure + azure-storage + com.nimbusds nimbus-jose-jwt diff --git a/pom.xml b/pom.xml index 1776dc0e8..5dfbf8def 100644 --- a/pom.xml +++ b/pom.xml @@ -815,6 +815,10 @@ com.azure ${shadeBase}.azure + + com.microsoft.azure + ${shadeBase}.microsoft.azure + com.fasterxml ${shadeBase}.fasterxml @@ -875,6 +879,14 @@ io.netty ${shadeBase}.io.netty + + io.projectreactor + ${shadeBase}.io.projectreactor + + + org.reactivestreams + ${shadeBase}.org.reactivestreams + com.carrotsearch ${shadeBase}.com.carrotsearch diff --git a/src/main/java/net/snowflake/client/core/HttpUtil.java b/src/main/java/net/snowflake/client/core/HttpUtil.java index 8b5f320e2..eaa5247b6 100644 --- a/src/main/java/net/snowflake/client/core/HttpUtil.java +++ b/src/main/java/net/snowflake/client/core/HttpUtil.java @@ -31,6 +31,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import javax.net.ssl.TrustManager; + +import com.microsoft.azure.storage.OperationContext; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.RestRequest; import net.snowflake.client.jdbc.SnowflakeDriver; @@ -179,6 +181,50 @@ public static void setSessionlessProxyForS3( S3HttpUtil.setSessionlessProxyForS3(proxyProperties, clientConfig); } + + /** + * A static function to set Azure proxy params for sessionless connections using the proxy params + * from the StageInfo + * + * @param proxyProperties proxy properties + * @param opContext the configuration needed by Azure to set the proxy + * @throws SnowflakeSQLException + * + * @deprecated, please use {@link HttpUtil#setSessionlessProxyForAzure(Properties, HttpClientOptions)} as + * it supports the up-to-date azure storage client. + */ + @Deprecated + public static void setSessionlessProxyForAzure( + Properties proxyProperties, OperationContext opContext) throws SnowflakeSQLException { + if (proxyProperties != null + && proxyProperties.size() > 0 + && proxyProperties.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()) != null) { + Boolean useProxy = + Boolean.valueOf( + proxyProperties.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey())); + if (useProxy) { + String proxyHost = + proxyProperties.getProperty(SFSessionProperty.PROXY_HOST.getPropertyKey()); + int proxyPort; + try { + proxyPort = + Integer.parseInt( + proxyProperties.getProperty(SFSessionProperty.PROXY_PORT.getPropertyKey())); + } catch (NumberFormatException | NullPointerException e) { + throw new SnowflakeSQLException( + ErrorCode.INVALID_PROXY_PROPERTIES, "Could not parse port number"); + } + Proxy azProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); + logger.debug("Setting sessionless Azure proxy. Host: {}, port: {}", proxyHost, proxyPort); + opContext.setProxy(azProxy); + } else { + logger.debug("Omitting sessionless Azure proxy setup as proxy is disabled"); + } + } else { + logger.debug("Omitting sessionless Azure proxy setup"); + } + } + /** * A static function to set Azure proxy params for sessionless connections using the proxy params * from the StageInfo @@ -237,6 +283,28 @@ public static void setSessionlessProxyForAzure( httpClientOptions.setProxyOptions(proxyOptions); } + /** + * A static function to set Azure proxy params when there is a valid session + * + * @param key key to HttpClient map containing OCSP and proxy info + * @param opContext the configuration needed by Azure to set the proxy + * + * @deprecated Use {@link HttpUtil#setProxyForAzure(HttpClientSettingsKey, OperationContext)} as it supports + * the most recent azure storage client + */ + @Deprecated + public static void setProxyForAzure(HttpClientSettingsKey key, OperationContext opContext) { + if (key != null && key.usesProxy()) { + Proxy azProxy = + new Proxy(Proxy.Type.HTTP, new InetSocketAddress(key.getProxyHost(), key.getProxyPort())); + logger.debug( + "Setting Azure proxy. Host: {}, port: {}", key.getProxyHost(), key.getProxyPort()); + opContext.setProxy(azProxy); + } else { + logger.debug("Omitting Azure proxy setup"); + } + } + /** * A static function to set Azure proxy params when there is a valid session * diff --git a/src/main/java/net/snowflake/client/jdbc/cloud/storage/AzureObjectSummariesIterator.java b/src/main/java/net/snowflake/client/jdbc/cloud/storage/AzureObjectSummariesIterator.java index 3f4b02729..7f284359c 100644 --- a/src/main/java/net/snowflake/client/jdbc/cloud/storage/AzureObjectSummariesIterator.java +++ b/src/main/java/net/snowflake/client/jdbc/cloud/storage/AzureObjectSummariesIterator.java @@ -10,6 +10,8 @@ import java.util.Iterator; import java.util.NoSuchElementException; +import com.microsoft.azure.storage.blob.CloudBlob; +import com.microsoft.azure.storage.blob.ListBlobItem; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; @@ -24,6 +26,19 @@ public class AzureObjectSummariesIterator implements Iterator itemIterator; + Iterator listBlobItemIterator; + + /* + * Constructs a summaries iterator object from an iterable derived by a + * lostBlobs method + * @param azCloudBlobIterable an iterable set of ListBlobItems + */ + @Deprecated + public AzureObjectSummariesIterator(Iterable azCloudBlobIterable) { + storageLocation = null; + itemIterator = null; + listBlobItemIterator = azCloudBlobIterable.iterator(); + } /* * Constructs a summaries iterator object from an iterable derived by a @@ -33,6 +48,9 @@ public class AzureObjectSummariesIterator implements Iterator azCloudBlobIterable, String azStorageLocation) { itemIterator = azCloudBlobIterable.iterator(); storageLocation = azStorageLocation; + + // listBlobItem support is deprecated as it comes from azure storage v8 + listBlobItemIterator = null; } public boolean hasNext() { @@ -49,15 +67,27 @@ public boolean hasNext() { } public StorageObjectSummary next() { - BlobItem blobItem = itemIterator.next(); + if (itemIterator != null) { + BlobItem blobItem = itemIterator.next(); // if (!(blobItem.getProperties().getBlobType() instanceof BlobClient)) { // // The only other possible type would a CloudDirectory // // This should never happen since we are listing items as a flat list // logger.debug("Unexpected listBlobItem instance type: {}", blobItem.getClass()); // throw new IllegalArgumentException("Unexpected listBlobItem instance type"); // } - - return StorageObjectSummary.createFromAzureListBlobItem(blobItem, storageLocation); + return StorageObjectSummary.createFromAzureListBlobItem(blobItem, storageLocation); + } + else if (listBlobItemIterator != null) { + ListBlobItem listBlobItem = listBlobItemIterator.next(); + if (!(listBlobItem instanceof CloudBlob)) { + // The only other possible type would a CloudDirectory + // This should never happen since we are listing items as a flat list + logger.debug("Unexpected listBlobItem instance type: {}", listBlobItem.getClass()); + throw new IllegalArgumentException("Unexpected listBlobItem instance type"); + } + return StorageObjectSummary.createFromAzureListBlobItem(listBlobItem); + } + throw new RuntimeException("No azure blob iterator was initialized, should never happen"); } public void remove() { diff --git a/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageObjectSummary.java b/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageObjectSummary.java index 5dc82db5f..d672f7061 100644 --- a/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageObjectSummary.java +++ b/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageObjectSummary.java @@ -9,9 +9,16 @@ import com.azure.storage.blob.models.BlobStorageException; import com.google.cloud.storage.Blob; +import com.microsoft.azure.storage.StorageException; +import com.microsoft.azure.storage.blob.BlobProperties; +import com.microsoft.azure.storage.blob.CloudBlob; +import com.microsoft.azure.storage.blob.CloudBlobContainer; +import com.microsoft.azure.storage.blob.ListBlobItem; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; +import java.net.URISyntaxException; + /** * Storage platform-agnostic class that encapsulates remote storage object properties * @@ -57,6 +64,50 @@ public static StorageObjectSummary createFromS3ObjectSummary(S3ObjectSummary obj objSummary.getSize()); } + /** + * Constructs a StorageObjectSummary object from Azure BLOB properties Using factory methods to + * create these objects since Azure can throw, while retrieving the BLOB properties + * + * @param listBlobItem an Azure ListBlobItem object + * @return the ObjectSummary object created + */ + @Deprecated + public static StorageObjectSummary createFromAzureListBlobItem(ListBlobItem listBlobItem) + throws StorageProviderException { + String location, key, md5; + long size; + CloudBlobContainer container; + + // Retrieve the BLOB properties that we need for the Summary + // Azure Storage stores metadata inside each BLOB, therefore the listBlobItem + // will point us to the underlying BLOB and will get the properties from it + // During the process the Storage Client could fail, hence we need to wrap the + // get calls in try/catch and handle possible exceptions + try { + container = listBlobItem.getContainer(); + location = container.getName(); + CloudBlob cloudBlob = (CloudBlob) listBlobItem; + key = cloudBlob.getName(); + BlobProperties blobProperties = cloudBlob.getProperties(); + // the content md5 property is not always the actual md5 of the file. But for here, it's only + // used for skipping file on PUT command, hence is ok. + md5 = convertBase64ToHex(blobProperties.getContentMD5().getBytes()); + size = blobProperties.getLength(); + } catch (URISyntaxException | StorageException ex) { + // This should only happen if somehow we got here with and invalid URI (it should never + // happen) + // ...or there is a Storage service error. Unlike S3, Azure fetches metadata from the BLOB + // itself, + // and its a lazy operation + logger.debug("Failed to create StorageObjectSummary from Azure ListBlobItem: {}", ex); + throw new StorageProviderException(ex); + } catch (Throwable th) { + logger.debug("Failed to create StorageObjectSummary from Azure ListBlobItem: {}", th); + throw th; + } + return new StorageObjectSummary(location, key, md5, size); + } + /** * Constructs a StorageObjectSummary object from Azure BLOB properties Using factory methods to * create these objects since Azure can throw, while retrieving the BLOB properties diff --git a/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageObjectSummaryCollection.java b/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageObjectSummaryCollection.java index 4460988b0..4a454332b 100644 --- a/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageObjectSummaryCollection.java +++ b/src/main/java/net/snowflake/client/jdbc/cloud/storage/StorageObjectSummaryCollection.java @@ -7,6 +7,8 @@ import com.azure.storage.blob.models.BlobItem; import com.google.api.gax.paging.Page; import com.google.cloud.storage.Blob; +import com.microsoft.azure.storage.blob.ListBlobItem; + import java.util.Iterator; import java.util.List; @@ -27,7 +29,8 @@ private enum storageType { private final storageType sType; private List s3ObjSummariesList = null; - private Iterable azCLoudBlobIterable = null; + private Iterable azCLoudBlobIterable = null; + private Iterable azCloudBlobItemIterable = null; private Page gcsIterablePage = null; // Constructs platform-agnostic collection of object summaries from S3 object summaries @@ -38,12 +41,17 @@ public StorageObjectSummaryCollection(List s3ObjectSummaries) { // Constructs platform-agnostic collection of object summaries from an Azure CloudBlobDirectory // object - public StorageObjectSummaryCollection(Iterable azCLoudBlobIterable, String remoteStorageLocation) { - this.azCLoudBlobIterable = azCLoudBlobIterable; + public StorageObjectSummaryCollection(Iterable azCLoudBlobItemIterable, String remoteStorageLocation) { + this.azCloudBlobItemIterable = azCLoudBlobItemIterable; this.azStorageLocation = remoteStorageLocation; sType = storageType.AZURE; } + public StorageObjectSummaryCollection(Iterable azCLoudBlobIterable) { + this.azCLoudBlobIterable = azCLoudBlobIterable; + sType = storageType.AZURE; + } + public StorageObjectSummaryCollection(Page gcsIterablePage) { this.gcsIterablePage = gcsIterablePage; sType = storageType.GCS; @@ -56,9 +64,12 @@ public Iterator iterator() { return new S3ObjectSummariesIterator(s3ObjSummariesList); case AZURE: if (azStorageLocation == null) { + if (azCLoudBlobIterable != null) { + return new AzureObjectSummariesIterator(azCLoudBlobIterable); + } throw new RuntimeException("Storage type is Azure but azStorageLocation field is not set. Should never happen"); } - return new AzureObjectSummariesIterator(azCLoudBlobIterable, azStorageLocation); + return new AzureObjectSummariesIterator(azCloudBlobItemIterable, azStorageLocation); case GCS: return new GcsObjectSummariesIterator(this.gcsIterablePage); default: diff --git a/src/test/java/net/snowflake/client/jdbc/SnowflakeAzureClientHandleExceptionLatestIT.java b/src/test/java/net/snowflake/client/jdbc/SnowflakeAzureClientHandleExceptionLatestIT.java index 251042d9b..8827f1bb8 100644 --- a/src/test/java/net/snowflake/client/jdbc/SnowflakeAzureClientHandleExceptionLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/SnowflakeAzureClientHandleExceptionLatestIT.java @@ -6,8 +6,6 @@ import java.io.File; import java.io.IOException; import java.net.SocketTimeoutException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.sql.Connection; import java.sql.SQLException; @@ -32,12 +30,16 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.when; /** Test for SnowflakeAzureClient handle exception function */ @Category(TestCategoryOthers.class) +@RunWith(MockitoJUnitRunner.class) public class SnowflakeAzureClientHandleExceptionLatestIT extends AbstractDriverIT { @Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); private Connection connection; @@ -47,6 +49,13 @@ public class SnowflakeAzureClientHandleExceptionLatestIT extends AbstractDriverI private SnowflakeAzureClient spyingClient; private int overMaxRetry; private int maxRetry; + @Mock + HttpResponse mockHttpResponse; + + /** + * @see https://learn.microsoft.com/en-us/rest/api/storageservices/status-and-error-codes2 + */ + private final static String AZURE_ERROR_CODE_HEADER = "x-ms-error-code"; @Before public void setup() throws SQLException { @@ -64,45 +73,10 @@ public void setup() throws SQLException { maxRetry = client.getMaxRetries(); overMaxRetry = maxRetry + 1; spyingClient = Mockito.spy(client); - } - private HttpResponse constructHttpResponse(int httpStatus) { - return new HttpResponse(null) { - @Override - public int getStatusCode() { - return httpStatus; - } - - @Override - public String getHeaderValue(String s) { - return ""; - } - - @Override - public HttpHeaders getHeaders() { - return null; - } - - @Override - public Flux getBody() { - return null; - } - - @Override - public Mono getBodyAsByteArray() { - return null; - } - - @Override - public Mono getBodyAsString() { - return null; - } - - @Override - public Mono getBodyAsString(Charset charset) { - return null; - } - }; + when(mockHttpResponse.getStatusCode()).thenReturn(HttpStatus.SC_FORBIDDEN); + when(mockHttpResponse.getHeaders()).thenReturn(new HttpHeaders() + .add(AZURE_ERROR_CODE_HEADER, "InvalidAuthenticationInfo")); } @Test @@ -111,7 +85,7 @@ public void error403RenewExpired() throws SQLException, InterruptedException { // Unauthenticated, renew is called. spyingClient.handleStorageException( new BlobStorageException( - "Unauthenticated", constructHttpResponse(HttpStatus.SC_FORBIDDEN), new Exception()), + "Unauthenticated", mockHttpResponse, new Exception()), 0, "upload", sfSession, @@ -130,7 +104,7 @@ public void run() { spyingClient.handleStorageException( new BlobStorageException( "Unauthenticated", - constructHttpResponse(HttpStatus.SC_FORBIDDEN), + mockHttpResponse, new Exception()), maxRetry, "upload", @@ -154,7 +128,7 @@ public void run() { public void error403OverMaxRetryThrow() throws SQLException { spyingClient.handleStorageException( new BlobStorageException( - "Unauthenticated", constructHttpResponse(HttpStatus.SC_FORBIDDEN), new Exception()), + "Unauthenticated", mockHttpResponse, new Exception()), overMaxRetry, "upload", sfSession, @@ -167,7 +141,7 @@ public void error403OverMaxRetryThrow() throws SQLException { public void error403NullSession() throws SQLException { spyingClient.handleStorageException( new BlobStorageException( - "Unauthenticated", constructHttpResponse(HttpStatus.SC_FORBIDDEN), new Exception()), + "Unauthenticated", mockHttpResponse, new Exception()), 0, "upload", null,