diff --git a/analytics/build.gradle b/analytics/build.gradle index cad3a94b7..b08cc73a1 100644 --- a/analytics/build.gradle +++ b/analytics/build.gradle @@ -1,7 +1,7 @@ plugins { id "application" id "jacoco" - id "com.github.johnrengelman.shadow" version "2.0.2" + id "com.github.johnrengelman.shadow" version "2.0.3" id "org.jetbrains.kotlin.jvm" version "1.2.31" id "idea" } @@ -9,8 +9,8 @@ plugins { dependencies { implementation project(':ocs-api') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" - implementation 'com.google.cloud:google-cloud-pubsub:0.41.0-beta' - implementation 'com.google.cloud.dataflow:google-cloud-dataflow-java-sdk-all:2.3.0' + implementation 'com.google.cloud:google-cloud-pubsub:0.43.0-beta' + implementation 'com.google.cloud.dataflow:google-cloud-dataflow-java-sdk-all:2.4.0' runtime 'org.apache.beam:beam-runners-google-cloud-dataflow-java:2.4.0' implementation 'ch.qos.logback:logback-classic:1.2.3' testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion" diff --git a/auth-server/build.gradle b/auth-server/build.gradle index b9574c1ae..175b5006e 100644 --- a/auth-server/build.gradle +++ b/auth-server/build.gradle @@ -1,7 +1,7 @@ plugins { id "application" id "jacoco" - id "com.github.johnrengelman.shadow" version "2.0.2" + id "com.github.johnrengelman.shadow" version "2.0.3" id "org.jetbrains.kotlin.jvm" version "1.2.31" id "idea" } @@ -35,8 +35,8 @@ task pack(type: Zip, dependsOn: 'shadowJar') { from('Dockerfile') { into(project.name) } - archiveName 'auth-server.zip' - destinationDir(file('build/deploy/')) + archiveName = "auth-server.zip" + destinationDir = file('build/deploy/') } sourceSets { diff --git a/build.gradle b/build.gradle index 3359fc09d..c4cce2a00 100644 --- a/build.gradle +++ b/build.gradle @@ -13,9 +13,9 @@ allprojects { repositories { mavenLocal() mavenCentral() - maven { url "https://repository.jboss.org/nexus/content/repositories/releases/" } - maven { url "https://maven.repository.redhat.com/ga/" } - maven { url "http://clojars.org/repo/" } + maven { url = "https://repository.jboss.org/nexus/content/repositories/releases/" } + maven { url = "https://maven.repository.redhat.com/ga/" } + maven { url = "http://clojars.org/repo/" } } } @@ -27,7 +27,7 @@ subprojects { } ext { kotlinVersion = "1.2.31" - dropwizardVersion = "1.3.0" + dropwizardVersion = "1.3.1" } } @@ -38,6 +38,6 @@ task pack(type: Zip, dependsOn: [':ocsgw:pack', ':prime:pack', ':auth-server:pac from 'docker-compose.yaml' from 'docker-compose.prod.yaml' rename 'docker-compose.prod.yaml','docker-compose.override.yaml' - archiveName 'ostelco-core.zip' - destinationDir(file('build/deploy/')) + archiveName = 'ostelco-core.zip' + destinationDir = file('build/deploy/') } \ No newline at end of file diff --git a/diameter-stack/build.gradle b/diameter-stack/build.gradle index b10be7658..acc35ea29 100644 --- a/diameter-stack/build.gradle +++ b/diameter-stack/build.gradle @@ -64,10 +64,10 @@ uploadArchives.repositories.mavenDeployer { } pom.project { - name 'ostelco diameter stack' + name = 'ostelco diameter stack' packaging 'jar' // optionally artifactId can be defined here - description 'A library to communicate over the Diameter protocol.' + description = 'A library to communicate over the Diameter protocol.' url 'https://github.com/ostelco/ostelco-core/tree/develop/diameter-stack' scm { @@ -78,7 +78,7 @@ uploadArchives.repositories.mavenDeployer { licenses { license { - name 'The Apache License, Version 2.0' + name = 'The Apache License, Version 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } @@ -86,7 +86,7 @@ uploadArchives.repositories.mavenDeployer { developers { developer { id 'la3lma' - name 'Bjørn Remseth' + name = 'Bjørn Remseth' email 'la3lma@gmail.com' } } diff --git a/diameter-stack/src/main/kotlin/org/ostelco/diameter/CreditControlContext.kt b/diameter-stack/src/main/kotlin/org/ostelco/diameter/CreditControlContext.kt index 082fa145c..d3cb58441 100644 --- a/diameter-stack/src/main/kotlin/org/ostelco/diameter/CreditControlContext.kt +++ b/diameter-stack/src/main/kotlin/org/ostelco/diameter/CreditControlContext.kt @@ -13,14 +13,12 @@ import org.ostelco.diameter.util.DiameterUtilities class CreditControlContext( val sessionId: String, - val originalCreditControlRequest: JCreditControlRequest) { + val originalCreditControlRequest: JCreditControlRequest, + val originHost: String) { private val LOG by logger() - private var sent: Boolean = false - - val originHost:String = originalCreditControlRequest.originHost - val originRealm:String = originalCreditControlRequest.originRealm + val originRealm:String = originalCreditControlRequest.destinationRealm val creditControlRequest: CreditControlRequest = AvpParser().parse( CreditControlRequest::class, @@ -59,26 +57,20 @@ class CreditControlContext( // This is a bug in jDiameter due to which this unsigned32 field has to be set as Int and not Long. answerMSCC.addAvp(Avp.SERVICE_IDENTIFIER_CCA, mscc.serviceIdentifier.toInt(), true, false) } - if (mscc.granted.total < 1 && originalCreditControlRequest.requestTypeAVPValue != RequestType.TERMINATION_REQUEST) { - resultCode = CreditControlResultCode.DIAMETER_CREDIT_LIMIT_REACHED.value - } - val gsuAvp = answerMSCC.addGroupedAvp(Avp.GRANTED_SERVICE_UNIT, true, false) - gsuAvp.addAvp(Avp.CC_INPUT_OCTETS, 0L, true, false) - gsuAvp.addAvp(Avp.CC_OUTPUT_OCTETS, 0L, true, false) + if (originalCreditControlRequest.requestTypeAVPValue != RequestType.TERMINATION_REQUEST) { - if (originalCreditControlRequest.requestTypeAVPValue == RequestType.TERMINATION_REQUEST || mscc.granted.total < 1) { - LOG.info("Terminate") - // Since this is a terminate reply no service is granted - gsuAvp.addAvp(Avp.CC_TIME, 0, true, false) - gsuAvp.addAvp(Avp.CC_TOTAL_OCTETS, 0L, true, false) - gsuAvp.addAvp(Avp.CC_SERVICE_SPECIFIC_UNITS, 0L, true, false) + if (mscc.finalUnitIndication != null) { + addFinalUnitAction(answerMSCC, mscc) + } - addFinalUnitAction(answerMSCC, mscc) - } else { - gsuAvp.addAvp(Avp.CC_TOTAL_OCTETS, mscc.granted.total, true, false) + if (mscc.granted.total > -1) { + val gsuAvp = answerMSCC.addGroupedAvp(Avp.GRANTED_SERVICE_UNIT, true, false) + gsuAvp.addAvp(Avp.CC_INPUT_OCTETS, 0L, true, false) + gsuAvp.addAvp(Avp.CC_OUTPUT_OCTETS, 0L, true, false) + gsuAvp.addAvp(Avp.CC_TOTAL_OCTETS, mscc.granted.total, true, false) + } } - answerMSCC.addAvp(Avp.RESULT_CODE, resultCode, true, false) answerMSCC.addAvp(Avp.VALIDITY_TIME, mscc.validityTime, true, false) } diff --git a/diameter-stack/src/main/kotlin/org/ostelco/diameter/model/Model.kt b/diameter-stack/src/main/kotlin/org/ostelco/diameter/model/Model.kt index 653d9a545..e223e8695 100644 --- a/diameter-stack/src/main/kotlin/org/ostelco/diameter/model/Model.kt +++ b/diameter-stack/src/main/kotlin/org/ostelco/diameter/model/Model.kt @@ -75,6 +75,9 @@ class ServiceUnit() { @AvpField(Avp.CC_OUTPUT_OCTETS) var output: Long = 0 + @AvpField(Avp.REPORTING_REASON) + var reportingReason: ReportingReason? = null + constructor(total: Long, input: Long, output: Long) : this() { this.total = total this.input = input @@ -102,6 +105,9 @@ class MultipleServiceCreditControl() { @AvpField(Avp.GRANTED_SERVICE_UNIT) var granted = ServiceUnit() + @AvpField(Avp.REPORTING_REASON) + var reportingReason: ReportingReason? = null + var validityTime = 86400 // https://tools.ietf.org/html/rfc4006#section-8.34 @@ -125,6 +131,22 @@ enum class RedirectAddressType { SIP_URL } +/** + * http://www.3gpp.org/ftp/Specs/html-info/32299.htm + */ +enum class ReportingReason { + THRESHOLD, + QHT, + FINAL, + QUOTA_EXHAUSTED, + VALIDITY_TIME, + OTHER_QUOTA_TYPE, + RATING_CONDITION_CHANGE, + FORCED_REAUTHORISATION , + POOL_EXHAUSTED, + UNUSED_QUOTA_TIMER +} + /** * https://tools.ietf.org/html/rfc4006#section-8.37 */ diff --git a/diameter-stack/src/main/kotlin/org/ostelco/diameter/util/DiameterUtilities.kt b/diameter-stack/src/main/kotlin/org/ostelco/diameter/util/DiameterUtilities.kt index 84437c56a..837e68984 100644 --- a/diameter-stack/src/main/kotlin/org/ostelco/diameter/util/DiameterUtilities.kt +++ b/diameter-stack/src/main/kotlin/org/ostelco/diameter/util/DiameterUtilities.kt @@ -5,14 +5,22 @@ import org.jdiameter.api.AvpDataException import org.jdiameter.api.AvpSet import org.jdiameter.common.impl.validation.DictionaryImpl import org.ostelco.diameter.logger +import org.ostelco.diameter.util.AvpType.ADDRESS import org.ostelco.diameter.util.AvpType.APP_ID +import org.ostelco.diameter.util.AvpType.FLOAT32 import org.ostelco.diameter.util.AvpType.FLOAT64 import org.ostelco.diameter.util.AvpType.GROUPED +import org.ostelco.diameter.util.AvpType.IDENTITY import org.ostelco.diameter.util.AvpType.INTEGER32 import org.ostelco.diameter.util.AvpType.INTEGER64 +import org.ostelco.diameter.util.AvpType.OCTET_STRING +import org.ostelco.diameter.util.AvpType.RAW +import org.ostelco.diameter.util.AvpType.RAW_DATA import org.ostelco.diameter.util.AvpType.TIME import org.ostelco.diameter.util.AvpType.UNSIGNED32 import org.ostelco.diameter.util.AvpType.UNSIGNED64 +import org.ostelco.diameter.util.AvpType.URI +import org.ostelco.diameter.util.AvpType.UTF8STRING import org.ostelco.diameter.util.AvpType.VENDOR_ID class DiameterUtilities { @@ -48,33 +56,25 @@ class DiameterUtilities { } private fun getAvpValue(avp: Avp): Any { - var avpValue: Any - try { - val avpType = AvpDictionary.getType(avp) - - when (avpType) { - INTEGER32, APP_ID -> avpValue = avp.integer32 - UNSIGNED32, VENDOR_ID -> avpValue = avp.unsigned32 - FLOAT64 -> avpValue = avp.float64 - INTEGER64 -> avpValue = avp.integer64 - TIME -> avpValue = avp.time - UNSIGNED64 -> avpValue = avp.unsigned64 - GROUPED -> avpValue = "" - else -> avpValue = (avp.utF8String as String) - .replace("\r", "") - .replace("\n", "") - } - } catch (ignore: Exception) { - try { - avpValue = avp.utF8String - .replace("\r", "") - .replace("\n", "") - } catch (e: AvpDataException) { - avpValue = avp.toString() - } - + val avpType = AvpDictionary.getType(avp) + return when (avpType) { + ADDRESS -> avp.address + IDENTITY -> avp.diameterIdentity + URI -> avp.diameterURI + FLOAT32 -> avp.float32 + FLOAT64 -> avp.float64 + GROUPED -> "" + INTEGER32, APP_ID -> avp.integer32 + INTEGER64 -> avp.integer64 + OCTET_STRING -> String(avp.octetString) + RAW -> avp.raw + RAW_DATA -> avp.rawData + TIME -> avp.time + UNSIGNED32, VENDOR_ID -> avp.unsigned32 + UNSIGNED64 -> avp.unsigned64 + UTF8STRING -> avp.utF8String + null -> "" } - return avpValue } // TODO for missing Avp, is code and vendorId as 0 okay? diff --git a/diameter-test/build.gradle b/diameter-test/build.gradle index 317266289..b1c39fad3 100644 --- a/diameter-test/build.gradle +++ b/diameter-test/build.gradle @@ -52,10 +52,10 @@ uploadArchives.repositories.mavenDeployer { } pom.project { - name 'ostelco diameter test' + name = 'ostelco diameter test' packaging 'jar' // optionally artifactId can be defined here - description 'A library for testing over the Diameter protocol.' + description = 'A library for testing over the Diameter protocol.' url 'https://github.com/ostelco/ostelco-core/tree/develop/diameter-test' scm { @@ -66,7 +66,7 @@ uploadArchives.repositories.mavenDeployer { licenses { license { - name 'The Apache License, Version 2.0' + name = 'The Apache License, Version 2.0' url 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } @@ -74,7 +74,7 @@ uploadArchives.repositories.mavenDeployer { developers { developer { id 'la3lma' - name 'Bjørn Remseth' + name = 'Bjørn Remseth' email 'la3lma@gmail.com' } } diff --git a/diameter-test/src/main/kotlin/org/ostelco/diameter/test/TestClient.kt b/diameter-test/src/main/kotlin/org/ostelco/diameter/test/TestClient.kt index a84dadb3e..48e9810be 100644 --- a/diameter-test/src/main/kotlin/org/ostelco/diameter/test/TestClient.kt +++ b/diameter-test/src/main/kotlin/org/ostelco/diameter/test/TestClient.kt @@ -1,22 +1,6 @@ package org.ostelco.diameter.test -import org.jdiameter.api.Answer -import org.jdiameter.api.ApplicationId -import org.jdiameter.api.Avp -import org.jdiameter.api.AvpSet -import org.jdiameter.api.Configuration -import org.jdiameter.api.EventListener -import org.jdiameter.api.IllegalDiameterStateException -import org.jdiameter.api.InternalException -import org.jdiameter.api.Message -import org.jdiameter.api.Network -import org.jdiameter.api.NetworkReqListener -import org.jdiameter.api.OverloadException -import org.jdiameter.api.Request -import org.jdiameter.api.RouteException -import org.jdiameter.api.Session -import org.jdiameter.api.SessionFactory -import org.jdiameter.api.Stack +import org.jdiameter.api.* import org.jdiameter.common.impl.app.cca.JCreditControlRequestImpl import org.jdiameter.server.impl.StackImpl import org.jdiameter.server.impl.helpers.XMLConfiguration @@ -56,10 +40,6 @@ class TestClient : EventListener { // session factory private lateinit var factory: SessionFactory - // session used as handle for communication - var session: Session? = null - private set - // set if an answer to a Request has been received var isAnswerReceived = false private set @@ -110,7 +90,7 @@ class TestClient : EventListener { try { LOG.info("Starting stack") - stack.start() + stack.start(Mode.ANY_PEER, 30000, TimeUnit.MILLISECONDS) LOG.info("Stack is running.") createSession() } catch (e: Exception) { @@ -144,8 +124,8 @@ class TestClient : EventListener { * @param destinationRealm Destination Realm * @param destinationHost Destination Host */ - fun createRequest(destinationRealm : String, destinationHost : String): Request? { - return session?.createRequest( + fun createRequest(destinationRealm : String, destinationHost : String, session : Session): Request? { + return session.createRequest( commandCode, ApplicationId.createByAuthAppId(applicationID), destinationRealm, @@ -153,16 +133,22 @@ class TestClient : EventListener { ); } - private fun createSession() { + /** + * Create a new DIAMETER session + */ + fun createSession() : Session? { try { - //wait for connection to peer - Thread.sleep(5000) - this.session = this.factory.getNewSession("BadCustomSessionId;" + System.currentTimeMillis() + ";0") + // FixMe : Need better way to make sure the session can be created + if (!stack.isActive) { + LOG.warn("Stack not active") + } + return this.factory.getNewSession("BadCustomSessionId;" + System.currentTimeMillis() + ";0") } catch (e: InternalException) { LOG.error("Start Failed", e) } catch (e: InterruptedException) { LOG.error("Start Failed", e) } + return null } /** @@ -171,12 +157,12 @@ class TestClient : EventListener { * @param request Request to send * @return false if send failed */ - fun sendNextRequest(request: Request): Boolean { + fun sendNextRequest(request: Request, session: Session?): Boolean { isAnswerReceived = false if (session != null) { val ccr = JCreditControlRequestImpl(request) try { - this.session?.send(ccr.message, this) + session.send(ccr.message, this) dumpMessage(ccr.message, true) //dump info on console return true } catch (e: InternalException) { diff --git a/diameter-test/src/main/kotlin/org/ostelco/diameter/test/TestHelper.kt b/diameter-test/src/main/kotlin/org/ostelco/diameter/test/TestHelper.kt index c7a128c02..ddaeb83a2 100644 --- a/diameter-test/src/main/kotlin/org/ostelco/diameter/test/TestHelper.kt +++ b/diameter-test/src/main/kotlin/org/ostelco/diameter/test/TestHelper.kt @@ -4,6 +4,7 @@ import org.jdiameter.api.Avp import org.jdiameter.api.Avp.PS_INFORMATION import org.jdiameter.api.AvpSet import org.ostelco.diameter.builder.set +import org.ostelco.diameter.model.ReportingReason import org.ostelco.diameter.model.RequestType import org.ostelco.diameter.model.SubscriptionType @@ -41,7 +42,7 @@ object TestHelper { } } - private fun addBucketRequest(ccrAvps: AvpSet, ratingGroup: Int, serviceIdentifier: Int, bucketSize: Long) { + private fun addBucketRequest(ccrAvps: AvpSet, ratingGroup: Int, serviceIdentifier: Int, bucketSize: Long, usedBucketSize: Long = 0) { set(ccrAvps) { @@ -56,6 +57,31 @@ object TestHelper { avp(Avp.CC_INPUT_OCTETS, 0L, pFlag = true) avp(Avp.CC_OUTPUT_OCTETS, 0L, pFlag = true) } + + if (usedBucketSize > 0) { + group(Avp.USED_SERVICE_UNIT) { + avp(Avp.CC_TOTAL_OCTETS, usedBucketSize, pFlag = true) + avp(Avp.REPORTING_REASON, ReportingReason.QUOTA_EXHAUSTED.ordinal, VENDOR_ID_3GPP, mFlag = true, pFlag = true) + } + } + } + } + } + + private fun addFinalBucketRequest(ccrAvps: AvpSet, ratingGroup: Int, serviceIdentifier: Int) { + + set(ccrAvps) { + + avp(Avp.MULTIPLE_SERVICES_INDICATOR, 1, pFlag = true) + + group(Avp.MULTIPLE_SERVICES_CREDIT_CONTROL) { + group(Avp.USED_SERVICE_UNIT) { + avp(Avp.CC_TIME, 0, pFlag = true) + avp(Avp.CC_SERVICE_SPECIFIC_UNITS, 0L, pFlag = true) + } + avp(Avp.RATING_GROUP, ratingGroup, pFlag = true) + avp(Avp.SERVICE_IDENTIFIER_CCA, serviceIdentifier, pFlag = true) + avp(Avp.REPORTING_REASON, ReportingReason.FINAL, VENDOR_ID_3GPP, pFlag = true) } } } @@ -70,7 +96,7 @@ object TestHelper { avp(Avp.RATING_GROUP, ratingGroup, pFlag = true) avp(Avp.SERVICE_IDENTIFIER_CCA, serviceIdentifier, pFlag = true) - group(Avp.REQUESTED_SERVICE_UNIT) { + group(Avp.USED_SERVICE_UNIT) { avp(Avp.CC_TOTAL_OCTETS, bucketSize, pFlag = true) avp(Avp.CC_INPUT_OCTETS, 0L, pFlag = true) avp(Avp.CC_OUTPUT_OCTETS, 0L, pFlag = true) @@ -105,10 +131,18 @@ object TestHelper { } @JvmStatic - fun creatUpdateRequest(ccrAvps: AvpSet, msisdn: String, bucketSize: Long) { + fun createUpdateRequest(ccrAvps: AvpSet, msisdn: String, bucketSize: Long, usedBucketSize: Long) { buildBasicRequest(ccrAvps, RequestType.UPDATE_REQUEST, requestNumber = 1) addUser(ccrAvps, msisdn = msisdn, imsi = IMSI) - addBucketRequest(ccrAvps, ratingGroup = 10, serviceIdentifier = 1, bucketSize = bucketSize) + addBucketRequest(ccrAvps, ratingGroup = 10, serviceIdentifier = 1, bucketSize = bucketSize, usedBucketSize = usedBucketSize) + addServiceInformation(ccrAvps, apn = APN, sgsnMncMcc = SGSN_MCC_MNC) + } + + @JvmStatic + fun createUpdateRequestFinal(ccrAvps: AvpSet, msisdn: String) { + buildBasicRequest(ccrAvps, RequestType.UPDATE_REQUEST, requestNumber = 1) + addUser(ccrAvps, msisdn = msisdn, imsi = IMSI) + addFinalBucketRequest(ccrAvps, ratingGroup = 10, serviceIdentifier = 1) addServiceInformation(ccrAvps, apn = APN, sgsnMncMcc = SGSN_MCC_MNC) } diff --git a/docker-compose.override.yaml b/docker-compose.override.yaml index 0272086c0..ff85c1077 100644 --- a/docker-compose.override.yaml +++ b/docker-compose.override.yaml @@ -26,6 +26,10 @@ services: command: ["./wait_for_ocsgw.sh"] pseudonym-server: + container_name: pseudonym-server + build: pseudonym-server + ports: + - "8090:8080" environment: - DATASTORE_EMULATOR_HOST=localhost:9090 - DATASTORE_PROJECT_ID=pantel-2decb @@ -36,8 +40,6 @@ services: pubsub-emulator: container_name: pubsub-emulator image: knarz/pubsub-emulator -# image: google/cloud-sdk -# command: ["gcloud", "beta", "emulators", "pubsub", "start"] gpubsub-emulator: container_name: gpubsub-emulator diff --git a/docker-compose.yaml b/docker-compose.yaml index 39feff5f6..3f07eb8ce 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -25,12 +25,6 @@ services: ports: - "8080:8080" - pseudonym-server: - container_name: pseudonym-server - build: pseudonym-server - ports: - - "8090:8080" - networks: net: driver: bridge diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index ccd86b3e4..779386526 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -15,20 +15,20 @@ With unit testing: * Upload and unzip `ostelco-core.zip` file. - scp build/deploy/ostelco-core.zip loltel@10.6.101.1:ostelco-core/ - ssh -A loltel@10.6.101.1 - scp ostelco-core/ostelco-core.zip ubuntu@192.168.0.123:. - ssh ubuntu@192.168.0.123 - unzip ostelco-core.zip -d ostelco-core + scp build/deploy/ostelco-core.zip loltel@10.6.101.1:ostelco-core/ + ssh -A loltel@10.6.101.1 + scp ostelco-core/ostelco-core.zip ubuntu@192.168.0.123:. + ssh ubuntu@192.168.0.123 + unzip ostelco-core.zip -d ostelco-core * Run in docker - cd ostelco-core - sudo docker-compose up -d --build - - sudo docker-compose logs -f - - sudo docker logs -f prime - sudo docker logs -f ocsgw - sudo docker logs -f auth-server + cd ostelco-core + sudo docker-compose up -d --build + + sudo docker-compose logs -f + + sudo docker logs -f prime + sudo docker logs -f ocsgw + sudo docker logs -f auth-server diff --git a/docs/DEV.md b/docs/DEV.md index ea40212e4..399469867 100644 --- a/docs/DEV.md +++ b/docs/DEV.md @@ -1,5 +1,9 @@ # Developer guidelines +## Checking for dependency updates + + gradle dependencyUpdates -Drevision=release + ## Package / Namespace naming convention ### Format diff --git a/ext-pgw/build.gradle b/ext-pgw/build.gradle index cb8b74462..d9e2ee95a 100644 --- a/ext-pgw/build.gradle +++ b/ext-pgw/build.gradle @@ -1,6 +1,6 @@ plugins { id "application" - id "com.github.johnrengelman.shadow" version "2.0.2" + id "com.github.johnrengelman.shadow" version "2.0.3" } dependencies { diff --git a/ext-pgw/src/main/java/org/ostelco/ext_pgw/OcsIntegrationTest.java b/ext-pgw/src/main/java/org/ostelco/ext_pgw/OcsIntegrationTest.java index 22df9bf79..e9f8a536c 100644 --- a/ext-pgw/src/main/java/org/ostelco/ext_pgw/OcsIntegrationTest.java +++ b/ext-pgw/src/main/java/org/ostelco/ext_pgw/OcsIntegrationTest.java @@ -1,14 +1,10 @@ package org.ostelco.ext_pgw; import org.apache.log4j.Logger; -import org.jdiameter.api.Avp; -import org.jdiameter.api.AvpDataException; -import org.jdiameter.api.AvpSet; -import org.jdiameter.api.Request; +import org.jdiameter.api.*; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.ostelco.diameter.model.FinalUnitAction; import org.ostelco.diameter.model.RequestType; import org.ostelco.diameter.test.TestClient; import org.ostelco.diameter.test.TestHelper; @@ -49,16 +45,17 @@ public void tearDown() { client = null; } - private void simpleCreditControlRequestInit() { + private void simpleCreditControlRequestInit(Session session) { Request request = client.createRequest( DEST_REALM, - DEST_HOST + DEST_HOST, + session ); TestHelper.createInitRequest(request.getAvps(), MSISDN, BUCKET_SIZE); - client.sendNextRequest(request); + client.sendNextRequest(request, session); waitForAnswer(); @@ -66,6 +63,8 @@ private void simpleCreditControlRequestInit() { assertEquals(2001L, client.getResultCodeAvp().getInteger32()); AvpSet resultAvps = client.getResultAvps(); assertEquals(RequestType.INITIAL_REQUEST, resultAvps.getAvp(Avp.CC_REQUEST_TYPE).getInteger32()); + assertEquals(DEST_HOST, resultAvps.getAvp(Avp.ORIGIN_HOST).getUTF8String()); + assertEquals(DEST_REALM, resultAvps.getAvp(Avp.ORIGIN_REALM).getUTF8String()); Avp resultMSCC = resultAvps.getAvp(Avp.MULTIPLE_SERVICES_CREDIT_CONTROL); assertEquals(2001L, resultMSCC.getGrouped().getAvp(Avp.RESULT_CODE).getInteger32()); assertEquals(1, resultMSCC.getGrouped().getAvp(Avp.SERVICE_IDENTIFIER_CCA).getUnsigned32()); @@ -77,22 +76,25 @@ private void simpleCreditControlRequestInit() { } } - private void simpleCreditControlRequestUpdate() { + private void simpleCreditControlRequestUpdate(Session session) { Request request = client.createRequest( DEST_REALM, - DEST_HOST + DEST_HOST, + session ); - TestHelper.creatUpdateRequest(request.getAvps(), MSISDN, BUCKET_SIZE); + TestHelper.createUpdateRequest(request.getAvps(), MSISDN, BUCKET_SIZE, BUCKET_SIZE); - client.sendNextRequest(request); + client.sendNextRequest(request, session); waitForAnswer(); try { assertEquals(2001L, client.getResultCodeAvp().getInteger32()); AvpSet resultAvps = client.getResultAvps(); + assertEquals(DEST_HOST, resultAvps.getAvp(Avp.ORIGIN_HOST).getUTF8String()); + assertEquals(DEST_REALM, resultAvps.getAvp(Avp.ORIGIN_REALM).getUTF8String()); assertEquals(RequestType.UPDATE_REQUEST, resultAvps.getAvp(Avp.CC_REQUEST_TYPE).getInteger32()); Avp resultMSCC = resultAvps.getAvp(Avp.MULTIPLE_SERVICES_CREDIT_CONTROL); assertEquals(2001L, resultMSCC.getGrouped().getAvp(Avp.RESULT_CODE).getInteger32()); @@ -106,30 +108,34 @@ private void simpleCreditControlRequestUpdate() { @Test public void simpleCreditControlRequestInitUpdateAndTerminate() { - simpleCreditControlRequestInit(); - simpleCreditControlRequestUpdate(); + Session session = client.createSession(); + simpleCreditControlRequestInit(session); + simpleCreditControlRequestUpdate(session); Request request = client.createRequest( DEST_REALM, - DEST_HOST + DEST_HOST, + session ); TestHelper.createTerminateRequest(request.getAvps(), MSISDN, BUCKET_SIZE); - client.sendNextRequest(request); + client.sendNextRequest(request, session); waitForAnswer(); try { assertEquals(2001L, client.getResultCodeAvp().getInteger32()); AvpSet resultAvps = client.getResultAvps(); + assertEquals(DEST_HOST, resultAvps.getAvp(Avp.ORIGIN_HOST).getUTF8String()); + assertEquals(DEST_REALM, resultAvps.getAvp(Avp.ORIGIN_REALM).getUTF8String()); assertEquals(RequestType.TERMINATION_REQUEST, resultAvps.getAvp(Avp.CC_REQUEST_TYPE).getInteger32()); Avp resultMSCC = resultAvps.getAvp(Avp.MULTIPLE_SERVICES_CREDIT_CONTROL); assertEquals(2001L, resultMSCC.getGrouped().getAvp(Avp.RESULT_CODE).getInteger32()); - Avp granted = resultMSCC.getGrouped().getAvp(Avp.GRANTED_SERVICE_UNIT); - assertEquals(0L, granted.getGrouped().getAvp(Avp.CC_TOTAL_OCTETS).getUnsigned64()); - AvpSet finalUnitIndication = resultMSCC.getGrouped().getAvp(Avp.FINAL_UNIT_INDICATION).getGrouped(); - assertEquals(FinalUnitAction.TERMINATE.ordinal(), finalUnitIndication.getAvp(Avp.FINAL_UNIT_ACTION).getInteger32()); + assertEquals(1, resultMSCC.getGrouped().getAvp(Avp.SERVICE_IDENTIFIER_CCA).getUnsigned32()); + assertEquals(10, resultMSCC.getGrouped().getAvp(Avp.RATING_GROUP).getUnsigned32()); + Avp validTime = resultMSCC.getGrouped().getAvp(Avp.VALIDITY_TIME); + assertEquals(86400L, validTime.getUnsigned32()); } catch (AvpDataException e) { LOG.error("Failed to get Result-Code", e); } @@ -139,31 +145,65 @@ public void simpleCreditControlRequestInitUpdateAndTerminate() { @Test public void creditControlRequestInitNoCredit() { + Session session = client.createSession(); Request request = client.createRequest( DEST_REALM, - DEST_HOST + DEST_HOST, + session ); - TestHelper.createInitRequest(request.getAvps(), "4333333333", 500000L); + TestHelper.createInitRequest(request.getAvps(), "4333333333", BUCKET_SIZE); - client.sendNextRequest(request); + client.sendNextRequest(request, session); waitForAnswer(); try { assertEquals(2001L, client.getResultCodeAvp().getInteger32()); AvpSet resultAvps = client.getResultAvps(); + assertEquals(DEST_HOST, resultAvps.getAvp(Avp.ORIGIN_HOST).getUTF8String()); + assertEquals(DEST_REALM, resultAvps.getAvp(Avp.ORIGIN_REALM).getUTF8String()); assertEquals(RequestType.INITIAL_REQUEST, resultAvps.getAvp(Avp.CC_REQUEST_TYPE).getInteger32()); Avp resultMSCC = resultAvps.getAvp(Avp.MULTIPLE_SERVICES_CREDIT_CONTROL); - assertEquals(4012L, resultMSCC.getGrouped().getAvp(Avp.RESULT_CODE).getInteger32()); + assertEquals(2001L, resultMSCC.getGrouped().getAvp(Avp.RESULT_CODE).getInteger32()); assertEquals(1, resultMSCC.getGrouped().getAvp(Avp.SERVICE_IDENTIFIER_CCA).getInteger32()); Avp granted = resultMSCC.getGrouped().getAvp(Avp.GRANTED_SERVICE_UNIT); assertEquals(0L, granted.getGrouped().getAvp(Avp.CC_TOTAL_OCTETS).getUnsigned64()); } catch (AvpDataException e) { LOG.error("Failed to get Result-Code", e); } + + // There is 2 step in graceful shutdown. First OCS send terminate, then P-GW report used units in a final update + + Request updateRequest = client.createRequest( + DEST_REALM, + DEST_HOST, + session + ); + + TestHelper.createUpdateRequestFinal(updateRequest.getAvps(), "4333333333"); + + client.sendNextRequest(updateRequest, session); + + waitForAnswer(); + + try { + assertEquals(2001L, client.getResultCodeAvp().getInteger32()); + AvpSet resultAvps = client.getResultAvps(); + assertEquals(DEST_HOST, resultAvps.getAvp(Avp.ORIGIN_HOST).getUTF8String()); + assertEquals(DEST_REALM, resultAvps.getAvp(Avp.ORIGIN_REALM).getUTF8String()); + assertEquals(RequestType.UPDATE_REQUEST, resultAvps.getAvp(Avp.CC_REQUEST_TYPE).getInteger32()); + Avp resultMSCC = resultAvps.getAvp(Avp.MULTIPLE_SERVICES_CREDIT_CONTROL); + assertEquals(2001L, resultMSCC.getGrouped().getAvp(Avp.RESULT_CODE).getInteger32()); + assertEquals(1, resultMSCC.getGrouped().getAvp(Avp.SERVICE_IDENTIFIER_CCA).getInteger32()); + Avp validTime = resultMSCC.getGrouped().getAvp(Avp.VALIDITY_TIME); + assertEquals(86400L, validTime.getUnsigned32()); + } catch (AvpDataException e) { + LOG.error("Failed to get Result-Code", e); + } } + private void waitForAnswer() { int i = 0; while (!client.isAnswerReceived() && i<10) { diff --git a/ocs-api/build.gradle b/ocs-api/build.gradle index 795a02064..3ee993fab 100644 --- a/ocs-api/build.gradle +++ b/ocs-api/build.gradle @@ -5,9 +5,9 @@ plugins { } // FIXME -// Updating to 1.10.* causes ClassNotFound exception at runtime -// Keeping it version 1.9.0 to be consistent with grpc via PubSub client lib -ext.grpcVersion = "1.9.0" +// Updating to 1.11.* causes ClassNotFound exception at runtime +// Keeping it version 1.10.1 to be consistent with grpc via PubSub client lib +ext.grpcVersion = "1.10.1" dependencies { api "io.grpc:grpc-netty:$grpcVersion" diff --git a/ocs-api/src/main/proto/ocs.proto b/ocs-api/src/main/proto/ocs.proto index b7fb6bf8e..2340f4528 100644 --- a/ocs-api/src/main/proto/ocs.proto +++ b/ocs-api/src/main/proto/ocs.proto @@ -19,6 +19,7 @@ message ServiceUnit { uint64 totalOctets = 1; uint64 inputOctets = 2; uint64 outputOctetes = 3; + ReportingReason reportingReason = 4; } enum FinalUnitAction { @@ -42,6 +43,19 @@ enum CreditControlRequestType { EVENT_REQUEST = 4; } +enum ReportingReason { + THRESHOLD = 0; + QHT = 1; + FINAL = 2; + QUOTA_EXHAUSTED = 3; + VALIDITY_TIME = 4; + OTHER_QUOTA_TYPE = 5; + RATING_CONDITION_CHANGE = 6; + FORCED_REAUTHORISATION = 7; + POOL_EXHAUSTED = 8; + UNUSED_QUOTA_TIMER = 9; +} + message RedirectServer { RedirectAddressType redirectAddressType = 1; string redirectServerAddress = 2; @@ -52,6 +66,7 @@ message FinalUnitIndication { repeated string restrictionFilterRule = 2; repeated string filterId = 3; RedirectServer redirectServer = 4; + bool isSet = 5; // Since no optional valued in proto3 this is used to detect if element is set } message MultipleServiceCreditControl { @@ -62,6 +77,7 @@ message MultipleServiceCreditControl { ServiceUnit granted = 5; FinalUnitIndication finalUnitIndication = 6; uint32 validityTime = 7; + ReportingReason reportingReason = 8; } message PsInformation { diff --git a/ocsgw/build.gradle b/ocsgw/build.gradle index 439d102c3..b6e850ca5 100644 --- a/ocsgw/build.gradle +++ b/ocsgw/build.gradle @@ -12,7 +12,7 @@ buildscript { plugins { id "application" id "jacoco" - id "com.github.johnrengelman.shadow" version "2.0.2" + id "com.github.johnrengelman.shadow" version "2.0.3" } apply plugin: 'org.junit.platform.gradle.plugin' @@ -22,12 +22,12 @@ dependencies { implementation project(':diameter-stack') testImplementation project(':diameter-test') - testCompile 'org.junit.jupiter:junit-jupiter-api:5.1.0' - testCompile 'org.junit.platform:junit-platform-runner:1.1.0' - testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.1.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.1' + testImplementation 'org.junit.platform:junit-platform-runner:1.1.1' + testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.1.1' - testCompile 'junit:junit:4.12' - testRuntime 'org.junit.vintage:junit-vintage-engine:5.1.0' + testImplementation 'junit:junit:4.12' + testRuntime 'org.junit.vintage:junit-vintage-engine:5.1.1' } test { @@ -63,6 +63,6 @@ task pack(type: Zip, dependsOn: 'shadowJar') { from ('Dockerfile') { into(project.name) } - archiveName 'ocsgw.zip' - destinationDir(file('build/deploy/')) + archiveName = 'ocsgw.zip' + destinationDir = file('build/deploy/') } \ No newline at end of file diff --git a/ocsgw/src/main/java/org/ostelco/ocsgw/OcsServer.java b/ocsgw/src/main/java/org/ostelco/ocsgw/OcsServer.java index e9c07a48f..474136a3f 100644 --- a/ocsgw/src/main/java/org/ostelco/ocsgw/OcsServer.java +++ b/ocsgw/src/main/java/org/ostelco/ocsgw/OcsServer.java @@ -44,7 +44,7 @@ private OcsServer() { public synchronized void handleRequest(ServerCCASession session, JCreditControlRequest request) { - final CreditControlContext ccrContext = new CreditControlContext(session.getSessionId(), request); + final CreditControlContext ccrContext = new CreditControlContext(session.getSessionId(), request, stack.getMetaData().getLocalPeer().getUri().getFQDN()); source.handleRequest(ccrContext); } diff --git a/ocsgw/src/main/java/org/ostelco/ocsgw/data/grpc/GrpcDataSource.java b/ocsgw/src/main/java/org/ostelco/ocsgw/data/grpc/GrpcDataSource.java index 75204131c..762efd444 100644 --- a/ocsgw/src/main/java/org/ostelco/ocsgw/data/grpc/GrpcDataSource.java +++ b/ocsgw/src/main/java/org/ostelco/ocsgw/data/grpc/GrpcDataSource.java @@ -16,15 +16,7 @@ import org.ostelco.diameter.model.MultipleServiceCreditControl; import org.ostelco.diameter.model.RedirectAddressType; import org.ostelco.diameter.model.RedirectServer; -import org.ostelco.ocs.api.ActivateRequest; -import org.ostelco.ocs.api.ActivateResponse; -import org.ostelco.ocs.api.CreditControlAnswerInfo; -import org.ostelco.ocs.api.CreditControlRequestInfo; -import org.ostelco.ocs.api.CreditControlRequestType; -import org.ostelco.ocs.api.OcsServiceGrpc; -import org.ostelco.ocs.api.PsInformation; -import org.ostelco.ocs.api.ServiceInfo; -import org.ostelco.ocs.api.ServiceUnit; +import org.ostelco.ocs.api.*; import org.ostelco.ocsgw.OcsServer; import org.ostelco.ocsgw.data.DataSource; import org.slf4j.Logger; @@ -163,9 +155,7 @@ public void handleRequest(final CreditControlContext context) { org.ostelco.diameter.model.ServiceUnit requested = mscc.getRequested().get(0); - protoMscc - .setRequested( - ServiceUnit.newBuilder() + protoMscc.setRequested(ServiceUnit.newBuilder() .setInputOctets(0L) .setOutputOctetes(0L) .setTotalOctets(requested.getTotal()) @@ -175,16 +165,21 @@ public void handleRequest(final CreditControlContext context) { org.ostelco.diameter.model.ServiceUnit used = mscc.getUsed(); - builder.addMscc( - protoMscc - .setUsed( - ServiceUnit.newBuilder() - .setInputOctets(used.getInput()) - .setOutputOctetes(used.getOutput()) - .setTotalOctets(used.getTotal()) - .build()) - .setRatingGroup(mscc.getRatingGroup()) - .setServiceIdentifier(mscc.getServiceIdentifier())); + protoMscc.setUsed(ServiceUnit.newBuilder() + .setInputOctets(used.getInput()) + .setOutputOctetes(used.getOutput()) + .setTotalOctets(used.getTotal()) + .build()); + + protoMscc.setRatingGroup(mscc.getRatingGroup()); + protoMscc.setServiceIdentifier(mscc.getServiceIdentifier()); + + if (mscc.getReportingReason() != null) { + protoMscc.setReportingReasonValue(mscc.getReportingReason().ordinal()); + } else { + protoMscc.setReportingReasonValue(ReportingReason.UNRECOGNIZED.ordinal()); + } + builder.addMscc(protoMscc.build()); } builder.setRequestId(context.getSessionId()) @@ -276,6 +271,9 @@ private MultipleServiceCreditControl convertMSCC(org.ostelco.ocs.api.MultipleSer } private FinalUnitIndication convertFinalUnitIndication(org.ostelco.ocs.api.FinalUnitIndication fuiGrpc) { + if (!fuiGrpc.getIsSet()) { + return null; + } return new FinalUnitIndication( FinalUnitAction.values()[fuiGrpc.getFinalUnitAction().getNumber()], fuiGrpc.getRestrictionFilterRuleList(), diff --git a/ocsgw/src/test/java/org/ostelco/ocsgw/OcsApplicationTest.java b/ocsgw/src/test/java/org/ostelco/ocsgw/OcsApplicationTest.java index 93049b824..295055bd3 100644 --- a/ocsgw/src/test/java/org/ostelco/ocsgw/OcsApplicationTest.java +++ b/ocsgw/src/test/java/org/ostelco/ocsgw/OcsApplicationTest.java @@ -1,10 +1,7 @@ package org.ostelco.ocsgw; import org.apache.log4j.Logger; -import org.jdiameter.api.Avp; -import org.jdiameter.api.AvpDataException; -import org.jdiameter.api.AvpSet; -import org.jdiameter.api.Request; +import org.jdiameter.api.*; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -65,27 +62,29 @@ protected void tearDown() { client = null; } - private void simpleCreditControlRequestInit() { + private void simpleCreditControlRequestInit(Session session) { Request request = client.createRequest( OCS_REALM, - OCS_HOST + OCS_HOST, + session ); TestHelper.createInitRequest(request.getAvps(), MSISDN, 500000L); - client.sendNextRequest(request); + client.sendNextRequest(request, session); waitForAnswer(); try { assertEquals(2001L, client.getResultCodeAvp().getInteger32()); AvpSet resultAvps = client.getResultAvps(); + assertEquals(OCS_HOST, resultAvps.getAvp(Avp.ORIGIN_HOST).getUTF8String()); + assertEquals(OCS_REALM, resultAvps.getAvp(Avp.ORIGIN_REALM).getUTF8String()); assertEquals(RequestType.INITIAL_REQUEST, resultAvps.getAvp(Avp.CC_REQUEST_TYPE).getInteger32()); Avp resultMSCC = resultAvps.getAvp(Avp.MULTIPLE_SERVICES_CREDIT_CONTROL); assertEquals(2001L, resultMSCC.getGrouped().getAvp(Avp.RESULT_CODE).getInteger32()); assertEquals(1, resultMSCC.getGrouped().getAvp(Avp.SERVICE_IDENTIFIER_CCA).getUnsigned32()); - assertEquals(10, resultMSCC.getGrouped().getAvp(Avp.RATING_GROUP).getUnsigned32()); Avp granted = resultMSCC.getGrouped().getAvp(Avp.GRANTED_SERVICE_UNIT); assertEquals(500000L, granted.getGrouped().getAvp(Avp.CC_TOTAL_OCTETS).getUnsigned64()); @@ -94,16 +93,17 @@ private void simpleCreditControlRequestInit() { } } - private void simpleCreditControlRequestUpdate() { + private void simpleCreditControlRequestUpdate(Session session) { Request request = client.createRequest( OCS_REALM, - OCS_HOST + OCS_HOST, + session ); - TestHelper.creatUpdateRequest(request.getAvps(), MSISDN, 400000L); + TestHelper.createUpdateRequest(request.getAvps(), MSISDN, 400000L, 500000L); - client.sendNextRequest(request); + client.sendNextRequest(request, session); waitForAnswer(); @@ -124,17 +124,19 @@ private void simpleCreditControlRequestUpdate() { @Test @DisplayName("Simple Credit-Control-Request Init Update and Terminate") public void simpleCreditControlRequestInitUpdateAndTerminate() { - simpleCreditControlRequestInit(); - simpleCreditControlRequestUpdate(); + Session session = client.createSession(); + simpleCreditControlRequestInit(session); + simpleCreditControlRequestUpdate(session); Request request = client.createRequest( OCS_REALM, - OCS_HOST + OCS_HOST, + session ); TestHelper.createTerminateRequest(request.getAvps(), MSISDN, 700000L); - client.sendNextRequest(request); + client.sendNextRequest(request, session); waitForAnswer(); @@ -144,10 +146,6 @@ public void simpleCreditControlRequestInitUpdateAndTerminate() { assertEquals(RequestType.TERMINATION_REQUEST, resultAvps.getAvp(Avp.CC_REQUEST_TYPE).getInteger32()); Avp resultMSCC = resultAvps.getAvp(Avp.MULTIPLE_SERVICES_CREDIT_CONTROL); assertEquals(2001L, resultMSCC.getGrouped().getAvp(Avp.RESULT_CODE).getInteger32()); - Avp granted = resultMSCC.getGrouped().getAvp(Avp.GRANTED_SERVICE_UNIT); - assertEquals(0L, granted.getGrouped().getAvp(Avp.CC_TOTAL_OCTETS).getUnsigned64()); - AvpSet finalUnitIndication = resultMSCC.getGrouped().getAvp(Avp.FINAL_UNIT_INDICATION).getGrouped(); - assertEquals(FinalUnitAction.TERMINATE.ordinal(), finalUnitIndication.getAvp(Avp.FINAL_UNIT_ACTION).getInteger32()); } catch (AvpDataException e) { LOG.error("Failed to get Result-Code", e); } @@ -155,9 +153,12 @@ public void simpleCreditControlRequestInitUpdateAndTerminate() { @Test public void testReAuthRequest() { - simpleCreditControlRequestInit(); + Session session = client.createSession(); + simpleCreditControlRequestInit(session); + + client.initRequestTest(); - OcsServer.getInstance().sendReAuthRequest(new SessionContext(client.getSession().getSessionId(), PGW_HOST, PGW_REALM)); + OcsServer.getInstance().sendReAuthRequest(new SessionContext(session.getSessionId(), PGW_HOST, PGW_REALM)); waitForRequest(); try { AvpSet resultAvps = client.getResultAvps(); @@ -176,9 +177,11 @@ public void testReAuthRequest() { @DisplayName("Service-Information Credit-Control-Request Init") public void serviceInformationCreditControlRequestInit() throws UnsupportedEncodingException { + Session session = client.createSession(); Request request = client.createRequest( OCS_REALM, - OCS_HOST + OCS_HOST, + session ); AvpSet ccrAvps = request.getAvps(); @@ -214,7 +217,7 @@ public void serviceInformationCreditControlRequestInit() throws UnsupportedEncod String s = "8242f21078b542f2100103c703"; psInformation.addAvp(Avp.GPP_USER_LOCATION_INFO, DatatypeConverter.parseHexBinary(s), VENDOR_ID_3GPP, true, false); - client.sendNextRequest(request); + client.sendNextRequest(request, session); waitForAnswer(); diff --git a/ostelco-lib/build.gradle b/ostelco-lib/build.gradle index ba02b16cd..4c328eb5c 100644 --- a/ostelco-lib/build.gradle +++ b/ostelco-lib/build.gradle @@ -8,15 +8,15 @@ dependencies { implementation "io.dropwizard:dropwizard-core:$dropwizardVersion" implementation "io.dropwizard:dropwizard-auth:$dropwizardVersion" implementation 'com.google.firebase:firebase-admin:5.9.0' - implementation 'com.lmax:disruptor:3.4.1' + implementation 'com.lmax:disruptor:3.4.2' implementation 'com.google.guava:guava:24.1-jre' testImplementation "io.dropwizard:dropwizard-testing:$dropwizardVersion" - testImplementation "org.mockito:mockito-core:2.16.0" + testImplementation "org.mockito:mockito-core:2.18.0" testImplementation 'org.assertj:assertj-core:3.9.1' // https://mvnrepository.com/artifact/org.glassfish.jersey.test-framework.providers/jersey-test-framework-provider-grizzly2 - testCompile("org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:2.26") { + testCompile("org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:2.27") { // 2.26 (latest) exclude group: 'javax.servlet', module: 'javax.servlet-api' exclude group: 'junit', module: 'junit' diff --git a/prime/build.gradle b/prime/build.gradle index cae5823e4..a4d81968a 100644 --- a/prime/build.gradle +++ b/prime/build.gradle @@ -1,7 +1,7 @@ plugins { id "application" id "jacoco" - id "com.github.johnrengelman.shadow" version "2.0.2" + id "com.github.johnrengelman.shadow" version "2.0.3" id "org.jetbrains.kotlin.jvm" version "1.2.31" id "idea" } @@ -25,12 +25,12 @@ dependencies { implementation project(':ostelco-lib') implementation "io.dropwizard:dropwizard-core:$dropwizardVersion" implementation 'com.google.firebase:firebase-admin:5.9.0' - implementation 'com.google.cloud:google-cloud-pubsub:0.41.0-beta' - implementation 'com.lmax:disruptor:3.4.1' + implementation 'com.google.cloud:google-cloud-pubsub:0.43.0-beta' + implementation 'com.lmax:disruptor:3.4.2' implementation 'com.google.guava:guava:24.1-jre' testImplementation "io.dropwizard:dropwizard-testing:$dropwizardVersion" - testImplementation 'org.mockito:mockito-core:2.16.0' + testImplementation 'org.mockito:mockito-core:2.18.0' } configurations { @@ -64,8 +64,8 @@ task pack(type: Zip, dependsOn: 'shadowJar') { from('Dockerfile') { into(project.name) } - archiveName 'prime.zip' - destinationDir(file('build/deploy/')) + archiveName = 'prime.zip' + destinationDir = file('build/deploy/') } jacocoTestReport { diff --git a/prime/src/main/kotlin/org/ostelco/prime/disruptor/PrimeEvent.kt b/prime/src/main/kotlin/org/ostelco/prime/disruptor/PrimeEvent.kt index 377c74000..29f1d1c9a 100644 --- a/prime/src/main/kotlin/org/ostelco/prime/disruptor/PrimeEvent.kt +++ b/prime/src/main/kotlin/org/ostelco/prime/disruptor/PrimeEvent.kt @@ -1,5 +1,7 @@ package org.ostelco.prime.disruptor +import org.ostelco.ocs.api.ReportingReason + class PrimeEvent { /** @@ -58,6 +60,12 @@ class PrimeEvent { */ var ratingGroup: Long = 0 + /** + * Reporting-Reason + * // FixMe: This is the Reporting-Reason for the MSCC. The PrimeEvent might be to generic since there is also Reporting-Reason used on ServiceUnit level + */ + var reportingReason: ReportingReason = ReportingReason.UNRECOGNIZED + fun clear() { msisdn = null requestedBucketBytes = 0 @@ -69,6 +77,7 @@ class PrimeEvent { serviceIdentifier = 0 ratingGroup = 0 messageType = null + reportingReason = ReportingReason.UNRECOGNIZED } //FixMe : We need to think about roaming!!! @@ -81,6 +90,7 @@ class PrimeEvent { reservedBucketBytes: Long, serviceIdentifier: Long, ratingGroup: Long, + reportingReason: ReportingReason, ocsgwStreamId: String?, ocsgwRequestId: String?) { this.messageType = messageType @@ -90,6 +100,7 @@ class PrimeEvent { this.reservedBucketBytes = reservedBucketBytes this.serviceIdentifier = serviceIdentifier this.ratingGroup = ratingGroup + this.reportingReason = reportingReason this.ocsgwStreamId = ocsgwStreamId this.ocsgwRequestId = ocsgwRequestId } diff --git a/prime/src/main/kotlin/org/ostelco/prime/disruptor/PrimeEventProducer.kt b/prime/src/main/kotlin/org/ostelco/prime/disruptor/PrimeEventProducer.kt index a747f17bc..364ff36fa 100644 --- a/prime/src/main/kotlin/org/ostelco/prime/disruptor/PrimeEventProducer.kt +++ b/prime/src/main/kotlin/org/ostelco/prime/disruptor/PrimeEventProducer.kt @@ -3,6 +3,8 @@ package org.ostelco.prime.disruptor import com.google.common.base.Preconditions.checkNotNull import com.lmax.disruptor.RingBuffer import org.ostelco.ocs.api.CreditControlRequestInfo +import org.ostelco.ocs.api.MultipleServiceCreditControl +import org.ostelco.ocs.api.ReportingReason import org.ostelco.prime.disruptor.PrimeEventMessageType.CREDIT_CONTROL_REQUEST import org.ostelco.prime.disruptor.PrimeEventMessageType.RELEASE_RESERVED_BUCKET import org.ostelco.prime.disruptor.PrimeEventMessageType.TOPUP_DATA_BUNDLE_BALANCE @@ -47,6 +49,7 @@ class PrimeEventProducer(private val ringBuffer: RingBuffer) { reservedBytes: Long = 0, serviceId: Long = 0, ratingGroup: Long = 0, + reportingReason: ReportingReason = ReportingReason.UNRECOGNIZED, streamId: String? = null, requestId: String? = null) { @@ -59,6 +62,7 @@ class PrimeEventProducer(private val ringBuffer: RingBuffer) { reservedBytes, serviceId, ratingGroup, + reportingReason, streamId, requestId) }) @@ -90,18 +94,21 @@ class PrimeEventProducer(private val ringBuffer: RingBuffer) { streamId: String) { if (request.msccList.isEmpty()) { - LOG.error("Received empty list") - return + injectIntoRingbuffer(CREDIT_CONTROL_REQUEST, + request.msisdn, + streamId = streamId, + requestId = request.requestId) + } else { + injectIntoRingbuffer(CREDIT_CONTROL_REQUEST, + request.msisdn, + request.getMscc(0).requested.totalOctets, + request.getMscc(0).used.totalOctets, + 0, + request.getMscc(0).serviceIdentifier, + request.getMscc(0).ratingGroup, + request.getMscc(0).reportingReason, + streamId, + request.requestId) } - - injectIntoRingbuffer(CREDIT_CONTROL_REQUEST, - request.msisdn, - request.getMscc(0).requested.totalOctets, - request.getMscc(0).used.totalOctets, - 0, - request.getMscc(0).serviceIdentifier, - request.getMscc(0).ratingGroup, - streamId, - request.requestId) } } diff --git a/prime/src/main/kotlin/org/ostelco/prime/events/EventProcessor.kt b/prime/src/main/kotlin/org/ostelco/prime/events/EventProcessor.kt index 8ca906dfb..77a163275 100644 --- a/prime/src/main/kotlin/org/ostelco/prime/events/EventProcessor.kt +++ b/prime/src/main/kotlin/org/ostelco/prime/events/EventProcessor.kt @@ -38,7 +38,6 @@ class EventProcessor( } catch (ex: NotATopupProductException) { LOG.info("Ignoring non-topup purchase request " + pr) } - } @Throws(EventProcessorException::class, NotATopupProductException::class) @@ -157,9 +156,9 @@ class EventProcessor( override fun onPurchaseRequest(request: PurchaseRequest) { try { handlePurchaseRequest(request) - } catch (e: EventProcessorException) { - LOG.error("Could not handle purchase request " + request, e) - } + } catch (e: EventProcessorException) { + LOG.error("Could not handle purchase request " + request, e) + } } }) } diff --git a/prime/src/main/kotlin/org/ostelco/prime/firebase/FbDatabaseFacade.kt b/prime/src/main/kotlin/org/ostelco/prime/firebase/FbDatabaseFacade.kt index f9909d0eb..3b20d44e3 100644 --- a/prime/src/main/kotlin/org/ostelco/prime/firebase/FbDatabaseFacade.kt +++ b/prime/src/main/kotlin/org/ostelco/prime/firebase/FbDatabaseFacade.kt @@ -22,7 +22,7 @@ import java.util.function.BiFunction import java.util.function.Consumer /** - * Presenting a facade for the many and varied firebase databases + * Presenting a facade for the many and varied firebase databases * we're using. */ class FbDatabaseFacade internal constructor(firebaseDatabase: FirebaseDatabase) { diff --git a/prime/src/main/kotlin/org/ostelco/prime/ocs/EventHandlerImpl.kt b/prime/src/main/kotlin/org/ostelco/prime/ocs/EventHandlerImpl.kt index 2dc11abc2..0c4f4da0e 100644 --- a/prime/src/main/kotlin/org/ostelco/prime/ocs/EventHandlerImpl.kt +++ b/prime/src/main/kotlin/org/ostelco/prime/ocs/EventHandlerImpl.kt @@ -44,8 +44,15 @@ internal class EventHandlerImpl(private val ocsService: OcsService) : EventHandl } private fun logEventProcessing(msg: String, event: PrimeEvent) { - LOG.info("{} :: for MSISDN: {} of {} requested bytes {} used bytes with request id: {}", - msg, event.msisdn, event.requestedBucketBytes, event.usedBucketBytes, event.ocsgwRequestId) + LOG.info("{}", msg); + LOG.info("MSISDN: {}", event.msisdn); + LOG.info("requested bytes: {}", event.requestedBucketBytes); + LOG.info("reserved bytes: {}", event.reservedBucketBytes); + LOG.info("used bytes: {}", event.usedBucketBytes); + LOG.info("bundle bytes: {}", event.bundleBytes); + LOG.info("Reporting reason: {}", event.reportingReason); + LOG.info("request id: {} ",event.ocsgwRequestId); + } private fun handleCreditControlRequest(event: PrimeEvent) { @@ -54,26 +61,40 @@ internal class EventHandlerImpl(private val ocsService: OcsService) : EventHandl // FixMe : This assume we only have one MSCC // ToDo : In case of zero balance we should add appropriate FinalUnitAction - try { - val finalUnitIndication = FinalUnitIndication.newBuilder() - .setFinalUnitAction(FinalUnitAction.TERMINATE) - .build() - val mscc = MultipleServiceCreditControl.newBuilder() - .setGranted(ServiceUnit.newBuilder() - .setTotalOctets(event.reservedBucketBytes) - .build()) - .setServiceIdentifier(event.serviceIdentifier) - .setRatingGroup(event.ratingGroup) - .setValidityTime(86400) - .setFinalUnitIndication(finalUnitIndication) - .build() + try { val creditControlAnswer = CreditControlAnswerInfo.newBuilder() .setMsisdn(event.msisdn) - .addMscc(mscc) .setRequestId(event.ocsgwRequestId) - .build() - ocsService.sendCreditControlAnswer(event.ocsgwStreamId ?: "", creditControlAnswer) + + // This is a hack to know when we have received an MSCC in the request or not. + // For Terminate request we might not have any MSCC and therefore no serviceIdentifier. + if (event.serviceIdentifier > 0) { + val msccBulder = MultipleServiceCreditControl.newBuilder() + msccBulder.setServiceIdentifier(event.serviceIdentifier) + .setRatingGroup(event.ratingGroup) + .setValidityTime(86400) + + if ((event.reportingReason != ReportingReason.FINAL) && (event.requestedBucketBytes > 0)) { + msccBulder.setGranted(ServiceUnit.newBuilder() + .setTotalOctets(event.reservedBucketBytes) + .build()) + if (event.reservedBucketBytes < event.requestedBucketBytes) { + msccBulder.setFinalUnitIndication(FinalUnitIndication.newBuilder() + .setFinalUnitAction(FinalUnitAction.TERMINATE) + .setIsSet(true) + .build()) + } + } else { + // Use -1 to indicate no granted service unit should be included in the answer + msccBulder.setGranted(ServiceUnit.newBuilder() + .setTotalOctets(-1) + .build()) + } + creditControlAnswer.addMscc(msccBulder.build()) + } + + ocsService.sendCreditControlAnswer(event.ocsgwStreamId ?: "", creditControlAnswer.build()) } catch (e: Exception) { LOG.warn("Exception handling prime event", e) logEventProcessing("Exception sending Credit-Control-Answer", event) diff --git a/prime/src/main/kotlin/org/ostelco/prime/ocs/OcsState.kt b/prime/src/main/kotlin/org/ostelco/prime/ocs/OcsState.kt index f5996c03e..2920f9ae0 100644 --- a/prime/src/main/kotlin/org/ostelco/prime/ocs/OcsState.kt +++ b/prime/src/main/kotlin/org/ostelco/prime/ocs/OcsState.kt @@ -2,6 +2,7 @@ package org.ostelco.prime.ocs import com.google.common.base.Preconditions import com.lmax.disruptor.EventHandler +import org.ostelco.ocs.api.ReportingReason import org.ostelco.prime.disruptor.PrimeEvent import org.ostelco.prime.disruptor.PrimeEventMessageType import org.ostelco.prime.logger @@ -28,6 +29,7 @@ class OcsState : EventHandler { when (event.messageType) { PrimeEventMessageType.CREDIT_CONTROL_REQUEST -> { consumeDataBytes(msisdn, event.usedBucketBytes) + // ToDo : Trigger push notification on low balance event.reservedBucketBytes = reserveDataBytes( msisdn, event.requestedBucketBytes) @@ -40,7 +42,6 @@ class OcsState : EventHandler { } catch (e: Exception) { LOG.warn("Exception handling prime event in OcsState", e) } - } /** @@ -132,7 +133,13 @@ class OcsState : EventHandler { */ val consumed = usedBytes - reserved - val newTotal = existing - consumed + var newTotal = existing - consumed + + // P-GW is allowed to overconsume a small amount. + if (newTotal < 0) { + newTotal = 0; + } + dataPackMap[msisdn] = newTotal return newTotal } @@ -149,6 +156,10 @@ class OcsState : EventHandler { Preconditions.checkArgument(bytes > -1, "Non-positive value for bytes") + if (bytes == 0L) { + return 0 + } + if (!dataPackMap.containsKey(msisdn)) { LOG.warn("Trying to reserve bucket for unknown msisdn {}", msisdn) return 0 diff --git a/prime/src/test/kotlin/org/ostelco/prime/ocs/OcsStateTest.kt b/prime/src/test/kotlin/org/ostelco/prime/ocs/OcsStateTest.kt index 14d86c16f..4528ba2bd 100644 --- a/prime/src/test/kotlin/org/ostelco/prime/ocs/OcsStateTest.kt +++ b/prime/src/test/kotlin/org/ostelco/prime/ocs/OcsStateTest.kt @@ -56,6 +56,11 @@ class OcsStateTest { assertEquals(REMAINING_BYTES.toLong(), ocsState.reserveDataBytes(MSISDN, SECOND_NUMBER_OF_BYTES_TO_REQUEST.toLong())) + // Now consume a bit more then resumed (P-GW is allowed to overconsume small amount) + // Balance should now be 0 + assertEquals(0, + ocsState.consumeDataBytes(MSISDN, SECOND_NUMBER_OF_BYTES_TO_REQUEST.toLong() + 45)) + //... so at this point even reserving a single byte will fail. assertEquals(0, ocsState.reserveDataBytes(MSISDN, 1)) } diff --git a/pseudonym-server/build.gradle b/pseudonym-server/build.gradle index 36ffe1913..ebc983859 100644 --- a/pseudonym-server/build.gradle +++ b/pseudonym-server/build.gradle @@ -1,8 +1,8 @@ plugins { id "application" id "jacoco" - id "com.github.johnrengelman.shadow" version "2.0.1" - id "org.jetbrains.kotlin.jvm" version "1.2.30" + id "com.github.johnrengelman.shadow" version "2.0.3" + id "org.jetbrains.kotlin.jvm" version "1.2.31" id "idea" } @@ -12,10 +12,10 @@ dependencies { implementation "io.dropwizard:dropwizard-client:$dropwizardVersion" implementation project(':ocs-api') implementation 'com.google.guava:guava:24.1-jre' - compile group: 'io.grpc', name: 'grpc-netty-shaded', version: '1.11.0' - implementation 'com.google.cloud:google-cloud-datastore:1.22.0' - implementation 'com.google.cloud:google-cloud-pubsub:0.42.0-beta' - compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.9.4.1' + implementation 'io.grpc:grpc-netty-shaded:1.11.0' + implementation 'com.google.cloud:google-cloud-datastore:1.25.0' + implementation 'com.google.cloud:google-cloud-pubsub:0.43.0-beta' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.5' testImplementation "io.dropwizard:dropwizard-testing:$dropwizardVersion" testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion" testRuntime 'org.hamcrest:hamcrest-all:1.3' @@ -28,23 +28,6 @@ shadowJar { version = null } -task pack(type: Zip, dependsOn: 'shadowJar') { - from('config/') { - into(project.name + '/config') - } - from('script/') { - into(project.name + '/script') - } - from('build/libs/pseudonym-server-uber.jar') { - into(project.name + '/build/libs/') - } - from('Dockerfile') { - into(project.name) - } - archiveName 'pseudonym-server.zip' - destinationDir(file('build/deploy/')) -} - sourceSets { integrationTest { kotlin { diff --git a/settings.gradle b/settings.gradle index d597f0b6b..4e9faf2e3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,8 +10,6 @@ include ':ocsgw' include ':ostelco-lib' include ':prime' include ':pseudonym-server' -include ':pseudonymiser' -include ':rule-engine' project(':analytics').projectDir = "$rootDir/analytics" as File project(':auth-server').projectDir = "$rootDir/auth-server" as File @@ -23,4 +21,3 @@ project(':ocsgw').projectDir = "$rootDir/ocsgw" as File project(':ostelco-lib').projectDir = "$rootDir/ostelco-lib" as File project(':prime').projectDir = "$rootDir/prime" as File project(':pseudonym-server').projectDir = "$rootDir/pseudonym-server" as File -project(':rule-engine').projectDir = "$rootDir/rule-engine" as File