Skip to content
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

Merged

Conversation

chrisgresty
Copy link
Contributor

See #518

@chrisgresty chrisgresty changed the title Extract fail/success count from state tag into new health tag Administrative state for routing objects Nov 15, 2019

private const val HEALTH = "health"
const val HEALTH_SUCCESS = "success"
const val HEALTH_FAIL = "fail"
Copy link
Contributor

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.

Copy link
Contributor Author

@chrisgresty chrisgresty Nov 18, 2019

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 ?

@chrisgresty chrisgresty marked this pull request as ready for review November 21, 2019 16:30
Copy link
Contributor

@mikkokar mikkokar left a 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) }
Copy link
Contributor

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)
Copy link
Contributor

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 {
Copy link
Contributor

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!!)
Copy link
Contributor

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) ->
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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 }
Copy link
Contributor

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.

Copy link
Contributor

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)

Copy link
Contributor Author

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 {
Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Contributor Author

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(
Copy link
Contributor

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

Copy link
Contributor Author

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
Copy link
Contributor

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 ;-)

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor

@mikkokar mikkokar left a 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 ;-)

private const val STATE = "state"
const val STATE_ACTIVE = "active"
const val STATE_UNREACHABLE = "unreachable"
const val STATE_CLOSED = "closed"
Copy link
Contributor

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,
Copy link
Contributor

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

Copy link
Contributor Author

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 {
Copy link
Contributor

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())
Copy link
Contributor

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() }
}
Copy link
Contributor

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()

Copy link
Contributor

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()

Copy link
Contributor Author

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.

Copy link
Contributor

@mikkokar mikkokar left a 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.

Copy link
Contributor

@mikkokar mikkokar left a 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.

@mikkokar mikkokar merged commit 2d0d16a into ExpediaGroup:master Nov 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants