-
Notifications
You must be signed in to change notification settings - Fork 79
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
Administrative state for routing objects #523
Administrative state for routing objects #523
Conversation
|
||
private const val HEALTH = "health" | ||
const val HEALTH_SUCCESS = "success" | ||
const val HEALTH_FAIL = "fail" |
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.
"Object health" is an ongoing state. But success
/fail
imply an outcome of some action. Therefore consider renaming tag values to healthy
/unhealthy
, or reachable
/unreachable
. Etc.
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.
These do indicate the outcomes of actions of a kind, although I do see your point. Perhaps the "health" name is wrong. How about healthcheck=[passing|failing]:N
?
…min-state-for-routing-objects
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.
First batch.
.filter { isTaggedWith(it, lbGroupTag(appId)) } | ||
.filterNot { isTaggedWith(it, "$INACTIVE_TAG.*".toRegex()) } | ||
.filter { checkTag(it, ::lbGroupTagValue, appId) } | ||
.filter { checkTag(it, ::stateTagValue, STATE_ACTIVE, null) } |
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.
(Warning, this is an opinionated comment)
.filter
takes a predicate. Consider renaming checkTag
to reflect this predicate, for example taggedWith(...)
etc.
is ObjectActive -> "$ACTIVE_TAG:${status.failedProbes}" | ||
is ObjectInactive -> "$INACTIVE_TAG:${status.successfulProbes}" | ||
} | ||
val RELEVANT_STATES = setOf(STATE_ACTIVE, STATE_UNREACHABLE) |
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.
Set private visibility for RELEVANT_STATES.
@@ -104,18 +98,18 @@ internal class HealthCheckMonitoringService( | |||
internal fun runChecks(application: String, objectStore: StyxObjectStore<RoutingObjectRecord>) { | |||
val monitoredObjects = discoverMonitoredObjects(application, objectStore) | |||
.map { |
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.
Consider unpacking the Pair members like so:
map { (name, record) -> ... }
} | ||
.filter { (_, _, objectHealth) -> objectHealth != null } | ||
|
||
val pendingHealthChecks = monitoredObjects | ||
.map { (name, record, objectHealth) -> | ||
healthCheck(probe, record.routingObject, objectHealth) | ||
healthCheck(probe, record.routingObject, objectHealth!!) |
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.
Unnecessary non-null assertion: !!
.
} | ||
.filter { (_, _, objectHealth) -> objectHealth != null } | ||
|
||
val pendingHealthChecks = monitoredObjects | ||
.map { (name, record, objectHealth) -> | ||
healthCheck(probe, record.routingObject, objectHealth) | ||
healthCheck(probe, record.routingObject, objectHealth!!) | ||
.map { newHealth -> Triple(name, objectHealth, newHealth) } | ||
.doOnNext { (name, currentHealth, newHealth) -> |
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.
Please collapse the separate map
and doOnNext
operators to a single doOnNext
.
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.
I think the map
is required to change the type of the pendingHealthChecks
mono to the triple, which is used in the following subscriber. The doOnNext
alone doesn't do this.
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.
Yeah I noticed that retrospectively
Triple(it.first, it.second, health) | ||
val tags = it.second.tags | ||
val objectHealth = objectHealthFrom(stateTagValue(tags), healthcheckTagValue(tags)) | ||
Triple(it.first, it.second, objectHealth) | ||
} | ||
.filter { (_, _, objectHealth) -> objectHealth != null } |
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.
My editor says that objectHealth != null
is always true. Therefore this filter
can be removed as useless.
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.
(Opinionated comment warning)
I'd consider inlining discoverMonitoredObjects
. It doesn't contribute much but to hide the selection criteria out of sight. :-)
(... okay some tests would be affected)
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.
The tests can be removed, I think. The behaviour of the filter is pretty much self evident, especially now the tag logic is elsewhere and standardised.
// a state where the object does not exist using compute alone. But even with ifPresent, as we are open to | ||
// the object disappearing between the ifPresent and the compute, which would again lead to the compute creating | ||
// a new object when we don't want it to. But at least this will happen much less frequently. | ||
db.get(name).ifPresent { |
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.
Good observation. Worth raising an enhancement issue?
override fun health() = if (failedProbes > 0) Pair(HEALTHCHECK_FAILING, failedProbes) else null | ||
} | ||
data class ObjectUnreachable(val successfulProbes: Int) : ObjectHealth() { | ||
override fun state() = STATE_UNREACHABLE |
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.
The state()
is redundant for ObjectActive
and ObjectUnreachable
cases. The class itself represents the object state.
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.
It does, and that's how it is used in the healthCheckFunction
. But in the retag
function, it is useful to be able to convert between the object state as an ObjectHealth object, and the state tag (and health tag) that it corresponds to. This seemed like the best place for that linkage.
.plus(stateTag(newStatus.state()))
.plus(healthcheckTag(newStatus.health()))
* This handler is configured with the base path /admin/providers/provider-name so that it can extract the remainder | ||
* One handler for all origins | ||
*/ | ||
internal class OriginsAdminHandler( |
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.
Please use UrlPatternRouter
to implement this handler. It provides most of the required functionality out of the the box:
- Match on HTTP methods
- Match on URL paths
- Extract variables from URL paths
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.
I completely missed that class. I'll re-implement. Thanks.
|
||
scenario("Ignores closed origins") { | ||
// TODO: Do this |
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.
Heads up, something still needs doing ;-)
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.
Ah. Yes. Thanks. :)
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
package com.hotels.styx.api; |
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.
This class should move away from the API module. The API module is only for definitions necessary for 3rd party extensions.
I believe styx-proxy
is the correct destination.
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.
Sorry meant to still request changes ;-)
…min-state-for-routing-objects
private const val STATE = "state" | ||
const val STATE_ACTIVE = "active" | ||
const val STATE_UNREACHABLE = "unreachable" | ||
const val STATE_CLOSED = "closed" |
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.
This is really inactive
that is different from closed
:
- inactive: the resources are preserved but object is out of load balancer
- closed: the resources are removed and object is due to be removed for good
* One handler for all origins | ||
*/ | ||
internal class OriginsAdminHandler( | ||
basePath: String, |
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.
Please ensure the enable/disable behaviour follows the existing behaviour described here:
https://github.com/HotelsDotCom/styx/blob/master/docs/user-guide/admin-interface.md#origin-toggle
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.
Created a Trello ticket to cover this work: https://trello.com/c/yO2hxOQx
|
||
private val router: UrlPatternRouter | ||
|
||
init { |
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.
This init block is unnecessary.
private fun errorResponse(status: HttpResponseStatus, message: String) = | ||
HttpResponse.response(status) | ||
.disableCaching() | ||
.addHeader(HttpHeaderNames.CONTENT_TYPE, JSON_UTF_8.toString()) |
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.
As per coding style we would static inline constants like CONTENT_TYPE
and response
.
.flatMap { | ||
router.handle(it, context) | ||
.map { response -> response.stream() } | ||
} |
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.
FYI: We could use HttpAggregator, instead of manually aggregating.
|
||
fun getResponse(handler: OriginsAdminHandler, request: LiveHttpRequest) = | ||
Mono.from(Mono.from(handler.handle(request, HttpInterceptorContext.create())).block().aggregate(1000)).block() | ||
|
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.
Unnecessary mono & .block()
. I'd do this:
fun Eventual<LiveHttpResponse>.wait(maxBytes: Int = 100*1024, debug: Boolean = false) = this.toMono()
.flatMap { it.aggregate(maxBytes).toMono() }
.doOnNext {
if (debug) {
println("${it.status()} - ${it.headers()} - ${it.bodyAs(StandardCharsets.UTF_8)}")
}
}
.block()
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.
I see. You've replaced the first block()
with a .flatMap()
to process the aggregated HttpResponse
, so you only need the final block()
- there's no need to wait twice.
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.
I'm going to push some small tidy up, and then we'll merge.
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.
Approving with tidied-up version.
See #518