Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/poll smdpplus for install and download statuses #971

Open
wants to merge 26 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
46aec84
whitespace
la3lma Nov 27, 2019
668a385
A bit of documentation
la3lma Nov 27, 2019
e6a1388
Non-compiling first stab at making a poller (cleanup necessary)
la3lma Nov 27, 2019
0a807e2
Merge remote-tracking branch 'origin/develop' into feature/poll-smdpp…
la3lma Nov 27, 2019
40e8284
Now compiles without error, about to start adding tests
la3lma Nov 28, 2019
9bcaabe
Merge remote-tracking branch 'origin/develop' into feature/poll-smdpp…
la3lma Nov 28, 2019
8ae86c6
Fix bug: Had removed parameter, that led to a failing test!
la3lma Nov 28, 2019
e62d8e2
Minimal test (don't throw runtime exceptions while running)
la3lma Nov 28, 2019
2ab97ae
extend pollng test to prepare profiles for allocation, not yet alloca…
la3lma Nov 28, 2019
aea25b4
Probably, mostly _complete_, but lala failing test.
la3lma Nov 28, 2019
985cd20
Use the right profile name
la3lma Nov 28, 2019
c2c93da
testcase still passing, but it still doesn't actually test the case …
la3lma Nov 28, 2019
31f4e1a
Add TODOs indicating what we should do in order to get a test that ac…
la3lma Nov 28, 2019
581844f
Factoring out task invocation into a class. Step 1: extract cocmmonal…
la3lma Nov 29, 2019
d1778ea
Refacgored testPollingOfOutstandingProfilesTask to use the TaskInvoca…
la3lma Nov 29, 2019
40a6f0f
Extending the smdp+ emulator to have callbacks disabled
la3lma Nov 29, 2019
bc5add5
Passing test, now testing actual workitude
la3lma Nov 29, 2019
deab9bb
Adding comment
la3lma Nov 29, 2019
315a17d
More refactoring for compactness and readability
la3lma Nov 29, 2019
5a00967
A little more abstraction
la3lma Nov 29, 2019
84a3830
Simplify even more, jsut use the original task type and apply the exe…
la3lma Nov 29, 2019
6f63782
Adding a bunch of comments
la3lma Nov 29, 2019
1d1ee0a
Whitespace
la3lma Nov 29, 2019
6c9bf2e
Merge branch 'develop' into feature/poll-smdpplus-for-install-and-dow…
la3lma Dec 2, 2019
461a9e1
Merge branch 'feature/poll-smdpplus-for-install-and-download-statuses…
la3lma Dec 11, 2019
b22b39c
Adding some of Kjell's excellent suggestions from code review
la3lma Dec 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class OcsGrpcServerTest {
fun `load test OCS using gRPC`() = runBlocking {

// Add delay to DB call and skip analytics and low balance notification
@ExperimentalUnsignedTypes
OnlineCharging.loadUnitTest = true

server = OcsGrpcServer(8082, OcsGrpcService(OnlineCharging))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ class ES2PlusClient(
return response
}

/* For test cases where content should be returned. */
//
// For test cases where content should be returned.
//
@Throws(ES2PlusClientException::class)
la3lma marked this conversation as resolved.
Show resolved Hide resolved
private fun <T, S> postEs2ProtocolCmd(
path: String,
Expand Down Expand Up @@ -231,7 +233,6 @@ class ES2PlusClient(
}



fun confirmOrder(eid: String? = null,
iccid: String,
matchingId: String? = null,
Expand All @@ -255,7 +256,13 @@ class ES2PlusClient(
returnValueClass = Es2ConfirmOrderResponse::class.java)
}

fun cancelOrder(iccid: String, finalProfileStatusIndicator: String, eid: String? = null, matchingId: String? = null): HeaderOnlyResponse {
/**
* Transmit a cancelOrder request for a particular ICCID.
*/
fun cancelOrder(iccid: String,
finalProfileStatusIndicator: String,
eid: String? = null,
matchingId: String? = null): HeaderOnlyResponse {
la3lma marked this conversation as resolved.
Show resolved Hide resolved
return postEs2ProtocolCmd("/gsma/rsp2/es2plus/cancelOrder",
es2ProtocolPayload = Es2CancelOrder(
header = ES2RequestHeader(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package org.ostelco.simcards.admin

import arrow.core.Either
import com.codahale.metrics.health.HealthCheck
import com.google.common.collect.ImmutableMultimap
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import io.dropwizard.client.HttpClientBuilder
import io.dropwizard.client.JerseyClientBuilder
import io.dropwizard.jdbi3.JdbiFactory
import io.dropwizard.servlets.tasks.Task
import io.dropwizard.testing.ConfigOverride
import io.dropwizard.testing.ResourceHelpers
import io.dropwizard.testing.junit.DropwizardAppRule
import org.apache.http.impl.client.CloseableHttpClient
import org.assertj.core.api.Assertions.assertThat
import org.glassfish.jersey.client.ClientProperties
import org.jdbi.v3.core.Jdbi
Expand All @@ -30,15 +33,21 @@ import org.ostelco.simcards.hss.SimManagerToHssDispatcherAdapter
import org.ostelco.simcards.inventory.HssState
import org.ostelco.simcards.inventory.ProvisionState
import org.ostelco.simcards.inventory.SimEntry
import org.ostelco.simcards.inventory.SimInventoryApi
import org.ostelco.simcards.inventory.SimInventoryDAO
import org.ostelco.simcards.inventory.SimProfileKeyStatistics
import org.ostelco.simcards.profilevendors.ProfileVendorAdapterFactory
import org.ostelco.simcards.smdpplus.SmDpPlusApplication
import org.testcontainers.containers.BindMode
import org.testcontainers.containers.FixedHostPortGenericContainer
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy
import java.io.FileInputStream
import java.io.PrintWriter
import java.io.StringWriter
import java.time.Duration
import java.time.temporal.ChronoUnit
import java.util.*
import javax.ws.rs.client.Client
import javax.ws.rs.client.Entity
import javax.ws.rs.core.MediaType
Expand All @@ -51,7 +60,6 @@ class SimAdministrationTest {
private val phoneType = "rababara"
private val expectedProfile = "IPHONE_PROFILE_2"


companion object {
private lateinit var jdbi: Jdbi
private lateinit var client: Client
Expand Down Expand Up @@ -168,7 +176,6 @@ class SimAdministrationTest {
dao.clearTables()
}


private fun presetTables() {
val dao = SIM_MANAGER_RULE.getApplication<SimAdministrationApplication>().getDAO()

Expand All @@ -178,17 +185,15 @@ class SimAdministrationTest {
}

/* The SIM dataset is the same that is used by the SM-DP+ emulator. */
private fun loadSimData(hssState: HssState? = null, queryParameterName: String = "initialHssState", expectedReturnCode: Int = 200) {
protected fun loadSimData(hssState: HssState? = null, queryParameterName: String = "initialHssState", expectedReturnCode: Int = 200) {
val entries = FileInputStream(SM_DP_PLUS_RULE.configuration.simBatchData)
var target = client.target("$simManagerEndpoint/$hssName/import-batch/profilevendor/$profileVendor")
if (hssState != null) {
target = target.queryParam(queryParameterName, hssState)
}

val response =
target
.request()
.put(Entity.entity(entries, MediaType.TEXT_PLAIN))
target.request().put(Entity.entity(entries, MediaType.TEXT_PLAIN))
assertThat(response.status).isEqualTo(expectedReturnCode)
}

Expand Down Expand Up @@ -303,64 +308,191 @@ class SimAdministrationTest {
}
}

@Test
fun testPeriodicProvisioningTask() {
loadSimData()
val simDao = SIM_MANAGER_RULE.getApplication<SimAdministrationApplication>()
.getDAO()
/**
* Helper class that is used to wrap invocations of the
* preallocation and polling of outstandng profiles tasks.
*/
class TaskInvocationFixture(val testClass: SimAdministrationTest) {
var simDao: SimInventoryDAO
var profileVendors: List<ProfileVendorConfig>
var hssConfigs: List<HssConfig>
var httpClient : CloseableHttpClient
var maxNoOfProfilesToAllocate = 10
var dispatcher: DirectHssDispatcher
var hssAdapterCache: SimManagerToHssDispatcherAdapter
var preStats: SimProfileKeyStatistics
var pvaf: ProfileVendorAdapterFactory
var preallocationTask: PreallocateProfilesTask

val profileVendors = SIM_MANAGER_RULE.configuration.profileVendors
val hssConfigs = SIM_MANAGER_RULE.configuration.hssVendors
val httpClient = HttpClientBuilder(SIM_MANAGER_RULE.environment)
.build("periodicProvisioningTaskClient")
val maxNoOfProfilesToAllocate = 10
val simApi: SimInventoryApi

val hlrs = simDao.getHssEntries()
assertThat(hlrs.isRight()).isTrue()
val pollingTask : PollOutstandingProfilesTask

var hssId: Long = 0
hlrs.map {
hssId = it[0].id
var hssId: Long = -1L

init {
testClass.loadSimData()

simDao = SIM_MANAGER_RULE.getApplication<SimAdministrationApplication>()
.getDAO()

profileVendors = SIM_MANAGER_RULE.configuration.profileVendors
hssConfigs = SIM_MANAGER_RULE.configuration.hssVendors
httpClient = HttpClientBuilder(SIM_MANAGER_RULE.environment)
.build("taskInvocationFixtureClient-${UUID.randomUUID()}")

val hlrs = simDao.getHssEntries()
assertThat(hlrs.isRight()).isTrue()

hlrs.map {
hssId = it[0].id
}

dispatcher = DirectHssDispatcher(
hssConfigs = hssConfigs,
httpClient = httpClient,
healthCheckRegistrar = object : HealthCheckRegistrar {
override fun registerHealthCheck(name: String, healthCheck: HealthCheck) {
SIM_MANAGER_RULE.environment.healthChecks().register(name, healthCheck)
}
})
hssAdapterCache = SimManagerToHssDispatcherAdapter(
dispatcher = dispatcher,
simInventoryDAO = simDao)
preStats = SimProfileKeyStatistics(
noOfEntries = 0L,
noOfEntriesAvailableForImmediateUse = 0L,
noOfReleasedEntries = 0L,
noOfUnallocatedEntries = 0L,
noOfReservedEntries = 0L)
pvaf = ProfileVendorAdapterFactory(
simInventoryDAO = simDao,
httpClient = httpClient,
profileVendors = ConfigRegistry.config.profileVendors)
preallocationTask = PreallocateProfilesTask(
maxNoOfProfileToAllocate = maxNoOfProfilesToAllocate,
simInventoryDAO = simDao,
hssAdapterProxy = hssAdapterCache,
pvaf = pvaf)
simApi = SimInventoryApi(httpClient, SIM_MANAGER_RULE.configuration, simDao)

pollingTask = PollOutstandingProfilesTask(simInventoryDAO = simDao, pvaf = pvaf)
}



private fun executeTask(task: Task): String {
val out = StringWriter();
val writer = PrintWriter(out);
val parameters = ImmutableMultimap.builder<String, String>().build()
task.execute(parameters, writer)
return out.toString()
}

/**
* Execute the preallocation task, returning the report it makes as a string.
*/
fun executePreallocateSimProfilesTask(): String {
la3lma marked this conversation as resolved.
Show resolved Hide resolved
return executeTask(preallocationTask)
}

/**
* Execute the polling of outstanding sim roles task, returning the report it makes as a string.
*/
fun executePollOutstandingSimrofilesTask() : String {
return executeTask(pollingTask)
}

val dispatcher = DirectHssDispatcher(
hssConfigs = hssConfigs,
httpClient = httpClient,
healthCheckRegistrar = object : HealthCheckRegistrar {
override fun registerHealthCheck(name: String, healthCheck: HealthCheck) {
SIM_MANAGER_RULE.environment.healthChecks().register(name, healthCheck)
}
})
val hssAdapterCache = SimManagerToHssDispatcherAdapter(
dispatcher = dispatcher,
simInventoryDAO = simDao)
val preStats = SimProfileKeyStatistics(
noOfEntries = 0L,
noOfEntriesAvailableForImmediateUse = 0L,
noOfReleasedEntries = 0L,
noOfUnallocatedEntries = 0L,
noOfReservedEntries = 0L)
val task = PreallocateProfilesTask(
profileVendors = profileVendors,
simInventoryDAO = simDao,
maxNoOfProfileToAllocate = maxNoOfProfilesToAllocate,
hssAdapterProxy = hssAdapterCache,
httpClient = httpClient)
task.preAllocateSimProfiles()

val postAllocationStats =
simDao.getProfileStats(hssId, expectedProfile)
assertThat(postAllocationStats.isRight()).isTrue()

var postStats = SimProfileKeyStatistics(0L, 0L, 0L, 0L, 0L)
postAllocationStats.map {
postStats = it
/**
* Allocate the next sim profile for a generic ("nokia") phone. This method
* doesn't actually use the tasks, but it is used as a convenience methods when
* testing the tasks.
*/
fun allocateNextEsimProfile ():SimEntry {
val simEntry = simApi.allocateNextEsimProfile(hssName = testClass.hssName, phoneType = "nokia")
.fold({ null }, { it })

assertNotNull(simEntry)
return simEntry!!
}

/**
* Switch off callbacks from the SM-DP+ emulator to the Prime emulator.
*/
fun disableEs2CallbacksFromSmdpPlus() {
SM_DP_PLUS_RULE.getApplication<SmDpPlusApplication>().disableCallbacks()
}

/**
* Instruct the SM-DP+ emulator to simulate an installation of a simcard.
*/
fun emulateDownloadOfIccid(iccid: String) {
SM_DP_PLUS_RULE.getApplication<SmDpPlusApplication>().emulateInstallOfIccid(iccid)
}


/**
* Get statistics for a specific type of sim profile. Used to test
* if the preallocation task is doing the right thing.
*/
fun getStatsForProfile(profile:String): SimProfileKeyStatistics {
val postAllocationStats =
simDao.getProfileStats(hssId, profile)
assertThat(postAllocationStats.isRight()).isTrue()

var stats : SimProfileKeyStatistics? = null
postAllocationStats.map {
stats = it
}
if (stats == null) {
fail("Failed to get stats for profile $profile")
}

return stats!!
}
}


@Test
fun testPollingOfOutstandingProfilesTask() {
val tif = TaskInvocationFixture(this)

tif.executePreallocateSimProfilesTask()

// Run the polling, and observe that nothing happens.
// Then run the polling task and see what happens.
var outString = tif.executePollOutstandingSimrofilesTask()
assertTrue(true) // TODO: Replace with something more useful

// Then allocate the next profile
val simEntry = tif.allocateNextEsimProfile()

// Then run the polling task and see what happens.
outString = tif.executePollOutstandingSimrofilesTask()
assertTrue(outString.contains("State for iccid=${simEntry.iccid} still set to RELEASED"))

// Disable callbacks and emulate dowload (emulation of failing callbacks from SMDP+, which
// is the situation we want the callbacks to help us with)
tif.disableEs2CallbacksFromSmdpPlus()
tif.emulateDownloadOfIccid(simEntry.iccid)

outString = tif.executePollOutstandingSimrofilesTask()
assertTrue(outString.contains("Updated state for iccid=${simEntry.iccid} to INSTALLED"))
}

@Test
fun testPeriodicProvisioningTask() {
val tif = TaskInvocationFixture(this)

val preStats = tif.getStatsForProfile(expectedProfile)
tif.executePreallocateSimProfilesTask()

val postStats = tif.getStatsForProfile(expectedProfile)

val noOfAllocatedProfiles =
postStats.noOfEntriesAvailableForImmediateUse - preStats.noOfEntriesAvailableForImmediateUse
assertEquals(
maxNoOfProfilesToAllocate.toLong(),
tif.maxNoOfProfilesToAllocate.toLong(),
noOfAllocatedProfiles)
}

Expand Down Expand Up @@ -491,7 +623,6 @@ class SimAdministrationTest {
assertGaugeValue(2, "sims.noOfReservedEntries.IPHONE_PROFILE_2")
}


// XXX MISSING TEST: SHould test periodic updater also in cases where
// either HSS or SM-DP+ entries are pre-allocated.

Expand Down
Loading