diff --git a/Makefile b/Makefile index bb9cb5a..85a79ed 100644 --- a/Makefile +++ b/Makefile @@ -85,3 +85,22 @@ docker-push: for target in $(DOCKER_TARGETS); do \ docker push ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}5gc-$$target:${DOCKER_TAG}; \ done + +.coverage: + rm -rf $(CURDIR)/.coverage + mkdir -p $(CURDIR)/.coverage + +test: .coverage + docker run --rm -v $(CURDIR):/nrf -w /nrf golang:latest \ + go test \ + -failfast \ + -coverprofile=.coverage/coverage-unit.txt \ + -covermode=atomic \ + -v \ + ./ ./... + +fmt: + @go fmt ./... + +golint: + @docker run --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:latest golangci-lint run -v --config /app/.golangci.yml \ No newline at end of file diff --git a/context/management_data.go b/context/management_data.go index c7f286c..79eddfc 100644 --- a/context/management_data.go +++ b/context/management_data.go @@ -26,8 +26,8 @@ const NRF_NFINST_RES_URI_PREFIX = factory.NRF_NFM_RES_URI_PREFIX + "/nf-instance // Generates a random int between 0 and 99 func GenerateRandomNumber() (int, error) { - max := big.NewInt(100) - randomNumber, err := rand.Int(rand.Reader, max) + maximum := big.NewInt(100) + randomNumber, err := rand.Int(rand.Reader, maximum) if err != nil { return 0, err } diff --git a/go.mod b/go.mod index 97c97ea..7f19854 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/uuid v1.6.0 github.com/mitchellh/mapstructure v1.5.0 github.com/omec-project/config5g v1.4.2 - github.com/omec-project/openapi v1.2.0 + github.com/omec-project/openapi v1.2.1 github.com/omec-project/util v1.1.0 github.com/prometheus/client_golang v1.19.1 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index 1b96a6e..cec5576 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/omec-project/config5g v1.4.2 h1:Mujr5RVeSyPOBXxFhH0FUqwuES+WwnY3ltt3ULByeYQ= github.com/omec-project/config5g v1.4.2/go.mod h1:r2v1CX9+Hye5SwQNieeSFEpDutGF5g9mLrIK+Ot9P5Q= -github.com/omec-project/openapi v1.2.0 h1:7Wvi0HLvhvxMyQtqGcqtMCPC/0QCGAFP5htrXCfWxRc= -github.com/omec-project/openapi v1.2.0/go.mod h1:hjU13MB1m9MHTko87JfsUNCdeD6/m6VkNZDD8Vq5U9M= +github.com/omec-project/openapi v1.2.1 h1:7ccFadoGfoqZq4sw7twXatbRGmkg4pARe6sWmCVVmrs= +github.com/omec-project/openapi v1.2.1/go.mod h1:hjU13MB1m9MHTko87JfsUNCdeD6/m6VkNZDD8Vq5U9M= github.com/omec-project/util v1.1.0 h1:TUuLmzqTLChIEXQlK9g5Ihgmw4FUm/UJnjfu0wT8Gz0= github.com/omec-project/util v1.1.0/go.mod h1:BEv8nCokB4j0fgAQ6VVkKuQ2PSP3DJMEmz25pFMw5X8= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= diff --git a/nrfcache/match_filters.go b/nrfcache/match_filters.go deleted file mode 100644 index 2c61078..0000000 --- a/nrfcache/match_filters.go +++ /dev/null @@ -1,259 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Infosys Limited -// -// SPDX-License-Identifier: Apache-2.0 -// - -// This file contains apis to match the nf profiles based on the parameters provided in the -// Nnrf_NFDiscovery.SearchNFInstancesParamOpts. There is a match function provided for each NF type -// which must be updated with logic to compare profiles based on the applicable params in -// Nnrf_NFDiscovery.SearchNFInstancesParamOpts - -package nrf_cache - -import ( - "encoding/json" - "regexp" - - "github.com/omec-project/nrf/logger" - "github.com/omec-project/openapi/Nnrf_NFDiscovery" - "github.com/omec-project/openapi/models" -) - -type MatchFilter func(profile *models.NfProfile, opts *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (bool, error) - -type MatchFilters map[models.NfType]MatchFilter - -var matchFilters = MatchFilters{ - models.NfType_SMF: MatchSmfProfile, - models.NfType_AUSF: MatchAusfProfile, - models.NfType_PCF: MatchPcfProfile, - models.NfType_NSSF: MatchNssfProfile, - models.NfType_UDM: MatchUdmProfile, - models.NfType_AMF: MatchAmfProfile, -} - -func MatchSmfProfile(profile *models.NfProfile, opts *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (bool, error) { - - if opts.ServiceNames.IsSet() { - reqServiceNames := opts.ServiceNames.Value().([]models.ServiceName) - matchCount := 0 - for _, sn := range reqServiceNames { - for i := 0; i < len(*profile.NfServices); i++ { - if (*profile.NfServices)[i].ServiceName == sn { - matchCount++ - break - } - } - } - - if matchCount == 0 { - return false, nil - } - } - - if opts.Snssais.IsSet() { - reqSnssais := opts.Snssais.Value().([]string) - matchCount := 0 - - for _, reqSnssai := range reqSnssais { - var snssai models.Snssai - err := json.Unmarshal([]byte(reqSnssai), &snssai) - if err != nil { - logger.UtilLog.Errorf("Error Unmarshaling nssai : %+v", err) - return false, err - } - - // Snssai in the smfInfo has priority - if profile.SmfInfo != nil && profile.SmfInfo.SNssaiSmfInfoList != nil { - for _, s := range *profile.SmfInfo.SNssaiSmfInfoList { - if s.SNssai != nil && (*s.SNssai) == snssai { - matchCount++ - } - } - } else if profile.AllowedNssais != nil { - for _, s := range *profile.AllowedNssais { - if s == snssai { - matchCount++ - } - } - } - - } - - // if at least one matching snssai has been found - if matchCount == 0 { - return false, nil - } - - } - - // validate dnn - if opts.Dnn.IsSet() { - // if a dnn is provided by the upper layer, check for the exact match - // or wild card match - dnnMatched := false - - if profile.SmfInfo != nil && profile.SmfInfo.SNssaiSmfInfoList != nil { - matchDnnLoop: - for _, s := range *profile.SmfInfo.SNssaiSmfInfoList { - if s.DnnSmfInfoList != nil { - for _, d := range *s.DnnSmfInfoList { - if d.Dnn == opts.Dnn.Value() || d.Dnn == "*" { - dnnMatched = true - break matchDnnLoop - } - } - } - } - } - - if dnnMatched == false { - return false, nil - } - } - logger.UtilLog.Tracef("SMF match found, nfInstance Id %v", profile.NfInstanceId) - return true, nil -} - -func MatchSupiRange(supi string, supiRange []models.SupiRange) bool { - matchFound := false - for _, s := range supiRange { - if len(s.Pattern) > 0 { - r, _ := regexp.Compile(s.Pattern) - if r.MatchString(supi) { - matchFound = true - break - } - - } else if s.Start <= supi && supi <= s.End { - matchFound = true - break - } - } - - return matchFound -} - -func MatchAusfProfile(profile *models.NfProfile, opts *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (bool, error) { - matchFound := true - if opts.Supi.IsSet() { - if profile.AusfInfo != nil && len(profile.AusfInfo.SupiRanges) > 0 { - matchFound = MatchSupiRange(opts.Supi.Value(), profile.AusfInfo.SupiRanges) - } - } - logger.UtilLog.Tracef("Ausf match found = %v", matchFound) - return matchFound, nil -} - -func MatchNssfProfile(profile *models.NfProfile, opts *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (bool, error) { - logger.UtilLog.Traceln("Nssf match found ") - return true, nil -} - -func MatchAmfProfile(profile *models.NfProfile, opts *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (bool, error) { - - if opts.TargetPlmnList.IsSet() { - if profile.PlmnList != nil { - plmnMatchCount := 0 - - targetPlmnList := opts.TargetPlmnList.Value().([]string) - for _, targetPlmn := range targetPlmnList { - var plmn models.PlmnId - err := json.Unmarshal([]byte(targetPlmn), &plmn) - - if err != nil { - logger.UtilLog.Errorf("Error Unmarshaling plmn : %+v", err) - return false, err - } - - for _, profilePlmn := range *profile.PlmnList { - if profilePlmn == plmn { - plmnMatchCount++ - break - } - } - } - if plmnMatchCount == 0 { - return false, nil - } - } - } - - if profile.AmfInfo != nil { - if opts.Guami.IsSet() { - if profile.AmfInfo.GuamiList != nil { - guamiMatchCount := 0 - - guamiList := opts.Guami.Value().([]string) - for _, guami := range guamiList { - var guamiOpt models.Guami - err := json.Unmarshal([]byte(guami), &guamiOpt) - - if err != nil { - logger.UtilLog.Errorf("Error Unmarshaling guami : %+v", err) - return false, err - } - - for _, guami := range *profile.AmfInfo.GuamiList { - if guamiOpt == guami { - guamiMatchCount++ - break - } - } - } - if guamiMatchCount == 0 { - return false, nil - } - } - } - - if opts.AmfRegionId.IsSet() { - if len(profile.AmfInfo.AmfRegionId) > 0 { - if profile.AmfInfo.AmfRegionId != opts.AmfRegionId.Value() { - return false, nil - } - } - } - - if opts.AmfSetId.IsSet() { - if len(profile.AmfInfo.AmfSetId) > 0 { - if profile.AmfInfo.AmfSetId != opts.AmfSetId.Value() { - return false, nil - } - } - } - - if opts.TargetNfInstanceId.IsSet() { - if profile.NfInstanceId != "" { - if profile.NfInstanceId != opts.TargetNfInstanceId.Value() { - return false, nil - } - } - } - } - - logger.UtilLog.Tracef("Amf match found = %v", profile.NfInstanceId) - return true, nil -} - -func MatchPcfProfile(profile *models.NfProfile, opts *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (bool, error) { - matchFound := true - if opts.Supi.IsSet() { - if profile.PcfInfo != nil && len(profile.PcfInfo.SupiRanges) > 0 { - matchFound = MatchSupiRange(opts.Supi.Value(), profile.PcfInfo.SupiRanges) - } - } - logger.UtilLog.Tracef("PCF match found = %v", matchFound) - return matchFound, nil -} - -func MatchUdmProfile(profile *models.NfProfile, opts *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (bool, error) { - matchFound := true - if opts.Supi.IsSet() { - if profile.UdmInfo != nil && len(profile.UdmInfo.SupiRanges) > 0 { - matchFound = MatchSupiRange(opts.Supi.Value(), profile.UdmInfo.SupiRanges) - } - } - logger.UtilLog.Tracef("UDM match found = %v", matchFound) - return matchFound, nil -} diff --git a/nrfcache/nrf_cache.go b/nrfcache/nrf_cache.go deleted file mode 100644 index 69c8c71..0000000 --- a/nrfcache/nrf_cache.go +++ /dev/null @@ -1,375 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Infosys Limited -// -// SPDX-License-Identifier: Apache-2.0 -// - -package nrf_cache - -import ( - "container/heap" - "sync" - "time" - - "github.com/omec-project/nrf/logger" - "github.com/omec-project/openapi/Nnrf_NFDiscovery" - "github.com/omec-project/openapi/models" -) - -const defaultCacheTTl = time.Hour -const defaultNfProfileTTl = time.Minute - -type NfProfileItem struct { - nfProfile *models.NfProfile - expiryTime time.Time - ttl time.Duration - index int // index of the entry in the priority queue -} - -// isExpired - returns true if the expiry time has passed. -func (item *NfProfileItem) isExpired() bool { - return item.expiryTime.Before(time.Now()) -} - -// updateExpiryTime - sets new expiry time based on the current time -func (item *NfProfileItem) updateExpiryTime() { - item.expiryTime = time.Now().Add(time.Second * item.ttl) -} - -func newNfProfileItem(profile *models.NfProfile, ttl time.Duration) *NfProfileItem { - item := &NfProfileItem{ - nfProfile: profile, - ttl: ttl, - } - item.updateExpiryTime() - return item -} - -// NfProfilePriorityQ : Priority Queue to store the profile. Queue is ordered by expiry time -type NfProfilePriorityQ []*NfProfileItem - -// Len - Number of entries in the priority queue -func (npq NfProfilePriorityQ) Len() int { - return len(npq) -} - -// Less - Comparator for the sort interface used by the heap. -// entries will be sorted by increasing order of expiry time -func (npq NfProfilePriorityQ) Less(i, j int) bool { - return npq[i].expiryTime.Before(npq[j].expiryTime) -} - -// Swap - implemented for the sort interface used by the heap pkg. -// swaps the element at i and j. -func (npq NfProfilePriorityQ) Swap(i, j int) { - npq[i], npq[j] = npq[j], npq[i] - npq[i].index = i - npq[j].index = j -} - -// at - returns the element at index i -func (npq NfProfilePriorityQ) at(index int) *NfProfileItem { - return npq[index] -} - -// push - adds an entry to the priority queue. Invokes heap api to -// push the entry to the correct location in the queue -func (npq *NfProfilePriorityQ) push(item interface{}) { - heap.Push(npq, item) -} - -// update - update fields of existing entry. Invokes heap.Fix to re-establish the ordering. -func (npq *NfProfilePriorityQ) update(item *NfProfileItem, value *models.NfProfile, ttl time.Duration) { - item.nfProfile = value - item.ttl = ttl - item.updateExpiryTime() - heap.Fix(npq, item.index) -} - -// remove -removes an entry at given index. -func (npq *NfProfilePriorityQ) remove(item *NfProfileItem) { - heap.Remove(npq, item.index) -} - -// Push - implemented for heap interface. appends an element to the priority queue -func (npq *NfProfilePriorityQ) Push(item interface{}) { - n := len(*npq) - entry := item.(*NfProfileItem) - entry.index = n - *npq = append(*npq, entry) -} - -// Pop - implemented for heap interface. removes the entry with least expiry time -func (npq *NfProfilePriorityQ) Pop() interface{} { - old := *npq - n := len(old) - item := old[n-1] - old[n-1] = nil - item.index = -1 - *npq = old[0 : n-1] - return item -} - -// newNfProfilePriorityQ - New prority queue for storing NF Profiles. -func newNfProfilePriorityQ() *NfProfilePriorityQ { - q := &NfProfilePriorityQ{} - heap.Init(q) - return q -} - -// NrfCache : cache of nf profiles -type NrfCache struct { - cache map[string]*NfProfileItem // map[nf-instance-id] =*NfProfile - - priorityQ *NfProfilePriorityQ // sorted by expiry time - - evictionTicker *time.Ticker - - done chan struct{} - - nrfDiscoveryQueryCb NrfDiscoveryQueryCb // nrf query callback - - evictionInterval time.Duration // timer interval in which the cache is checked for eviction of expired entries - - mutex sync.RWMutex -} - -// handleLookup - Checks if the cache has nf cache entry corresponding to the parameters specified. -// If entry does not exist, perform nrf discovery query. To avoid concurrency issues, -// nrf discovery query is mutex protected. -func (c *NrfCache) handleLookup(nrfUri string, targetNfType, requestNfType models.NfType, param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) { - var searchResult models.SearchResult - var err error - - c.mutex.RLock() - searchResult.NfInstances = c.get(param) - c.mutex.RUnlock() - - if len(searchResult.NfInstances) == 0 { - logger.UtilLog.Tracef("Cache miss for nftype %s", targetNfType) - - c.mutex.Lock() - defer c.mutex.Unlock() - searchResult.NfInstances = c.get(param) - if len(searchResult.NfInstances) == 0 { - searchResult, err = c.nrfDiscoveryQueryCb(nrfUri, targetNfType, requestNfType, param) - if err != nil { - return searchResult, err - } - - for i := 0; i < len(searchResult.NfInstances); i++ { - c.set(&searchResult.NfInstances[i], time.Duration(searchResult.ValidityPeriod)) - } - } - } - return searchResult, err -} - -// set - Adds nf profile entry to the map and the priority queue -func (c *NrfCache) set(nfProfile *models.NfProfile, ttl time.Duration) { - if ttl == 0 { - ttl = defaultNfProfileTTl - } - - item, exists := c.cache[nfProfile.NfInstanceId] - if exists { - // if item.isExpired() - c.priorityQ.update(item, nfProfile, ttl) - } else { - newItem := newNfProfileItem(nfProfile, ttl) - c.cache[nfProfile.NfInstanceId] = newItem - c.priorityQ.push(newItem) - } -} - -// get - checks if nf profile corresponding to the search opts exist in the cache. -func (c *NrfCache) get(opts *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) []models.NfProfile { - var nfProfiles []models.NfProfile - - for _, element := range c.cache { - if !element.isExpired() { - if opts != nil { - cb, ok := matchFilters[element.nfProfile.NfType] - if ok { - matchFound, err := cb(element.nfProfile, opts) - if err != nil { - logger.UtilLog.Errorf("match filter returned error %v", err) - } else if matchFound { - nfProfiles = append(nfProfiles, *(element.nfProfile)) - } - } - } else { - nfProfiles = append(nfProfiles, *(element.nfProfile)) - } - } - } - return nfProfiles -} - -// removeByNfInstanceId - removes nf profile with nfInstanceId from the cache and queue -func (c *NrfCache) removeByNfInstanceId(nfInstanceId string) bool { - c.mutex.Lock() - defer c.mutex.Unlock() - - NfProfileItem, rc := c.cache[nfInstanceId] - if rc { - c.remove(NfProfileItem) - } - return rc -} - -// remove - -func (c *NrfCache) remove(item *NfProfileItem) { - c.priorityQ.remove(item) - delete(c.cache, item.nfProfile.NfInstanceId) -} - -// cleanupExpiredItems - removes the profiles with expired TTLs -func (c *NrfCache) cleanupExpiredItems() { - logger.UtilLog.Infoln("nrf cache: cleanup expired items") - - for item := c.priorityQ.at(0); item.isExpired(); { - - logger.UtilLog.Tracef("evicted nf instance %s", item.nfProfile.NfInstanceId) - - c.remove(item) - if c.priorityQ.Len() == 0 { - break - } else { - item = c.priorityQ.at(0) - } - } -} - -// purge - release the cache and its resources. -func (c *NrfCache) purge() { - c.mutex.Lock() - defer c.mutex.Unlock() - - close(c.done) - c.priorityQ = newNfProfilePriorityQ() - c.cache = make(map[string]*NfProfileItem) - c.evictionTicker.Stop() -} - -func (c *NrfCache) startExpiryProcessing() { - for { - select { - case <-c.evictionTicker.C: - c.mutex.Lock() - if c.priorityQ.Len() == 0 { - c.mutex.Unlock() - continue - } - - c.cleanupExpiredItems() - c.mutex.Unlock() - - case <-c.done: - return - } - } -} - -func NewNrfCache(duration time.Duration, dbqueryCb NrfDiscoveryQueryCb) *NrfCache { - cache := &NrfCache{ - cache: make(map[string]*NfProfileItem), - priorityQ: newNfProfilePriorityQ(), - evictionInterval: defaultCacheTTl, - nrfDiscoveryQueryCb: dbqueryCb, - done: make(chan struct{}), - } - - cache.evictionTicker = time.NewTicker(duration) - - go cache.startExpiryProcessing() - - return cache -} - -type NrfMasterCache struct { - nrfDiscoveryQueryCb NrfDiscoveryQueryCb - nfTypeToCacheMap map[models.NfType]*NrfCache - evictionInterval time.Duration - mutex sync.Mutex -} - -func (c *NrfMasterCache) GetNrfCacheInstance(targetNfType models.NfType) *NrfCache { - c.mutex.Lock() - defer c.mutex.Unlock() - - cache, exists := c.nfTypeToCacheMap[targetNfType] - if exists == false { - logger.UtilLog.Infof("Creating cache for nftype %v", targetNfType) - - cache = NewNrfCache(c.evictionInterval, c.nrfDiscoveryQueryCb) - c.nfTypeToCacheMap[targetNfType] = cache - } - return cache -} - -func (c *NrfMasterCache) clearNrfMasterCache() { - c.mutex.Lock() - defer c.mutex.Unlock() - - for k, cache := range c.nfTypeToCacheMap { - cache.purge() - delete(c.nfTypeToCacheMap, k) - } -} - -func (c *NrfMasterCache) removeNfProfile(nfInstanceId string) bool { - c.mutex.Lock() - defer c.mutex.Unlock() - - var ok bool - for _, nrfCache := range c.nfTypeToCacheMap { - if ok = nrfCache.removeByNfInstanceId(nfInstanceId); ok { - break - } - } - return ok -} - -var masterCache *NrfMasterCache - -type NrfDiscoveryQueryCb func(nrfUri string, targetNfType, requestNfType models.NfType, param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) - -func InitNrfCaching(interval time.Duration, cb NrfDiscoveryQueryCb) { - m := &NrfMasterCache{ - nfTypeToCacheMap: make(map[models.NfType]*NrfCache), - evictionInterval: interval, - nrfDiscoveryQueryCb: cb, - } - masterCache = m -} - -func disableNrfCaching() { - masterCache.clearNrfMasterCache() - masterCache = nil -} - -func SearchNFInstances(nrfUri string, targetNfType, requestNfType models.NfType, param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) { - - logger.UtilLog.Traceln("SearchNFInstances nrf cache") - - var searchResult models.SearchResult - var err error - - c := masterCache.GetNrfCacheInstance(targetNfType) - if c != nil { - searchResult, err = c.handleLookup(nrfUri, targetNfType, requestNfType, param) - } else { - logger.UtilLog.Infoln("Failed to find cache for nftype") - } - - for _, np := range searchResult.NfInstances { - logger.UtilLog.Tracef("%v", np) - } - - return searchResult, err - -} - -func RemoveNfProfileFromNrfCache(nfInstanceId string) bool { - return masterCache.removeNfProfile(nfInstanceId) -} diff --git a/nrfcache/nrf_cache_test.go b/nrfcache/nrf_cache_test.go deleted file mode 100644 index c0543e3..0000000 --- a/nrfcache/nrf_cache_test.go +++ /dev/null @@ -1,898 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Open Networking Foundation -// -// SPDX-License-Identifier: Apache-2.0 -package nrf_cache - -import ( - "encoding/json" - "fmt" - "github.com/antihax/optional" - "github.com/omec-project/nrf/logger" - "github.com/omec-project/openapi/Nnrf_NFDiscovery" - "github.com/omec-project/openapi/models" - "reflect" - "strings" - "sync" - "testing" - "time" -) - -var nfProfilesDb map[string]string -var validityPeriod int32 -var evictionInterval int32 -var nrfDbCallbackCallCount int32 - -func init() { - - validityPeriod = 60 - evictionInterval = 120 - - nrfDbCallbackCallCount = 0 - - nfProfilesDb = make(map[string]string) - - nfProfilesDb["SMF-010203-internet"] = `{ - "ipv4Addresses": [ - "smf" - ], - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "smfInfo": { - "sNssaiSmfInfoList": [ - { - "sNssai": { - "sst": 1, - "sd": "010203" - }, - "dnnSmfInfoList": [ - { - "dnn": "internet" - } - ] - } - ] - }, - "nfServices": [ - { - "apiPrefix": "http://smf:29502", - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "serviceInstanceId": "b926f193-1083-49a8-adb3-5fcf57a1f0bfnsmf-pdusession", - "serviceName": "nsmf-pdusession", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "https://smf:29502/nsmf-pdusession/v1", - "expiry": "2022-08-17T05:31:40.997097141Z" - } - ], - "scheme": "https", - "nfServiceStatus": "REGISTERED" - }, - { - "scheme": "https", - "nfServiceStatus": "REGISTERED", - "apiPrefix": "http://smf:29502", - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "serviceInstanceId": "b926f193-1083-49a8-adb3-5fcf57a1f0bfnsmf-event-exposure", - "serviceName": "nsmf-event-exposure", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "https://smf:29502/nsmf-pdusession/v1", - "expiry": "2022-08-17T05:31:40.997097141Z" - } - ] - } - ], - "nfInstanceId": "b926f193-1083-49a8-adb3-5fcf57a1f0bf", - "plmnList": [ - { - "mnc": "93", - "mcc": "208" - } - ], - "sNssais": [ - { - "sd": "010203", - "sst": 1 - } - ], - "nfType": "SMF", - "nfStatus": "REGISTERED" - }` - nfProfilesDb["SMF-010203-ims"] = `{ - "ipv4Addresses": [ - "smf" - ], - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "smfInfo": { - "sNssaiSmfInfoList": [ - { - "sNssai": { - "sst": 1, - "sd": "010203" - }, - "dnnSmfInfoList": [ - { - "dnn": "ims" - } - ] - } - ] - }, - "nfServices": [ - { - "apiPrefix": "http://smf:29502", - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "serviceInstanceId": "c926f193-1083-49a8-adb3-5fcf57a1f0bfnsmf-pdusession", - "serviceName": "nsmf-pdusession", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "https://smf:29502/nsmf-pdusession/v1", - "expiry": "2022-08-17T05:31:40.997097141Z" - } - ], - "scheme": "https", - "nfServiceStatus": "REGISTERED" - }, - { - "scheme": "https", - "nfServiceStatus": "REGISTERED", - "apiPrefix": "http://smf:29502", - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "serviceInstanceId": "c926f193-1083-49a8-adb3-5fcf57a1f0bfnsmf-event-exposure", - "serviceName": "nsmf-event-exposure", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "https://smf:29502/nsmf-pdusession/v1", - "expiry": "2022-08-17T05:31:40.997097141Z" - } - ] - } - ], - "nfInstanceId": "c926f193-1083-49a8-adb3-5fcf57a1f0bf", - "plmnList": [ - { - "mnc": "93", - "mcc": "208" - } - ], - "sNssais": [ - { - "sd": "010203", - "sst": 1 - } - ], - "nfType": "SMF", - "nfStatus": "REGISTERED" - } -` - nfProfilesDb["SMF-0a0b0c-internet"] = `{ - "ipv4Addresses": [ - "smf" - ], - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "smfInfo": { - "sNssaiSmfInfoList": [ - { - "sNssai": { - "sst": 1, - "sd": "0a0b0c" - }, - "dnnSmfInfoList": [ - { - "dnn": "internet" - } - ] - } - ] - }, - "nfServices": [ - { - "apiPrefix": "http://smf:29502", - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "serviceInstanceId": "d926f193-1083-49a8-adb3-5fcf57a1f0bfnsmf-pdusession", - "serviceName": "nsmf-pdusession", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "https://smf:29502/nsmf-pdusession/v1", - "expiry": "2022-08-17T05:31:40.997097141Z" - } - ], - "scheme": "https", - "nfServiceStatus": "REGISTERED" - }, - { - "scheme": "https", - "nfServiceStatus": "REGISTERED", - "apiPrefix": "http://smf:29502", - "allowedPlmns": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "serviceInstanceId": "d926f193-1083-49a8-adb3-5fcf57a1f0bfnsmf-event-exposure", - "serviceName": "nsmf-event-exposure", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "https://smf:29502/nsmf-pdusession/v1", - "expiry": "2022-08-17T05:31:40.997097141Z" - } - ] - } - ], - "nfInstanceId": "d926f193-1083-49a8-adb3-5fcf57a1f0bf", - "plmnList": [ - { - "mnc": "93", - "mcc": "208" - } - ], - "sNssais": [ - { - "sd": "0a0b0c", - "sst": 1 - } - ], - "nfType": "SMF", - "nfStatus": "REGISTERED" - }` - nfProfilesDb["AUSF-1"] = `{ "nfServices": [ - { - "serviceName": "nausf-auth", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "1.0.0" - } - ], - "scheme": "http", - "nfServiceStatus": "REGISTERED", - "ipEndPoints": [ - { - "ipv4Address": "ausf", - "port": 29509 - } - ], - "serviceInstanceId": "57d0a167-5283-4170-bdd8-881076049a81" - } - ], - "ausfInfo": { - "supiRanges": [ - { "start": "123456789040000", "end": "123456789049999" } - ] - }, - "nfInstanceId": "57d0a167-5283-4170-bdd8-881076049a81", - "nfType": "AUSF", - "nfStatus": "REGISTERED", - "plmnList": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "ipv4Addresses": [ - "ausf" - ], - "ausfInfo": { - "groupId": "ausfGroup001" - } - }` - nfProfilesDb["AUSF-2"] = `{ "nfServices": [ - { - "serviceName": "nausf-auth", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "1.0.0" - } - ], - "scheme": "http", - "nfServiceStatus": "REGISTERED", - "ipEndPoints": [ - { - "ipv4Address": "ausf", - "port": 29509 - } - ], - "serviceInstanceId": "67d0a167-5283-4170-bdd8-881076049a81" - } - ], - "ausfInfo": { - "supiRanges": [ - { "pattern": "^imsi-22345678904[0-9]{4}$" } - ] - }, - "nfInstanceId": "67d0a167-5283-4170-bdd8-881076049a81", - "nfType": "AUSF", - "nfStatus": "REGISTERED", - "plmnList": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "ipv4Addresses": [ - "ausf" - ], - "ausfInfo": { - "groupId": "ausfGroup001" - } - }` - - nfProfilesDb["AMF-01"] = ` - { - "nfServices": [ - { - "serviceInstanceId": "0", - "serviceName": "namf-comm", - "versions": [ - { - "apiVersionInUri": "v1", - "apiFullVersion": "1.0.0" - } - ], - "scheme": "http", - "nfServiceStatus": "REGISTERED", - "ipEndPoints": [ - { - "ipv4Address": "amf", - "transport": "TCP", - "port": 29518 - } - ], - "apiPrefix": "http://amf:29518" - } - ], - "nfInstanceId": "9f7d5a3f-88ab-4525-b31e-334da7faedab", - "nfType": "AMF", - "nfStatus": "REGISTERED", - "plmnList": [ - { - "mcc": "208", - "mnc": "93" - } - ], - "sNssais": [ - { - "sst": 1, - "sd": "010203" - } - ], - "ipv4Addresses": [ - "amf" - ], - "amfInfo": { - "amfSetId": "3f8", - "amfRegionId": "ca", - "guamiList": [ - { - "plmnId": { - "mcc": "208", - "mnc": "93" - }, - "amfId": "cafe00" - } - ], - "taiList": [ - { - "plmnId": { - "mcc": "208", - "mnc": "93" - }, - "tac": "1" - } - ] - } - } ` - -} - -func MarshToJsonString(v interface{}) (result []string) { - types := reflect.TypeOf(v) - val := reflect.ValueOf(v) - if types.Kind() == reflect.Slice { - for i := 0; i < val.Len(); i++ { - tmp, err := json.Marshal(val.Index(i).Interface()) - if err != nil { - logger.UtilLog.Errorf("Marshal error: %+v", err) - } - - result = append(result, string(tmp)) - } - } else { - tmp, err := json.Marshal(v) - if err != nil { - logger.UtilLog.Errorf("Marshal error: %+v", err) - } - - result = append(result, string(tmp)) - } - return -} - -func getNfProfile(key string) (models.NfProfile, error) { - var err error - var profile models.NfProfile - - nfProfileStr, exists := nfProfilesDb[key] - - if exists { - err = json.Unmarshal([]byte(nfProfileStr), &profile) - } else { - err = fmt.Errorf("failed to find nf profile for %s", key) - } - - return profile, err -} - -func getNfProfiles(targetNfType models.NfType) ([]models.NfProfile, error) { - var nfProfiles []models.NfProfile - - for key, elem := range nfProfilesDb { - if strings.Contains(key, string(targetNfType)) { - - var profile models.NfProfile - err := json.Unmarshal([]byte(elem), &profile) - if err != nil { - return nil, err - } - - nfProfiles = append(nfProfiles, profile) - } - } - - return nfProfiles, nil -} - -func nrfDbCallback(nrfUri string, targetNfType, requestNfType models.NfType, - param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) { - fmt.Println("nrfDbCallback Entry") - - nrfDbCallbackCallCount++ - - var searchResult models.SearchResult - var nfProfile models.NfProfile - var err error - var key string - - searchResult.ValidityPeriod = validityPeriod - - if targetNfType == models.NfType_SMF { - key = "SMF" - - if param != nil { - if param.Snssais.IsSet() { - snssais := param.Snssais.Value().([]string) - - var snssai models.Snssai - err = json.Unmarshal([]byte(snssais[0]), &snssai) - if err != nil { - err = fmt.Errorf("snssai invalid %s", snssais[0]) - return searchResult, err - } - - key += "-" + snssai.Sd - } - if param.Dnn.IsSet() == true { - key += "-" + param.Dnn.Value() - } - - nfProfile, err = getNfProfile(key) - if err != nil { - return searchResult, err - } - - searchResult.NfInstances = append(searchResult.NfInstances, nfProfile) - - } else { - searchResult.NfInstances, err = getNfProfiles(targetNfType) - } - } else if targetNfType == models.NfType_AUSF { - searchResult.NfInstances, err = getNfProfiles(targetNfType) - } else if targetNfType == models.NfType_AMF { - searchResult.NfInstances, err = getNfProfiles(targetNfType) - } - - return searchResult, err -} - -func TestCacheMissAndHits(t *testing.T) { - - var result models.SearchResult - var err error - - expectedCallCount := nrfDbCallbackCallCount - - evictionTimerVal := time.Duration(evictionInterval) - InitNrfCaching(evictionTimerVal*time.Second, nrfDbCallback) - - // Cache Miss for dnn - 'internet' - param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}), - Dnn: optional.NewString("internet"), - Snssais: optional.NewInterface(MarshToJsonString([]models.Snssai{{Sst: 1, Sd: "010203"}})), - } - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - expectedCallCount++ - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - - // Cache hit scenario - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - - // Cache Miss for dnn 'ims' - param = Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}), - Dnn: optional.NewString("ims"), - Snssais: optional.NewInterface(MarshToJsonString([]models.Snssai{{Sst: 1, Sd: "010203"}})), - } - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - expectedCallCount++ - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - - // Cache Miss for dnn 'internet' sd '0a0b0c' - param = Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}), - Dnn: optional.NewString("internet"), - Snssais: optional.NewInterface(MarshToJsonString([]models.Snssai{{Sst: 1, Sd: "0a0b0c"}})), - } - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - expectedCallCount++ - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - - disableNrfCaching() -} - -func TestCacheMissOnTTlExpiry(t *testing.T) { - var result models.SearchResult - var err error - - expectedCallCount := nrfDbCallbackCallCount - - evictionTimerVal := time.Duration(evictionInterval) - InitNrfCaching(evictionTimerVal*time.Second, nrfDbCallback) - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, nil) - expectedCallCount++ - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Errorf("nrfDbCallbackCallCount: expected = %d, current = %d", - expectedCallCount, nrfDbCallbackCallCount) - } - - t.Log("wait for profile validity timeout") - time.Sleep(65 * time.Second) - - // Cache Miss for dnn 'internet' sd '0a0b0c' as ttl expired.. - param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}), - Dnn: optional.NewString("internet"), - Snssais: optional.NewInterface(MarshToJsonString([]models.Snssai{{Sst: 1, Sd: "0a0b0c"}})), - } - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - expectedCallCount++ - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Errorf("nrfDbCallbackCallCount: expected = %d, current = %d", - expectedCallCount, nrfDbCallbackCallCount) - } - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Errorf("nrfDbCallbackCallCount: expected = %d, current = %d", - expectedCallCount, nrfDbCallbackCallCount) - } - - disableNrfCaching() -} - -func TestCacheEviction(t *testing.T) { - var result models.SearchResult - var err error - - evictionTimerVal := time.Duration(evictionInterval) - InitNrfCaching(evictionTimerVal*time.Second, nrfDbCallback) - - // Cache Miss for dnn 'internet' sd '0a0b0c' as ttl expired.. - param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}), - Dnn: optional.NewString("internet"), - Snssais: optional.NewInterface(MarshToJsonString([]models.Snssai{{Sst: 1, Sd: "010203"}})), - } - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - - validityPeriod = 30 - param = Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}), - Dnn: optional.NewString("ims"), - Snssais: optional.NewInterface(MarshToJsonString([]models.Snssai{{Sst: 1, Sd: "010203"}})), - } - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - - validityPeriod = 90 - param = Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}), - Dnn: optional.NewString("internet"), - Snssais: optional.NewInterface(MarshToJsonString([]models.Snssai{{Sst: 1, Sd: "0a0b0c"}})), - } - - result, err = SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - - if len(result.NfInstances) == 0 { - t.Errorf("nf instances len 0") - } - - t.Log("wait for eviction timeout") - - time.Sleep(125 * time.Second) - - disableNrfCaching() -} - -func TestCacheConcurrency(t *testing.T) { - - evictionTimerVal := time.Duration(evictionInterval) - InitNrfCaching(evictionTimerVal*time.Second, nrfDbCallback) - - n := 100 - wg := sync.WaitGroup{} - wg.Add(n) - - param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NSMF_PDUSESSION}), - Dnn: optional.NewString("internet"), - Snssais: optional.NewInterface(MarshToJsonString([]models.Snssai{{Sst: 1, Sd: "010203"}})), - } - - expectedCallCount := nrfDbCallbackCallCount + 1 - - errCh := make(chan error) - - for i := 0; i < n; i++ { - go func() { - _, err := SearchNFInstances("testNrf", models.NfType_SMF, models.NfType_AMF, ¶m) - - if err != nil { - errCh <- err - } - wg.Done() - }() - } - - done := make(chan struct{}) - go func() { - wg.Wait() - close(done) - }() - - select { - case <-done: - case <-time.After(5 * time.Second): - t.Errorf("test timed out") - case e := <-errCh: - t.Errorf("error %s", e.Error()) - - } - - if expectedCallCount != nrfDbCallbackCallCount { - t.Errorf("nrfDbCallbackCallCount: expected = %d, current = %d", - expectedCallCount, nrfDbCallbackCallCount) - } - - disableNrfCaching() -} - -func TestAusfMatchFilters(t *testing.T) { - evictionTimerVal := time.Duration(evictionInterval) - InitNrfCaching(evictionTimerVal*time.Second, nrfDbCallback) - - // Cache Miss for dnn - 'internet' - param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - Supi: optional.NewString("123456789040001"), - } - - expectedCallCount := nrfDbCallbackCallCount - expectedCallCount++ - - result, err := SearchNFInstances("testNrf", models.NfType_AUSF, models.NfType_AMF, ¶m) - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - - result, err = SearchNFInstances("testNrf", models.NfType_AUSF, models.NfType_AMF, ¶m) - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - - param = Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - Supi: optional.NewString("imsi-223456789041111"), - } - - result, err = SearchNFInstances("testNrf", models.NfType_AUSF, models.NfType_AMF, ¶m) - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - disableNrfCaching() -} - -func TestAmfMatchFilters(t *testing.T) { - evictionTimerVal := time.Duration(evictionInterval) - InitNrfCaching(evictionTimerVal*time.Second, nrfDbCallback) - - param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - TargetPlmnList: optional.NewInterface(MarshToJsonString( - []models.PlmnId{{Mcc: "208", Mnc: "93"}, {Mcc: "209", Mnc: "94"}})), - } - - expectedCallCount := nrfDbCallbackCallCount - expectedCallCount++ - - result, err := SearchNFInstances("testNrf", models.NfType_AMF, models.NfType_AMF, ¶m) - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - - param = Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ - TargetPlmnList: optional.NewInterface(MarshToJsonString([]models.PlmnId{{Mcc: "208", Mnc: "93"}})), - AmfRegionId: optional.NewString("ca"), - AmfSetId: optional.NewString("3f8"), - } - - result, err = SearchNFInstances("testNrf", models.NfType_AMF, models.NfType_AMF, ¶m) - - if err != nil { - t.Errorf("test failed, %s", err.Error()) - } - if len(result.NfInstances) == 0 { - t.Error("nrf search did not return any records") - } - if expectedCallCount != nrfDbCallbackCallCount { - t.Error("Unexpected nrfDbCallbackCallCount") - } - - disableNrfCaching() -}