com.netflix.spinnaker.keel.services.ResourceStatusService.kt Maven / Gradle / Ivy
package com.netflix.spinnaker.keel.services
import com.netflix.spinnaker.keel.events.ApplicationActuationPaused
import com.netflix.spinnaker.keel.events.ApplicationActuationResumed
import com.netflix.spinnaker.keel.events.ResourceActuationLaunched
import com.netflix.spinnaker.keel.events.ResourceActuationResumed
import com.netflix.spinnaker.keel.events.ResourceDiffNotActionable
import com.netflix.spinnaker.keel.events.ResourceActuationVetoed
import com.netflix.spinnaker.keel.events.ResourceCheckError
import com.netflix.spinnaker.keel.events.ResourceCheckUnresolvable
import com.netflix.spinnaker.keel.events.ResourceCreated
import com.netflix.spinnaker.keel.events.ResourceDeltaDetected
import com.netflix.spinnaker.keel.events.ResourceDeltaResolved
import com.netflix.spinnaker.keel.events.ResourceHistoryEvent
import com.netflix.spinnaker.keel.events.ResourceMissing
import com.netflix.spinnaker.keel.events.ResourceTaskFailed
import com.netflix.spinnaker.keel.events.ResourceTaskSucceeded
import com.netflix.spinnaker.keel.events.ResourceValid
import com.netflix.spinnaker.keel.pause.ActuationPauser
import com.netflix.spinnaker.keel.persistence.ResourceRepository
import com.netflix.spinnaker.keel.persistence.ResourceStatus
import com.netflix.spinnaker.keel.persistence.ResourceStatus.ACTUATING
import com.netflix.spinnaker.keel.persistence.ResourceStatus.CREATED
import com.netflix.spinnaker.keel.persistence.ResourceStatus.CURRENTLY_UNRESOLVABLE
import com.netflix.spinnaker.keel.persistence.ResourceStatus.DIFF
import com.netflix.spinnaker.keel.persistence.ResourceStatus.DIFF_NOT_ACTIONABLE
import com.netflix.spinnaker.keel.persistence.ResourceStatus.ERROR
import com.netflix.spinnaker.keel.persistence.ResourceStatus.HAPPY
import com.netflix.spinnaker.keel.persistence.ResourceStatus.MISSING_DEPENDENCY
import com.netflix.spinnaker.keel.persistence.ResourceStatus.PAUSED
import com.netflix.spinnaker.keel.persistence.ResourceStatus.RESUMED
import com.netflix.spinnaker.keel.persistence.ResourceStatus.UNHAPPY
import com.netflix.spinnaker.keel.persistence.ResourceStatus.UNKNOWN
import com.netflix.spinnaker.keel.persistence.ResourceStatus.WAITING
import org.springframework.stereotype.Component
/**
* Service object that offers high-level APIs around resource (event) history and status.
*/
@Component
class ResourceStatusService(
private val resourceRepository: ResourceRepository,
private val actuationPauser: ActuationPauser
) {
/**
* Returns the status of the specified resource by first checking whether or not it or the parent application are
* paused, then looking into the last few events in the resource's history.
*/
fun getStatus(id: String): ResourceStatus {
// For the PAUSED status, we look at the `paused` table as opposed to events, since these records
// persist even when a delivery config/resource (and associated events) have been deleted. We do
// this so we don't inadvertently start actuating on a resource that had been previously paused,
// without explicit action from the user to resume.
if (actuationPauser.isPaused(id)) {
return PAUSED
}
val history = resourceRepository.eventHistory(id, 10)
return when {
history.isEmpty() -> UNKNOWN // shouldn't happen, but is a safeguard since events are persisted asynchronously
history.isHappy() -> HAPPY
history.isMissingDependency() -> MISSING_DEPENDENCY
history.isUnhappy() -> UNHAPPY // order matters! must be after all other veto-related statuses
history.isDiff() -> DIFF
history.isActuating() -> ACTUATING
history.isDiffNotActionable() -> DIFF_NOT_ACTIONABLE
history.isError() -> ERROR
history.isCreated() -> CREATED
history.isResumed() -> RESUMED
history.isWaiting() -> WAITING // must be before CURRENTLY_UNRESOLVABLE because it's a special case of that status
history.isCurrentlyUnresolvable() -> CURRENTLY_UNRESOLVABLE
else -> UNKNOWN
}
}
private fun List.isHappy(): Boolean {
return first() is ResourceValid || first() is ResourceDeltaResolved
}
private fun List.isDiffNotActionable(): Boolean {
return first() is ResourceDiffNotActionable
}
private fun List.isActuating(): Boolean {
return first() is ResourceActuationLaunched || first() is ResourceTaskSucceeded ||
// we might want to move ResourceTaskFailed to isError later on
first() is ResourceTaskFailed
}
private fun List.isError(): Boolean {
return first() is ResourceCheckError
}
private fun List.isCreated(): Boolean {
return first() is ResourceCreated
}
private fun List.isWaiting(): Boolean {
// we expect to have only two events (after we scrub paused/resumed events),
// but we will accept several different "unresolvable" events
// in order to be less brittle and show the user this status instead of an error
val filtered = filterNot { it is ApplicationActuationPaused || it is ApplicationActuationResumed }
return filtered.size < 5 && filtered.all { it is ResourceCreated || it is ResourceCheckUnresolvable }
}
private fun List.isDiff(): Boolean {
return first() is ResourceDeltaDetected || first() is ResourceMissing
}
private fun List.isResumed(): Boolean {
return first() is ResourceActuationResumed || first() is ApplicationActuationResumed
}
private fun List.isCurrentlyUnresolvable(): Boolean {
return first() is ResourceCheckUnresolvable
}
/**
* Returns true if a resource has been vetoed by the unhappy veto,
* or if the last 10 events are only ResourceActuationLaunched or ResourceDeltaDetected events,
* or if the resource has been vetoed by an unspecified veto that we don't have an explicit status mapping for.
*/
private fun List.isUnhappy(): Boolean {
if (first() is ResourceActuationVetoed && (first() as ResourceActuationVetoed).getStatus() == UNHAPPY) {
return true
}
val recentSliceOfHistory = this.subList(0, Math.min(10, this.size))
val filteredHistory = recentSliceOfHistory.filter { it is ResourceDeltaDetected || it is ResourceActuationLaunched }
if (filteredHistory.size == recentSliceOfHistory.size) {
return true
}
return false
}
/**
* Determines if last event was a veto because of a missing dependency
*/
private fun List.isMissingDependency(): Boolean =
first() is ResourceActuationVetoed && (first() as ResourceActuationVetoed).getStatus() == MISSING_DEPENDENCY
/**
* Determines the correct status to show for veto events
*/
private fun ResourceActuationVetoed.getStatus(): ResourceStatus =
when {
// new style veto, gives us the status the resource should be
suggestedStatus != null -> suggestedStatus
// we can determine missing dependency by parsing the message
isMissingDependency() -> MISSING_DEPENDENCY
// all vetos get unhappy status if not specified
else -> UNHAPPY
}
/**
* Looks at the veto event and determines if it was vetoed by any of the [Required*Veto]s, which indicate a
* missing dependency. Parses this information from the [reason]. This is used for backwards compatibility.
*/
private fun ResourceActuationVetoed.isMissingDependency(): Boolean =
reason?.contains("is not found in", true) ?: false
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy