OCN Tutorial v1.1 incl. a billing service

Issue #3 resolved
Christopher Burgahn created an issue

The OCN tutorial must be brought on a next level to also showcase the power of the OCN Service Interface. The idea is that a mock billing service is created and used by the mock CPO and mock eMSP. Following set-up is proposed:

Comments (17)

  1. John Henderson

    @Christopher Burgahn Could I confirm the permissions configuration for this scenario with you? Permission 7 is to forward all OCPI requests of the cdrs module which are sent from an OCPI Party. But the “Sender” OCPI Interface for the CDRs module only defines a “GET” method. The method to send a new CDR is the “POST” method on the “Receiver” CDRs OCPI Interface. Is meaning of “SENDER” in “FORWARD_MODULE_CDRS_SENDER” different from the meaning “Sender” in the context of the Sender Inteface in the OCPI CDRs module?

    @Adam Staveley @Arzon Barua Does the distinction above make sense to you? The way that the service interface request forwarding is setup in the ocn-node, it seems to use the Module and InterfaceRole when deciding if a request should be forwarded, see the forwardOcpiRequestToLinkedServices in AsyncTaskService below.

    In the case of the CDRs for example, the interfaceRole is RECEIVER (as per OCPI) when sending a CDR (see CdrsController below). I have found that I need to use permission “8” to get a request to this PostMapping to be forwarded. Is this what you would expect?

    Snippet from AsyncTaskService: (https://bitbucket.org/shareandcharge/ocn-node/src/63fc9fb74362bd3bc0a90db4245ae1977c033a1d/src/main/kotlin/snc/openchargingnetwork/node/services/AsyncTaskService.kt)

    /**
    * Finds all services, linked to a sender, with permissions that grant them access to a given request type.
    * Once services have been found, sends via provided request handler.
    */
    @Async
    fun forwardOcpiRequestToLinkedServices(requestHandler: OcpiRequestHandler<*>, fromLocalPlatform: Boolean = true) {
        // we only want to forward to services if the module is one of the default OCPI modules,
        // and only if the sender is a local platform (to avoid repeat forwarding on the recipient node)
        // and also forward if service interface option is enabled
        val isDefaultModule = requestHandler.request.module != ModuleID.CUSTOM
    
        if (isDefaultModule && fromLocalPlatform && properties.serviceInterfaceEnabled) {
            val request = requestHandler.request
            registryService.getAgreementsByInterface(request.headers.sender, request.module, request.interfaceRole)
                    .forEach {
                        try {
                            requestHandler.forwardAgain(it.provider)
                        } catch (e: Exception) {
                            // fire and forget
                            logger.warn("Error forwarding request to service ${it.provider}: ${e.message}")
                        }
                    }
        }
    }
    

    Snippet from CdrsController: (https://bitbucket.org/shareandcharge/ocn-node/src/63fc9fb74362bd3bc0a90db4245ae1977c033a1d/src/main/kotlin/snc/openchargingnetwork/node/controllers/ocpi/v2_2/CdrsController.kt)

    @PostMapping("/ocpi/receiver/2.2/cdrs")
    fun postClientOwnedCdr(@RequestHeader("authorization") authorization: String,
                            @RequestHeader("OCN-Signature") signature: String? = null,
                            @RequestHeader("X-Request-ID") requestID: String,
                            @RequestHeader("X-Correlation-ID") correlationID: String,
                            @RequestHeader("OCPI-from-country-code") fromCountryCode: String,
                            @RequestHeader("OCPI-from-party-id") fromPartyID: String,
                            @RequestHeader("OCPI-to-country-code") toCountryCode: String,
                            @RequestHeader("OCPI-to-party-id") toPartyID: String,
                            @RequestBody body: CDR): ResponseEntity<OcpiResponse<Unit>> {
    
        val sender = BasicRole(fromPartyID, fromCountryCode)
        val receiver = BasicRole(toPartyID, toCountryCode)
    
        val requestVariables = OcpiRequestVariables(
                module = ModuleID.CDRS,
                interfaceRole = InterfaceRole.RECEIVER,
                method = HttpMethod.POST,
                headers = OcnHeaders(authorization, signature, requestID, correlationID, sender, receiver),
                body = body)
    
        return requestHandlerBuilder
                .build<Unit>(requestVariables)
                .forwardDefault()
                .getResponseWithLocationHeader("/ocpi/receiver/2.2/cdrs")
    }
    

  2. Christopher Burgahn reporter

    @John Henderson Thanks for this. I agree with you that we should follow the OCPI description of interfaces to not confuse people. The description of the permissions is currently slightly confusing. But @Adam Staveley

    @Adam Staveley

    or @Arzon Barua should confirm this.
    Following this argumentation I’d say that there is a mistake in the description of this use-case.
    This is what would change in the use-case:

    • Billing Service defines Permission 8 as required

    I’m wondering whether we should change the description of the permissions slightly to make clear that we follow the interface descriptions of OCPI e.g. in this case:

    Permission 8 FORWARD_MODULE_CDRS_RECEIVER OCN Node forwards all OCPI requests of the cdrs module which are sent to the receiver interface of an OCPI Party
    

  3. Arzon Barua

    @John Henderson “ it seems to use the Module and InterfaceRole when deciding if a request should be forwarded, see the forwardOcpiRequestToLinkedServices“ Yes you are right, we are checking the module ID and interface role to check the permission. You can also find the all permission checking in the following url
    https://bitbucket.org/shareandcharge/ocn-node/src/develop/src/main/kotlin/snc/openchargingnetwork/node/models/Ocn.kt

    enum class OcnServicePermission(val matches: (request: BasicRequestType) -> Boolean) {
        FORWARD_ALL({true}),
        FORWARD_ALL_SENDER({it.interfaceRole == InterfaceRole.SENDER}),
        FORWARD_ALL_RECEIVER({it.interfaceRole == InterfaceRole.RECEIVER}),
        FORWARD_MODULE_LOCATIONS_SENDER({it.moduleID == ModuleID.LOCATIONS && it.interfaceRole == InterfaceRole.SENDER}),
        FORWARD_MODULE_LOCATIONS_RECEIVER({it.moduleID == ModuleID.LOCATIONS && it.interfaceRole == InterfaceRole.RECEIVER}),
        FORWARD_MODULE_SESSIONS_SENDER({it.moduleID == ModuleID.SESSIONS && it.interfaceRole == InterfaceRole.SENDER}),
        FORWARD_MODULE_SESSIONS_RECEIVER({it.moduleID == ModuleID.SESSIONS && it.interfaceRole == InterfaceRole.RECEIVER }),
        FORWARD_MODULE_CDRS_SENDER({it.moduleID == ModuleID.CDRS && it.interfaceRole == InterfaceRole.SENDER}),
        FORWARD_MODULE_CDRS_RECEIVER({it.moduleID == ModuleID.CDRS && it.interfaceRole == InterfaceRole.RECEIVER}),
        FORWARD_MODULE_TARIFFS_SENDER({it.moduleID == ModuleID.TARIFFS && it.interfaceRole == InterfaceRole.SENDER}),
        FORWARD_MODULE_TARIFFS_RECEIVER({it.moduleID == ModuleID.TARIFFS && it.interfaceRole == InterfaceRole.RECEIVER}),
        FORWARD_MODULE_TOKENS_SENDER({it.moduleID == ModuleID.TOKENS && it.interfaceRole == InterfaceRole.SENDER}),
        FORWARD_MODULE_TOKENS_RECEIVER({it.moduleID == ModuleID.TOKENS && it.interfaceRole == InterfaceRole.RECEIVER}),
        FORWARD_MODULE_COMMANDS_SENDER({it.moduleID == ModuleID.COMMANDS && it.interfaceRole == InterfaceRole.SENDER}),
        FORWARD_MODULE_COMMANDS_RECEIVER({it.moduleID == ModuleID.COMMANDS && it.interfaceRole == InterfaceRole.RECEIVER}),
        FORWARD_MODULE_CHARGINGPROFILES_SENDER({it.moduleID == ModuleID.CHARGING_PROFILES && it.interfaceRole == InterfaceRole.SENDER}),
        FORWARD_MODULE_CHARGINGPROFILES_RECEIVER({it.moduleID == ModuleID.CHARGING_PROFILES && it.interfaceRole == InterfaceRole.RECEIVER});
    
    
        companion object {
            fun getByIndex(index: BigInteger): OcnServicePermission? {
                return try {
                    values()[index.intValueExact()]
                } catch (e: ArrayIndexOutOfBoundsException) {
                    null
                }
            }
        }
    }
    

  4. John Henderson

    Thanks for the confirmation @Arzon Barua . I will proceed with the implementation of this demo scenario using the FORWARD_MODULE_CDRS_RECEIVER permission. @Christopher Burgahn I agree that we should change the permission descriptions as you described:

    Permission 8 FORWARD_MODULE_CDRS_RECEIVER OCN Node forwards all OCPI requests of the cdrs module which are sent to the receiver interface of an OCPI Party

  5. John Henderson

    @Christopher Burgahn Sorry, after thinking about it a bit more, I think a wording like this might be more accurate:

    Permission 8 FORWARD_MODULE_CDRS_RECEIVER OCN Node forwards all OCPI requests sent by a party to the receiver interface of the cdrs module

    This is because the OCPI party which needs to accept the permissions is the party which is sending the request and so I think the sending party should be the one that is mentioned in the description.

  6. Christopher Burgahn reporter

    Sounds good. I’ll create an issue on the ocn-registry repository for this change.

  7. John Henderson

    Continuing with work on this using the ocn-node:bug/custom-module-post branch for the ocn-node.

    @Christopher Burgahn Question for you when you have a moment: When the BillingService receives the forwarded CDR, how does it know the countryCode/partyID of the MSP to bill?

    I am considering putting the MSP information in the “remark” field of the CDR, does that seem appropriate to you?

  8. Christopher Burgahn reporter

    @John Henderson good question. Remark field is a good option, also the CdrToken class contains some properties that are helpful with this, e.g. uid of the token, that could be used to identify the MSP to which the invoice is sent to. We can just use any of those options, as a billing service would have to decide by themselves how to do it.

  9. John Henderson

    @Christopher Burgahn Would you consider the billing service to have the “SCSP” OCPI role?

  10. Log in to comment