Free5GC源码研究(3) - 单个NF的软件架构(下)

前文以一个example-nf为例研究了free5gc中单个NF的软件架构,然而example-nf毕竟不是真正的NF,有一些真正的NF的细节没能在其中展现,比如最重要的:各个NF之间如何交互。本文以AUSF@v1.2.3,一个真正的NF为例,继续深入研究单个NF软件架构的更多细节。

首先我们对比一下ausfexample-nf在目录结构上的不同:

$ tree nf-example $ tree NFs/ausf/
├── cmd ├── cmd
│   └── main.go │   └── main.go
├── config ├── internal
│   └── nfcfg.yaml │   ├── context
├── internal │   │   ├── ausf_context_init.go
│   ├── context │   │   └── context.go
│   │   └── context.go │   ├── logger
│   ├── logger │   │   └── logger.go
│   │   └── logger.go │   ├── sbi
│   └── sbi │   │   ├── consumer
│   ├── processor │   │   │   ├── consumer.go
│   │   ├── processor.go │   │   │   ├── nrf_service.go
│   │   ├── processor_mock.go │   │   │   └── udm_service.go
│   │   ├── spy_family.go │   │   ├── processor
│   │   └── spy_family_test.go │   │   │   ├── processor.go
│   ├── api_default.go │   │   │   └── ue_authentication.go
│   ├── api_spyfamily.go │   │   ├── api_sorprotection.go
│   ├── api_spyfamily_test.go │   │   ├── api_ueauthentication.go
│   ├── router.go │   │   ├── api_upuprotection.go
│   ├── server.go │   │   ├── routes.go
│   └── server_mock.go │   │   └── server.go
├── pkg │   └── util
│   ├── app │   ├── router_auth_check.go
│   │   └── app.go │   └── router_auth_check_test.go
│   ├── factory ├── pkg
│   │   ├── config.go │ ├── app
│   │   └── factory.go │ │   └── app.go
│   └── service │ ├── factory
│   └── init.go │ │   ├── config.go
├── Makefile │ │   └── factory.go
├── go.mod │ └── service
├── go.sum │ └── init.go
└── README.md ├── LICENSE
├── go.mod
└── go.sum

可以看到两者的差别并不大,本质的差别在于ausf/sbi/目录下多了一个consumer/目录以及相应的代码文件。考察AusfApp以及ServerAusf这两个承担主要任务的类型,会发现他们的函数定义里也多了一些与consumer相关的代码:

/* https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/server.go */
type ServerAusf interface {
app.App
Consumer() *consumer.Consumer // import "github.com/free5gc/ausf/internal/sbi/consumer"
Processor() *processor.Processor
}
// other functions ......
func (s *Server) Run(traceCtx context.Context, wg *sync.WaitGroup) error {
var err error
_, s.Context().NfId, err = s.Consumer().RegisterNFInstance(context.Background())
// other code
}

上面的代码中,Server结构体里多了一个Consumer属性,而Run函数里也调用了consumerRegisterNFInstance函数。

/* https://github.com/free5gc/ausf/blob/v1.2.3/pkg/service/init.go */
type AusfApp struct {
ausfCtx *ausf_context.AUSFContext
cfg *factory.Config
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
sbiServer *sbi.Server
consumer *consumer.Consumer // import "github.com/free5gc/ausf/internal/sbi/consumer"
processor *processor.Processor
}
// other functions ......
func (a *AusfApp) terminateProcedure() {
problemDetails, err := a.Consumer().SendDeregisterNFInstance()
// other code ......
a.CallServerStop()
}

类似的,AusfApp结构体里也了一个Consumer属性,而terminateProcedure函数里也调用了consumerSendDeregisterNFInstance函数。

从字面上看,AUSF的consumer有着帮忙“注册NF”和“注销NF”的功能。那么consumer到底是干什么用的?

internal/sbi/consumer

深入查看,consumer包定义了Consumer结构体,该结构体包含两个子结构体nnrfServicenudmService

/* https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/consumer/consumer.go */
package consumer
import (
"github.com/free5gc/ausf/pkg/app"
"github.com/free5gc/openapi/Nnrf_NFDiscovery"
"github.com/free5gc/openapi/Nnrf_NFManagement"
"github.com/free5gc/openapi/Nudm_UEAuthentication"
)
type ConsumerAusf interface {
app.App
}
type Consumer struct {
ConsumerAusf
*nnrfService
*nudmService
}
func NewConsumer(ausf ConsumerAusf) (*Consumer, error) {
c := &Consumer{
ConsumerAusf: ausf,
}
c.nnrfService = &nnrfService{
consumer: c,
nfMngmntClients: make(map[string]*Nnrf_NFManagement.APIClient),
nfDiscClients: make(map[string]*Nnrf_NFDiscovery.APIClient),
}
c.nudmService = &nudmService{
consumer: c,
ueauClients: make(map[string]*Nudm_UEAuthentication.APIClient),
}
return c, nil
}

NewConsumer函数用于创建Consumer实例,并初始化其子结构体。其主要做的事情就是创建并初始化Consumer的两个重要属性:ndmfServicennrfmService,他们分别被定义在下面两个代码文件里:

/* https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/consumer/udm_service.go */
import (
"sync"
// other imports ...
Nudm_UEAU "github.com/free5gc/openapi/Nudm_UEAuthentication"
"github.com/free5gc/openapi/models"
)
type nudmService struct {
consumer *Consumer
ueauMu sync.RWMutex
ueauClients map[string]*Nudm_UEAU.APIClient
}
/* https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/consumer/nrf_service.go */
import (
"sync"
// other imports ...
"github.com/free5gc/openapi/Nnrf_NFDiscovery"
"github.com/free5gc/openapi/Nnrf_NFManagement"
"github.com/free5gc/openapi/models"
)
type nnrfService struct {
consumer *Consumer
nfMngmntMu sync.RWMutex
nfDiscMu sync.RWMutex
nfMngmntClients map[string]*Nnrf_NFManagement.APIClient
nfDiscClients map[string]*Nnrf_NFDiscovery.APIClient
}
func (s *nnrfService) SendDeregisterNFInstance() (problemDetails *models.ProblemDetails, err error) {
// ...
client := s.getNFManagementClient(ausfContext.NrfUri)
res, err = client.NFInstanceIDDocumentApi.DeregisterNFInstance(ctx, ausfContext.NfId)
// ...
}
func (s *nnrfService) RegisterNFInstance(ctx context.Context) (
resouceNrfUri string, retrieveNfInstanceID string, err error,
) {
// ...
client := s.getNFManagementClient(ausfContext.NrfUri)
nf, res, err = client.NFInstanceIDDocumentApi.RegisterNFInstance(ctx, ausfContext.NfId, nfProfile)
// ...
}
// other fucntions ...

读到这里,我们初步可以得出结论,ausf的Consumer提供了分别用于与nrfudm这两外两个NF进行通信的功能,其中我们在AusfApp以及ServerAusf里看到的RegisterNFInstanceSendDeregisterNFInstance函数就是与nrf通信的函数——也就是当ausf自己创建成功时,告诉网络中的nrf自己的存在,可以为其他NF提供服务了;而当ausf被终止时,也告诉nrf自己不呢个再提供服务了。

加入了consumer后,前文的NF结构图可以进一步迭代成如下形式:

nf-arch

现在的问题是,ausf的consumer具体如何与nrf和udm进行通信?在consumer的代码文件中可以看到对free5gc/openapi的依赖,这个openapi又是什么?

OpenAPI

OpenAPI是一种描述HTTP API的规范。5G标准选择使用OpenAPI来描述每一个NF可以提供怎样的HTTP服务,以及如何使用这些服务。下面是一个经过简化的OpenAPI的例子:

# https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/uspto.yaml
openapi: "3.0.0"
info:
title: Dataset API
version: 2.0.0
paths:
/{dataset}/fields:
get:
tags:
- metadata
summary: >-
Provides the general information about the API and the list of fields
that can be used to query the dataset.
operationId: list-searchable-fields
parameters:
- name: dataset
in: path
description: 'Name of the dataset.'
required: true
schema:
type: string
responses:
'200':
description: >-
The dataset API for the given version is found and it is accessible
to consume.
content:
application/json:
schema:
type: string
'404':
description: >-
The combination of dataset name and version is not found in the
system or it is not published yet to be consumed by public.
content:
application/json:
schema:
type: string

以上yaml文档描述了这么一个HTTP API:API路径为/{dataset}/fields,支持GET方法,并且需要传入一个名为dataset的字符串类型参数。可能的返回值有200和404,分别对应API成功和失败,而返回的内容都是字符串,且能被解析为json对象。假设我们在本地有这么一个API服务器,那么我们就可以通过GET /{dataset}/fields来获取这个API的元数据,包括可以查询的字段列表,例如$curl -X GET https://localhost:8080/api/userDataSet/userName

OpenAPI的水很深,以上的例子仅仅揭示了冰山一角。更多的内容还需研读OpenAPI规范。言归正传,5G标准为每一个NF的NFS(Network Function Service)提供了相应的OpenAPI文档,通过研究i这些文档,我们可以清楚地知道每一个NFS提供什么服务,应该怎么使用这些服务。也正因为有了这些清晰定义的文档,我们甚至可以(应该)使用代码生成工具,例如OpenAPI Generator,来生成对应的服务端和客户端的部分代码,从而大大减少开发工作量和手工编程中的bug,并提高代码的规范性。

free5gc/openapi这个代码仓库中的代码就是使用OpenAPI Generator通过解析相应NFS的OpenAPI文档生成的。需要注意的是,free5gc团队使用的OpenAPI Generator是定制版的,所以我们使用开源版工具生成的代码与官方的代码是不相同的。

目前 free5GC 使用的 openapi generator 為團隊維護的客製版本,且暫時尚沒有公開的計畫。如果你有貢獻 free5GC 的計畫,可以告訴我們需要使用的規格書和版本,由我們來產生對應的程式碼。

那么官方的openapi代码又是怎样的?

$ls openapi
CHANGELOG.md Nnrf_AccessToken Nudm_ParameterProvision json_query_builder.go
LICENSE.txt Nnrf_NFDiscovery Nudm_SubscriberDataManagement media_type.go
Namf_Communication Nnrf_NFManagement Nudm_UEAuthentication models
Namf_EventExposure Nnssf_NSSAIAvailability Nudm_UEContextManagement models_nef
Namf_Location Nnssf_NSSelection Nudr_DataRepository multipart_related.go
Namf_MT Npcf_AMPolicy PfdManagement oauth
Nausf_SoRProtection Npcf_BDTPolicyControl auth.go problem_details.go
Nausf_UEAuthentication Npcf_PolicyAuthorization client.go serialize.go
Nausf_UPUProtection Npcf_SMPolicyControl convert.go supported_feature.go
Nbsf_Management Npcf_UEPolicy convert_test.go supported_feature_test.go
Nchf_ConvergedCharging Nsmf_EventExposure error.go util.go
Nnef_PFDmanagement Nsmf_PDUSession go.mod util_test.go
Nnef_TrafficInfluence Nudm_EventExposure go.sum

可以看到,官方的openapi代码中包含了每一个NFS的客户端和服务端代码,以及一些辅助代码。这些代码都是通过OpenAPI Generator生成的。我们再哪一个NFS深入看看。就拿会与ausf以及所有其他NF通信的nrf来说,

$ tree openapi/Nnrf_NFManagement/
.
├── api
│   └── openapi.yaml
├── api_nf_instance_id_document.go
├── api_nf_instances_store.go
├── api_notification.go
├── api_subscription_id_document.go
├── api_subscriptions_collection.go
├── CHANGELOG.md
├── client.go
└── configuration.go

可以看到,除了api/openapi.yaml之外,还有client.goconfiguration.go,以及四个api_*.go文件。这四个api_*.go文件实现了对具体API服务的调用,而client.go则整合了这些API服务,并提供了对外的接口。因此,我们可以看到ausf通过创建一个Nnrf_NFManagement.APIClient对象来调用Nnrf_NFManagement提供的所有API服务。

client.go的代码非常简短,且每一个NFS的client.go文件都是这样大同小异,因此我们在这里不妨仔细阅读一个。

// https://github.com/free5gc/openapi/blob/v1.0.8/Nnrf_NFManagement/client.go
/*
* NRF NFManagement Service
*
* API version: 1.0.0
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package Nnrf_NFManagement
// APIClient manages communication with the NRF NFManagement Service API v1.0.0
// In most cases there should be only one, shared, APIClient.
type APIClient struct {
cfg *Configuration
common service // Reuse a single struct instead of allocating one for each service on the heap.
// API Services
NFInstanceIDDocumentApi *NFInstanceIDDocumentApiService
NFInstancesStoreApi *NFInstancesStoreApiService
SubscriptionIDDocumentApi *SubscriptionIDDocumentApiService
SubscriptionsCollectionApi *SubscriptionsCollectionApiService
NotificationApi *NotificationApiService
}
type service struct {
client *APIClient
}
// NewAPIClient creates a new API client. Requires a userAgent string describing your application.
// optionally a custom http.Client to allow for advanced features such as caching.
func NewAPIClient(cfg *Configuration) *APIClient {
c := &APIClient{}
c.cfg = cfg
c.common.client = c
// API Services
c.NFInstanceIDDocumentApi = (*NFInstanceIDDocumentApiService)(&c.common)
c.NFInstancesStoreApi = (*NFInstancesStoreApiService)(&c.common)
c.SubscriptionIDDocumentApi = (*SubscriptionIDDocumentApiService)(&c.common)
c.SubscriptionsCollectionApi = (*SubscriptionsCollectionApiService)(&c.common)
c.NotificationApi = (*NotificationApiService)(&c.common)
return c
}

api_*.go文件则实现了对具体API服务的调用,例如,api_nf_instance_id_document.go文件实现了对curl -X PUT https://example.com/nnrf/v1/nf-instances/{nfInstanceID}的调用,也就是往nrf注册一个新的NF,简化版的代码如下:

// https://github.com/free5gc/openapi/blob/v1.0.8/Nnrf_NFManagement/api_nf_instance_id_document.go#L260
func (a *NFInstanceIDDocumentApiService) RegisterNFInstance(
ctx context.Context, nfInstanceID string, nfProfile models.NfProfile)
(models.NfProfile, *http.Response, error) {
// create path and map variables
// set Accept header
// body params
r, err := openapi.PrepareRequest(ctx, a.client.cfg, localVarPath, localVarHTTPMethod,
localVarPostBody, localVarHeaderParams, localVarQueryParams,
localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes)
localVarHTTPResponse, err := openapi.CallAPI(a.client.cfg, r)
switch localVarHTTPResponse.StatusCode {
case 200: // ......
}
}

这个函数做的事情就是把各种信息整合并且构造一个http.Request对象,然后调用openapi.CallAPI函数发送请求并处理返回的响应。

另外值得一提的是,free5gc/openapi里除了NFS的客户端和服务端代码,还有很大一部分是数据模型/数据结构,主要集中在modelsmodels_nef目录下,例如models/model_nf_service.go文件定义了NFService类型。这个类型主要是会被Nnrf_NFManagement用到,但其他的NFS也可能会使用到。

// https://github.com/free5gc/openapi/blob/v1.0.8/models/model_nf_service.go
/*
* NRF NFManagement Service
*
* API version: 1.0.1
* Generated by: OpenAPI Generator (https://openapi-generator.tech)
*/
package models
import (
"time"
)
type NfService struct {
ServiceInstanceId string `json:"serviceInstanceId" yaml:"serviceInstanceId" bson:"serviceInstanceId" mapstructure:"ServiceInstanceId"`
ServiceName ServiceName `json:"serviceName" yaml:"serviceName" bson:"serviceName" mapstructure:"ServiceName"`
Versions *[]NfServiceVersion `json:"versions" yaml:"versions" bson:"versions" mapstructure:"Versions"`
Scheme UriScheme `json:"scheme" yaml:"scheme" bson:"scheme" mapstructure:"Scheme"`
NfServiceStatus NfServiceStatus `json:"nfServiceStatus" yaml:"nfServiceStatus" bson:"nfServiceStatus" mapstructure:"NfServiceStatus"`
Fqdn string `json:"fqdn,omitempty" yaml:"fqdn" bson:"fqdn" mapstructure:"Fqdn"`
InterPlmnFqdn string `json:"interPlmnFqdn,omitempty" yaml:"interPlmnFqdn" bson:"interPlmnFqdn" mapstructure:"InterPlmnFqdn"`
IpEndPoints *[]IpEndPoint `json:"ipEndPoints,omitempty" yaml:"ipEndPoints" bson:"ipEndPoints" mapstructure:"IpEndPoints"`
ApiPrefix string `json:"apiPrefix,omitempty" yaml:"apiPrefix" bson:"apiPrefix" mapstructure:"ApiPrefix"`
DefaultNotificationSubscriptions []DefaultNotificationSubscription `json:"defaultNotificationSubscriptions,omitempty" yaml:"defaultNotificationSubscriptions" bson:"defaultNotificationSubscriptions" mapstructure:"DefaultNotificationSubscriptions"`
AllowedPlmns *[]PlmnId `json:"allowedPlmns,omitempty" yaml:"allowedPlmns" bson:"allowedPlmns" mapstructure:"AllowedPlmns"`
AllowedNfTypes []NfType `json:"allowedNfTypes,omitempty" yaml:"allowedNfTypes" bson:"allowedNfTypes" mapstructure:"AllowedNfTypes"`
AllowedNfDomains []string `json:"allowedNfDomains,omitempty" yaml:"allowedNfDomains" bson:"allowedNfDomains" mapstructure:"AllowedNfDomains"`
AllowedNssais *[]Snssai `json:"allowedNssais,omitempty" yaml:"allowedNssais" bson:"allowedNssais" mapstructure:"AllowedNssais"`
Priority int32 `json:"priority,omitempty" yaml:"priority" bson:"priority" mapstructure:"Priority"`
Capacity int32 `json:"capacity,omitempty" yaml:"capacity" bson:"capacity" mapstructure:"Capacity"`
Load int32 `json:"load,omitempty" yaml:"load" bson:"load" mapstructure:"Load"`
RecoveryTime *time.Time `json:"recoveryTime,omitempty" yaml:"recoveryTime" bson:"recoveryTime" mapstructure:"RecoveryTime"`
ChfServiceInfo *ChfServiceInfo `json:"chfServiceInfo,omitempty" yaml:"chfServiceInfo" bson:"chfServiceInfo" mapstructure:"ChfServiceInfo"`
SupportedFeatures string `json:"supportedFeatures,omitempty" yaml:"supportedFeatures" bson:"supportedFeatures" mapstructure:"SupportedFeatures"`
Oauth2Required string `json:"oauth2Required,omitempty" yaml:"oauth2Required" bson:"oauth2Required" mapstructure:"oauth2Required"`
}

这些数据类型全部都在TS29.571由定义。


至此,我们就完成了对单个NF的软件架构的探究。可以看到,Free5GC的软件架构非常清晰(至少在v3.4.3),每一个NF都有自己的模块,并且通过API服务进行通信。我们将开始对每个NF源码的研究,而第一个NF就是AUSF

posted @   zrq96  阅读(161)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示