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

Add Delete REST API to user_plane_information (upNodes, links). #69

Merged
merged 6 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 18 additions & 13 deletions internal/context/upf.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package context

import (
"context"
"errors"
"fmt"
"math"
Expand Down Expand Up @@ -69,9 +70,13 @@ type UPF struct {
UPIPInfo pfcpType.UserPlaneIPResourceInformation
UPFStatus UPFStatus
RecoveryTimeStamp time.Time
SNssaiInfos []SnssaiUPFInfo
N3Interfaces []UPFInterfaceInfo
N9Interfaces []UPFInterfaceInfo

Ctx context.Context
CancelFunc context.CancelFunc

SNssaiInfos []SnssaiUPFInfo
N3Interfaces []UPFInterfaceInfo
N9Interfaces []UPFInterfaceInfo

pdrPool sync.Map
farPool sync.Map
Expand Down Expand Up @@ -126,7 +131,7 @@ func NewUPFInterfaceInfo(i *factory.InterfaceUpfInfoItem) *UPFInterfaceInfo {
return interfaceInfo
}

//*** add unit test ***//
// *** add unit test ***//
// IP returns the IP of the user plane IP information of the pduSessType
func (i *UPFInterfaceInfo) IP(pduSessType uint8) (net.IP, error) {
if (pduSessType == nasMessage.PDUSessionTypeIPv4 ||
Expand Down Expand Up @@ -197,7 +202,7 @@ func NewUPTunnel() (tunnel *UPTunnel) {
return
}

//*** add unit test ***//
// *** add unit test ***//
func (upTunnel *UPTunnel) AddDataPath(dataPath *DataPath) {
pathID, err := upTunnel.PathIDGenerator.Allocate()
if err != nil {
Expand All @@ -208,7 +213,7 @@ func (upTunnel *UPTunnel) AddDataPath(dataPath *DataPath) {
upTunnel.DataPathPool[pathID] = dataPath
}

//*** add unit test ***//
// *** add unit test ***//
// NewUPF returns a new UPF context in SMF
func NewUPF(nodeID *pfcpType.NodeID, ifaces []factory.InterfaceUpfInfoItem) (upf *UPF) {
upf = new(UPF)
Expand Down Expand Up @@ -243,7 +248,7 @@ func NewUPF(nodeID *pfcpType.NodeID, ifaces []factory.InterfaceUpfInfoItem) (upf
return upf
}

//*** add unit test ***//
// *** add unit test ***//
// GetInterface return the UPFInterfaceInfo that match input cond
func (upf *UPF) GetInterface(interfaceType models.UpInterfaceType, dnn string) *UPFInterfaceInfo {
switch interfaceType {
Expand Down Expand Up @@ -286,7 +291,7 @@ func (upf *UPF) PFCPAddr() *net.UDPAddr {
}
}

//*** add unit test ***//
// *** add unit test ***//
func RetrieveUPFNodeByNodeID(nodeID pfcpType.NodeID) *UPF {
var targetUPF *UPF = nil
upfPool.Range(func(key, value interface{}) bool {
Expand All @@ -310,7 +315,7 @@ func RetrieveUPFNodeByNodeID(nodeID pfcpType.NodeID) *UPF {
return targetUPF
}

//*** add unit test ***//
// *** add unit test ***//
func RemoveUPFNodeByNodeID(nodeID pfcpType.NodeID) bool {
upfID := ""
upfPool.Range(func(key, value interface{}) bool {
Expand Down Expand Up @@ -498,7 +503,7 @@ func (upf *UPF) AddQER() (*QER, error) {
return qer, nil
}

//*** add unit test ***//
// *** add unit test ***//
func (upf *UPF) RemovePDR(pdr *PDR) (err error) {
if upf.UPFStatus != AssociatedSetUpSuccess {
err = fmt.Errorf("this upf not associate with smf")
Expand All @@ -510,7 +515,7 @@ func (upf *UPF) RemovePDR(pdr *PDR) (err error) {
return nil
}

//*** add unit test ***//
// *** add unit test ***//
func (upf *UPF) RemoveFAR(far *FAR) (err error) {
if upf.UPFStatus != AssociatedSetUpSuccess {
err = fmt.Errorf("this upf not associate with smf")
Expand All @@ -522,7 +527,7 @@ func (upf *UPF) RemoveFAR(far *FAR) (err error) {
return nil
}

//*** add unit test ***//
// *** add unit test ***//
func (upf *UPF) RemoveBAR(bar *BAR) (err error) {
if upf.UPFStatus != AssociatedSetUpSuccess {
err = fmt.Errorf("this upf not associate with smf")
Expand All @@ -534,7 +539,7 @@ func (upf *UPF) RemoveBAR(bar *BAR) (err error) {
return nil
}

//*** add unit test ***//
// *** add unit test ***//
func (upf *UPF) RemoveQER(qer *QER) (err error) {
if upf.UPFStatus != AssociatedSetUpSuccess {
err = fmt.Errorf("this upf not associate with smf")
Expand Down
83 changes: 69 additions & 14 deletions internal/context/user_plane_information.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net"
"reflect"
"sort"
"sync"

"github.com/free5gc/openapi/models"
"github.com/free5gc/pfcp/pfcpType"
Expand All @@ -19,6 +20,7 @@ import (

// UserPlaneInformation store userplane topology
type UserPlaneInformation struct {
Mu sync.RWMutex // protect UPF and topology structure
UPNodes map[string]*UPNode
UPFs map[string]*UPNode
AccessNetwork map[string]*UPNode
Expand Down Expand Up @@ -160,7 +162,7 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan
logger.InitLog.Warningf("One of link edges does not exist. UPLink [%s] <=> [%s] not establish\n", link.A, link.B)
continue
}
if nodeInLink(nodeB, nodeA.Links) || nodeInLink(nodeA, nodeB.Links) {
if nodeInLink(nodeB, nodeA.Links) != -1 || nodeInLink(nodeA, nodeB.Links) != -1 {
logger.InitLog.Warningf("One of link edges already exist. UPLink [%s] <=> [%s] not establish\n", link.A, link.B)
continue
}
Expand Down Expand Up @@ -304,7 +306,7 @@ func (upi *UserPlaneInformation) LinksToConfiguration() []factory.UPLink {
func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.UserPlaneInformation) {
for name, node := range upTopology.UPNodes {
if _, ok := upi.UPNodes[name]; ok {
logger.InitLog.Warningf("Node [%s] already exists in SMF. Ignoring request.\n", name)
logger.InitLog.Warningf("Node [%s] already exists in SMF.\n", name)
continue
}
upNode := new(UPNode)
Expand Down Expand Up @@ -371,6 +373,13 @@ func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.Us
}
upNode.UPF.SNssaiInfos = snssaiInfos
upi.UPFs[name] = upNode

// AllocateUPFID
upfid := upNode.UPF.UUID()
upfip := upNode.NodeID.ResolveNodeIdToIp().String()
upi.UPFsID[name] = upfid
upi.UPFsIPtoID[upfip] = upfid

case UPNODE_AN:
upNode.ANIP = net.ParseIP(node.ANIP)
upi.AccessNetwork[name] = upNode
Expand All @@ -382,12 +391,6 @@ func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.Us

ipStr := upNode.NodeID.ResolveNodeIdToIp().String()
upi.UPFIPToName[ipStr] = name

// AllocateUPFID
upfid := upNode.UPF.UUID()
upfip := upNode.NodeID.ResolveNodeIdToIp().String()
upi.UPFsID[name] = upfid
upi.UPFsIPtoID[upfip] = upfid
}

// overlap UE IP pool validation
Expand All @@ -412,7 +415,7 @@ func (upi *UserPlaneInformation) LinksFromConfiguration(upTopology *factory.User
logger.InitLog.Warningf("One of link edges does not exist. UPLink [%s] <=> [%s] not establish\n", link.A, link.B)
continue
}
if nodeInLink(nodeB, nodeA.Links) || nodeInLink(nodeA, nodeB.Links) {
if nodeInLink(nodeB, nodeA.Links) != -1 || nodeInLink(nodeA, nodeB.Links) != -1 {
logger.InitLog.Warningf("One of link edges already exist. UPLink [%s] <=> [%s] not establish\n", link.A, link.B)
continue
}
Expand All @@ -421,6 +424,43 @@ func (upi *UserPlaneInformation) LinksFromConfiguration(upTopology *factory.User
}
}

func (upi *UserPlaneInformation) UpNodeDelete(upNodeName string) {
upNode, ok := upi.UPNodes[upNodeName]
if ok {
logger.InitLog.Infof("UPNode [%s] found. Deleting it.\n", upNodeName)
if upNode.Type == UPNODE_UPF {
logger.InitLog.Tracef("Delete UPF [%s] from its NodeID.\n", upNodeName)
RemoveUPFNodeByNodeID(upNode.UPF.NodeID)
if _, ok = upi.UPFs[upNodeName]; ok {
logger.InitLog.Tracef("Delete UPF [%s] from upi.UPFs.\n", upNodeName)
delete(upi.UPFs, upNodeName)
}
for selectionStr, destMap := range upi.DefaultUserPlanePathToUPF {
for destIp, path := range destMap {
if nodeInPath(upNode, path) != -1 {
logger.InitLog.Infof("Invalidate cache entry: DefaultUserPlanePathToUPF[%s][%s].\n", selectionStr, destIp)
delete(upi.DefaultUserPlanePathToUPF[selectionStr], destIp)
}
}
}
}
if upNode.Type == UPNODE_AN {
logger.InitLog.Tracef("Delete AN [%s] from upi.AccessNetwork.\n", upNodeName)
delete(upi.AccessNetwork, upNodeName)
}
logger.InitLog.Tracef("Delete UPNode [%s] from upi.UPNodes.\n", upNodeName)
delete(upi.UPNodes, upNodeName)

// update links
for name, n := range upi.UPNodes {
if index := nodeInLink(upNode, n.Links); index != -1 {
logger.InitLog.Infof("Delete UPLink [%s] <=> [%s].\n", name, upNodeName)
n.Links = removeNodeFromLink(n.Links, index)
}
}
}
}

func NewUEIPPool(factoryPool *factory.UEIPPool) *UeIPPool {
_, ipNet, err := net.ParseCIDR(factoryPool.Cidr)
if err != nil {
Expand Down Expand Up @@ -476,13 +516,27 @@ func isOverlap(pools []*UeIPPool) bool {
return false
}

func nodeInLink(upNode *UPNode, links []*UPNode) bool {
for _, n := range links {
func nodeInPath(upNode *UPNode, path []*UPNode) int {
for i, u := range path {
if u == upNode {
return i
}
}
return -1
}

func removeNodeFromLink(links []*UPNode, index int) []*UPNode {
links[index] = links[len(links)-1]
return links[:len(links)-1]
}

func nodeInLink(upNode *UPNode, links []*UPNode) int {
for i, n := range links {
if n == upNode {
return true
return i
}
}
return false
return -1
}

func (upi *UserPlaneInformation) GetUPFNameByIp(ip string) string {
Expand Down Expand Up @@ -525,8 +579,9 @@ func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNNAndUPF(

if upi.DefaultUserPlanePathToUPF[selection.String()] != nil {
path, pathExist := upi.DefaultUserPlanePathToUPF[selection.String()][nodeID]
logger.CtxLog.Traceln("In GetDefaultUserPlanePathByDNN")
logger.CtxLog.Traceln("In GetDefaultUserPlanePathByDNNAndUPF")
logger.CtxLog.Traceln("selection: ", selection.String())
logger.CtxLog.Traceln("pathExist: ", pathExist)
if pathExist {
return path
}
Expand Down
18 changes: 13 additions & 5 deletions internal/sbi/producer/pdu_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ func HandlePDUSessionSMContextCreate(request models.PostSmContextsRequest) *http
smContext.SMLock.Lock()
defer smContext.SMLock.Unlock()

upi := smf_context.GetUserPlaneInformation()
upi.Mu.RLock()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read lock is being used for session creation because it seems that multiple session creations are allowed in respect to user_plane_information consistency. Same comment for HandlePDUSessionSMContextUpdate.

defer upi.Mu.RUnlock()

// DNN Information from config
smContext.DNNInfo = smf_context.RetrieveDnnInformation(createData.SNssai, createData.Dnn)
if smContext.DNNInfo == nil {
Expand Down Expand Up @@ -92,11 +96,11 @@ func HandlePDUSessionSMContextCreate(request models.PostSmContextsRequest) *http
defaultPathPool := smf_context.GetUEDefaultPathPool(groupName)
if defaultPathPool != nil {
selectedUPFName, ip = defaultPathPool.SelectUPFAndAllocUEIPForULCL(
smf_context.GetUserPlaneInformation(), upfSelectionParams)
selectedUPF = smf_context.GetUserPlaneInformation().UPFs[selectedUPFName]
upi, upfSelectionParams)
selectedUPF = upi.UPFs[selectedUPFName]
}
} else {
selectedUPF, ip = smf_context.GetUserPlaneInformation().SelectUPFAndAllocUEIP(upfSelectionParams)
selectedUPF, ip = upi.SelectUPFAndAllocUEIP(upfSelectionParams)
smContext.PDUAddress = ip
logger.PduSessLog.Infof("UE[%s] PDUSessionID[%d] IP[%s]",
smContext.Supi, smContext.PDUSessionID, smContext.PDUAddress.String())
Expand Down Expand Up @@ -194,7 +198,7 @@ func HandlePDUSessionSMContextCreate(request models.PostSmContextsRequest) *http
// UE has no pre-config path.
// Use default route
logger.PduSessLog.Infof("SUPI[%s] has no pre-config route", createData.Supi)
defaultUPPath := smf_context.GetUserPlaneInformation().GetDefaultUserPlanePathByDNNAndUPF(
defaultUPPath := upi.GetDefaultUserPlanePathByDNNAndUPF(
upfSelectionParams, smContext.SelectedUPF)
defaultPath = smf_context.GenerateDataPath(defaultUPPath, smContext)
if defaultPath != nil {
Expand Down Expand Up @@ -251,6 +255,10 @@ func HandlePDUSessionSMContextUpdate(smContextRef string, body models.UpdateSmCo
logger.PduSessLog.Infoln("In HandlePDUSessionSMContextUpdate")
smContext := smf_context.GetSMContextByRef(smContextRef)

upi := smf_context.GetUserPlaneInformation()
upi.Mu.RLock()
defer upi.Mu.RUnlock()

if smContext == nil {
logger.PduSessLog.Warnf("SMContext[%s] is not found", smContextRef)

Expand Down Expand Up @@ -318,7 +326,7 @@ func HandlePDUSessionSMContextUpdate(smContextRef string, body models.UpdateSmCo
if smContext.SelectedUPF != nil && smContext.PDUAddress != nil {
logger.PduSessLog.Infof("UE[%s] PDUSessionID[%d] Release IP[%s]",
smContext.Supi, smContext.PDUSessionID, smContext.PDUAddress.String())
smf_context.GetUserPlaneInformation().ReleaseUEIP(smContext.SelectedUPF, smContext.PDUAddress)
upi.ReleaseUEIP(smContext.SelectedUPF, smContext.PDUAddress)
smContext.PDUAddress = nil
// keep SelectedUPF until PDU Session Release is completed
}
Expand Down
32 changes: 32 additions & 0 deletions internal/sbi/upi/api_upi.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package upi

import (
"context"
"net/http"

"github.com/gin-gonic/gin"
Expand All @@ -13,6 +14,9 @@ import (

func GetUpNodesLinks(c *gin.Context) {
upi := smf_context.SMF_Self().UserPlaneInformation
upi.Mu.RLock()
defer upi.Mu.RUnlock()

nodes := upi.UpNodesToConfiguration()
links := upi.LinksToConfiguration()

Expand All @@ -31,6 +35,9 @@ func GetUpNodesLinks(c *gin.Context) {

func PostUpNodesLinks(c *gin.Context) {
upi := smf_context.SMF_Self().UserPlaneInformation
upi.Mu.Lock()
defer upi.Mu.Unlock()

var json factory.UserPlaneInformation
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
Expand All @@ -43,8 +50,33 @@ func PostUpNodesLinks(c *gin.Context) {
for _, upf := range upi.UPFs {
// only associate new ones
if upf.UPF.UPFStatus == smf_context.NotAssociated {
upf.UPF.Ctx, upf.UPF.CancelFunc = context.WithCancel(context.Background())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Save context and cancel function on the UPF data model so that it can be used to cancel heartbeat thread of this UPF.

go association.ToBeAssociatedWithUPF(smf_context.SMF_Self().Ctx, upf.UPF)
}
}
c.JSON(http.StatusOK, gin.H{"status": "OK"})
}

func DeleteUpNodeLink(c *gin.Context) {
// current version does not allow node deletions when ulcl is enabled
if smf_context.SMF_Self().ULCLSupport {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to support ULCL case under a different PR.

c.JSON(http.StatusForbidden, gin.H{})
} else {
req := httpwrapper.NewRequest(c.Request, nil)
req.Params["upNodeRef"] = c.Params.ByName("upNodeRef")
upNodeRef := req.Params["upNodeRef"]
upi := smf_context.SMF_Self().UserPlaneInformation
upi.Mu.Lock()
defer upi.Mu.Unlock()
if upNode, ok := upi.UPNodes[upNodeRef]; ok {
if upNode.Type == smf_context.UPNODE_UPF {
go association.ReleaseAllResourcesOfUPF(upNode.UPF)
}
upi.UpNodeDelete(upNodeRef)
upNode.UPF.CancelFunc()
c.JSON(http.StatusOK, gin.H{"status": "OK"})
} else {
c.JSON(http.StatusNotFound, gin.H{})
}
}
}
Loading