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

com.avito.android.runner.devices.internal.kubernetes.ReservationDeploymentFactoryImpl.kt Maven / Gradle / Ivy

Go to download

Collection of infrastructure libraries and gradle plugins of Avito Android project

There is a newer version: 2024.32
Show newest version
package com.avito.android.runner.devices.internal.kubernetes

import com.avito.android.runner.devices.model.ReservationData
import com.avito.instrumentation.reservation.request.Device
import com.avito.k8s.toValidKubernetesName
import com.avito.logger.LoggerFactory
import com.avito.logger.create
import com.fkorotkov.kubernetes.apps.metadata
import com.fkorotkov.kubernetes.apps.newDeployment
import com.fkorotkov.kubernetes.apps.selector
import com.fkorotkov.kubernetes.apps.spec
import com.fkorotkov.kubernetes.apps.template
import com.fkorotkov.kubernetes.metadata
import com.fkorotkov.kubernetes.newContainer
import com.fkorotkov.kubernetes.newEnvVar
import com.fkorotkov.kubernetes.newToleration
import com.fkorotkov.kubernetes.resources
import com.fkorotkov.kubernetes.securityContext
import com.fkorotkov.kubernetes.spec
import io.fabric8.kubernetes.api.model.PodSpec
import io.fabric8.kubernetes.api.model.Quantity
import io.fabric8.kubernetes.api.model.apps.Deployment

internal class ReservationDeploymentFactoryImpl(
    private val configurationName: String,
    private val projectName: String,
    private val buildId: String,
    private val buildType: String,
    private val deploymentNameGenerator: DeploymentNameGenerator,
    private val useLegacyExtensionsV1Beta: Boolean,
    loggerFactory: LoggerFactory
) : ReservationDeploymentFactory {

    private val logger = loggerFactory.create()

    init {
        val prefix = { reason: String -> "Can't create configuration, precondition failed: $reason" }
        require(configurationName.isNotBlank()) { prefix.invoke("configurationName is blank; used to label pods") }
        require(buildId.isNotBlank()) { prefix.invoke("buildId is blank, client can't distinguish reservations") }
    }

    override fun createDeployment(namespace: String, reservation: ReservationData): Deployment {
        logger.info("Creating deployment for configuration: $configurationName")
        val deploymentName = deploymentNameGenerator.generateName(namespace)
        logger.info("Deployment name will be: $deploymentName")

        return when (val device = reservation.device) {
            is Device.LocalEmulator -> throw IllegalStateException(
                "Local emulator $device is unsupported in kubernetes reservation"
            )
            is Device.CloudEmulator -> {
                logger.info("Creating ${reservation.count} replicas of cloud emulator deployment: $device")
                getCloudEmulatorDeployment(
                    emulator = device,
                    deploymentName = deploymentName,
                    count = reservation.count
                )
            }
            is Device.MockEmulator -> throw IllegalStateException(
                "Mock emulator ${reservation.device} is unsupported in kubernetes reservation"
            )
        }
    }

    private fun getCloudEmulatorDeployment(
        emulator: Device.CloudEmulator,
        deploymentName: String,
        count: Int
    ): Deployment {
        return deviceDeployment(
            deploymentMatchLabels = deviceMatchLabels(emulator),
            deploymentName = deploymentName,
            count = count
        ) {
            containers = listOf(
                newContainer {
                    name = emulator.name.toValidKubernetesName()
                    image = emulator.image

                    securityContext {
                        privileged = true
                    }

                    resources {
                        limits = mutableMapOf().apply {
                            if (!emulator.cpuCoresLimit.isNullOrBlank()) {
                                plusAssign("cpu" to Quantity(emulator.cpuCoresLimit))
                            }
                            if (!emulator.memoryLimit.isNullOrBlank()) {
                                plusAssign("memory" to Quantity(emulator.memoryLimit))
                            }
                        }

                        requests = mutableMapOf().apply {
                            if (!emulator.cpuCoresRequest.isNullOrBlank()) {
                                plusAssign("cpu" to Quantity(emulator.cpuCoresRequest))
                            }
                            if (!emulator.memoryRequest.isNullOrBlank()) {
                                plusAssign("memory" to Quantity(emulator.memoryRequest))
                            }
                        }
                    }

                    env = listOf(
                        // used to start from prepared snapshot, see `prepare_snapshot.sh`
                        newEnvVar {
                            name = "SNAPSHOT_ENABLED"
                            value = "true"
                        },
                        // run headless
                        newEnvVar {
                            name = "WINDOW"
                            value = "false"
                        }
                    )
                }
            )

            tolerations = listOf(
                newToleration {
                    key = "dedicated"
                    operator = "Equal"
                    value = "android"
                    effect = "NoSchedule"
                }
            )
        }
    }

    private fun deviceDeployment(
        deploymentMatchLabels: Map,
        deploymentName: String,
        count: Int,
        block: PodSpec.() -> Unit
    ): Deployment {
        val deploymentSpecificationsMatchLabels = deploymentMatchLabels
            .plus("deploymentName" to deploymentName)

        return newDeployment {
            apiVersion = if (useLegacyExtensionsV1Beta) "extensions/v1beta1" else "apps/v1"
            metadata {
                name = deploymentName
                labels = deploymentMatchLabels
                finalizers = listOf(
                    // Remove all dependencies (replicas) in foreground after removing deployment
                    "foregroundDeletion"
                )
            }
            spec {
                replicas = count
                selector {
                    matchLabels = deploymentSpecificationsMatchLabels
                }

                template {
                    metadata {
                        labels = deploymentSpecificationsMatchLabels
                    }
                    spec(block)
                }
            }
        }
    }

    private fun deviceMatchLabels(
        device: Device
    ): Map {
        return mapOf(

            /**
             * used to distinguish different strategies for clearing leaked kubernetes deployments
             * for incorrectly finished builds
             * see [com.avito.ci.DeploymentEnvironment]
             */
            "type" to buildType,
            "id" to buildId, // teamcity_build_id or local synthetic
            "project" to projectName,
            "instrumentationConfiguration" to configurationName,
            "device" to device.description
        )
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy