diff --git a/frontend/src/Main.elm b/frontend/src/Main.elm index 67820fa..b45e4b8 100644 --- a/frontend/src/Main.elm +++ b/frontend/src/Main.elm @@ -240,6 +240,7 @@ nsTable status model = table [ spacing 10 , padding 10 + , width (fill |> maximum 950) , Font.alignRight , Font.size 18 ] @@ -491,15 +492,15 @@ title : String -> Element Msg title clock = row [ width fill ] [ el [ Font.size 40 ] <| text "Pod Reaper" - , el [ Font.size 14, Font.alignRight, width fill ] <| text clock + , el [ Font.size 14, Font.alignRight, width (fill |> maximum 610) ] <| + text clock ] page : Status -> Model -> Element Msg page status model = column - [ Background.color dark - , Font.color grey + [ Font.color grey , spacing 20 , padding 20 , width fill @@ -559,4 +560,4 @@ view model = Err x -> { namespaces = [], clock = "JSON decoding error: " ++ D.errorToString x } in - layout [] <| page status model + layout defaultStyle <| page status model diff --git a/src/main/kotlin/org/sgdan/podreaper/Actions.kt b/src/main/kotlin/org/sgdan/podreaper/Actions.kt index 0636626..2593417 100644 --- a/src/main/kotlin/org/sgdan/podreaper/Actions.kt +++ b/src/main/kotlin/org/sgdan/podreaper/Actions.kt @@ -66,7 +66,12 @@ fun reap(client: KubernetesClient, status: Status, ns: NamespaceStatus) { // change up/down state if (!ns.hasDownQuota && !shouldRun) bringDown(client, ns.name) - if (ns.hasDownQuota && shouldRun) bringUp(client, ns.name) + if (ns.hasDownQuota && shouldRun) { + bringUp(client, ns.name) + val existing = status.settings[ns.name] ?: NamespaceConfig() + saveSettings(client, status.settings.plus( + ns.name to existing.copy(lastStarted = started.toEpochSecond() * 1000))) + } // kill any pods that are running if (!shouldRun) client.pods().inNamespace(ns.name).delete() diff --git a/src/main/kotlin/org/sgdan/podreaper/Status.kt b/src/main/kotlin/org/sgdan/podreaper/Status.kt index e46cc9f..42d8f0d 100644 --- a/src/main/kotlin/org/sgdan/podreaper/Status.kt +++ b/src/main/kotlin/org/sgdan/podreaper/Status.kt @@ -106,9 +106,12 @@ fun remainingTime(remaining: Long): String { fun readNamespace(client: KubernetesClient, name: String, status: Status): NamespaceStatus { val rq = readResourceQuota(client, name) - val lastStarted = status.settings[name]?.lastStarted ?: 0 - val remaining = remainingSeconds(lastStarted, status.now) val autoStartHour = status.settings[name]?.autoStartHour + val lastScheduled = lastScheduled(autoStartHour, status.zdt) + val lastScheduledMillis = lastScheduled.toEpochSecond() * 1000 + val lastStarted = max(status.settings[name]?.lastStarted ?: 0, lastScheduledMillis) + log.debug { "last started: ${toZDT(lastStarted, status.zone)}"} + val remaining = remainingSeconds(lastStarted, status.now) return NamespaceStatus( name, hasLimitRange(client, name), @@ -119,7 +122,7 @@ fun readNamespace(client: KubernetesClient, name: String, status: Status): Names toGigs(rq?.spec?.hard?.get(LIMITS_MEMORY)?.amount), autoStartHour, remainingTime(remaining), - lastScheduled(autoStartHour, status.zdt), + lastScheduled, lastStarted ) } diff --git a/src/main/kotlin/org/sgdan/podreaper/Time.kt b/src/main/kotlin/org/sgdan/podreaper/Time.kt index 88b25d4..b84396d 100644 --- a/src/main/kotlin/org/sgdan/podreaper/Time.kt +++ b/src/main/kotlin/org/sgdan/podreaper/Time.kt @@ -1,11 +1,14 @@ package org.sgdan.podreaper +import mu.KotlinLogging import java.time.DayOfWeek import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime import java.time.temporal.ChronoUnit +private val log = KotlinLogging.logger {} + private fun isWeekend(day: DayOfWeek) = setOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY).contains(day) @@ -31,7 +34,10 @@ fun toZDT(millis: Long, zone: ZoneId): ZonedDateTime = * @return same time as "now" but from most recent weekday, or null * if no start hour has been specified */ -fun lastScheduled(startHour: Int?, now: ZonedDateTime) = startHour?.let { - val start = weekday(now.withHour(it)) - if (now.isAfter(start)) start else weekday(start.minusDays(1)) -} ?: toZDT(0, now.zone) +fun lastScheduled(startHour: Int?, now: ZonedDateTime): ZonedDateTime { + val last = startHour?.let { + val start = weekday(now.withHour(it)) + if (start.isAfter(now)) weekday(start.minusDays(1)) else start + } + return last?.withMinute(0)?.withSecond(0) ?: toZDT(0, now.zone) +} diff --git a/src/test/kotlin/org/sgdan/podreaper/BackendTest.kt b/src/test/kotlin/org/sgdan/podreaper/BackendTest.kt index 796e263..6e03032 100644 --- a/src/test/kotlin/org/sgdan/podreaper/BackendTest.kt +++ b/src/test/kotlin/org/sgdan/podreaper/BackendTest.kt @@ -5,6 +5,7 @@ import mu.KotlinLogging import org.junit.Assert.* import org.junit.Rule import org.junit.Test +import java.time.ZoneId import java.time.ZonedDateTime private val log = KotlinLogging.logger {} @@ -66,6 +67,16 @@ class K8sBackendTest { assertEquals(fri9am, lastScheduled(9, mon8am)) } + @Test + fun autoStart() { + val wed8pm = ZonedDateTime.parse("2019-11-13T20:00Z") + val wedAfter8pm = ZonedDateTime.parse("2019-11-13T20:32Z") + val started = mostRecent(wed8pm, toZDT(0, ZoneId.systemDefault())) + assertEquals("2019-11-13T20:00Z", "$started") + assertEquals("2019-11-13T20:00Z", "${lastScheduled(20, wedAfter8pm)}") + assertTrue(hoursFrom(started, wedAfter8pm) < 8) + } + private fun remaining(lastStarted: Long, now: Long) = remainingTime(remainingSeconds(lastStarted, now))