This is an automated email from the ASF dual-hosted git repository.
siyao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 80ba150e7f HDDS-8489. Refactor GET request to POST for OM DB snapshot
(#4695)
80ba150e7f is described below
commit 80ba150e7ffd7bbdba0a07ded65f37f347eb878b
Author: Mladjan Gadzic <[email protected]>
AuthorDate: Wed Jun 21 00:56:21 2023 +0200
HDDS-8489. Refactor GET request to POST for OM DB snapshot (#4695)
---
.../java/org/apache/hadoop/ozone/OzoneConsts.java | 2 +
hadoop-hdds/framework/pom.xml | 5 +
.../hadoop/hdds/utils/DBCheckpointServlet.java | 80 +++++-
.../hadoop/ozone/om/helpers/OMNodeDetails.java | 13 +-
hadoop-ozone/dist/src/main/license/bin/LICENSE.txt | 1 +
hadoop-ozone/dist/src/main/license/jar-report.txt | 1 +
.../hdds/scm/TestSCMDbCheckpointServlet.java | 199 ++++++++++++---
.../org/apache/hadoop/hdds/scm/package-info.java | 21 ++
.../hadoop/ozone/om/TestOMDbCheckpointServlet.java | 270 ++++++++++++++++-----
.../om/ratis_snapshot/OmRatisSnapshotProvider.java | 70 +++++-
.../TestOmRatisSnapshotProvider.java | 145 +++++++++++
pom.xml | 1 +
12 files changed, 684 insertions(+), 124 deletions(-)
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
index f45c455f53..97e8169ae4 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
@@ -149,6 +149,8 @@ public final class OzoneConsts {
public static final String RANGER_OZONE_SERVICE_VERSION_KEY =
"#RANGEROZONESERVICEVERSION";
+ public static final String MULTIPART_FORM_DATA_BOUNDARY = "---XXX";
+
/**
* Supports Bucket Versioning.
*/
diff --git a/hadoop-hdds/framework/pom.xml b/hadoop-hdds/framework/pom.xml
index 746129aae2..d806637e23 100644
--- a/hadoop-hdds/framework/pom.xml
+++ b/hadoop-hdds/framework/pom.xml
@@ -71,6 +71,11 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
</dependency>
+ <dependency>
+ <groupId>commons-fileupload</groupId>
+ <artifactId>commons-fileupload</artifactId>
+ <version>${commons-fileupload.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
diff --git
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DBCheckpointServlet.java
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DBCheckpointServlet.java
index 291dd3b27d..7d19fc4568 100644
---
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DBCheckpointServlet.java
+++
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DBCheckpointServlet.java
@@ -33,6 +33,10 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileItemStream;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload.util.Streams;
import org.apache.hadoop.hdds.server.OzoneAdmins;
import org.apache.hadoop.hdds.utils.db.DBCheckpoint;
import org.apache.hadoop.hdds.utils.db.DBStore;
@@ -53,6 +57,8 @@ import org.slf4j.LoggerFactory;
*/
public class DBCheckpointServlet extends HttpServlet {
+ private static final String FIELD_NAME =
+ OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST + "[]";
private static final Logger LOG =
LoggerFactory.getLogger(DBCheckpointServlet.class);
private static final long serialVersionUID = 1L;
@@ -94,15 +100,13 @@ public class DBCheckpointServlet extends HttpServlet {
}
/**
- * Process a GET request for the DB checkpoint snapshot.
- *
- * @param request The servlet request we are processing
- * @param response The servlet response we are creating
+ * Generates Snapshot checkpoint as tar ball.
+ * @param request the HTTP servlet request
+ * @param response the HTTP servlet response
+ * @param isFormData indicator whether request is form data
*/
- @Override
- public void doGet(HttpServletRequest request, HttpServletResponse response) {
-
- LOG.info("Received request to obtain DB checkpoint snapshot");
+ private void generateSnapshotCheckpoint(HttpServletRequest request,
+ HttpServletResponse response, boolean isFormData) {
if (dbStore == null) {
LOG.error(
"Unable to process metadata snapshot request. DB Store is null");
@@ -151,7 +155,8 @@ public class DBCheckpointServlet extends HttpServlet {
List<String> receivedSstList = new ArrayList<>();
List<String> excludedSstList = new ArrayList<>();
- String[] sstParam = request.getParameterValues(
+ String[] sstParam = isFormData ?
+ parseFormDataParameters(request) : request.getParameterValues(
OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST);
if (sstParam != null) {
receivedSstList.addAll(
@@ -216,6 +221,63 @@ public class DBCheckpointServlet extends HttpServlet {
}
}
+ /**
+ * Parses request form data parameters.
+ * @param request the HTTP servlet request
+ * @return array of parsed sst form data parameters for exclusion
+ */
+ private static String[] parseFormDataParameters(HttpServletRequest request) {
+ ServletFileUpload upload = new ServletFileUpload();
+ List<String> sstParam = new ArrayList<>();
+
+ try {
+ FileItemIterator iter = upload.getItemIterator(request);
+ while (iter.hasNext()) {
+ FileItemStream item = iter.next();
+ if (!item.isFormField() || !FIELD_NAME.equals(item.getFieldName())) {
+ continue;
+ }
+
+ sstParam.add(Streams.asString(item.openStream()));
+ }
+ } catch (Exception e) {
+ LOG.warn("Exception occured during form data parsing {}",
e.getMessage());
+ }
+
+ return sstParam.size() == 0 ? null : sstParam.toArray(new String[0]);
+ }
+
+ /**
+ * Process a GET request for the DB checkpoint snapshot.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ */
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response) {
+ LOG.info("Received GET request to obtain DB checkpoint snapshot");
+
+ generateSnapshotCheckpoint(request, response, false);
+ }
+
+ /**
+ * Process a POST request for the DB checkpoint snapshot.
+ *
+ * @param request The servlet request we are processing
+ * @param response The servlet response we are creating
+ */
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
{
+ LOG.info("Received POST request to obtain DB checkpoint snapshot");
+
+ if (!ServletFileUpload.isMultipartContent(request)) {
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ generateSnapshotCheckpoint(request, response, true);
+ }
+
/**
* Write checkpoint to the stream.
*
diff --git
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OMNodeDetails.java
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OMNodeDetails.java
index b061c14b1f..e9f30b1e9e 100644
---
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OMNodeDetails.java
+++
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OMNodeDetails.java
@@ -31,12 +31,10 @@ import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
-import java.util.List;
import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_INCLUDE_SNAPSHOT_DATA;
import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_REQUEST_FLUSH;
import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_HTTP_ENDPOINT;
-import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_RATIS_PORT_DEFAULT;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_RATIS_PORT_KEY;
@@ -164,8 +162,8 @@ public final class OMNodeDetails extends NodeDetails {
}
}
- public URL getOMDBCheckpointEndpointUrl(boolean isHttp, boolean flush,
- List<String> sstList) throws IOException {
+ public URL getOMDBCheckpointEndpointUrl(boolean isHttp, boolean flush)
+ throws IOException {
URL url;
try {
URIBuilder urlBuilder = new URIBuilder().
@@ -175,12 +173,7 @@ public final class OMNodeDetails extends NodeDetails {
addParameter(OZONE_DB_CHECKPOINT_INCLUDE_SNAPSHOT_DATA, "true").
addParameter(OZONE_DB_CHECKPOINT_REQUEST_FLUSH,
flush ? "true" : "false");
- if (sstList != null && !sstList.isEmpty()) {
- for (String s: sstList) {
- urlBuilder.addParameter(
- OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST, s);
- }
- }
+
url = urlBuilder.build().toURL();
} catch (URISyntaxException | MalformedURLException e) {
throw new IOException("Could not get OM DB Checkpoint Endpoint Url", e);
diff --git a/hadoop-ozone/dist/src/main/license/bin/LICENSE.txt
b/hadoop-ozone/dist/src/main/license/bin/LICENSE.txt
index 58cbc18b31..d02c3bcceb 100644
--- a/hadoop-ozone/dist/src/main/license/bin/LICENSE.txt
+++ b/hadoop-ozone/dist/src/main/license/bin/LICENSE.txt
@@ -314,6 +314,7 @@ Apache License 2.0
commons-logging:commons-logging
commons-net:commons-net
commons-validator:commons-validator
+ commons-fileupload:commons-fileupload
info.picocli:picocli
io.dropwizard.metrics:metrics-core
io.grpc:grpc-api
diff --git a/hadoop-ozone/dist/src/main/license/jar-report.txt
b/hadoop-ozone/dist/src/main/license/jar-report.txt
index cb13a1106e..d54591b4a9 100644
--- a/hadoop-ozone/dist/src/main/license/jar-report.txt
+++ b/hadoop-ozone/dist/src/main/license/jar-report.txt
@@ -33,6 +33,7 @@ share/ozone/lib/commons-net.jar
share/ozone/lib/commons-pool2.jar
share/ozone/lib/commons-text.jar
share/ozone/lib/commons-validator.jar
+share/ozone/lib/commons-fileupload.jar
share/ozone/lib/curator-client.jar
share/ozone/lib/curator-framework.jar
share/ozone/lib/derby.jar
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestSCMDbCheckpointServlet.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestSCMDbCheckpointServlet.java
index 28184a8ff8..4217d347d1 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestSCMDbCheckpointServlet.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestSCMDbCheckpointServlet.java
@@ -19,17 +19,23 @@ package org.apache.hadoop.hdds.scm;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.UUID;
+import java.util.stream.Stream;
-import org.apache.commons.compress.compressors.CompressorException;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.scm.container.placement.metrics.SCMMetrics;
import org.apache.hadoop.hdds.scm.server.SCMDBCheckpointServlet;
@@ -39,6 +45,7 @@ import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.commons.io.FileUtils;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED;
+import static org.apache.hadoop.ozone.OzoneConsts.MULTIPART_FORM_DATA_BOUNDARY;
import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_REQUEST_FLUSH;
import org.junit.jupiter.api.AfterEach;
@@ -46,9 +53,16 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
-import org.mockito.Matchers;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
+import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
@@ -66,6 +80,11 @@ public class TestSCMDbCheckpointServlet {
private String clusterId;
private String scmId;
private String omId;
+ private HttpServletRequest requestMock;
+ private HttpServletResponse responseMock;
+ private String method;
+ private SCMDBCheckpointServlet scmDbCheckpointServletMock;
+ private ServletContext servletContextMock;
/**
* Create a MiniDFSCluster for testing.
@@ -89,6 +108,34 @@ public class TestSCMDbCheckpointServlet {
cluster.waitForClusterToBeReady();
scm = cluster.getStorageContainerManager();
scmMetrics = StorageContainerManager.getMetrics();
+
+ requestMock = mock(HttpServletRequest.class);
+ when(requestMock.getParameter(OZONE_DB_CHECKPOINT_REQUEST_FLUSH))
+ .thenReturn("true");
+
+ responseMock = mock(HttpServletResponse.class);
+
+ scmDbCheckpointServletMock = mock(SCMDBCheckpointServlet.class);
+ doCallRealMethod().when(scmDbCheckpointServletMock).init();
+ doCallRealMethod().when(scmDbCheckpointServletMock).initialize(
+ scm.getScmMetadataStore().getStore(),
+ scmMetrics.getDBCheckpointMetrics(),
+ false,
+ Collections.emptyList(),
+ Collections.emptyList(),
+ false);
+ doCallRealMethod().when(scmDbCheckpointServletMock)
+ .writeDbDataToStream(any(), any(), any(), any(), any());
+ doCallRealMethod().when(scmDbCheckpointServletMock).doPost(requestMock,
+ responseMock);
+ doCallRealMethod().when(scmDbCheckpointServletMock).doGet(requestMock,
+ responseMock);
+
+ servletContextMock = mock(ServletContext.class);
+ when(scmDbCheckpointServletMock.getServletContext())
+ .thenReturn(servletContextMock);
+ when(servletContextMock.getAttribute(OzoneConsts.SCM_CONTEXT_ATTRIBUTE))
+ .thenReturn(cluster.getStorageContainerManager());
}
/**
@@ -101,43 +148,25 @@ public class TestSCMDbCheckpointServlet {
}
}
- @Test
- public void testDoGet()
- throws ServletException, IOException, CompressorException,
- InterruptedException {
+ @ParameterizedTest
+ @MethodSource("getHttpMethods")
+ public void testEndpoint(String httpMethod)
+ throws ServletException, IOException, InterruptedException {
+ this.method = httpMethod;
File tempFile = null;
try {
- SCMDBCheckpointServlet scmDbCheckpointServletMock =
- mock(SCMDBCheckpointServlet.class);
-
- doCallRealMethod().when(scmDbCheckpointServletMock).init();
- doCallRealMethod().when(scmDbCheckpointServletMock).initialize(
- scm.getScmMetadataStore().getStore(),
- scmMetrics.getDBCheckpointMetrics(),
- false,
- Collections.emptyList(),
- Collections.emptyList(),
- false);
- doCallRealMethod().when(scmDbCheckpointServletMock)
- .writeDbDataToStream(any(), any(), any(), any(), any());
-
- HttpServletRequest requestMock = mock(HttpServletRequest.class);
- HttpServletResponse responseMock = mock(HttpServletResponse.class);
-
- ServletContext servletContextMock = mock(ServletContext.class);
- when(scmDbCheckpointServletMock.getServletContext())
- .thenReturn(servletContextMock);
-
- when(servletContextMock.getAttribute(OzoneConsts.SCM_CONTEXT_ATTRIBUTE))
- .thenReturn(cluster.getStorageContainerManager());
- when(requestMock.getParameter(OZONE_DB_CHECKPOINT_REQUEST_FLUSH))
- .thenReturn("true");
+ List<String> toExcludeList = new ArrayList<>();
+ toExcludeList.add("sstFile1.sst");
+ toExcludeList.add("sstFile2.sst");
+
+ setupHttpMethod(toExcludeList);
+
doNothing().when(responseMock).setContentType("application/x-tgz");
- doNothing().when(responseMock).setHeader(Matchers.anyString(),
- Matchers.anyString());
+ doNothing().when(responseMock).setHeader(Mockito.anyString(),
+ Mockito.anyString());
- tempFile = File.createTempFile("testDoGet_" + System
+ tempFile = File.createTempFile("testEndpoint_" + System
.currentTimeMillis(), ".tar");
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
@@ -158,14 +187,11 @@ public class TestSCMDbCheckpointServlet {
}
});
- doCallRealMethod().when(scmDbCheckpointServletMock).doGet(requestMock,
- responseMock);
-
scmDbCheckpointServletMock.init();
long initialCheckpointCount =
scmMetrics.getDBCheckpointMetrics().getNumCheckpoints();
- scmDbCheckpointServletMock.doGet(requestMock, responseMock);
+ doEndpoint();
Assertions.assertTrue(tempFile.length() > 0);
Assertions.assertTrue(
@@ -176,9 +202,106 @@ public class TestSCMDbCheckpointServlet {
getLastCheckpointStreamingTimeTaken() > 0);
Assertions.assertTrue(scmMetrics.getDBCheckpointMetrics().
getNumCheckpoints() > initialCheckpointCount);
+
+ Mockito.verify(scmDbCheckpointServletMock).writeDbDataToStream(any(),
+ any(), any(), eq(toExcludeList), any());
} finally {
FileUtils.deleteQuietly(tempFile);
}
}
+
+ @Test
+ public void testDoPostWithInvalidContentType() throws ServletException {
+ when(requestMock.getContentType()).thenReturn("application/json");
+
+ scmDbCheckpointServletMock.init();
+
+ scmDbCheckpointServletMock.doPost(requestMock, responseMock);
+
+ Mockito.verify(responseMock).setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ }
+
+ /**
+ * Calls endpoint in regards to parametrized HTTP method.
+ */
+ private void doEndpoint() {
+ if (method.equals("POST")) {
+ scmDbCheckpointServletMock.doPost(requestMock, responseMock);
+ } else {
+ scmDbCheckpointServletMock.doGet(requestMock, responseMock);
+ }
+ }
+
+ /**
+ * Parametrizes test with HTTP method.
+ * @return HTTP method.
+ */
+ private static Stream<Arguments> getHttpMethods() {
+ return Stream.of(arguments("POST"), arguments("GET"));
+ }
+
+ /**
+ * Setups HTTP method details depending on parametrized HTTP method.
+ * @param toExcludeList SST file names to be excluded.
+ * @throws IOException
+ */
+ private void setupHttpMethod(List<String> toExcludeList) throws IOException {
+ if (method.equals("POST")) {
+ setupPostMethod(toExcludeList);
+ } else {
+ setupGetMethod(toExcludeList);
+ }
+ }
+
+ /**
+ * Setups details for HTTP POST request.
+ * @param toExcludeList SST file names to be excluded.
+ * @throws IOException
+ */
+ private void setupPostMethod(List<String> toExcludeList)
+ throws IOException {
+ when(requestMock.getMethod()).thenReturn("POST");
+ when(requestMock.getContentType()).thenReturn("multipart/form-data; " +
+ "boundary=" + MULTIPART_FORM_DATA_BOUNDARY);
+
+ // Generate form data
+ String crNl = "\r\n";
+ String contentDisposition = "Content-Disposition: form-data; name=\"" +
+ OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST + "[]\"" + crNl + crNl;
+ String boundary = "--" + MULTIPART_FORM_DATA_BOUNDARY;
+ String endBoundary = boundary + "--" + crNl;
+ StringBuilder sb = new StringBuilder();
+ toExcludeList.forEach(sfn -> {
+ sb.append(boundary).append(crNl);
+ sb.append(contentDisposition);
+ sb.append(sfn).append(crNl);
+ });
+ sb.append(endBoundary);
+
+ // Use generated form data as input stream to the HTTP request
+ InputStream input = new ByteArrayInputStream(
+ sb.toString().getBytes(StandardCharsets.UTF_8));
+ ServletInputStream inputStream = Mockito.mock(ServletInputStream.class);
+ when(requestMock.getInputStream()).thenReturn(inputStream);
+ when(inputStream.read(any(byte[].class), anyInt(), anyInt()))
+ .thenAnswer(invocation -> {
+ byte[] buffer = invocation.getArgument(0);
+ int offset = invocation.getArgument(1);
+ int length = invocation.getArgument(2);
+ return input.read(buffer, offset, length);
+ });
+ }
+
+ /**
+ * Setups details for HTTP GET request.
+ * @param toExcludeList SST file names to be excluded.
+ */
+ private void setupGetMethod(List<String> toExcludeList) {
+ when(requestMock.getMethod()).thenReturn("GET");
+ when(requestMock
+ .getParameterValues(OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST))
+ .thenReturn(toExcludeList.toArray(new String[0]));
+ }
+
}
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/package-info.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/package-info.java
new file mode 100644
index 0000000000..0628cf68d3
--- /dev/null
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Package info tests.
+ */
+package org.apache.hadoop.hdds.scm;
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMDbCheckpointServlet.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMDbCheckpointServlet.java
index ade5196304..2a6b35ae63 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMDbCheckpointServlet.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMDbCheckpointServlet.java
@@ -19,13 +19,16 @@
package org.apache.hadoop.ozone.om;
import javax.servlet.ServletContext;
+import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -69,6 +72,7 @@ import static
org.apache.hadoop.hdds.recon.ReconConfig.ConfigStrings.OZONE_RECON
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS;
import static
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS_WILDCARD;
+import static org.apache.hadoop.ozone.OzoneConsts.MULTIPART_FORM_DATA_BOUNDARY;
import static org.apache.hadoop.ozone.OzoneConsts.OM_DB_NAME;
import static org.apache.hadoop.ozone.OzoneConsts.OM_KEY_PREFIX;
import static org.apache.hadoop.ozone.OzoneConsts.DB_COMPACTION_SST_BACKUP_DIR;
@@ -76,24 +80,32 @@ import static
org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_DIFF_DIR;
import static org.apache.hadoop.ozone.OzoneConsts.OM_SNAPSHOT_DIR;
import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_INCLUDE_SNAPSHOT_DATA;
import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_REQUEST_FLUSH;
+import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_HTTP_AUTH_TYPE;
import org.apache.ozone.test.GenericTestUtils;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.Timeout;
-import org.mockito.Matchers;
+import org.junit.Assert;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
import static org.apache.hadoop.ozone.om.OmSnapshotManager.OM_HARDLINK_FILE;
import static
org.apache.hadoop.ozone.om.snapshot.OmSnapshotUtils.truncateFileName;
import static org.apache.hadoop.ozone.om.OmSnapshotManager.getSnapshotPath;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -106,6 +118,7 @@ import static org.mockito.Mockito.when;
/**
* Class used for testing the OM DB Checkpoint provider servlet.
*/
+@Timeout(240)
public class TestOMDbCheckpointServlet {
private OzoneConfiguration conf;
private File tempFile;
@@ -121,13 +134,10 @@ public class TestOMDbCheckpointServlet {
private String snapshotDirName2;
private Path compactionDirPath;
private DBCheckpoint dbCheckpoint;
+ private String method;
+ private File folder;
private static final String FABRICATED_FILE_NAME = "fabricatedFile.sst";
- @Rule
- public Timeout timeout = Timeout.seconds(240);
-
- @Rule
- public TemporaryFolder folder = new TemporaryFolder();
/**
* Create a MiniDFSCluster for testing.
* <p>
@@ -135,11 +145,12 @@ public class TestOMDbCheckpointServlet {
*
* @throws Exception
*/
- @Before
- public void init() throws Exception {
+ @BeforeEach
+ public void init(@TempDir File tempDir) throws Exception {
+ folder = tempDir;
conf = new OzoneConfiguration();
- tempFile = File.createTempFile("testDoGet_" + System
+ tempFile = File.createTempFile("temp_" + System
.currentTimeMillis(), ".tar");
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
@@ -164,7 +175,7 @@ public class TestOMDbCheckpointServlet {
/**
* Shutdown MiniDFSCluster.
*/
- @After
+ @AfterEach
public void shutdown() throws InterruptedException {
if (cluster != null) {
cluster.shutdown();
@@ -202,6 +213,8 @@ public class TestOMDbCheckpointServlet {
doCallRealMethod().when(omDbCheckpointServletMock).doGet(requestMock,
responseMock);
+ doCallRealMethod().when(omDbCheckpointServletMock).doPost(requestMock,
+ responseMock);
doCallRealMethod().when(omDbCheckpointServletMock)
.writeDbDataToStream(any(), any(), any(), any(), any());
@@ -210,15 +223,17 @@ public class TestOMDbCheckpointServlet {
.thenReturn(lock);
}
- @Test
- public void testDoGet() throws Exception {
+ @ParameterizedTest
+ @MethodSource("getHttpMethods")
+ public void testEndpoint(String httpMethod) throws Exception {
+ this.method = httpMethod;
+
conf.setBoolean(OZONE_ACL_ENABLED, false);
conf.set(OZONE_ADMINISTRATORS, OZONE_ADMINISTRATORS_WILDCARD);
setupCluster();
final OzoneManager om = cluster.getOzoneManager();
-
doCallRealMethod().when(omDbCheckpointServletMock).initialize(
om.getMetadataManager().getStore(),
om.getMetrics().getDBCheckpointMetrics(),
@@ -228,8 +243,13 @@ public class TestOMDbCheckpointServlet {
om.isSpnegoEnabled());
doNothing().when(responseMock).setContentType("application/x-tar");
- doNothing().when(responseMock).setHeader(Matchers.anyString(),
- Matchers.anyString());
+ doNothing().when(responseMock).setHeader(anyString(), anyString());
+
+ List<String> toExcludeList = new ArrayList<>();
+ toExcludeList.add("sstFile1.sst");
+ toExcludeList.add("sstFile2.sst");
+
+ setupHttpMethod(toExcludeList);
when(responseMock.getOutputStream()).thenReturn(servletOutputStream);
@@ -237,21 +257,57 @@ public class TestOMDbCheckpointServlet {
long initialCheckpointCount =
omMetrics.getDBCheckpointMetrics().getNumCheckpoints();
- omDbCheckpointServletMock.doGet(requestMock, responseMock);
+ doEndpoint();
- Assert.assertTrue(tempFile.length() > 0);
- Assert.assertTrue(
+ Assertions.assertTrue(tempFile.length() > 0);
+ Assertions.assertTrue(
omMetrics.getDBCheckpointMetrics().
getLastCheckpointCreationTimeTaken() > 0);
- Assert.assertTrue(
+ Assertions.assertTrue(
omMetrics.getDBCheckpointMetrics().
getLastCheckpointStreamingTimeTaken() > 0);
- Assert.assertTrue(omMetrics.getDBCheckpointMetrics().
+ Assertions.assertTrue(omMetrics.getDBCheckpointMetrics().
getNumCheckpoints() > initialCheckpointCount);
+
+ Mockito.verify(omDbCheckpointServletMock).writeDbDataToStream(any(),
+ any(), any(), eq(toExcludeList), any());
}
@Test
- public void testSpnegoEnabled() throws Exception {
+ public void testDoPostWithInvalidContentType() throws Exception {
+ conf.setBoolean(OZONE_ACL_ENABLED, false);
+ conf.set(OZONE_ADMINISTRATORS, OZONE_ADMINISTRATORS_WILDCARD);
+
+ setupCluster();
+
+ final OzoneManager om = cluster.getOzoneManager();
+
+ doCallRealMethod().when(omDbCheckpointServletMock).initialize(
+ om.getMetadataManager().getStore(),
+ om.getMetrics().getDBCheckpointMetrics(),
+ om.getAclsEnabled(),
+ om.getOmAdminUsernames(),
+ om.getOmAdminGroups(),
+ om.isSpnegoEnabled());
+
+ when(requestMock.getContentType()).thenReturn("application/json");
+ doNothing().when(responseMock).setContentType("application/x-tar");
+ doNothing().when(responseMock).setHeader(anyString(),
+ anyString());
+
+ when(responseMock.getOutputStream()).thenReturn(servletOutputStream);
+
+ omDbCheckpointServletMock.init();
+ omDbCheckpointServletMock.doPost(requestMock, responseMock);
+
+ Mockito.verify(responseMock).setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ }
+
+ @ParameterizedTest
+ @MethodSource("getHttpMethods")
+ public void testSpnegoEnabled(String httpMethod) throws Exception {
+ this.method = httpMethod;
+
conf.setBoolean(OZONE_ACL_ENABLED, true);
conf.set(OZONE_ADMINISTRATORS, "");
conf.set(OZONE_OM_HTTP_AUTH_TYPE, "kerberos");
@@ -273,7 +329,10 @@ public class TestOMDbCheckpointServlet {
om.isSpnegoEnabled());
omDbCheckpointServletMock.init();
- omDbCheckpointServletMock.doGet(requestMock, responseMock);
+
+ setupHttpMethod(new ArrayList<>());
+
+ doEndpoint();
// Response status should be set to 403 Forbidden since there was no user
// principal set in the request
@@ -286,7 +345,7 @@ public class TestOMDbCheckpointServlet {
when(userPrincipalMock.getName()).thenReturn("dn/localhost@REALM");
when(requestMock.getUserPrincipal()).thenReturn(userPrincipalMock);
- omDbCheckpointServletMock.doGet(requestMock, responseMock);
+ doEndpoint();
// Verify that the Response status is set to 403 again for DN user.
verify(responseMock, times(2)).setStatus(HttpServletResponse.SC_FORBIDDEN);
@@ -297,11 +356,11 @@ public class TestOMDbCheckpointServlet {
when(requestMock.getUserPrincipal()).thenReturn(userPrincipalMock);
when(responseMock.getOutputStream()).thenReturn(servletOutputStream);
- omDbCheckpointServletMock.doGet(requestMock, responseMock);
+ doEndpoint();
// Recon user should be able to access the servlet and download the
// snapshot
- Assert.assertTrue(tempFile.length() > 0);
+ Assertions.assertTrue(tempFile.length() > 0);
}
@Test
@@ -318,16 +377,16 @@ public class TestOMDbCheckpointServlet {
}
// Untar the file into a temp folder to be examined.
- String testDirName = folder.newFolder().getAbsolutePath();
+ String testDirName = folder.getAbsolutePath();
int testDirLength = testDirName.length() + 1;
String newDbDirName = testDirName + OM_KEY_PREFIX + OM_DB_NAME;
int newDbDirLength = newDbDirName.length() + 1;
File newDbDir = new File(newDbDirName);
- Assert.assertTrue(newDbDir.mkdirs());
+ Assertions.assertTrue(newDbDir.mkdirs());
FileUtil.unTar(tempFile, newDbDir);
// Move snapshot dir to correct location.
- Assert.assertTrue(new File(newDbDirName, OM_SNAPSHOT_DIR)
+ Assertions.assertTrue(new File(newDbDirName, OM_SNAPSHOT_DIR)
.renameTo(new File(newDbDir.getParent(), OM_SNAPSHOT_DIR)));
// Confirm the checkpoint directories match, (after remove extras).
@@ -338,10 +397,10 @@ public class TestOMDbCheckpointServlet {
Set<String> finalCheckpointSet = getFiles(finalCheckpointLocation,
newDbDirLength);
- Assert.assertTrue("hardlink file exists in checkpoint dir",
- finalCheckpointSet.contains(OM_HARDLINK_FILE));
+ Assertions.assertTrue(finalCheckpointSet.contains(OM_HARDLINK_FILE),
+ "hardlink file exists in checkpoint dir");
finalCheckpointSet.remove(OM_HARDLINK_FILE);
- Assert.assertEquals(initialCheckpointSet, finalCheckpointSet);
+ Assertions.assertEquals(initialCheckpointSet, finalCheckpointSet);
int metaDirLength = metaDir.toString().length() + 1;
String shortSnapshotLocation =
@@ -360,8 +419,8 @@ public class TestOMDbCheckpointServlet {
OM_HARDLINK_FILE))) {
for (String line : lines.collect(Collectors.toList())) {
- Assert.assertFalse("CURRENT file is not a hard link",
- line.contains("CURRENT"));
+ Assertions.assertFalse(line.contains("CURRENT"),
+ "CURRENT file is not a hard link");
if (line.contains(FABRICATED_FILE_NAME)) {
fabricatedLinkLines.add(line);
} else {
@@ -378,8 +437,8 @@ public class TestOMDbCheckpointServlet {
Set<String> initialFullSet =
getFiles(Paths.get(metaDir.toString(), OM_SNAPSHOT_DIR),
metaDirLength);
- Assert.assertEquals("expected snapshot files not found",
- initialFullSet, finalFullSet);
+ Assertions.assertEquals(initialFullSet, finalFullSet,
+ "expected snapshot files not found");
}
@Test
@@ -398,7 +457,7 @@ public class TestOMDbCheckpointServlet {
}
// Untar the file into a temp folder to be examined.
- String testDirName = folder.newFolder().getAbsolutePath();
+ String testDirName = folder.getAbsolutePath();
int testDirLength = testDirName.length() + 1;
FileUtil.unTar(tempFile, new File(testDirName));
@@ -410,7 +469,7 @@ public class TestOMDbCheckpointServlet {
Set<String> finalCheckpointSet = getFiles(finalCheckpointLocation,
testDirLength);
- Assert.assertEquals(initialCheckpointSet, finalCheckpointSet);
+ Assertions.assertEquals(initialCheckpointSet, finalCheckpointSet);
}
@Test
@@ -424,7 +483,7 @@ public class TestOMDbCheckpointServlet {
new FileOutputStream(dummyFile), StandardCharsets.UTF_8)) {
writer.write("Dummy data.");
}
- Assert.assertTrue(dummyFile.exists());
+ Assertions.assertTrue(dummyFile.exists());
List<String> toExcludeList = new ArrayList<>();
List<String> excludedList = new ArrayList<>();
toExcludeList.add(dummyFile.getName());
@@ -440,7 +499,7 @@ public class TestOMDbCheckpointServlet {
}
// Untar the file into a temp folder to be examined.
- String testDirName = folder.newFolder().getAbsolutePath();
+ String testDirName = folder.getAbsolutePath();
int testDirLength = testDirName.length() + 1;
FileUtil.unTar(tempFile, new File(testDirName));
@@ -453,7 +512,89 @@ public class TestOMDbCheckpointServlet {
testDirLength);
initialCheckpointSet.removeAll(finalCheckpointSet);
- Assert.assertTrue(initialCheckpointSet.contains(dummyFile.getName()));
+ Assertions.assertTrue(initialCheckpointSet.contains(dummyFile.getName()));
+ }
+
+ /**
+ * Calls endpoint in regards to parametrized HTTP method.
+ */
+ private void doEndpoint() {
+ if (method.equals("POST")) {
+ omDbCheckpointServletMock.doPost(requestMock, responseMock);
+ } else {
+ omDbCheckpointServletMock.doGet(requestMock, responseMock);
+ }
+ }
+
+ /**
+ * Parametrizes test with HTTP method.
+ * @return HTTP method.
+ */
+ private static Stream<Arguments> getHttpMethods() {
+ return Stream.of(arguments("POST"), arguments("GET"));
+ }
+
+ /**
+ * Setups HTTP method details depending on parametrized HTTP method.
+ * @param toExcludeList SST file names to be excluded.
+ * @throws IOException
+ */
+ private void setupHttpMethod(List<String> toExcludeList) throws IOException {
+ if (method.equals("POST")) {
+ setupPostMethod(toExcludeList);
+ } else {
+ setupGetMethod(toExcludeList);
+ }
+ }
+
+ /**
+ * Setups details for HTTP POST request.
+ * @param toExcludeList SST file names to be excluded.
+ * @throws IOException
+ */
+ private void setupPostMethod(List<String> toExcludeList)
+ throws IOException {
+ when(requestMock.getMethod()).thenReturn("POST");
+ when(requestMock.getContentType()).thenReturn("multipart/form-data; " +
+ "boundary=" + MULTIPART_FORM_DATA_BOUNDARY);
+
+ // Generate form data
+ String crNl = "\r\n";
+ String contentDisposition = "Content-Disposition: form-data; name=\"" +
+ OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST + "[]\"" + crNl + crNl;
+ String boundary = "--" + MULTIPART_FORM_DATA_BOUNDARY;
+ String endBoundary = boundary + "--" + crNl;
+ StringBuilder sb = new StringBuilder();
+ toExcludeList.forEach(sfn -> {
+ sb.append(boundary).append(crNl);
+ sb.append(contentDisposition);
+ sb.append(sfn).append(crNl);
+ });
+ sb.append(endBoundary);
+
+ // Use generated form data as input stream to the HTTP request
+ InputStream input = new ByteArrayInputStream(
+ sb.toString().getBytes(StandardCharsets.UTF_8));
+ ServletInputStream inputStream = Mockito.mock(ServletInputStream.class);
+ when(requestMock.getInputStream()).thenReturn(inputStream);
+ when(inputStream.read(any(byte[].class), anyInt(), anyInt()))
+ .thenAnswer(invocation -> {
+ byte[] buffer = invocation.getArgument(0);
+ int offset = invocation.getArgument(1);
+ int length = invocation.getArgument(2);
+ return input.read(buffer, offset, length);
+ });
+ }
+
+ /**
+ * Setups details for HTTP GET request.
+ * @param toExcludeList SST file names to be excluded.
+ */
+ private void setupGetMethod(List<String> toExcludeList) {
+ when(requestMock.getMethod()).thenReturn("GET");
+ when(requestMock
+ .getParameterValues(OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST))
+ .thenReturn(toExcludeList.toArray(new String[0]));
}
private void prepSnapshotData() throws Exception {
@@ -478,8 +619,8 @@ public class TestOMDbCheckpointServlet {
Path fabricatedSnapshot = Paths.get(
new File(snapshotDirName).getParent(),
"fabricatedSnapshot");
- Assert.assertTrue(fabricatedSnapshot.toFile().mkdirs());
- Assert.assertTrue(Paths.get(fabricatedSnapshot.toString(),
+ fabricatedSnapshot.toFile().mkdirs();
+ Assertions.assertTrue(Paths.get(fabricatedSnapshot.toString(),
FABRICATED_FILE_NAME).toFile().createNewFile());
// Create fabricated links to snapshot dirs
@@ -567,31 +708,32 @@ public class TestOMDbCheckpointServlet {
String realDir = null;
for (String dir: directories) {
if (Paths.get(testDirName, dir, FABRICATED_FILE_NAME).toFile().exists())
{
- Assert.assertNull(
- "Exactly one copy of the fabricated file exists in the tarball",
- realDir);
+ Assertions.assertNull(realDir,
+ "Exactly one copy of the fabricated file exists in the tarball");
realDir = dir;
}
}
- Assert.assertNotNull("real directory found", realDir);
+ Assertions.assertNotNull(realDir, "real directory found");
directories.remove(realDir);
Iterator<String> directoryIterator = directories.iterator();
String dir0 = directoryIterator.next();
String dir1 = directoryIterator.next();
- Assert.assertNotEquals("link directories are different", dir0, dir1);
+ Assertions.assertNotEquals("link directories are different", dir0, dir1);
for (String line : lines) {
String[] files = line.split("\t");
- Assert.assertTrue("fabricated entry contains valid first directory",
- files[0].startsWith(dir0) || files[0].startsWith(dir1));
- Assert.assertTrue("fabricated entry contains correct real directory",
- files[1].startsWith(realDir));
+ Assertions.assertTrue(
+ files[0].startsWith(dir0) || files[0].startsWith(dir1),
+ "fabricated entry contains valid first directory");
+ Assertions.assertTrue(files[1].startsWith(realDir),
+ "fabricated entry contains correct real directory");
Path path0 = Paths.get(files[0]);
Path path1 = Paths.get(files[1]);
- Assert.assertTrue("fabricated entries contains correct file name",
+ Assertions.assertTrue(
path0.getFileName().toString().equals(FABRICATED_FILE_NAME) &&
- path1.getFileName().toString().equals(FABRICATED_FILE_NAME));
+ path1.getFileName().toString().equals(FABRICATED_FILE_NAME),
+ "fabricated entries contains correct file name");
}
}
@@ -601,13 +743,13 @@ public class TestOMDbCheckpointServlet {
String shortSnapshotLocation2,
String line) {
String[] files = line.split("\t");
- Assert.assertTrue("hl entry starts with valid snapshot dir",
- files[0].startsWith(shortSnapshotLocation) ||
- files[0].startsWith(shortSnapshotLocation2));
+ Assertions.assertTrue(files[0].startsWith(shortSnapshotLocation) ||
+ files[0].startsWith(shortSnapshotLocation2),
+ "hl entry starts with valid snapshot dir");
String file0 = files[0].substring(shortSnapshotLocation.length() + 1);
String file1 = files[1];
- Assert.assertEquals("hl filenames are the same", file0, file1);
+ Assertions.assertEquals(file0, file1, "hl filenames are the same");
}
@Test
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis_snapshot/OmRatisSnapshotProvider.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis_snapshot/OmRatisSnapshotProvider.java
index b924a2294f..7031ca0f3d 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis_snapshot/OmRatisSnapshotProvider.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis_snapshot/OmRatisSnapshotProvider.java
@@ -18,11 +18,13 @@
package org.apache.hadoop.ozone.om.ratis_snapshot;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -39,7 +41,9 @@ import static java.net.HttpURLConnection.HTTP_CREATED;
import static java.net.HttpURLConnection.HTTP_OK;
import org.apache.commons.io.FileUtils;
+import static org.apache.hadoop.ozone.OzoneConsts.MULTIPART_FORM_DATA_BOUNDARY;
import static org.apache.hadoop.ozone.OzoneConsts.OM_DB_NAME;
+import static
org.apache.hadoop.ozone.OzoneConsts.OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_HTTP_AUTH_TYPE;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_PROVIDER_CONNECTION_TIMEOUT_DEFAULT;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_SNAPSHOT_PROVIDER_CONNECTION_TIMEOUT_KEY;
@@ -78,6 +82,17 @@ public class OmRatisSnapshotProvider extends
RDBSnapshotProvider {
private final boolean spnegoEnabled;
private final URLConnectionFactory connectionFactory;
+ public OmRatisSnapshotProvider(File snapshotDir,
+ Map<String, OMNodeDetails> peerNodesMap, HttpConfig.Policy httpPolicy,
+ boolean spnegoEnabled, URLConnectionFactory connectionFactory) {
+ super(snapshotDir, OM_DB_NAME);
+ this.peerNodesMap = new ConcurrentHashMap<>(peerNodesMap);
+ this.httpPolicy = httpPolicy;
+ this.spnegoEnabled = spnegoEnabled;
+ this.connectionFactory = connectionFactory;
+ }
+
+
public OmRatisSnapshotProvider(MutableConfigurationSource conf,
File omRatisSnapshotDir, Map<String, OMNodeDetails> peerNodeDetails) {
super(omRatisSnapshotDir, OM_DB_NAME);
@@ -127,14 +142,21 @@ public class OmRatisSnapshotProvider extends
RDBSnapshotProvider {
throws IOException {
OMNodeDetails leader = peerNodesMap.get(leaderNodeID);
URL omCheckpointUrl = leader.getOMDBCheckpointEndpointUrl(
- httpPolicy.isHttpEnabled(), true,
- HAUtils.getExistingSstFiles(getCandidateDir()));
+ httpPolicy.isHttpEnabled(), true);
LOG.info("Downloading latest checkpoint from Leader OM {}. Checkpoint " +
"URL: {}", leaderNodeID, omCheckpointUrl);
SecurityUtil.doAsCurrentUser(() -> {
HttpURLConnection connection = (HttpURLConnection)
connectionFactory.openConnection(omCheckpointUrl, spnegoEnabled);
- connection.setRequestMethod("GET");
+
+ connection.setRequestMethod("POST");
+ String contentTypeValue = "multipart/form-data; boundary=" +
+ MULTIPART_FORM_DATA_BOUNDARY;
+ connection.setRequestProperty("Content-Type", contentTypeValue);
+ connection.setDoOutput(true);
+ writeFormData(connection,
+ HAUtils.getExistingSstFiles(getCandidateDir()));
+
connection.connect();
int errorCode = connection.getResponseCode();
if ((errorCode != HTTP_OK) && (errorCode != HTTP_CREATED)) {
@@ -159,10 +181,52 @@ public class OmRatisSnapshotProvider extends
RDBSnapshotProvider {
});
}
+ /**
+ * Writes form data to output stream as any HTTP client would for a
+ * multipart/form-data request.
+ * Proper form data includes separator, content disposition and value
+ * separated by a new line.
+ * Example:
+ * <pre>
+ * -----XXX
+ * Content-Disposition: form-data; name="field1"
+ *
+ * value1</pre>
+ * @param connection HTTP URL connection which output stream is used.
+ * @param sstFiles SST files for exclusion.
+ * @throws IOException if an exception occured during writing to output
+ * stream.
+ */
+ public static void writeFormData(HttpURLConnection connection,
+ List<String> sstFiles) throws IOException {
+ try (DataOutputStream out =
+ new DataOutputStream(connection.getOutputStream())) {
+ String toExcludeSstField =
+ "name=\"" + OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST + "[]" + "\"";
+ String crNl = "\r\n";
+ String contentDisposition =
+ "Content-Disposition: form-data; " + toExcludeSstField + crNl + crNl;
+ String separator = "--" + MULTIPART_FORM_DATA_BOUNDARY;
+
+ if (sstFiles.isEmpty()) {
+ out.writeBytes(separator + crNl);
+ out.writeBytes(contentDisposition);
+ }
+
+ for (String sstFile : sstFiles) {
+ out.writeBytes(separator + crNl);
+ out.writeBytes(contentDisposition);
+ out.writeBytes(sstFile + crNl);
+ }
+ out.writeBytes(separator + "--" + crNl);
+ }
+ }
+
@Override
public void close() throws IOException {
if (connectionFactory != null) {
connectionFactory.destroy();
}
}
+
}
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis_snapshot/TestOmRatisSnapshotProvider.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis_snapshot/TestOmRatisSnapshotProvider.java
new file mode 100644
index 0000000000..6576399922
--- /dev/null
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis_snapshot/TestOmRatisSnapshotProvider.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.ozone.om.ratis_snapshot;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.hdds.server.http.HttpConfig;
+import org.apache.hadoop.hdfs.web.URLConnectionFactory;
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.om.helpers.OMNodeDetails;
+import
org.apache.hadoop.security.authentication.client.AuthenticationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.apache.hadoop.ozone.OzoneConsts.MULTIPART_FORM_DATA_BOUNDARY;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link OmRatisSnapshotProvider}.
+ */
+public class TestOmRatisSnapshotProvider {
+
+ private OmRatisSnapshotProvider omRatisSnapshotProvider;
+ private URLConnectionFactory connectionFactory;
+ private OMNodeDetails leader;
+ private String leaderNodeId;
+ private static final String CR_NL = "\r\n";
+ public static final String CONTENT_DISPOSITION =
+ "Content-Disposition: form-data; name=\""
+ + OzoneConsts.OZONE_DB_CHECKPOINT_REQUEST_TO_EXCLUDE_SST + "[]\""
+ + CR_NL + CR_NL;
+ private StringBuilder sb;
+ private File targetFile;
+
+ @BeforeEach
+ public void setup(@TempDir File snapshotDir,
+ @TempDir File downloadDir) throws IOException {
+ targetFile = new File(downloadDir, "newfile");
+
+ Map<String, OMNodeDetails> peerNodesMap = new HashMap<>();
+ leaderNodeId = "1";
+ leader = mock(OMNodeDetails.class);
+ peerNodesMap.put(leaderNodeId, leader);
+
+ HttpConfig.Policy httpPolicy = mock(HttpConfig.Policy.class);
+ connectionFactory = mock(URLConnectionFactory.class);
+
+ omRatisSnapshotProvider =
+ new OmRatisSnapshotProvider(snapshotDir, peerNodesMap, httpPolicy,
+ false, connectionFactory);
+
+ sb = new StringBuilder();
+ sb.append("--" + MULTIPART_FORM_DATA_BOUNDARY + CR_NL);
+ sb.append(CONTENT_DISPOSITION);
+ }
+
+ @Test
+ public void testDownloadSnapshot() throws IOException,
+ AuthenticationException {
+ URL omCheckpointUrl = mock(URL.class);
+ when(leader.getOMDBCheckpointEndpointUrl(anyBoolean(), anyBoolean()))
+ .thenReturn(omCheckpointUrl);
+
+ HttpURLConnection connection = mock(HttpURLConnection.class);
+ when(connectionFactory.openConnection(any(URL.class), anyBoolean()))
+ .thenReturn(connection);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ when(connection.getOutputStream()).thenReturn(outputStream);
+ when(connection.getResponseCode()).thenReturn(HTTP_OK);
+ InputStream inputStream =
+ new ByteArrayInputStream(outputStream.toByteArray());
+ when(connection.getInputStream()).thenReturn(inputStream);
+
+ omRatisSnapshotProvider.downloadSnapshot(leaderNodeId, targetFile);
+
+ sb.append("--" + MULTIPART_FORM_DATA_BOUNDARY + "--" + CR_NL);
+ Assertions.assertEquals(sb.toString(),
+ new String(outputStream.toByteArray(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testWriteFormDataWithSstFile() throws IOException {
+ HttpURLConnection connection = mock(HttpURLConnection.class);
+ List<String> sstFiles = new ArrayList<>();
+ String fileName = "file1.sst";
+ sstFiles.add(fileName);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ when(connection.getOutputStream()).thenReturn(outputStream);
+
+
+ OmRatisSnapshotProvider.writeFormData(connection, sstFiles);
+
+ sb.append(fileName).append(CR_NL);
+ sb.append("--" + MULTIPART_FORM_DATA_BOUNDARY + "--" + CR_NL);
+ Assertions.assertEquals(sb.toString(),
+ new String(outputStream.toByteArray(), StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testWriteFormDataWithoutSstFile() throws IOException {
+ HttpURLConnection connection = mock(HttpURLConnection.class);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ when(connection.getOutputStream()).thenReturn(outputStream);
+
+ OmRatisSnapshotProvider.writeFormData(connection, new ArrayList<>());
+
+ sb.append("--" + MULTIPART_FORM_DATA_BOUNDARY + "--" + CR_NL);
+ Assertions.assertEquals(sb.toString(),
+ new String(outputStream.toByteArray(), StandardCharsets.UTF_8));
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 0d88152bf7..0d8e0c7153 100644
--- a/pom.xml
+++ b/pom.xml
@@ -128,6 +128,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xs
<commons-pool2.version>2.6.0</commons-pool2.version>
<commons-text.version>1.10.0</commons-text.version>
<commons-validator.version>1.6</commons-validator.version>
+ <commons-fileupload.version>1.5</commons-fileupload.version>
<download-maven-plugin.version>1.6.8</download-maven-plugin.version>
<test.build.dir>${project.build.directory}/test-dir</test.build.dir>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]