
com.avito.android.runner.devices.internal.kubernetes.KubernetesReservationClaimer.kt Maven / Gradle / Ivy
package com.avito.android.runner.devices.internal.kubernetes
import com.avito.android.runner.devices.internal.EmulatorsLogsReporter
import com.avito.android.runner.devices.internal.RemoteDevice
import com.avito.android.runner.devices.model.ReservationData
import com.avito.k8s.KubernetesApi
import com.avito.k8s.model.KubePod
import com.avito.logger.LoggerFactory
import com.avito.logger.create
import com.avito.runner.service.worker.device.DeviceCoordinate
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.channels.distinctBy
import kotlinx.coroutines.channels.filter
import kotlinx.coroutines.channels.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.coroutineContext
@ExperimentalCoroutinesApi
internal class KubernetesReservationClaimer(
private val reservationDeploymentFactory: ReservationDeploymentFactory,
private val kubernetesApi: KubernetesApi,
private val deploymentPodsListener: DeploymentPodsListener,
private val deviceProvider: RemoteDeviceProvider,
private val emulatorsLogsReporter: EmulatorsLogsReporter,
private val kubernetesReservationListener: KubernetesReservationListener,
private val lock: Mutex,
loggerFactory: LoggerFactory
) {
private val logger = loggerFactory.create()
suspend fun claim(
reservations: Collection,
serialsChannel: Channel,
podsChannel: Channel,
deploymentsChannel: Channel
) = with(CoroutineScope(coroutineContext)) {
reservations.forEach { reservation ->
launch(CoroutineName("create-deployment")) {
createDeployment(
reservation,
deploymentsChannel,
podsChannel,
serialsChannel
)
}
}
initializeDevices(podsChannel, serialsChannel)
}
private suspend fun createDeployment(
reservation: ReservationData,
deploymentsChannel: Channel,
podsChannel: Channel,
serialsChannel: Channel
) {
logger.info("Create deployment $reservation")
val deployment = reservationDeploymentFactory.createDeployment(
namespace = kubernetesApi.namespace,
reservation = reservation
)
val deploymentName = lock.withLock {
val deploymentName = deployment.metadata.name
deploymentsChannel.send(deploymentName)
kubernetesApi.createDeployment(deployment)
deploymentName
}
deploymentPodsListener.start(
deploymentName, podsChannel
).onFailure {
podsChannel.cancel()
serialsChannel.close()
}
}
private suspend fun initializeDevices(
podsChannel: ReceiveChannel,
serialsChannel: Channel
) {
with(CoroutineScope(coroutineContext + CoroutineName("waiting-pods"))) {
launch(CoroutineName("waiting-pods")) {
@Suppress("DEPRECATION")
podsChannel
.map {
if (serialsChannel.isClosedForSend) {
logger.info("cancel waiting-pods")
podsChannel.cancel()
}
it
}
.filter { it.phase is KubePod.PodPhase.Running }
.distinctBy { it.name }
.consumeEach { pod ->
kubernetesReservationListener.onPodAcquired()
launch(CoroutineName("boot-pod-${pod.name}")) {
deviceProvider.create(pod)
.onSuccess { device ->
device.sendTo(
pod.name,
serialsChannel
)
}
}
}
logger.info("initializeDevices finished")
}
}
}
private suspend fun RemoteDevice.sendTo(
podName: String,
serials: Channel,
) {
if (serials.isClosedForSend) {
logger.info("Pod $podName boot device but serials channel closed")
return
}
emulatorsLogsReporter.redirectLogcat(
emulatorName = serial,
device = this
)
serials.send(
DeviceCoordinate.Kubernetes(
serial = serial,
podName = podName
)
)
logger.info("Pod $podName sent outside for further usage")
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy