Skip to content

Commit 8bbfc0f

Browse files
[b406163439] Verify if specified Cloudera cluster exists before processing tasks
Establishes a new utility class, `ClouderaConnectorVerifier`, to serve as the central entry point for validating connector configuration and fail fast in case of incorrect cluster name before tasks execution.
1 parent 9624a94 commit 8bbfc0f

File tree

4 files changed

+292
-0
lines changed

4 files changed

+292
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2022-2025 Google LLC
3+
* Copyright 2013-2021 CompilerWorks
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.google.edwmigration.dumper.application.dumper.connector.cloudera.manager;
18+
19+
import com.google.edwmigration.dumper.application.dumper.ConnectorArguments;
20+
import java.io.IOException;
21+
import javax.annotation.Nonnull;
22+
import org.apache.http.client.methods.CloseableHttpResponse;
23+
import org.apache.http.client.methods.HttpGet;
24+
import org.apache.http.util.EntityUtils;
25+
26+
/** Utility class for verifying the preconditions and configuration of a Cloudera Connector. */
27+
public final class ClouderaConnectorVerifier {
28+
29+
private ClouderaConnectorVerifier() {}
30+
31+
/**
32+
* Verifies the validity of the connector configuration and environment.
33+
*
34+
* <p>This involves checking connectivity to the API, verifying the existence of the specified
35+
* cluster (if provided), and ensuring other preconditions are met.
36+
*
37+
* @param handle The handle to the Cloudera Manager API.
38+
* @param arguments The connector arguments containing target cluster details.
39+
* @throws ClouderaConnectorException If verification fails due to missing resources, API errors,
40+
* or connectivity issues.
41+
*/
42+
public static void verify(
43+
@Nonnull ClouderaManagerHandle handle, @Nonnull ConnectorArguments arguments)
44+
throws ClouderaConnectorException {
45+
verifyClusterExists(handle, arguments.getCluster());
46+
}
47+
48+
private static void verifyClusterExists(ClouderaManagerHandle handle, String clusterName)
49+
throws ClouderaConnectorException {
50+
if (clusterName == null) {
51+
return;
52+
}
53+
String endpoint = String.format("%s/clusters/%s", handle.getApiURI(), clusterName);
54+
HttpGet httpGet = new HttpGet(endpoint);
55+
56+
try (CloseableHttpResponse response = handle.getHttpClient().execute(httpGet)) {
57+
58+
int statusCode = response.getStatusLine().getStatusCode();
59+
60+
if (statusCode == 404) {
61+
throw new ClouderaConnectorException(
62+
String.format("Specified cluster '%s' not found.", clusterName));
63+
}
64+
65+
if (!isHttpStatusSuccess(statusCode)) {
66+
String errorMsg = EntityUtils.toString(response.getEntity());
67+
throw new ClouderaConnectorException(
68+
String.format(
69+
"Unexpected API error checking cluster '%s'. Code: %d. Message: %s",
70+
clusterName, statusCode, errorMsg));
71+
}
72+
} catch (IOException e) {
73+
throw new ClouderaConnectorException(
74+
String.format(
75+
"Failed to communicate with Cloudera Manager API while checking cluster '%s'.",
76+
clusterName),
77+
e);
78+
}
79+
}
80+
81+
private static boolean isHttpStatusSuccess(int statusCode) {
82+
return statusCode >= 200 && statusCode < 300;
83+
}
84+
}

dumper/app/src/main/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnector.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ public ClouderaManagerHandle open(@Nonnull ConnectorArguments arguments) throws
126126
String user = arguments.getUser();
127127
String password = arguments.getPasswordOrPrompt();
128128
doClouderaManagerLogin(handle.getBaseURI(), httpClient, user, password);
129+
130+
ClouderaConnectorVerifier.verify(handle, arguments);
131+
129132
return handle;
130133
}
131134

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright 2022-2025 Google LLC
3+
* Copyright 2013-2021 CompilerWorks
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.google.edwmigration.dumper.application.dumper.connector.cloudera.manager;
18+
19+
import static org.hamcrest.CoreMatchers.containsString;
20+
import static org.hamcrest.MatcherAssert.assertThat;
21+
import static org.junit.Assert.assertThrows;
22+
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.when;
25+
26+
import com.google.edwmigration.dumper.application.dumper.ConnectorArguments;
27+
import java.io.IOException;
28+
import java.net.URI;
29+
import java.net.URISyntaxException;
30+
import org.apache.http.StatusLine;
31+
import org.apache.http.client.methods.CloseableHttpResponse;
32+
import org.apache.http.entity.StringEntity;
33+
import org.apache.http.impl.client.CloseableHttpClient;
34+
import org.junit.Before;
35+
import org.junit.Test;
36+
import org.junit.runner.RunWith;
37+
import org.mockito.junit.MockitoJUnitRunner;
38+
39+
@RunWith(MockitoJUnitRunner.class)
40+
public class ClouderaConnectorVerifierTest {
41+
42+
private final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
43+
private final ClouderaManagerHandle handle = mock(ClouderaManagerHandle.class);
44+
private final ConnectorArguments arguments = mock(ConnectorArguments.class);
45+
46+
@Before
47+
public void setUp() throws URISyntaxException {
48+
when(handle.getHttpClient()).thenReturn(httpClient);
49+
when(handle.getApiURI()).thenReturn(new URI("http://localhost:7183/api/v57"));
50+
}
51+
52+
@Test
53+
public void verify_clusterIsNull_doesNothing() {
54+
when(arguments.getCluster()).thenReturn(null);
55+
56+
ClouderaConnectorVerifier.verify(handle, arguments);
57+
58+
// No exception thrown expected
59+
}
60+
61+
@Test
62+
public void verify_clusterExists_doesNothing() throws Exception {
63+
when(arguments.getCluster()).thenReturn("cluster-1");
64+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
65+
StatusLine statusLine = mock(StatusLine.class);
66+
when(statusLine.getStatusCode()).thenReturn(200);
67+
when(response.getStatusLine()).thenReturn(statusLine);
68+
when(httpClient.execute(any())).thenReturn(response);
69+
70+
ClouderaConnectorVerifier.verify(handle, arguments);
71+
72+
// No exception thrown expected
73+
}
74+
75+
@Test
76+
public void verify_clusterNotFound_throwsException() throws Exception {
77+
String clusterName = "cluster-1";
78+
when(arguments.getCluster()).thenReturn(clusterName);
79+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
80+
StatusLine statusLine = mock(StatusLine.class);
81+
when(statusLine.getStatusCode()).thenReturn(404);
82+
when(response.getStatusLine()).thenReturn(statusLine);
83+
when(httpClient.execute(any())).thenReturn(response);
84+
85+
ClouderaConnectorException e =
86+
assertThrows(
87+
ClouderaConnectorException.class,
88+
() -> ClouderaConnectorVerifier.verify(handle, arguments));
89+
90+
assertThat(e.getMessage(), containsString("Specified cluster 'cluster-1' not found."));
91+
}
92+
93+
@Test
94+
public void verify_unexpectedApiError_throwsException() throws Exception {
95+
// Arrange
96+
String clusterName = "cluster-1";
97+
when(arguments.getCluster()).thenReturn(clusterName);
98+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
99+
StatusLine statusLine = mock(StatusLine.class);
100+
when(statusLine.getStatusCode()).thenReturn(500);
101+
when(response.getStatusLine()).thenReturn(statusLine);
102+
when(response.getEntity()).thenReturn(new StringEntity("Internal Server Error"));
103+
when(httpClient.execute(any())).thenReturn(response);
104+
105+
ClouderaConnectorException e =
106+
assertThrows(
107+
ClouderaConnectorException.class,
108+
() -> ClouderaConnectorVerifier.verify(handle, arguments));
109+
110+
assertThat(
111+
e.getMessage(),
112+
containsString(
113+
"Unexpected API error checking cluster 'cluster-1'. Code: 500. Message: Internal Server Error"));
114+
}
115+
116+
@Test
117+
public void verify_ioException_throwsException() throws Exception {
118+
String clusterName = "cluster-1";
119+
when(arguments.getCluster()).thenReturn(clusterName);
120+
when(httpClient.execute(any())).thenThrow(new IOException("some error"));
121+
122+
ClouderaConnectorException e =
123+
assertThrows(
124+
ClouderaConnectorException.class,
125+
() -> ClouderaConnectorVerifier.verify(handle, arguments));
126+
127+
assertThat(
128+
e.getMessage(),
129+
containsString(
130+
"Failed to communicate with Cloudera Manager API while checking cluster 'cluster-1'."));
131+
}
132+
}

dumper/app/src/test/java/com/google/edwmigration/dumper/application/dumper/connector/cloudera/manager/ClouderaManagerConnectorTest.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import static org.junit.Assert.assertEquals;
2020
import static org.junit.Assert.assertNotNull;
2121
import static org.junit.Assert.assertThrows;
22+
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.ArgumentMatchers.eq;
24+
import static org.mockito.Mockito.mockStatic;
2225

2326
import com.google.common.collect.ImmutableMap;
2427
import com.google.edwmigration.dumper.application.dumper.ConnectorArguments;
@@ -27,11 +30,14 @@
2730
import com.google.edwmigration.dumper.application.dumper.task.FormatTask;
2831
import com.google.edwmigration.dumper.application.dumper.task.Task;
2932
import com.google.edwmigration.dumper.application.dumper.task.TaskCategory;
33+
import java.net.URI;
3034
import java.util.ArrayList;
3135
import java.util.List;
3236
import java.util.Map;
3337
import java.util.stream.Collectors;
38+
import org.apache.http.impl.client.CloseableHttpClient;
3439
import org.junit.Test;
40+
import org.mockito.MockedStatic;
3541

3642
public class ClouderaManagerConnectorTest {
3743
private static final String clouderaRequiredArgs =
@@ -249,6 +255,73 @@ public void addTasksTo_containsDefaultTasks() throws Exception {
249255
assertEquals("One FormatTask is expected", formatCount, 1);
250256
}
251257

258+
@Test
259+
public void open_success() throws Exception {
260+
try (MockedStatic<ClouderaManagerLoginHelper> loginHelper =
261+
mockStatic(ClouderaManagerLoginHelper.class);
262+
MockedStatic<ClouderaConnectorVerifier> connectorVerifier =
263+
mockStatic(ClouderaConnectorVerifier.class)) {
264+
ConnectorArguments arguments =
265+
new ConnectorArguments(
266+
"--connector",
267+
"cloudera-manager",
268+
"--url",
269+
"http://localhost",
270+
"--user",
271+
"user",
272+
"--password",
273+
"password");
274+
ClouderaManagerConnector connector = new ClouderaManagerConnector();
275+
276+
// Act
277+
ClouderaManagerHandle handle = connector.open(arguments);
278+
279+
// Assert
280+
assertNotNull(handle);
281+
loginHelper.verify(
282+
() ->
283+
ClouderaManagerLoginHelper.login(
284+
any(URI.class), any(CloseableHttpClient.class), eq("user"), eq("password")));
285+
connectorVerifier.verify(() -> ClouderaConnectorVerifier.verify(any(), any()));
286+
}
287+
}
288+
289+
@Test
290+
public void open_verifierFails_throwsException() throws Exception {
291+
try (MockedStatic<ClouderaManagerLoginHelper> loginHelper =
292+
mockStatic(ClouderaManagerLoginHelper.class);
293+
MockedStatic<ClouderaConnectorVerifier> connectorVerifier =
294+
mockStatic(ClouderaConnectorVerifier.class)) {
295+
ConnectorArguments arguments =
296+
new ConnectorArguments(
297+
"--connector",
298+
"cloudera-manager",
299+
"--url",
300+
"http://localhost",
301+
"--user",
302+
"user",
303+
"--password",
304+
"password");
305+
ClouderaManagerConnector connector = new ClouderaManagerConnector();
306+
307+
RuntimeException expectedException = new RuntimeException("Verification failed");
308+
connectorVerifier
309+
.when(() -> ClouderaConnectorVerifier.verify(any(), any()))
310+
.thenThrow(expectedException);
311+
312+
// Act & Assert
313+
RuntimeException actualException =
314+
assertThrows(RuntimeException.class, () -> connector.open(arguments));
315+
assertEquals(expectedException, actualException);
316+
317+
loginHelper.verify(
318+
() ->
319+
ClouderaManagerLoginHelper.login(
320+
any(URI.class), any(CloseableHttpClient.class), eq("user"), eq("password")));
321+
connectorVerifier.verify(() -> ClouderaConnectorVerifier.verify(any(), any()));
322+
}
323+
}
324+
252325
private static ConnectorArguments args(String s) throws Exception {
253326
return new ConnectorArguments(s.split(" "));
254327
}

0 commit comments

Comments
 (0)