Skip to content

Commit

Permalink
feat(optOut): tag image with expiration_time:never (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
emjburns authored Nov 6, 2018
1 parent 40d6c2a commit 8751a82
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ open class InMemoryCache<out T : Cacheable>(

override fun contains(key: String?): Boolean {
if (key == null) return false
return cache.get().find { it.name == key } != null
return get().find { it.name == key } != null
}

private val cache = AtomicReference<Set<T>>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ package com.netflix.spinnaker.swabbie.events

import com.netflix.spectator.api.Id
import com.netflix.spectator.api.Registry
import com.netflix.spinnaker.moniker.frigga.FriggaReflectiveNamer
import com.netflix.spinnaker.swabbie.InMemoryCache
import com.netflix.spinnaker.swabbie.MetricsSupport
import com.netflix.spinnaker.swabbie.model.MarkedResource
import com.netflix.spinnaker.swabbie.model.ResourceState
import com.netflix.spinnaker.swabbie.model.Status
import com.netflix.spinnaker.swabbie.model.humanReadableDeletionTime
import com.netflix.spinnaker.swabbie.model.*
import com.netflix.spinnaker.swabbie.repository.ResourceStateRepository
import com.netflix.spinnaker.swabbie.repository.TaskCompleteEventInfo
import com.netflix.spinnaker.swabbie.repository.TaskTrackingRepository
import com.netflix.spinnaker.swabbie.tagging.ResourceTagger
import com.netflix.spinnaker.swabbie.tagging.TaggingService
import com.netflix.spinnaker.swabbie.tagging.UpsertImageTagsRequest
import net.logstash.logback.argument.StructuredArguments
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.event.EventListener
Expand All @@ -36,7 +40,10 @@ class ResourceStateManager(
private val resourceStateRepository: ResourceStateRepository,
private val clock: Clock,
private val registry: Registry,
@Autowired(required = false) private val resourceTagger: ResourceTagger?
@Autowired(required = false) private val resourceTagger: ResourceTagger?,
private val taggingService: TaggingService,
private val taskTrackingRepository: TaskTrackingRepository,
private val applicationsCaches: List<InMemoryCache<Application>>
) : MetricsSupport(registry) {

private val log = LoggerFactory.getLogger(javaClass)
Expand Down Expand Up @@ -134,34 +141,72 @@ class ResourceStateManager(
}

private fun updateState(event: Event) {
event.markedResource.let { markedResource ->
resourceStateRepository.get(
resourceId = markedResource.resourceId,
namespace = markedResource.namespace
).let { currentState ->
val statusName = if (event is OrcaTaskFailureEvent) "${event.action.name} FAILED" else event.action.name
val status = Status(statusName, clock.instant().toEpochMilli())
currentState?.statuses?.add(status)
(currentState?.copy(
statuses = currentState.statuses,
markedResource = markedResource,
softDeleted = event is SoftDeleteResourceEvent,
deleted = event is DeleteResourceEvent,
optedOut = event is OptOutResourceEvent,
currentStatus = status
) ?: ResourceState(
markedResource = markedResource,
softDeleted = event is SoftDeleteResourceEvent,
deleted = event is DeleteResourceEvent,
optedOut = event is OptOutResourceEvent,
statuses = mutableListOf(status),
currentStatus = status
)).let {
resourceStateRepository.upsert(it)
}
}
val currentState = resourceStateRepository.get(
resourceId = event.markedResource.resourceId,
namespace = event.markedResource.namespace
)
val statusName = if (event is OrcaTaskFailureEvent) "${event.action.name} FAILED" else event.action.name
val status = Status(statusName, clock.instant().toEpochMilli())

currentState?.statuses?.add(status)
val newState = (currentState?.copy(
statuses = currentState.statuses,
markedResource = event.markedResource,
softDeleted = event is SoftDeleteResourceEvent,
deleted = event is DeleteResourceEvent,
optedOut = event is OptOutResourceEvent,
currentStatus = status
) ?: ResourceState(
markedResource = event.markedResource,
softDeleted = event is SoftDeleteResourceEvent,
deleted = event is DeleteResourceEvent,
optedOut = event is OptOutResourceEvent,
statuses = mutableListOf(status),
currentStatus = status
))

resourceStateRepository.upsert(newState)

if (event is OptOutResourceEvent) {
log.debug("Tagging resource ${event.markedResource.uniqueId()} with \"expiration_time\":\"never\"")
val taskId = tagResource(event.markedResource, event.workConfiguration)
log.debug("Tagging resource ${event.markedResource.uniqueId()} in {}", StructuredArguments.kv("taskId", taskId))
}
}

//todo eb: pull to another kind of ResourceTagger?
private fun tagResource(
resource: MarkedResource,
workConfiguration: WorkConfiguration
): String {
val taskId = taggingService.upsertImageTag(
UpsertImageTagsRequest(
imageNames = setOf(resource.name ?: resource.resourceId),
regions = setOf(SwabbieNamespace.namespaceParser(resource.namespace).region),
tags = mapOf("expiration_time" to "never"),
cloudProvider = "aws",
cloudProviderType = "aws",
application = resolveApplicationOrNull(resource) ?: "swabbie",
description = "Setting `expiration_time` to `never` for image ${resource.uniqueId()}"
)
)

taskTrackingRepository.add(
taskId,
TaskCompleteEventInfo(
action = Action.OPTOUT,
markedResources = listOf(resource),
workConfiguration = workConfiguration,
submittedTimeMillis = clock.instant().toEpochMilli()
)
)
return taskId
}

private fun resolveApplicationOrNull(markedResource: MarkedResource): String? {
val appName = FriggaReflectiveNamer().deriveMoniker(markedResource).app ?: return null
return if (applicationsCaches.any { it.contains(appName) }) appName else null
}
}

internal fun MarkedResource.typeAndName(): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
package com.netflix.spinnaker.swabbie.events

import com.netflix.spectator.api.NoopRegistry
import com.netflix.spinnaker.swabbie.InMemoryCache
import com.netflix.spinnaker.swabbie.ResourceTypeHandlerTest.workConfiguration
import com.netflix.spinnaker.swabbie.repository.ResourceStateRepository
import com.netflix.spinnaker.swabbie.tagging.ResourceTagger
import com.netflix.spinnaker.swabbie.model.*
import com.netflix.spinnaker.swabbie.repository.TaskTrackingRepository
import com.netflix.spinnaker.swabbie.tagging.TaggingService
import com.netflix.spinnaker.swabbie.test.TestResource
import com.nhaarman.mockito_kotlin.*
import org.junit.jupiter.api.AfterEach
Expand All @@ -34,6 +37,12 @@ object ResourceStateManagerTest {
private val resourceTagger = mock<ResourceTagger>()
private val clock = Clock.fixed(Instant.parse("2018-05-24T12:34:56Z"), ZoneOffset.UTC)
private val registry = NoopRegistry()
private val taggingService = mock<TaggingService>()
private val taskTrackingRepository = mock<TaskTrackingRepository>()

private val app = Application(name = "testapp", email = "[email protected]")
private val inMemCache = InMemoryCache {setOf(app)}
private val applicationsCaches = listOf(inMemCache)

private var resource = TestResource("testResource")
private var configuration = workConfiguration()
Expand All @@ -58,7 +67,10 @@ object ResourceStateManagerTest {
resourceStateRepository = resourceStateRepository,
clock = clock,
registry = registry,
resourceTagger = resourceTagger
resourceTagger = resourceTagger,
taggingService = taggingService,
taskTrackingRepository = taskTrackingRepository,
applicationsCaches = applicationsCaches
)

@AfterEach
Expand Down Expand Up @@ -162,6 +174,8 @@ object ResourceStateManagerTest {
)
)

whenever(taggingService.upsertImageTag(any())) doReturn "1234"

subject.handleEvents(event)

verify(resourceTagger).unTag(
Expand All @@ -180,6 +194,17 @@ object ResourceStateManagerTest {
&& it.currentStatus!!.timestamp > it.statuses.first().timestamp
}
)

verify(taggingService).upsertImageTag(argWhere {
it.tags.containsKey("expiration_time")
&& it.tags.containsValue("never")
&& it.imageNames.contains("testResource")
})

verify(taskTrackingRepository).add(
argWhere { it == "1234" },
argWhere { it.action == Action.OPTOUT }
)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ class OrcaTaskMonitoringAgent (
applicationEventPublisher.publishEvent(OptOutResourceEvent(markedResource, taskInfo.workConfiguration))
}
}
Action.OPTOUT -> {
// no action needs to be taken because the status was already updated
}
else -> {
TODO("Not implemented: event publishing not implemented for action ${taskInfo.action}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ class ResourceController(
@PathVariable namespace: String
) : ResourceState {
resourceTrackingRepository.find(resourceId, namespace)?.let { markedResource ->
log.debug("Found resource ${markedResource.uniqueId()} to opt out.")
resourceTrackingRepository.remove(markedResource)
workConfigurations.find {
it.namespace.equals(namespace, true)
}?.also { configuration ->
log.debug("Publishing opt out event for ${markedResource.uniqueId()}")
applicationEventPublisher.publishEvent(OptOutResourceEvent(markedResource, configuration))
}
}
Expand Down

0 comments on commit 8751a82

Please sign in to comment.