Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0ca8d19

Browse files
committedJan 3, 2024
refactor(deploymentmonitor): Configure SpinnakerRetrofitErrorHandler for DeploymentMonitorService
Update the error handler for the retrofit client, DeploymentMonitorService, to use SpinnakerRetrofitErrorHandler. Additionally, modify catch blocks with RetrofitError to use Spinnaker*Exception. Purpose : This commit is foundational work for upgrading the Retrofit version to retrofit2.x Behavioural change : The modification includes updating catch blocks, leading to a change in the format of logger messages. This aligns with the upcoming modifications discussed in the PR: #4608 Additionally, when the error response body is null, an appropriate error message will be printed in the logger.
1 parent 023a2d9 commit 0ca8d19

File tree

5 files changed

+88
-47
lines changed

5 files changed

+88
-47
lines changed
 

‎orca-clouddriver/src/main/java/com/netflix/spinnaker/orca/clouddriver/tasks/monitoreddeploy/MonitoredDeployBaseTask.java

+18-23
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package com.netflix.spinnaker.orca.clouddriver.tasks.monitoreddeploy;
1818

19-
import com.google.common.io.CharStreams;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
2020
import com.netflix.spectator.api.Registry;
2121
import com.netflix.spinnaker.config.DeploymentMonitorDefinition;
2222
import com.netflix.spinnaker.kork.annotations.VisibleForTesting;
23+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException;
24+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException;
2325
import com.netflix.spinnaker.orca.api.pipeline.RetryableTask;
2426
import com.netflix.spinnaker.orca.api.pipeline.TaskResult;
2527
import com.netflix.spinnaker.orca.api.pipeline.models.ExecutionStatus;
@@ -31,19 +33,13 @@
3133
import com.netflix.spinnaker.orca.deploymentmonitor.models.EvaluateHealthResponse;
3234
import com.netflix.spinnaker.orca.deploymentmonitor.models.MonitoredDeployInternalStageData;
3335
import com.netflix.spinnaker.orca.deploymentmonitor.models.StatusExplanation;
34-
import java.io.InputStreamReader;
35-
import java.nio.charset.StandardCharsets;
3636
import java.time.Duration;
3737
import java.util.*;
3838
import java.util.concurrent.TimeUnit;
39-
import java.util.stream.Collectors;
4039
import javax.annotation.Nonnull;
4140
import javax.annotation.Nullable;
4241
import org.slf4j.Logger;
4342
import org.slf4j.LoggerFactory;
44-
import retrofit.RetrofitError;
45-
import retrofit.client.Header;
46-
import retrofit.client.Response;
4743

4844
public class MonitoredDeployBaseTask implements RetryableTask {
4945
static final int MAX_RETRY_COUNT = 3;
@@ -52,6 +48,7 @@ public class MonitoredDeployBaseTask implements RetryableTask {
5248
private DeploymentMonitorServiceProvider deploymentMonitorServiceProvider;
5349
private final Map<EvaluateHealthResponse.NextStepDirective, String> summaryMapping =
5450
new HashMap<>();
51+
ObjectMapper objectMapper = new ObjectMapper();
5552

5653
MonitoredDeployBaseTask(
5754
DeploymentMonitorServiceProvider deploymentMonitorServiceProvider, Registry registry) {
@@ -138,12 +135,12 @@ public long getDynamicTimeout(StageExecution stage) {
138135

139136
try {
140137
return executeInternal(stage, monitorDefinition);
141-
} catch (RetrofitError e) {
138+
} catch (SpinnakerServerException e) {
142139
log.warn(
143140
"HTTP Error encountered while talking to {}->{}, {}}",
144141
monitorDefinition,
145142
e.getUrl(),
146-
getRetrofitLogMessage(e.getResponse()),
143+
getErrorMessage(e),
147144
e);
148145

149146
return handleError(stage, e, true, monitorDefinition);
@@ -269,28 +266,26 @@ private MonitoredDeployStageData getStageContext(StageExecution stage) {
269266
}
270267

271268
@VisibleForTesting
272-
String getRetrofitLogMessage(Response response) {
273-
if (response == null) {
269+
String getErrorMessage(SpinnakerServerException spinnakerException) {
270+
if (!(spinnakerException instanceof SpinnakerHttpException)) {
274271
return "<NO RESPONSE>";
275272
}
276273

277-
String body = "";
278-
String status = "";
279-
String headers = "";
274+
SpinnakerHttpException httpException = (SpinnakerHttpException) spinnakerException;
280275

281276
try {
282-
status = String.format("%d (%s)", response.getStatus(), response.getReason());
283-
body =
284-
CharStreams.toString(
285-
new InputStreamReader(response.getBody().in(), StandardCharsets.UTF_8));
286-
headers =
287-
response.getHeaders().stream().map(Header::toString).collect(Collectors.joining("\n"));
277+
String body = "";
278+
if (httpException.getResponseBody() != null) {
279+
body = objectMapper.writeValueAsString(httpException.getResponseBody());
280+
} else {
281+
body = "Failed to serialize the error response body";
282+
}
283+
return String.format("headers: %s\nresponse body: %s", httpException.getHeaders(), body);
288284
} catch (Exception e) {
289-
log.error(
290-
"Failed to fully parse retrofit error while reading response from deployment monitor", e);
285+
log.error("Failed to fully serialize http error response details", e);
291286
}
292287

293-
return String.format("status: %s\nheaders: %s\nresponse body: %s", status, headers, body);
288+
return "headers: \nresponse body: ";
294289
}
295290

296291
private boolean shouldFailOnError(StageExecution stage, DeploymentMonitorDefinition definition) {

‎orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/monitoreddeploy/EvaluateDeploymentHealthTaskSpec.groovy

+6-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package com.netflix.spinnaker.orca.clouddriver.tasks.monitoreddeploy
1919
import com.fasterxml.jackson.databind.ObjectMapper
2020
import com.netflix.spectator.api.NoopRegistry
2121
import com.netflix.spinnaker.config.DeploymentMonitorDefinition
22+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException
23+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException
2224
import com.netflix.spinnaker.orca.api.pipeline.models.ExecutionStatus
2325
import com.netflix.spinnaker.orca.api.pipeline.TaskResult
2426
import com.netflix.spinnaker.orca.clouddriver.MortServiceSpec
@@ -46,11 +48,11 @@ class EvaluateDeploymentHealthTaskSpec extends Specification {
4648
PipelineExecutionImpl pipe = pipeline {
4749
}
4850

49-
def "should retry retrofit errors"() {
51+
def "should retry on SpinnakerServerException"() {
5052
given:
5153
def monitorServiceStub = Stub(DeploymentMonitorService) {
5254
evaluateHealth(_) >> {
53-
throw RetrofitError.networkError("url", new IOException())
55+
throw new SpinnakerServerException(RetrofitError.networkError("url", new IOException()))
5456
}
5557
}
5658

@@ -203,8 +205,7 @@ class EvaluateDeploymentHealthTaskSpec extends Specification {
203205
false | null || ExecutionStatus.FAILED_CONTINUE
204206
}
205207

206-
def "should return status as RUNNING when Retrofit http error is thrown"() {
207-
208+
def "should return status as RUNNING when SpinnakerHttpException is thrown"() {
208209
def converter = new JacksonConverter(new ObjectMapper())
209210

210211
Response response =
@@ -224,7 +225,7 @@ class EvaluateDeploymentHealthTaskSpec extends Specification {
224225
given:
225226
def monitorServiceStub = Stub(DeploymentMonitorService) {
226227
evaluateHealth(_) >> {
227-
throw RetrofitError.httpError("https://foo.com/deployment/evaluateHealth", response, converter, null)
228+
throw new SpinnakerHttpException(RetrofitError.httpError("https://foo.com/deployment/evaluateHealth", response, converter, null))
228229
}
229230
}
230231

‎orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/monitoreddeploy/MonitoredDeployBaseTaskTest.java

+61-19
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717
package com.netflix.spinnaker.orca.clouddriver.tasks.monitoreddeploy;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.mockito.Mockito.mock;
2021

2122
import com.fasterxml.jackson.databind.ObjectMapper;
2223
import com.netflix.spectator.api.NoopRegistry;
2324
import com.netflix.spinnaker.config.DeploymentMonitorDefinition;
25+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerConversionException;
26+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException;
27+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerNetworkException;
28+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException;
2429
import com.netflix.spinnaker.orca.deploymentmonitor.DeploymentMonitorServiceProvider;
2530
import java.io.ByteArrayInputStream;
2631
import java.io.ByteArrayOutputStream;
@@ -50,6 +55,8 @@ public class MonitoredDeployBaseTaskTest {
5055

5156
private final ObjectMapper objectMapper = new ObjectMapper();
5257

58+
private DeploymentMonitorServiceProvider deploymentMonitorServiceProvider;
59+
5360
@BeforeEach
5461
void setup() {
5562
OkClient okClient = new OkClient();
@@ -63,7 +70,7 @@ void setup() {
6370
var deploymentMonitorDefinitions = new ArrayList<DeploymentMonitorDefinition>();
6471
deploymentMonitorDefinitions.add(deploymentMonitorDefinition);
6572

66-
DeploymentMonitorServiceProvider deploymentMonitorServiceProvider =
73+
deploymentMonitorServiceProvider =
6774
new DeploymentMonitorServiceProvider(
6875
okClient, retrofitLogLevel, requestInterceptor, deploymentMonitorDefinitions);
6976
monitoredDeployBaseTask =
@@ -93,14 +100,14 @@ public void shouldParseHttpErrorResponseDetailsWhenHttpErrorHasOccurred() {
93100
RetrofitError.httpError(
94101
"https://foo.com/deployment/evaluateHealth", response, new JacksonConverter(), null);
95102

96-
String logMessageOnHttpError =
97-
monitoredDeployBaseTask.getRetrofitLogMessage(httpError.getResponse());
98-
String status = HttpStatus.BAD_REQUEST.value() + " (" + HttpStatus.BAD_REQUEST.name() + ")";
103+
SpinnakerHttpException httpException = new SpinnakerHttpException(httpError);
104+
105+
String logMessageOnSpinHttpException = monitoredDeployBaseTask.getErrorMessage(httpException);
99106
String body = "{\"error\":\"400 - Bad request, application name cannot be empty\"}";
100107

101-
assertThat(logMessageOnHttpError)
108+
assertThat(logMessageOnSpinHttpException)
102109
.isEqualTo(
103-
String.format("status: %s\nheaders: %s\nresponse body: %s", status, header, body));
110+
String.format("headers: %s\nresponse body: %s", httpException.getHeaders(), body));
104111
}
105112

106113
@Test
@@ -130,14 +137,13 @@ public void shouldParseHttpErrorResponseDetailsWhenConversionErrorHasOccurred()
130137
null,
131138
new ConversionException("Failed to parse response"));
132139

133-
String status = HttpStatus.BAD_REQUEST.value() + " (" + HttpStatus.BAD_REQUEST.name() + ")";
134-
String body = "{\"error\":\"400 - Bad request, application name cannot be empty\"}";
135-
String logMessageOnConversionError =
136-
monitoredDeployBaseTask.getRetrofitLogMessage(conversionError.getResponse());
140+
SpinnakerConversionException conversionException =
141+
new SpinnakerConversionException(conversionError);
137142

138-
assertThat(logMessageOnConversionError)
139-
.isEqualTo(
140-
String.format("status: %s\nheaders: %s\nresponse body: %s", status, header, body));
143+
String logMessageOnSpinConversionException =
144+
monitoredDeployBaseTask.getErrorMessage(conversionException);
145+
146+
assertThat(logMessageOnSpinConversionException).isEqualTo("<NO RESPONSE>");
141147
}
142148

143149
@Test
@@ -148,10 +154,12 @@ void shouldReturnDefaultLogMsgWhenNetworkErrorHasOccurred() {
148154
"https://foo.com/deployment/evaluateHealth",
149155
new IOException("Failed to connect to the host : foo.com"));
150156

151-
String logMessageOnNetworkError =
152-
monitoredDeployBaseTask.getRetrofitLogMessage(networkError.getResponse());
157+
SpinnakerNetworkException networkException = new SpinnakerNetworkException(networkError);
158+
159+
String logMessageOnSpinNetworkException =
160+
monitoredDeployBaseTask.getErrorMessage(networkException);
153161

154-
assertThat(logMessageOnNetworkError).isEqualTo("<NO RESPONSE>");
162+
assertThat(logMessageOnSpinNetworkException).isEqualTo("<NO RESPONSE>");
155163
}
156164

157165
@Test
@@ -162,10 +170,44 @@ void shouldReturnDefaultLogMsgWhenUnexpectedErrorHasOccurred() {
162170
"https://foo.com/deployment/evaluateHealth",
163171
new IOException("Failed to connect to the host : foo.com"));
164172

165-
String logMessageOnUnexpectedError =
166-
monitoredDeployBaseTask.getRetrofitLogMessage(unexpectedError.getResponse());
173+
SpinnakerServerException serverException = new SpinnakerServerException(unexpectedError);
174+
175+
String logMessageOnSpinServerException =
176+
monitoredDeployBaseTask.getErrorMessage(serverException);
177+
178+
assertThat(logMessageOnSpinServerException).isEqualTo("<NO RESPONSE>");
179+
}
180+
181+
@Test
182+
void shouldReturnHeadersAndErrorMessageWhenErrorResponseBodyIsNull() {
183+
184+
var converter = new JacksonConverter(objectMapper);
185+
var headers = new ArrayList<Header>();
186+
var header = new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
187+
monitoredDeployBaseTask.objectMapper = mock(ObjectMapper.class);
188+
189+
headers.add(header);
167190

168-
assertThat(logMessageOnUnexpectedError).isEqualTo("<NO RESPONSE>");
191+
Response response =
192+
new Response(
193+
"/deployment/evaluateHealth",
194+
HttpStatus.BAD_REQUEST.value(),
195+
HttpStatus.BAD_REQUEST.name(),
196+
headers,
197+
new MockTypedInput(converter, null));
198+
199+
RetrofitError httpError =
200+
RetrofitError.httpError(
201+
"https://foo.com/deployment/evaluateHealth", response, converter, null);
202+
203+
SpinnakerHttpException httpException = new SpinnakerHttpException(httpError);
204+
205+
String logMessageOnSpinHttpException = monitoredDeployBaseTask.getErrorMessage(httpException);
206+
String body = "Failed to serialize the error response body";
207+
208+
assertThat(logMessageOnSpinHttpException)
209+
.isEqualTo(
210+
String.format("headers: %s\nresponse body: %s", httpException.getHeaders(), body));
169211
}
170212

171213
static class MockTypedInput implements TypedInput {

‎orca-deploymentmonitor/orca-deploymentmonitor.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ apply from: "$rootDir/gradle/spock.gradle"
1919
dependencies {
2020
implementation(project(":orca-core"))
2121
implementation(project(":orca-retrofit"))
22+
implementation("io.spinnaker.kork:kork-retrofit")
2223

2324
implementation("org.springframework.boot:spring-boot-autoconfigure")
2425

‎orca-deploymentmonitor/src/main/java/com/netflix/spinnaker/orca/deploymentmonitor/DeploymentMonitorServiceProvider.java

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.netflix.spinnaker.config.DeploymentMonitorDefinition;
2020
import com.netflix.spinnaker.kork.exceptions.UserException;
21+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler;
2122
import com.netflix.spinnaker.orca.retrofit.logging.RetrofitSlf4jLog;
2223
import java.util.HashMap;
2324
import java.util.List;
@@ -81,6 +82,7 @@ private synchronized DeploymentMonitorService getServiceByDefinition(
8182
.setLogLevel(retrofitLogLevel)
8283
.setLog(new RetrofitSlf4jLog(DeploymentMonitorService.class))
8384
.setConverter(new JacksonConverter())
85+
.setErrorHandler(SpinnakerRetrofitErrorHandler.getInstance())
8486
.build()
8587
.create(DeploymentMonitorService.class);
8688

0 commit comments

Comments
 (0)
Please sign in to comment.