Skip to content

Commit

Permalink
Add tests for Hawkbit's downloadOnly feature
Browse files Browse the repository at this point in the history
This commit includes tests for the download-only feature in Hawkbit. It
also introduces an abstract class, AbstractDeploymentTest, to offer a
structured approach for testing deployments and streamline development.

Signed-off-by: Saeed Rezaee <[email protected]>
  • Loading branch information
SaeedRe committed Feb 16, 2024
1 parent 762126f commit e2df3dd
Show file tree
Hide file tree
Showing 3 changed files with 336 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright © 2017-2024 Kynetics LLC
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hara.ddiclient.integrationtest

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import org.eclipse.hara.ddiclient.api.ConfigDataProvider
import org.eclipse.hara.ddiclient.api.DeploymentPermitProvider
import org.eclipse.hara.ddiclient.api.DirectoryForArtifactsProvider
import org.eclipse.hara.ddiclient.api.DownloadBehavior
import org.eclipse.hara.ddiclient.api.HaraClient
import org.eclipse.hara.ddiclient.api.HaraClientData
import org.eclipse.hara.ddiclient.api.HaraClientDefaultImpl
import org.eclipse.hara.ddiclient.api.MessageListener
import org.eclipse.hara.ddiclient.api.Updater
import org.eclipse.hara.ddiclient.integrationtest.api.management.Action
import org.eclipse.hara.ddiclient.integrationtest.api.management.HawkbitAssignDistributionBody
import org.eclipse.hara.ddiclient.integrationtest.api.management.HawkbitTargetInfo
import org.eclipse.hara.ddiclient.integrationtest.api.management.ManagementApi
import org.eclipse.hara.ddiclient.integrationtest.api.management.ManagementClient
import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils
import org.eclipse.hara.ddiclient.integrationtest.utils.addOkhttpLogger
import org.eclipse.hara.ddiclient.integrationtest.utils.internalLog
import org.testng.Assert
import org.testng.annotations.AfterTest
import org.testng.annotations.BeforeTest
import java.io.File
import java.lang.Exception
import kotlin.coroutines.cancellation.CancellationException
import kotlin.time.Duration.Companion.seconds

abstract class AbstractDeploymentTest {

protected lateinit var managementApi: ManagementApi
abstract val targetId: String
protected var actionId: Int = 0

private var assertServerActionsScope = CoroutineScope(Dispatchers.IO)
private var assertServerActionsOnCompleteJob: Deferred<Unit>? = null
private var testScope = CoroutineScope(Dispatchers.Default)

private val throwableScope = CoroutineScope(Dispatchers.Default)
private var throwableJob: Deferred<Unit>? = null

private val eventListener = object : MessageListener {
override fun onMessage(message: MessageListener.Message) {
"Message received: $message".internalLog()
when (message) {

is MessageListener.Message.Event.UpdateFinished,
MessageListener.Message.State.CancellingUpdate -> {
testScope.launch {
try {
assertServerActionsOnCompleteJob?.await()
} catch (ignored: CancellationException) {
}
}
}

else -> {
}
}
}
}

@BeforeTest
open fun beforeTest() {
managementApi = ManagementClient.newInstance(TestUtils.hawkbitUrl)
}

@AfterTest
open fun afterTest() {
}

protected fun defaultClientFromTargetId(
directoryDataProvider: DirectoryForArtifactsProvider = TestUtils.directoryDataProvider,
configDataProvider: ConfigDataProvider = TestUtils.configDataProvider,
updater: Updater = TestUtils.updater,
messageListeners: List<MessageListener> = listOf(eventListener),
deploymentPermitProvider: DeploymentPermitProvider = object :
DeploymentPermitProvider {},
downloadBehavior: DownloadBehavior = TestUtils.downloadBehavior,
okHttpClientBuilder: OkHttpClient.Builder = OkHttpClient.Builder().addOkhttpLogger(),
targetToken: String? = "",
gatewayToken: String? = TestUtils.gatewayToken
): (String) -> HaraClient = { targetId ->
val clientData = HaraClientData(
TestUtils.tenantName,
targetId,
TestUtils.hawkbitUrl,
gatewayToken,
targetToken)

val client = HaraClientDefaultImpl()

client.init(
clientData,
directoryDataProvider,
configDataProvider,
deploymentPermitProvider,
listOf(*messageListeners.toTypedArray()),
listOf(updater),
downloadBehavior,
httpBuilder = okHttpClientBuilder
)
client
}

protected suspend fun reCreateTestTargetOnServer() {
runCatching {
managementApi.deleteTarget(TestUtils.basic, targetId)
}
runCatching {
managementApi.createTarget(
TestUtils.basic, listOf(HawkbitTargetInfo(targetId)))
}
}

protected suspend fun assignDistributionToTheTarget(
distribution: HawkbitAssignDistributionBody) {
val response =
managementApi.assignDistributionToTarget(TestUtils.basic,
targetId, distribution)
if (response.assignedActions.isNotEmpty()) {
actionId = response.assignedActions.first().id
}
}

protected suspend fun startTheTestAndWaitForResult(client: HaraClient,
deployment: TestUtils.TargetDeployments) {
client.startAsync()
assertServerActionsOnCompleteJob = assertServerActionsOnComplete(client, deployment)

testScope.async {
while (assertServerActionsOnCompleteJob?.isCompleted == false) {
delay(1.seconds)
}
}.await()
}

private fun assertServerActionsOnComplete(
client: HaraClient,
deployment: TestUtils.TargetDeployments): Deferred<Unit> {
return assertServerActionsScope.async(start = CoroutineStart.LAZY) {
val deploymentInfo = deployment.deploymentInfo.first()
while (managementApi.getActionAsync(TestUtils.basic, deployment.targetId,
deploymentInfo.actionId).status != Action.Status.finished
) {
delay(5.seconds)
}

val actionStatus =
managementApi.getTargetActionStatusAsync(TestUtils.basic, deployment.targetId,
deploymentInfo.actionId)
assertEquals(actionStatus.content,
deploymentInfo.actionStatusOnFinish.content)

deploymentInfo.filesDownloadedPairedWithServerFile.forEach { (fileDownloaded, serverFile) ->
assertEquals(File(fileDownloaded).readText(),
File(serverFile).readText())
File(fileDownloaded).deleteRecursively()
}

client.safeStopClient()
}
}

private suspend fun assertEquals(actual: Any?, expected: Any?) {
throwableJob = throwableScope.async {
Assert.assertEquals(actual, expected)
}
try {
throwableJob?.await()
} catch (ignored: CancellationException) {
}
}

private fun HaraClient.safeStopClient() {
try {
stop()
} catch (ignored: Exception) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright © 2017-2024 Kynetics LLC
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hara.ddiclient.integrationtest

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.runBlocking
import org.eclipse.hara.ddiclient.api.DeploymentPermitProvider
import org.eclipse.hara.ddiclient.api.HaraClient
import org.eclipse.hara.ddiclient.integrationtest.api.management.ActionStatus
import org.eclipse.hara.ddiclient.integrationtest.api.management.AssignDistributionType
import org.eclipse.hara.ddiclient.integrationtest.api.management.HawkbitAssignDistributionBody
import org.eclipse.hara.ddiclient.integrationtest.api.management.ServerSystemConfig
import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils
import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.endMessagesOnSuccessUpdate
import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.messagesOnSoftUpdateAuthorization
import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.messagesOnSuccessfullyDownloadDistribution
import org.eclipse.hara.ddiclient.integrationtest.utils.TestUtils.targetRetrievedUpdateAction
import org.testng.annotations.BeforeTest
import org.testng.annotations.Test

class HawkbitDownloadOnlyDeploymentTest : AbstractDeploymentTest() {

override val targetId: String = "DownloadOnlyTest"

companion object {
const val DISTRIBUTION_ID = 3
}

@BeforeTest
override fun beforeTest() {
super.beforeTest()
setPollingTime()
}

private fun setPollingTime() = runBlocking {
managementApi.setPollingTime(TestUtils.basic, ServerSystemConfig("00:00:05"))
}

@Test(enabled = true, timeOut = 60_000)
fun testDownloadOnlyWhileWaitingForUpdateAuthorization() = runBlocking {

reCreateTestTargetOnServer()

assignDownloadOnlyDistribution()

val client = createClient()

startTheTestAndWaitForResult(client,
createTargetTestDeployment(getActionsOnFinishForForceUpdateScenario))
}

private suspend fun assignDownloadOnlyDistribution() {
val distribution = HawkbitAssignDistributionBody(DISTRIBUTION_ID,
AssignDistributionType.DOWNLOAD_ONLY, 0)
assignDistributionToTheTarget(distribution)
}

private fun createClient(): HaraClient {

val deploymentBehavior = object : DeploymentPermitProvider {
override fun downloadAllowed() = CompletableDeferred(false)
override fun updateAllowed() = CompletableDeferred(true)
}
return defaultClientFromTargetId(
deploymentPermitProvider = deploymentBehavior)
.invoke(targetId)
}

private fun createTargetTestDeployment(
actionsOnFinish: ActionStatus): TestUtils.TargetDeployments {
val filesDownloadedPairedToServerFile = setOf(
TestUtils.pathResolver.fromArtifact(actionId.toString()).invoke(
TestUtils.test1Artifact) to TestUtils.locationOfFileNamed("test1"))

return TestUtils.TargetDeployments(
targetId = targetId,
targetToken = "",
deploymentInfo = listOf(
TestUtils.TargetDeployments.DeploymentInfo(
actionId = actionId,
actionStatusOnStart = ActionStatus(
setOf(
TestUtils.firstActionWithAssignmentEntry
)),
actionStatusOnFinish = actionsOnFinish,
filesDownloadedPairedWithServerFile = filesDownloadedPairedToServerFile
)
)
)
}

private val getActionsOnFinishForForceUpdateScenario : ActionStatus =
ActionStatus(setOf(
*endMessagesOnSuccessUpdate,
*messagesOnSoftUpdateAuthorization,
*messagesOnSuccessfullyDownloadDistribution(
TestUtils.md5OfFileNamed("test1"), targetId,
"1", "test_1"),
targetRetrievedUpdateAction,
TestUtils.firstActionWithAssignmentEntry
))
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,13 @@ object TestUtils {
listOf("Assignment initiated by user 'test'")
)

val targetRetrievedUpdateAction = ActionStatus.ContentEntry(
ActionStatus.ContentEntry.Type.retrieved,
listOf("Update Server: Target retrieved update action and should start now the download.")
)

val startMessagesOnUpdateFond = arrayOf(
ActionStatus.ContentEntry(
ActionStatus.ContentEntry.Type.retrieved,
listOf("Update Server: Target retrieved update action and should start now the download.")
),
targetRetrievedUpdateAction,
firstActionEntry
)

Expand All @@ -301,6 +303,30 @@ object TestUtils {

val defaultActionStatusOnStart =
ActionStatus(setOf(firstActionEntry))

fun messagesOnSuccessfullyDownloadDistribution(
md5: String, targetId: String, softwareModuleId: String,
fileName: String) = arrayOf(
ActionStatus.ContentEntry(
ActionStatus.ContentEntry.Type.running,
listOf("Successfully downloaded all files")
),
ActionStatus.ContentEntry(
ActionStatus.ContentEntry.Type.running,
listOf(
"Successfully downloaded file with md5 $md5"
)
),
ActionStatus.ContentEntry(
ActionStatus.ContentEntry.Type.download,
listOf(
"Update Server: Target downloads /$tenantNameToLower/controller/v1/$targetId/softwaremodules/$softwareModuleId/artifacts/$fileName")
),
ActionStatus.ContentEntry(
ActionStatus.ContentEntry.Type.running,
listOf("Start downloading 1 files")
)
)
}


Expand Down

0 comments on commit e2df3dd

Please sign in to comment.