All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.spinnaker.front50.model.CompositeStorageService.kt Maven / Gradle / Ivy

There is a newer version: 2.37.0
Show newest version
/*
 * Copyright 2019 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.netflix.spinnaker.front50.model

import com.netflix.spectator.api.Registry
import com.netflix.spinnaker.front50.api.model.Timestamped
import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService
import com.netflix.spinnaker.kork.web.exceptions.NotFoundException
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Scheduled

class CompositeStorageService(
  private val dynamicConfigService: DynamicConfigService,
  private val registry: Registry,
  private val primary: StorageService,
  private val previous: StorageService
) : StorageService, BulkStorageService, AdminOperations {

  companion object {
    private val log = LoggerFactory.getLogger(CompositeStorageService::class.java)
  }

  var primaryReadStatusGauge = registry.gauge(
    registry.createId("compositeStorageService.read")
      .withTag("type", "primary")
      .withTag("class", primary.javaClass.simpleName)
  )
  var previousReadStatusGauge = registry.gauge(
    registry.createId("compositeStorageService.read")
      .withTag("type", "previous")
      .withTag("class", previous.javaClass.simpleName)
  )

  override fun supportsEventing(objectType: ObjectType): Boolean {
    if (!isPrimaryReadEnabled()) {
      return true
    }

    return objectType == ObjectType.ENTITY_TAGS
  }

  @Scheduled(fixedDelay = 60000L)
  fun status() {
    log.debug(
      "Composite Storage Service Read Status ({}: {}, {}: {})",
      primary.javaClass.simpleName,
      isPrimaryReadEnabled(),
      previous.javaClass.simpleName,
      isPreviousReadEnabled()
    )

    primaryReadStatusGauge.set(if (isPrimaryReadEnabled()) 1.0 else 0.0)
    previousReadStatusGauge.set(if (isPreviousReadEnabled()) 1.0 else 0.0)
  }

  override fun supportsVersioning(): Boolean {
    return primary.supportsVersioning()
  }

  override fun  loadObject(objectType: ObjectType?, objectKey: String?): T {
    var exception: Exception? = null

    if (isPrimaryReadEnabled()) {
      try {
        return primary.loadObject(objectType, objectKey)
      } catch (e: NotFoundException) {
        log.debug("{}.loadObject({}, {}) not found (primary)", primary.javaClass.simpleName, objectType, objectKey, e)

        exception = e
      } catch (e: Exception) {
        log.error("{}.loadObject({}, {}) failed (primary)", primary.javaClass.simpleName, objectType, objectKey, e)

        exception = e
      }
    }

    return when {
      isPreviousReadEnabled() -> previous.loadObject(objectType, objectKey)
      exception != null -> throw exception
      else -> throw IllegalStateException("Primary and previous storage services are disabled")
    }
  }

  override fun  loadObjects(objectType: ObjectType, objectKeys: List): List {
    var exception: Exception? = null

    if (isPrimaryReadEnabled()) {
      try {
        return primary.loadObjects(objectType, objectKeys)
      } catch (e: Exception) {
        log.error("{}.loadObjects({}) failed (primary)", primary.javaClass.simpleName, objectType)

        exception = e
      }
    }

    return when {
      isPreviousReadEnabled() -> previous.loadObjects(objectType, objectKeys)
      exception != null -> throw exception
      else -> throw IllegalStateException("Primary and previous storage services are disabled")
    }
  }

  override fun deleteObject(objectType: ObjectType?, objectKey: String?) {
    primary.deleteObject(objectType, objectKey)
    previous.deleteObject(objectType, objectKey)
  }

  override fun  storeObject(objectType: ObjectType?, objectKey: String?, item: T) {
    try {
      /*
       * Ensure that writes are first successful against the current source of truth (aka 'previous').
       *
       * The migration process (StorageServiceMigrator) is capable of detecting and migrating any records
       * that failed the subsequent 'primary' write.
       */
      previous.storeObject(objectType, objectKey, item)
    } catch (e: Exception) {
      log.error(
        "{}.storeObject({}, {}) failed",
        previous.javaClass.simpleName,
        objectType,
        objectKey,
        e
      )

      throw e
    }

    try {
      primary.storeObject(objectType, objectKey, item)
    } catch (e: Exception) {
      log.error(
        "{}.storeObject({}, {}) failed",
        primary.javaClass.simpleName,
        objectType,
        objectKey,
        e
      )

      throw e
    }
  }

  override fun listObjectKeys(objectType: ObjectType?): Map {
    val objectKeys = mutableMapOf()

    if (isPreviousReadEnabled()) {
      objectKeys.putAll(previous.listObjectKeys(objectType))
    }

    if (isPrimaryReadEnabled()) {
      objectKeys.putAll(primary.listObjectKeys(objectType))
    }

    return objectKeys
  }

  override fun  listObjectVersions(
    objectType: ObjectType?,
    objectKey: String?,
    maxResults: Int
  ): MutableCollection {
    var exception: Exception? = null

    if (isPrimaryReadEnabled()) {
      try {
        return primary.listObjectVersions(objectType, objectKey, maxResults)
      } catch (e: NotFoundException) {
        log.debug(
          "{}.listObjectVersions({}, {}, {}) not found (primary)",
          primary.javaClass.simpleName,
          objectType,
          objectKey,
          maxResults
        )

        exception = e
      } catch (e: Exception) {
        log.error(
          "{}.listObjectVersions({}, {}, {}) failed (primary)",
          primary.javaClass.simpleName,
          objectType,
          objectKey,
          maxResults,
          e
        )

        exception = e
      }
    }

    return when {
      isPreviousReadEnabled() -> return previous.listObjectVersions(objectType, objectKey, maxResults)
      exception != null -> throw exception
      else -> mutableListOf()
    }
  }

  override fun getLastModified(objectType: ObjectType?): Long {
    var exception: Exception? = null

    if (isPrimaryReadEnabled()) {
      try {
        return primary.getLastModified(objectType)
      } catch (e: Exception) {
        log.error("{}.getLastModified({}) failed (primary)", primary.javaClass.simpleName, objectType, e)

        exception = e
      }
    }

    return when {
      isPreviousReadEnabled() -> previous.getLastModified(objectType)
      exception != null -> throw exception
      else -> throw IllegalStateException("Primary and previous storage services are disabled")
    }
  }

  private fun isPrimaryReadEnabled() = isReadEnabled("primary")

  private fun isPreviousReadEnabled() = isReadEnabled("previous")

  private fun isReadEnabled(type: String) =
    dynamicConfigService.getConfig(
      Boolean::class.java,
      "spinnaker.migration.compositeStorageService.reads.$type",
      false
    )

  override fun  storeObjects(objectType: ObjectType?, items: MutableCollection?) {
    if (previous is BulkStorageService) {
      previous.storeObjects(objectType, items)
    }
    if (primary is BulkStorageService) {
      primary.storeObjects(objectType, items)
    }
  }

  override fun recover(operation: AdminOperations.Recover?) {
    if (previous is AdminOperations) {
      previous.recover(operation)
    }
    if (primary is AdminOperations) {
      primary.recover(operation)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy