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

Charging #58

Merged
merged 64 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
dc7467a
add charging rules & billing domain
May 23, 2023
d88968d
setup billing server by config
May 24, 2023
47ed389
fix: waiting group usage for billing server
May 24, 2023
9d24820
update pdu charging data filter
May 26, 2023
2f5d3c5
frontend: add UE CHARGING RECORD
brianchennn Aug 24, 2023
6220309
finish SubscriberCreate & SubscriberUpdate
brianchennn Aug 24, 2023
9371f29
finish Charging Config in SubscriberRead.tsx
brianchennn Aug 25, 2023
3fa2119
fix: DELETE and PUT policyData.ues.chargingData when editing Subscrib…
brianchennn Aug 29, 2023
b02ef05
fix: CheckAuth in GetChargingRecord()
brianchennn Aug 29, 2023
b55c025
fix golangci-linter & reduce frontend vulnerabilities
brianchennn Sep 16, 2023
9b65817
remove useless code & log in api_webui.go
brianchennn Sep 16, 2023
7cfa7b2
config/TLS -> cert
brianchennn Sep 16, 2023
bd5b43c
fix webuicfg.yaml
brianchennn Sep 17, 2023
444e45b
Enable to add multiple flow rules in 1 DNN
brianchennn Sep 19, 2023
321b0e3
remove SubscriberUpdate.tsx and integrate it into SubscriberCreate.tsx
brianchennn Sep 21, 2023
6b1c472
change all qfi to QosRef
brianchennn Sep 21, 2023
b2d7db9
fix React Component's id naming style
brianchennn Sep 23, 2023
c82145d
When Offline Charging, disable Quota input box
brianchennn Sep 23, 2023
d2c7ae9
update UI
brianchennn Oct 13, 2023
b1f6665
change default IP Filter
brianchennn Oct 13, 2023
f374c59
update UI
brianchennn Oct 13, 2023
6ad79fd
fix: when offline, quota != 0
brianchennn Oct 13, 2023
17322ef
refactor
brianchennn Oct 16, 2023
b0b97f5
implement per S-NSSAI charging config
brianchennn Oct 21, 2023
23989c7
use frontend/build instead of ./public
brianchennn Oct 22, 2023
9a6f84d
add per flow UE Charging Record
brianchennn Oct 24, 2023
0865ea5
fix: 2 flow rules use same charging config
brianchennn Oct 24, 2023
6f8c05a
add expand icon
brianchennn Oct 24, 2023
f33730b
update readme.md
brianchennn Oct 25, 2023
27fbd6e
add PerFlowTableView as a React Component
brianchennn Oct 25, 2023
ce21f2c
fix: undefined object use map()
brianchennn Oct 25, 2023
e2d5287
fix: the conflict of replicate IP Filter
brianchennn Oct 27, 2023
fab8e32
when delete a subscriber, remove it's CDR file
brianchennn Nov 7, 2023
93e273e
fix: per slice charging config disappered when edit subscriber data
brianchennn Nov 7, 2023
2b0f2b6
fix: dnn, filter should not omit empty
brianchennn Nov 24, 2023
cd429a1
update util's hash
brianchennn Nov 27, 2023
21b2146
fix: per slice Charging Config display error
brianchennn Nov 30, 2023
97ac8d0
fix: backend pdu level quota wasn't sent to frontend
brianchennn Nov 30, 2023
c01fc4d
fix: when no Charging record && sort charging record by Filter
brianchennn Dec 5, 2023
7ca763f
fix: duplicated SMF Information in REALTIME STATUS
brianchennn Dec 7, 2023
d645d83
remove cdr file by path
brianchennn Dec 8, 2023
6859b16
add snssai, dnn to FlowInfo & RatingGroupDataUsage
brianchennn Dec 11, 2023
015395c
redesign backend: GetChargingRecord()
brianchennn Dec 12, 2023
df61287
frontend: implemented multi snssai expand table for UE Charging Recor…
brianchennn Dec 12, 2023
d8ad204
remove redundant log
brianchennn Dec 12, 2023
4a53edb
sort flow level charging record
brianchennn Dec 12, 2023
f902261
Bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1 (#42)
dependabot[bot] Oct 1, 2023
d76b466
Query to NRF for NFProfile instead of getting from mongodb. (#63)
kishiguro Dec 1, 2023
19c459c
Fix: MSISDN -> GPSI (#65)
brianchennn Dec 1, 2023
039fe92
Bump @adobe/css-tools from 4.3.1 to 4.3.2 in /frontend (#67)
dependabot[bot] Dec 1, 2023
fec279e
Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#68)
dependabot[bot] Dec 19, 2023
7ce1d43
Fix: rebase and fix linter error
andy89923 Jan 3, 2024
0da14c9
add singleNssais sd:112233
brianchennn Jan 11, 2024
ae16eb0
Refactor: refactor WebUI and fix bugs
andy89923 Jan 25, 2024
da3493a
Feature: support offline charging
andy89923 Jan 31, 2024
0da6fe5
Fix: remove unused code
andy89923 Jan 31, 2024
861fc92
UI Modify and fix bugs
andy89923 Feb 21, 2024
cb84045
Fix conflicts
andy89923 Feb 21, 2024
1d9c708
Change default values and enhance UX
andy89923 Feb 26, 2024
9fa4a55
PDU Level to silce level
andy89923 Feb 27, 2024
b494491
Remove comment code and add error handling
andy89923 Mar 1, 2024
a843507
Add postman collection
andy89923 Mar 1, 2024
de26e54
Frontend code improvement and cleaner
andy89923 Mar 3, 2024
33c8ac7
Merge branch 'main' into charging
tim-ywliu Mar 3, 2024
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
260 changes: 260 additions & 0 deletions backend/WebUI/api_charging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package WebUI

import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"reflect"
"strconv"

"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"

"github.com/free5gc/chf/cdr/asn"
"github.com/free5gc/chf/cdr/cdrFile"
"github.com/free5gc/chf/cdr/cdrType"
"github.com/free5gc/openapi/models"
"github.com/free5gc/util/mongoapi"
"github.com/free5gc/webconsole/backend/logger"
"github.com/free5gc/webconsole/backend/webui_context"
)

// Get vol from CDR
// TS 32.297: Charging Data Record (CDR) file format and transfer
func parseCDR(supi string) (map[int64]RatingGroupDataUsage, error) {
logger.BillingLog.Traceln("parseCDR")
fileName := "/tmp/webconsole/" + supi + ".cdr"
if _, err := os.Stat(fileName); err != nil {
return nil, err
}

newCdrFile := cdrFile.CDRFile{}

newCdrFile.Decoding(fileName)
dataUsage := make(map[int64]RatingGroupDataUsage)

for _, cdr := range newCdrFile.CdrList {
recvByte := cdr.CdrByte
val := reflect.New(reflect.TypeOf(&cdrType.ChargingRecord{}).Elem()).Interface()
err := asn.UnmarshalWithParams(recvByte, val, "")
if err != nil {
logger.BillingLog.Errorf("parseCDR error when unmarshal with params: %+v", err)
continue
}

chargingRecord := *(val.(*cdrType.ChargingRecord))

for _, multipleUnitUsage := range chargingRecord.ListOfMultipleUnitUsage {
rg := multipleUnitUsage.RatingGroup.Value
du := dataUsage[rg]

du.Snssai = fmt.Sprintf("%02d", chargingRecord.PDUSessionChargingInformation.NetworkSliceInstanceID.SST.Value) +
string(chargingRecord.PDUSessionChargingInformation.NetworkSliceInstanceID.SD.Value)

du.Dnn = string(chargingRecord.PDUSessionChargingInformation.DataNetworkNameIdentifier.Value)

for _, usedUnitContainer := range multipleUnitUsage.UsedUnitContainers {
du.TotalVol += usedUnitContainer.DataTotalVolume.Value
du.UlVol += usedUnitContainer.DataVolumeUplink.Value
du.DlVol += usedUnitContainer.DataVolumeDownlink.Value
}

dataUsage[rg] = du
}
}

return dataUsage, nil
}

func GetChargingData(c *gin.Context) {
logger.BillingLog.Info("Get Charging Data")
setCorsHeader(c)

if !CheckAuth(c) {
c.JSON(http.StatusUnauthorized, gin.H{"cause": "Illegal Token"})
return
}

chargingMethod, exist := c.Params.Get("chargingMethod")
if !exist {
c.JSON(http.StatusBadRequest, gin.H{"cause": "chargingMethod not provided"})
andy89923 marked this conversation as resolved.
Show resolved Hide resolved
return
}
logger.BillingLog.Traceln(chargingMethod)

if chargingMethod != "Offline" && chargingMethod != "Online" {
c.JSON(http.StatusBadRequest, gin.H{"cause": "not support chargingMethod" + chargingMethod})
andy89923 marked this conversation as resolved.
Show resolved Hide resolved
return
}

filter := bson.M{"chargingMethod": chargingMethod}
chargingDataInterface, err := mongoapi.RestfulAPIGetMany(chargingDataColl, filter)
if err != nil {
logger.BillingLog.Errorf("mongoapi error: %+v", err)
}

chargingDataBsonA := toBsonA(chargingDataInterface)

c.JSON(http.StatusOK, chargingDataBsonA)
}

func GetChargingRecord(c *gin.Context) {
logger.BillingLog.Info("Get Charging Record")
setCorsHeader(c)

if !CheckAuth(c) {
c.JSON(http.StatusUnauthorized, gin.H{"cause": "Illegal Token"})
return
}

webuiSelf := webui_context.GetSelf()
webuiSelf.UpdateNfProfiles()

// Get supi of UEs
var uesJsonData interface{}
if amfUris := webuiSelf.GetOamUris(models.NfType_AMF); amfUris != nil {
requestUri := fmt.Sprintf("%s/namf-oam/v1/registered-ue-context", amfUris[0])

res, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, requestUri, nil)
if err != nil {
logger.ProcLog.Error(err)
c.JSON(http.StatusInternalServerError, gin.H{})
return
}
resp, res_err := httpsClient.Do(res)
if res_err != nil {
logger.ProcLog.Error(err)
c.JSON(http.StatusInternalServerError, gin.H{})
return
}

defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
logger.ProcLog.Error(closeErr)
}
}()

err = json.NewDecoder(resp.Body).Decode(&uesJsonData)
if err != nil {
logger.BillingLog.Error(err)
}
}

// build charging records
uesBsonA := toBsonA(uesJsonData)
chargingRecordsBsonA := make([]interface{}, 0, len(uesBsonA))

type OfflineSliceTypeMap struct {
supi string
snssai string
dnn string
unitcost int64
flowTotalVolum int64
flowTotalUsage int64
}
// Use for sum all the flow-based charging, and add to the slice at the end.
offlineChargingSliceTypeMap := make(map[string]OfflineSliceTypeMap)

for _, ueData := range uesBsonA {
ueBsonM := toBsonM(ueData)

supi := ueBsonM["Supi"].(string)

ratingGroupDataUsages, err := parseCDR(supi)
if err != nil {
logger.BillingLog.Warnln(err)
continue
}

for rg, du := range ratingGroupDataUsages {
filter := bson.M{"ratingGroup": rg}
chargingDataInterface, err := mongoapi.RestfulAPIGetOne(chargingDataColl, filter)
if err != nil {
logger.ProcLog.Errorf("PostSubscriberByID err: %+v", err)
}
if len(chargingDataInterface) == 0 {
logger.BillingLog.Warningf("ratingGroup: %d not found in mongoapi, may change the rg id", rg)
continue
}

var chargingData ChargingData
err = json.Unmarshal(mapToByte(chargingDataInterface), &chargingData)
if err != nil {
logger.BillingLog.Error(err)
}
logger.BillingLog.Debugf("add ratingGroup: %d, supi: %s, method: %s", rg, supi, chargingData.ChargingMethod)

switch chargingData.ChargingMethod {
case "Offline":
unitcost, err := strconv.ParseInt(chargingData.UnitCost, 10, 64)
if err != nil {
logger.BillingLog.Error("Offline unitCost strconv: ", err.Error())
unitcost = 1
}

key := chargingData.UeId + chargingData.Snssai
pdu_level, exist := offlineChargingSliceTypeMap[key]
if !exist {
pdu_level = OfflineSliceTypeMap{}
}
if chargingData.Filter != "" {
// Flow-based charging
du.Usage = du.TotalVol * unitcost
pdu_level.flowTotalUsage += du.Usage
pdu_level.flowTotalVolum += du.TotalVol
} else {
// Slice-level charging
pdu_level.snssai = chargingData.Snssai
pdu_level.dnn = chargingData.Dnn
pdu_level.supi = chargingData.UeId
pdu_level.unitcost = unitcost
}
offlineChargingSliceTypeMap[key] = pdu_level
case "Online":
tmpInt, err1 := strconv.Atoi(chargingData.Quota)
if err1 != nil {
logger.BillingLog.Error("Quota strconv: ", err1, rg, du, chargingData)
}
du.QuotaLeft = int64(tmpInt)
}
du.Snssai = chargingData.Snssai
du.Dnn = chargingData.Dnn
du.Supi = supi
du.Filter = chargingData.Filter

ratingGroupDataUsages[rg] = du
chargingRecordsBsonA = append(chargingRecordsBsonA, toBsonM(du))
}
}
for idx, record := range chargingRecordsBsonA {
tmp, err := json.Marshal(record)
if err != nil {
logger.BillingLog.Errorln("Marshal chargingRecordsBsonA error:", err.Error())
continue
}

var rd RatingGroupDataUsage

err = json.Unmarshal(tmp, &rd)
if err != nil {
logger.BillingLog.Errorln("Unmarshall RatingGroupDataUsage error:", err.Error())
continue
}

if rd.Filter != "" {
// Skip the Flow-based charging
continue
}

key := rd.Supi + rd.Snssai
if val, exist := offlineChargingSliceTypeMap[key]; exist {
rd.Usage += val.flowTotalUsage
rd.Usage += (rd.TotalVol - val.flowTotalVolum) * val.unitcost
chargingRecordsBsonA[idx] = toBsonM(rd)
}
}

c.JSON(http.StatusOK, chargingRecordsBsonA)
}
Loading
Loading