-
Notifications
You must be signed in to change notification settings - Fork 807
Fix(orca-front50): front50 timeout fallback #4851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
a260018
943fd62
8c0af21
3ea06c5
28cad45
25c9a38
05e309c
109c125
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -19,6 +19,30 @@ | |||||
import lombok.Data; | ||||||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||||||
|
||||||
/** | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't decide whether I think this comment is helpful enough vs. the chances of it getting stale. Since these are the higher-precedence properties, I'm pretty tempted to ditch the comment. |
||||||
* Configuration properties for Front50 service. | ||||||
* | ||||||
* <p>These properties can be configured in your YAML configuration: | ||||||
* | ||||||
* <pre> | ||||||
* front50: | ||||||
* baseUrl: http://front50.example.com | ||||||
* enabled: true | ||||||
* useTriggeredByEndpoint: true | ||||||
* okhttp: | ||||||
* connectTimeoutMs: 10000 | ||||||
* readTimeoutMs: 60000 | ||||||
* writeTimeoutMs: 60000 | ||||||
* </pre> | ||||||
* | ||||||
* <p>If not explicitly configured, a fallback chain will be used for timeouts: | ||||||
* | ||||||
* <ol> | ||||||
* <li>Use explicit okhttp configuration if present | ||||||
* <li>Fall back to global okhttp client configuration | ||||||
* <li>Use default fallback values (10s connect, 60s read/write) | ||||||
* </ol> | ||||||
*/ | ||||||
@Data | ||||||
@ConfigurationProperties("front50") | ||||||
public class Front50ConfigurationProperties { | ||||||
|
@@ -34,14 +58,33 @@ public class Front50ConfigurationProperties { | |||||
*/ | ||||||
boolean useTriggeredByEndpoint = true; | ||||||
|
||||||
/** HTTP client configuration for connecting to Front50 service */ | ||||||
OkHttpConfigurationProperties okhttp = new OkHttpConfigurationProperties(); | ||||||
|
||||||
/** | ||||||
* Configuration properties for the OkHttp client connecting to Front50. These will only be used | ||||||
* if explicitly set in the configuration. Otherwise, global client timeouts will be used as | ||||||
* fallback. | ||||||
*/ | ||||||
@Data | ||||||
public static class OkHttpConfigurationProperties { | ||||||
int readTimeoutMs = 10000; | ||||||
/** Read timeout in milliseconds. Default is 120 seconds (120000ms) */ | ||||||
private Long readTimeoutMs = 120000L; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
/** Write timeout in milliseconds. Default is 60 seconds (60000ms) */ | ||||||
private Long writeTimeoutMs = 60000L; | ||||||
|
||||||
int writeTimeoutMs = 10000; | ||||||
/** Connection timeout in milliseconds. Default is 5 seconds (5000ms) */ | ||||||
private Long connectTimeoutMs = 5000L; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
int connectTimeoutMs = 10000; | ||||||
/** | ||||||
* Checks if this instance has any custom timeout configuration. | ||||||
* | ||||||
* @return true if any timeout is non-default, false otherwise | ||||||
*/ | ||||||
public boolean hasCustomTimeouts() { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't think we need this method. |
||||||
// Compare with default values to determine if explicit config was provided | ||||||
return readTimeoutMs != 120000L || writeTimeoutMs != 60000L || connectTimeoutMs != 5000L; | ||||||
} | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* Copyright 2024 Armory, Inc. | ||
* | ||
* 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.netflix.spinnaker.orca.front50.config | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.netflix.spinnaker.okhttp.OkHttpClientConfigurationProperties | ||
import retrofit.Endpoint | ||
import spock.lang.Specification | ||
import spock.lang.Subject | ||
import java.util.concurrent.TimeUnit | ||
|
||
/** | ||
* Tests for Front50 timeout configuration | ||
*/ | ||
class Front50ConfigurationSpec extends Specification { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you mind converting this to java please. The less groovy code we have in the world, the better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, let me know if it looks better now :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still looks like groovy to me... |
||
|
||
@Subject | ||
Front50Configuration front50Configuration = new Front50Configuration() | ||
|
||
/** | ||
* Verifies that the default timeout values in Front50ConfigurationProperties match | ||
* those in OkHttpClientConfigurationProperties to ensure backward compatibility | ||
*/ | ||
def "default timeout values should match OkHttpClientConfigurationProperties"() { | ||
given: | ||
OkHttpClientConfigurationProperties globalProps = new OkHttpClientConfigurationProperties() | ||
Front50ConfigurationProperties.OkHttpConfigurationProperties front50Props = | ||
new Front50ConfigurationProperties.OkHttpConfigurationProperties() | ||
|
||
expect: | ||
front50Props.connectTimeoutMs == globalProps.connectTimeoutMs | ||
front50Props.readTimeoutMs == globalProps.readTimeoutMs | ||
} | ||
|
||
/** | ||
* Verifies that the front50Service method accepts OkHttpClientConfigurationProperties | ||
* as a parameter, which is necessary for the timeout fallback mechanism to work | ||
*/ | ||
def "front50Service method should accept OkHttpClientConfigurationProperties parameter"() { | ||
given: | ||
def method = Front50Configuration.class.getDeclaredMethod( | ||
"front50Service", | ||
Endpoint.class, | ||
ObjectMapper.class, | ||
Front50ConfigurationProperties.class, | ||
OkHttpClientConfigurationProperties.class) | ||
|
||
expect: | ||
method != null | ||
} | ||
|
||
/** | ||
* Verifies that hasCustomTimeouts correctly identifies when custom timeouts exist | ||
*/ | ||
def "hasCustomTimeouts should return #expected when #description"() { | ||
given: | ||
def props = new Front50ConfigurationProperties.OkHttpConfigurationProperties() | ||
if (connectTimeout != null) { | ||
props.connectTimeoutMs = connectTimeout | ||
} | ||
if (readTimeout != null) { | ||
props.readTimeoutMs = readTimeout | ||
} | ||
|
||
expect: | ||
props.hasCustomTimeouts() == expected | ||
|
||
where: | ||
description | connectTimeout | readTimeout || expected | ||
"using default values" | 5000L | 120000L || false | ||
"only connect timeout changed" | 10000L | 120000L || true | ||
"only read timeout changed" | 5000L | 30000L || true | ||
"both timeouts changed" | 10000L | 30000L || true | ||
} | ||
|
||
/** | ||
* This test examines the source code of Front50Configuration to verify that | ||
* the timeout fallback pattern is correctly implemented. This method was chosen | ||
* because OkHttpClient.Builder is a final class and cannot be easily mocked. | ||
*/ | ||
def "front50Service uses the correct timeout fallback pattern"() { | ||
given: | ||
def sourceFile = new File("/Users/shlomodaari/armory/spinnaker-oss-services/orca/orca-front50/src/main/groovy/com/netflix/spinnaker/orca/front50/config/Front50Configuration.groovy") | ||
def sourceCode = sourceFile.exists() ? sourceFile.text : null | ||
|
||
expect: | ||
sourceCode != null | ||
|
||
// First apply global timeouts | ||
sourceCode.contains("builder.connectTimeout(okHttpClientConfigurationProperties.getConnectTimeoutMs(), TimeUnit.MILLISECONDS)") | ||
sourceCode.contains("builder.readTimeout(okHttpClientConfigurationProperties.getReadTimeoutMs(), TimeUnit.MILLISECONDS)") | ||
|
||
// Then conditionally override with Front50-specific timeouts if defined | ||
sourceCode.contains("if (front50ConfigurationProperties.okhttp?.connectTimeoutMs != null) {") | ||
sourceCode.contains("builder.connectTimeout(front50ConfigurationProperties.okhttp.connectTimeoutMs, TimeUnit.MILLISECONDS)") | ||
sourceCode.contains("if (front50ConfigurationProperties.okhttp?.readTimeoutMs != null) {") | ||
sourceCode.contains("builder.readTimeout(front50ConfigurationProperties.okhttp.readTimeoutMs, TimeUnit.MILLISECONDS)") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
server: | ||
port: 8083 | ||
|
||
redis: | ||
enabled: false # Disable Redis dependency for local testing | ||
|
||
sql: | ||
enabled: true # Use SQL backend instead | ||
connectionPool: | ||
jdbcUrl: jdbc:h2:mem:orca;MODE=MYSQL;DB_CLOSE_DELAY=-1 | ||
driver: org.h2.Driver | ||
|
||
spring: | ||
datasource: | ||
url: jdbc:h2:mem:orca;MODE=MYSQL;DB_CLOSE_DELAY=-1 | ||
driver-class-name: org.h2.Driver | ||
|
||
keiko: | ||
queue: | ||
memory: | ||
enabled: true # Use in-memory queue | ||
|
||
services: | ||
echo: | ||
enabled: false # Disable echo service dependency for testing | ||
front50: | ||
enabled: false # Disable front50 service dependency for testing | ||
clouddriver: | ||
enabled: false # Disable clouddriver service dependency for testing | ||
|
||
# Debug logging for our fix | ||
logging: | ||
level: | ||
com.netflix.spinnaker.orca.echo.spring.EchoNotifyingStageListener: DEBUG |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even with capital-L long, I don't think this way of checking works, since the properties have default values. I think we gotta do the
environment.containsProperty
thing. There's a failing test, but I'm not sure it's failing because of this...so perhaps we need some more test coverage like WebhookConfigurationTest.For me the code is easier to follow using local variables and then calling the builder once, like: