diff --git a/dumper/app/src/main/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaConnectorVerifier.java b/dumper/app/src/main/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaConnectorVerifier.java new file mode 100644 index 000000000..7ff7b78fb --- /dev/null +++ b/dumper/app/src/main/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaConnectorVerifier.java @@ -0,0 +1,84 @@ +/* + * Copyright 2022-2025 Google LLC + * Copyright 2013-2021 CompilerWorks + * + * Licensed 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 com.google.edwmigration.dumper.application.dumper.connector.cloudera.manager; + +import com.google.edwmigration.dumper.application.dumper.ConnectorArguments; +import java.io.IOException; +import javax.annotation.Nonnull; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.util.EntityUtils; + +/** Utility class for verifying the preconditions and configuration of a Cloudera Connector. */ +public final class ClouderaConnectorVerifier { + + private ClouderaConnectorVerifier() {} + + /** + * Verifies the validity of the connector configuration and environment. + * + *
This involves checking connectivity to the API, verifying the existence of the specified
+ * cluster (if provided), and ensuring other preconditions are met.
+ *
+ * @param handle The handle to the Cloudera Manager API.
+ * @param arguments The connector arguments containing target cluster details.
+ * @throws ClouderaConnectorException If verification fails due to missing resources, API errors,
+ * or connectivity issues.
+ */
+ public static void verify(
+ @Nonnull ClouderaManagerHandle handle, @Nonnull ConnectorArguments arguments)
+ throws ClouderaConnectorException {
+ verifyClusterExists(handle, arguments.getCluster());
+ }
+
+ private static void verifyClusterExists(ClouderaManagerHandle handle, String clusterName)
+ throws ClouderaConnectorException {
+ if (clusterName == null) {
+ return;
+ }
+ String endpoint = String.format("%s/clusters/%s", handle.getApiURI(), clusterName);
+ HttpGet httpGet = new HttpGet(endpoint);
+
+ try (CloseableHttpResponse response = handle.getHttpClient().execute(httpGet)) {
+
+ int statusCode = response.getStatusLine().getStatusCode();
+
+ if (statusCode == 404) {
+ throw new ClouderaConnectorException(
+ String.format("Specified cluster '%s' not found.", clusterName));
+ }
+
+ if (!isHttpStatusSuccess(statusCode)) {
+ String errorMsg = EntityUtils.toString(response.getEntity());
+ throw new ClouderaConnectorException(
+ String.format(
+ "Unexpected API error checking cluster '%s'. Code: %d. Message: %s",
+ clusterName, statusCode, errorMsg));
+ }
+ } catch (IOException e) {
+ throw new ClouderaConnectorException(
+ String.format(
+ "Failed to communicate with Cloudera Manager API while checking cluster '%s'.",
+ clusterName),
+ e);
+ }
+ }
+
+ private static boolean isHttpStatusSuccess(int statusCode) {
+ return statusCode >= 200 && statusCode < 300;
+ }
+}
diff --git a/dumper/app/src/main/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnector.java b/dumper/app/src/main/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnector.java
index 178c08e45..aa9e34b91 100644
--- a/dumper/app/src/main/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnector.java
+++ b/dumper/app/src/main/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnector.java
@@ -129,6 +129,9 @@ public ClouderaManagerHandle open(@Nonnull ConnectorArguments arguments) throws
String user = arguments.getUser();
String password = arguments.getPasswordOrPrompt();
doClouderaManagerLogin(handle.getBaseURI(), httpClient, user, password);
+
+ ClouderaConnectorVerifier.verify(handle, arguments);
+
return handle;
}
diff --git a/dumper/app/src/test/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaConnectorVerifierTest.java b/dumper/app/src/test/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaConnectorVerifierTest.java
new file mode 100644
index 000000000..c33ab469d
--- /dev/null
+++ b/dumper/app/src/test/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaConnectorVerifierTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2022-2025 Google LLC
+ * Copyright 2013-2021 CompilerWorks
+ *
+ * Licensed 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 com.google.edwmigration.dumper.application.dumper.connector.cloudera.manager;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.edwmigration.dumper.application.dumper.ConnectorArguments;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClouderaConnectorVerifierTest {
+
+ private final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+ private final ClouderaManagerHandle handle = mock(ClouderaManagerHandle.class);
+ private final ConnectorArguments arguments = mock(ConnectorArguments.class);
+
+ @Before
+ public void setUp() throws URISyntaxException {
+ when(handle.getHttpClient()).thenReturn(httpClient);
+ when(handle.getApiURI()).thenReturn(new URI("http://localhost:7183/api/v57"));
+ }
+
+ @Test
+ public void verify_clusterIsNull_doesNothing() {
+ when(arguments.getCluster()).thenReturn(null);
+
+ ClouderaConnectorVerifier.verify(handle, arguments);
+
+ // No exception thrown expected
+ }
+
+ @Test
+ public void verify_clusterExists_doesNothing() throws Exception {
+ when(arguments.getCluster()).thenReturn("cluster-1");
+ CloseableHttpResponse response = mock(CloseableHttpResponse.class);
+ StatusLine statusLine = mock(StatusLine.class);
+ when(statusLine.getStatusCode()).thenReturn(200);
+ when(response.getStatusLine()).thenReturn(statusLine);
+ when(httpClient.execute(any())).thenReturn(response);
+
+ ClouderaConnectorVerifier.verify(handle, arguments);
+
+ // No exception thrown expected
+ }
+
+ @Test
+ public void verify_clusterNotFound_throwsException() throws Exception {
+ String clusterName = "cluster-1";
+ when(arguments.getCluster()).thenReturn(clusterName);
+ CloseableHttpResponse response = mock(CloseableHttpResponse.class);
+ StatusLine statusLine = mock(StatusLine.class);
+ when(statusLine.getStatusCode()).thenReturn(404);
+ when(response.getStatusLine()).thenReturn(statusLine);
+ when(httpClient.execute(any())).thenReturn(response);
+
+ ClouderaConnectorException e =
+ assertThrows(
+ ClouderaConnectorException.class,
+ () -> ClouderaConnectorVerifier.verify(handle, arguments));
+
+ assertThat(e.getMessage(), containsString("Specified cluster 'cluster-1' not found."));
+ }
+
+ @Test
+ public void verify_unexpectedApiError_throwsException() throws Exception {
+ // Arrange
+ String clusterName = "cluster-1";
+ when(arguments.getCluster()).thenReturn(clusterName);
+ CloseableHttpResponse response = mock(CloseableHttpResponse.class);
+ StatusLine statusLine = mock(StatusLine.class);
+ when(statusLine.getStatusCode()).thenReturn(500);
+ when(response.getStatusLine()).thenReturn(statusLine);
+ when(response.getEntity()).thenReturn(new StringEntity("Internal Server Error"));
+ when(httpClient.execute(any())).thenReturn(response);
+
+ ClouderaConnectorException e =
+ assertThrows(
+ ClouderaConnectorException.class,
+ () -> ClouderaConnectorVerifier.verify(handle, arguments));
+
+ assertThat(
+ e.getMessage(),
+ containsString(
+ "Unexpected API error checking cluster 'cluster-1'. Code: 500. Message: Internal Server Error"));
+ }
+
+ @Test
+ public void verify_ioException_throwsException() throws Exception {
+ String clusterName = "cluster-1";
+ when(arguments.getCluster()).thenReturn(clusterName);
+ when(httpClient.execute(any())).thenThrow(new IOException("some error"));
+
+ ClouderaConnectorException e =
+ assertThrows(
+ ClouderaConnectorException.class,
+ () -> ClouderaConnectorVerifier.verify(handle, arguments));
+
+ assertThat(
+ e.getMessage(),
+ containsString(
+ "Failed to communicate with Cloudera Manager API while checking cluster 'cluster-1'."));
+ }
+}
diff --git a/dumper/app/src/test/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnectorTest.java b/dumper/app/src/test/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnectorTest.java
index c862339fc..4646a0e1e 100644
--- a/dumper/app/src/test/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnectorTest.java
+++ b/dumper/app/src/test/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnectorTest.java
@@ -19,6 +19,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mockStatic;
import com.google.common.collect.ImmutableMap;
import com.google.edwmigration.dumper.application.dumper.ConnectorArguments;
@@ -27,11 +30,14 @@
import com.google.edwmigration.dumper.application.dumper.task.FormatTask;
import com.google.edwmigration.dumper.application.dumper.task.Task;
import com.google.edwmigration.dumper.application.dumper.task.TaskCategory;
+import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.Test;
+import org.mockito.MockedStatic;
public class ClouderaManagerConnectorTest {
private static final String clouderaRequiredArgs =
@@ -255,6 +261,73 @@ public void addTasksTo_containsDefaultTasks() throws Exception {
assertEquals("One FormatTask is expected", formatCount, 1);
}
+ @Test
+ public void open_success() throws Exception {
+ try (MockedStatic