Skip to content

Commit b681cef

Browse files
committed
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: spinnaker#4608 Additionally, a new specific exception handler is introduced to manage JsonProcessing during the serialization of the HTTP error body. The impact of these changes should be compared with another related PR: spinnaker#4617
1 parent d481b84 commit b681cef

File tree

5 files changed

+100
-47
lines changed

5 files changed

+100
-47
lines changed

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

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616

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

19-
import com.google.common.io.CharStreams;
19+
import com.fasterxml.jackson.core.JsonProcessingException;
20+
import com.fasterxml.jackson.databind.ObjectMapper;
2021
import com.netflix.spectator.api.Registry;
2122
import com.netflix.spinnaker.config.DeploymentMonitorDefinition;
2223
import com.netflix.spinnaker.kork.annotations.VisibleForTesting;
24+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException;
25+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException;
2326
import com.netflix.spinnaker.orca.api.pipeline.RetryableTask;
2427
import com.netflix.spinnaker.orca.api.pipeline.TaskResult;
2528
import com.netflix.spinnaker.orca.api.pipeline.models.ExecutionStatus;
@@ -31,19 +34,13 @@
3134
import com.netflix.spinnaker.orca.deploymentmonitor.models.EvaluateHealthResponse;
3235
import com.netflix.spinnaker.orca.deploymentmonitor.models.MonitoredDeployInternalStageData;
3336
import com.netflix.spinnaker.orca.deploymentmonitor.models.StatusExplanation;
34-
import java.io.InputStreamReader;
35-
import java.nio.charset.StandardCharsets;
3637
import java.time.Duration;
3738
import java.util.*;
3839
import java.util.concurrent.TimeUnit;
39-
import java.util.stream.Collectors;
4040
import javax.annotation.Nonnull;
4141
import javax.annotation.Nullable;
4242
import org.slf4j.Logger;
4343
import org.slf4j.LoggerFactory;
44-
import retrofit.RetrofitError;
45-
import retrofit.client.Header;
46-
import retrofit.client.Response;
4744

4845
public class MonitoredDeployBaseTask implements RetryableTask {
4946
static final int MAX_RETRY_COUNT = 3;
@@ -52,6 +49,7 @@ public class MonitoredDeployBaseTask implements RetryableTask {
5249
private DeploymentMonitorServiceProvider deploymentMonitorServiceProvider;
5350
private final Map<EvaluateHealthResponse.NextStepDirective, String> summaryMapping =
5451
new HashMap<>();
52+
ObjectMapper objectMapper = new ObjectMapper();
5553

5654
MonitoredDeployBaseTask(
5755
DeploymentMonitorServiceProvider deploymentMonitorServiceProvider, Registry registry) {
@@ -138,12 +136,12 @@ public long getDynamicTimeout(StageExecution stage) {
138136

139137
try {
140138
return executeInternal(stage, monitorDefinition);
141-
} catch (RetrofitError e) {
139+
} catch (SpinnakerServerException e) {
142140
log.warn(
143141
"HTTP Error encountered while talking to {}->{}, {}}",
144142
monitorDefinition,
145143
e.getUrl(),
146-
getRetrofitLogMessage(e.getResponse()),
144+
getErrorMessage(e),
147145
e);
148146

149147
return handleError(stage, e, true, monitorDefinition);
@@ -269,28 +267,28 @@ private MonitoredDeployStageData getStageContext(StageExecution stage) {
269267
}
270268

271269
@VisibleForTesting
272-
String getRetrofitLogMessage(Response response) {
273-
if (response == null) {
270+
String getErrorMessage(SpinnakerServerException spinnakerException) {
271+
if (!(spinnakerException instanceof SpinnakerHttpException)) {
274272
return "<NO RESPONSE>";
275273
}
276274

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

281277
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"));
278+
String body = "";
279+
if (httpException.getResponseBody() != null) {
280+
body = objectMapper.writeValueAsString(httpException.getResponseBody());
281+
}
282+
return String.format("headers: %s\nresponse body: %s", httpException.getHeaders(), body);
283+
} catch (JsonProcessingException e) {
284+
log.error("Failed to serialize the error response body : ", e);
285+
return String.format(
286+
"headers: %s\nresponse body: %s", httpException.getHeaders(), e.getMessage());
288287
} catch (Exception e) {
289-
log.error(
290-
"Failed to fully parse retrofit error while reading response from deployment monitor", e);
288+
log.error("Failed to fully serialize http error response details", e);
291289
}
292290

293-
return String.format("status: %s\nheaders: %s\nresponse body: %s", status, headers, body);
291+
return "headers: \nresponse body: ";
294292
}
295293

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

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

Lines changed: 6 additions & 5 deletions
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

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@
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;
21+
import static org.mockito.Mockito.when;
2022

23+
import com.fasterxml.jackson.core.JsonProcessingException;
2124
import com.fasterxml.jackson.databind.ObjectMapper;
2225
import com.netflix.spectator.api.NoopRegistry;
2326
import com.netflix.spinnaker.config.DeploymentMonitorDefinition;
27+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerConversionException;
28+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException;
29+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerNetworkException;
30+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException;
2431
import com.netflix.spinnaker.orca.deploymentmonitor.DeploymentMonitorServiceProvider;
2532
import java.io.ByteArrayInputStream;
2633
import java.io.ByteArrayOutputStream;
@@ -50,6 +57,8 @@ public class MonitoredDeployBaseTaskTest {
5057

5158
private final ObjectMapper objectMapper = new ObjectMapper();
5259

60+
private DeploymentMonitorServiceProvider deploymentMonitorServiceProvider;
61+
5362
@BeforeEach
5463
void setup() {
5564
OkClient okClient = new OkClient();
@@ -63,7 +72,7 @@ void setup() {
6372
var deploymentMonitorDefinitions = new ArrayList<DeploymentMonitorDefinition>();
6473
deploymentMonitorDefinitions.add(deploymentMonitorDefinition);
6574

66-
DeploymentMonitorServiceProvider deploymentMonitorServiceProvider =
75+
deploymentMonitorServiceProvider =
6776
new DeploymentMonitorServiceProvider(
6877
okClient, retrofitLogLevel, requestInterceptor, deploymentMonitorDefinitions);
6978
monitoredDeployBaseTask =
@@ -93,14 +102,14 @@ public void shouldParseHttpErrorResponseDetailsWhenHttpErrorHasOccurred() {
93102
RetrofitError.httpError(
94103
"https://foo.com/deployment/evaluateHealth", response, new JacksonConverter(), null);
95104

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

101-
assertThat(logMessageOnHttpError)
110+
assertThat(logMessageOnSpinHttpException)
102111
.isEqualTo(
103-
String.format("status: %s\nheaders: %s\nresponse body: %s", status, header, body));
112+
String.format("headers: %s\nresponse body: %s", httpException.getHeaders(), body));
104113
}
105114

106115
@Test
@@ -130,14 +139,13 @@ public void shouldParseHttpErrorResponseDetailsWhenConversionErrorHasOccurred()
130139
null,
131140
new ConversionException("Failed to parse response"));
132141

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());
142+
SpinnakerConversionException conversionException =
143+
new SpinnakerConversionException(conversionError);
137144

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

143151
@Test
@@ -148,10 +156,12 @@ void shouldReturnDefaultLogMsgWhenNetworkErrorHasOccurred() {
148156
"https://foo.com/deployment/evaluateHealth",
149157
new IOException("Failed to connect to the host : foo.com"));
150158

151-
String logMessageOnNetworkError =
152-
monitoredDeployBaseTask.getRetrofitLogMessage(networkError.getResponse());
159+
SpinnakerNetworkException networkException = new SpinnakerNetworkException(networkError);
153160

154-
assertThat(logMessageOnNetworkError).isEqualTo("<NO RESPONSE>");
161+
String logMessageOnSpinNetworkException =
162+
monitoredDeployBaseTask.getErrorMessage(networkException);
163+
164+
assertThat(logMessageOnSpinNetworkException).isEqualTo("<NO RESPONSE>");
155165
}
156166

157167
@Test
@@ -162,10 +172,51 @@ void shouldReturnDefaultLogMsgWhenUnexpectedErrorHasOccurred() {
162172
"https://foo.com/deployment/evaluateHealth",
163173
new IOException("Failed to connect to the host : foo.com"));
164174

165-
String logMessageOnUnexpectedError =
166-
monitoredDeployBaseTask.getRetrofitLogMessage(unexpectedError.getResponse());
175+
SpinnakerServerException serverException = new SpinnakerServerException(unexpectedError);
176+
177+
String logMessageOnSpinServerException =
178+
monitoredDeployBaseTask.getErrorMessage(serverException);
179+
180+
assertThat(logMessageOnSpinServerException).isEqualTo("<NO RESPONSE>");
181+
}
182+
183+
@Test
184+
void shouldReturnHeadersAndErrorMessageWhenSerializingFailsWhileReadingBody()
185+
throws JsonProcessingException {
186+
187+
var converter = new JacksonConverter(objectMapper);
188+
var responseBody = new HashMap<String, String>();
189+
var headers = new ArrayList<Header>();
190+
var header = new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
191+
monitoredDeployBaseTask.objectMapper = mock(ObjectMapper.class);
192+
193+
headers.add(header);
194+
responseBody.put("error", "400 - Bad request, application name cannot be empty");
167195

168-
assertThat(logMessageOnUnexpectedError).isEqualTo("<NO RESPONSE>");
196+
Response response =
197+
new Response(
198+
"/deployment/evaluateHealth",
199+
HttpStatus.BAD_REQUEST.value(),
200+
HttpStatus.BAD_REQUEST.name(),
201+
headers,
202+
new MockTypedInput(converter, responseBody));
203+
204+
RetrofitError httpError =
205+
RetrofitError.httpError(
206+
"https://foo.com/deployment/evaluateHealth", response, converter, null);
207+
208+
SpinnakerHttpException httpException = new SpinnakerHttpException(httpError);
209+
JsonProcessingException jpe = new JsonProcessingException("Failed to parse the error body") {};
210+
211+
when(monitoredDeployBaseTask.objectMapper.writeValueAsString(httpException.getResponseBody()))
212+
.thenThrow(jpe);
213+
214+
String logMessageOnSpinHttpException = monitoredDeployBaseTask.getErrorMessage(httpException);
215+
String body = jpe.getMessage();
216+
217+
assertThat(logMessageOnSpinHttpException)
218+
.isEqualTo(
219+
String.format("headers: %s\nresponse body: %s", httpException.getHeaders(), body));
169220
}
170221

171222
static class MockTypedInput implements TypedInput {

orca-deploymentmonitor/orca-deploymentmonitor.gradle

Lines changed: 1 addition & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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)