diff --git a/cmd/main.go b/cmd/main.go index 1ef53ab..653fc92 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,27 +1,26 @@ package main import ( - "fmt" "os" "path/filepath" "runtime/debug" - "github.com/asaskevich/govalidator" "github.com/urfave/cli" "github.com/free5gc/udr/internal/logger" - "github.com/free5gc/udr/internal/util" - udr_service "github.com/free5gc/udr/pkg/service" + "github.com/free5gc/udr/pkg/factory" + "github.com/free5gc/udr/pkg/service" + logger_util "github.com/free5gc/util/logger" "github.com/free5gc/util/version" ) -var UDR = &udr_service.UDR{} +var UDR *service.UdrApp func main() { defer func() { if p := recover(); p != nil { // Print stack for panic to log. Fatalf() will let program exit. - logger.AppLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) + logger.MainLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) } }() @@ -29,57 +28,64 @@ func main() { app.Name = "udr" app.Usage = "5G Unified Data Repository (UDR)" app.Action = action - app.Flags = UDR.GetCliCmd() + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + cli.StringSliceFlag{ + Name: "log, l", + Usage: "Output NF log to `FILE`", + }, + } if err := app.Run(os.Args); err != nil { - fmt.Printf("UDR Run error: %v\n", err) + logger.MainLog.Errorf("UDR Run error: %v\n", err) } } -func action(c *cli.Context) error { - if err := initLogFile(c.String("log"), c.String("log5gc")); err != nil { - logger.AppLog.Errorf("%+v", err) +func action(cliCtx *cli.Context) error { + tlsKeyLogPath, err := initLogFile(cliCtx.StringSlice("log")) + if err != nil { return err } - if err := UDR.Initialize(c); err != nil { - switch errType := err.(type) { - case govalidator.Errors: - validErrs := err.(govalidator.Errors).Errors() - for _, validErr := range validErrs { - logger.CfgLog.Errorf("%+v", validErr) - } - default: - logger.CfgLog.Errorf("%+v", errType) - } - logger.CfgLog.Errorf("[-- PLEASE REFER TO SAMPLE CONFIG FILE COMMENTS --]") - return fmt.Errorf("Failed to initialize !!") + logger.MainLog.Infoln("UDR version: ", version.GetVersion()) + cfg, err := factory.ReadConfig(cliCtx.String("config")) + if err != nil { + return err } + factory.UdrConfig = cfg + udr, err := service.NewApp(cfg) + if err != nil { + return err + } + UDR = udr - logger.AppLog.Infoln(c.App.Name) - logger.AppLog.Infoln("UDR version: ", version.GetVersion()) - - UDR.Start() + udr.Start(tlsKeyLogPath) return nil } -func initLogFile(logNfPath, log5gcPath string) error { - UDR.KeyLogPath = util.UdrDefaultKeyLogPath +func initLogFile(logNfPath []string) (string, error) { + logTlsKeyPath := "" - if err := logger.LogFileHook(logNfPath, log5gcPath); err != nil { - return err - } + for _, path := range logNfPath { + if err := logger_util.LogFileHook(logger.Log, path); err != nil { + return "", err + } + if logTlsKeyPath != "" { + continue + } - if logNfPath != "" { - nfDir, _ := filepath.Split(logNfPath) + nfDir, _ := filepath.Split(path) tmpDir := filepath.Join(nfDir, "key") if err := os.MkdirAll(tmpDir, 0o775); err != nil { logger.InitLog.Errorf("Make directory %s failed: %+v", tmpDir, err) - return err + return "", err } - _, name := filepath.Split(util.UdrDefaultKeyLogPath) - UDR.KeyLogPath = filepath.Join(tmpDir, name) + _, name := filepath.Split(factory.UdrDefaultTLSKeyLogPath) + logTlsKeyPath = filepath.Join(tmpDir, name) } - return nil + return logTlsKeyPath, nil } diff --git a/go.mod b/go.mod index 31b4aae..75dfbc7 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,61 @@ module github.com/free5gc/udr -go 1.14 +go 1.17 require ( - github.com/antonfisher/nested-logrus-formatter v1.3.1 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/evanphx/json-patch v0.5.2 - github.com/free5gc/openapi v1.0.4 - github.com/free5gc/util v1.0.3 + github.com/free5gc/openapi v1.0.6 + github.com/free5gc/util v1.0.5-0.20230306071612-a52909216bd2 github.com/gin-gonic/gin v1.7.7 github.com/google/uuid v1.3.0 - github.com/mitchellh/mapstructure v1.4.1 + github.com/mitchellh/mapstructure v1.4.3 github.com/sirupsen/logrus v1.8.1 + github.com/stretchr/testify v1.8.0 github.com/urfave/cli v1.22.5 go.mongodb.org/mongo-driver v1.8.4 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/antihax/optional v1.0.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.13.0 // indirect + github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/golang-jwt/jwt v3.2.1+incompatible // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/kr/pretty v0.2.0 // indirect + github.com/leodido/go-urn v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect + github.com/ugorji/go/codec v1.1.7 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.0.2 // indirect + github.com/xdg-go/stringprep v1.0.2 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/h2non/gock.v1 v1.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index fc26e70..ccb3b26 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ= -github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -45,8 +43,9 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -56,13 +55,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/free5gc/openapi v1.0.4 h1:bC6oqXy8Z+3e532xLMFmrTHvdyv4sNGDPezQSslw5gQ= -github.com/free5gc/openapi v1.0.4/go.mod h1:KRCnnp0GeK0Bl4gnrX79cQAidKXNENf8VRdG0y9R0Fc= -github.com/free5gc/util v1.0.3 h1:or/gqHCAi3j2YKd+nzViRnc/tl1tuuJAYxCao6IbOAU= -github.com/free5gc/util v1.0.3/go.mod h1:DL1Dnryh//Ps5B+hfXbhU1R07fVfrmPs4uuTO4g9yTg= +github.com/free5gc/openapi v1.0.6 h1:ytRjU/YZRI8UhKKyfajXSyGB6s1YDFkJ1weeAGJ8LXw= +github.com/free5gc/openapi v1.0.6/go.mod h1:iw/N0E+FlX44EEx24IBi2EdZW8v+bkj3ETWPGnlK9DI= +github.com/free5gc/util v1.0.5-0.20230306071612-a52909216bd2 h1:FG8KlJ46Epscj3F9XBAKuDGJD9kSKJdstCL9fttjUjE= +github.com/free5gc/util v1.0.5-0.20230306071612-a52909216bd2/go.mod h1:fgV0hXf5arxAWs8D9LfrrfNByZ1IDCWYlgBzncy5GtE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -103,8 +101,10 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -116,8 +116,10 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -140,29 +142,36 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= @@ -172,23 +181,28 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tim-ywliu/nested-logrus-formatter v1.3.2 h1:jugNJ2/CNCI79SxOJCOhwUHeN3O7/7/bj+ZRGOFlCSw= +github.com/tim-ywliu/nested-logrus-formatter v1.3.2/go.mod h1:oGPmcxZB65j9Wo7mCnQKSrKEJtVDqyjD666SGmyStXI= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= @@ -206,6 +220,7 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.8.4 h1:NruvZPPL0PBcRJKmbswoWSrmHeUvzdxA3GCPfD/NEOA= go.mongodb.org/mongo-driver v1.8.4/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -218,8 +233,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -250,6 +267,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -276,15 +294,19 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o= golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -292,8 +314,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -322,18 +345,28 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -379,6 +412,7 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -405,8 +439,9 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -457,11 +492,15 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= @@ -469,8 +508,9 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/context/context.go b/internal/context/context.go index db6f884..5436cff 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -2,9 +2,16 @@ package context import ( "fmt" + "math/rand" + "os" "sync" + "time" + + "github.com/google/uuid" "github.com/free5gc/openapi/models" + "github.com/free5gc/udr/internal/logger" + "github.com/free5gc/udr/pkg/factory" ) var udrContext = UDRContext{} @@ -17,14 +24,15 @@ const ( NUDR_DR UDRServiceType = iota ) -func init() { - UDR_Self().Name = "udr" - UDR_Self().EeSubscriptionIDGenerator = 1 - UDR_Self().SdmSubscriptionIDGenerator = 1 - UDR_Self().SubscriptionDataSubscriptionIDGenerator = 1 - UDR_Self().PolicyDataSubscriptionIDGenerator = 1 - UDR_Self().SubscriptionDataSubscriptions = make(map[subsId]*models.SubscriptionDataSubscriptions) - UDR_Self().PolicyDataSubscriptions = make(map[subsId]*models.PolicyDataSubscription) +func Init() { + GetSelf().Name = "udr" + GetSelf().EeSubscriptionIDGenerator = 1 + GetSelf().SdmSubscriptionIDGenerator = 1 + GetSelf().SubscriptionDataSubscriptionIDGenerator = 1 + GetSelf().PolicyDataSubscriptionIDGenerator = 1 + GetSelf().SubscriptionDataSubscriptions = make(map[subsId]*models.SubscriptionDataSubscriptions) + GetSelf().PolicyDataSubscriptions = make(map[subsId]*models.PolicyDataSubscription) + GetSelf().InfluenceDataSubscriptionIDGenerator = rand.New(rand.NewSource(time.Now().UTC().UnixNano())) } type UDRContext struct { @@ -38,12 +46,14 @@ type UDRContext struct { NrfUri string EeSubscriptionIDGenerator int SdmSubscriptionIDGenerator int + SubscriptionDataSubscriptionIDGenerator int PolicyDataSubscriptionIDGenerator int + InfluenceDataSubscriptionIDGenerator *rand.Rand UESubsCollection sync.Map // map[ueId]*UESubsData UEGroupCollection sync.Map // map[ueGroupId]*UEGroupSubsData - SubscriptionDataSubscriptionIDGenerator int SubscriptionDataSubscriptions map[subsId]*models.SubscriptionDataSubscriptions PolicyDataSubscriptions map[subsId]*models.PolicyDataSubscription + InfluenceDataSubscriptions sync.Map appDataInfluDataSubscriptionIdGenerator uint64 mtx sync.RWMutex } @@ -78,14 +88,54 @@ func (context *UDRContext) Reset() { for key := range context.PolicyDataSubscriptions { delete(context.PolicyDataSubscriptions, key) } + context.InfluenceDataSubscriptions.Range(func(key, value interface{}) bool { + context.InfluenceDataSubscriptions.Delete(key) + return true + }) context.EeSubscriptionIDGenerator = 1 context.SdmSubscriptionIDGenerator = 1 context.SubscriptionDataSubscriptionIDGenerator = 1 context.PolicyDataSubscriptionIDGenerator = 1 + context.InfluenceDataSubscriptionIDGenerator = rand.New(rand.NewSource(time.Now().UTC().UnixNano())) context.UriScheme = models.UriScheme_HTTPS context.Name = "udr" } +func InitUdrContext(context *UDRContext) { + config := factory.UdrConfig + logger.UtilLog.Infof("udrconfig Info: Version[%s] Description[%s]", config.Info.Version, config.Info.Description) + configuration := config.Configuration + context.NfId = uuid.New().String() + context.RegisterIPv4 = factory.UDR_DEFAULT_IPV4 // default localhost + context.SBIPort = factory.UDR_DEFAULT_PORT_INT // default port + if sbi := configuration.Sbi; sbi != nil { + context.UriScheme = models.UriScheme(sbi.Scheme) + if sbi.RegisterIPv4 != "" { + context.RegisterIPv4 = sbi.RegisterIPv4 + } + if sbi.Port != 0 { + context.SBIPort = sbi.Port + } + + context.BindingIPv4 = os.Getenv(sbi.BindingIPv4) + if context.BindingIPv4 != "" { + logger.UtilLog.Info("Parsing ServerIPv4 address from ENV Variable.") + } else { + context.BindingIPv4 = sbi.BindingIPv4 + if context.BindingIPv4 == "" { + logger.UtilLog.Warn("Error parsing ServerIPv4 address as string. Using the 0.0.0.0 address as default.") + context.BindingIPv4 = "0.0.0.0" + } + } + } + if configuration.NrfUri != "" { + context.NrfUri = configuration.NrfUri + } else { + logger.UtilLog.Warn("NRF Uri is empty! Using localhost as NRF IPv4 address.") + context.NrfUri = fmt.Sprintf("%s://%s:%d", context.UriScheme, "127.0.0.1", 29510) + } +} + func (context *UDRContext) GetIPv4Uri() string { return fmt.Sprintf("%s://%s:%d", context.UriScheme, context.RegisterIPv4, context.SBIPort) } @@ -95,7 +145,7 @@ func (context *UDRContext) GetIPv4GroupUri(udrServiceType UDRServiceType) string switch udrServiceType { case NUDR_DR: - serviceUri = "/nudr-dr/v1" + serviceUri = factory.UdrDrResUriPrefix default: serviceUri = "" } @@ -104,7 +154,7 @@ func (context *UDRContext) GetIPv4GroupUri(udrServiceType UDRServiceType) string } // Create new UDR context -func UDR_Self() *UDRContext { +func GetSelf() *UDRContext { return &udrContext } @@ -114,3 +164,10 @@ func (context *UDRContext) NewAppDataInfluDataSubscriptionID() uint64 { context.appDataInfluDataSubscriptionIdGenerator++ return context.appDataInfluDataSubscriptionIdGenerator } + +func NewInfluenceDataSubscriptionId() string { + if GetSelf().InfluenceDataSubscriptionIDGenerator == nil { + GetSelf().InfluenceDataSubscriptionIDGenerator = rand.New(rand.NewSource(time.Now().UTC().UnixNano())) + } + return fmt.Sprintf("%08x", GetSelf().InfluenceDataSubscriptionIDGenerator.Uint32()) +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index b04ed8e..ee28a2c 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,81 +1,42 @@ package logger import ( - "os" - "time" - - formatter "github.com/antonfisher/nested-logrus-formatter" "github.com/sirupsen/logrus" logger_util "github.com/free5gc/util/logger" ) var ( - log *logrus.Logger - AppLog *logrus.Entry + Log *logrus.Logger + NfLog *logrus.Entry + MainLog *logrus.Entry InitLog *logrus.Entry CfgLog *logrus.Entry - HandlerLog *logrus.Entry + CtxLog *logrus.Entry DataRepoLog *logrus.Entry UtilLog *logrus.Entry HttpLog *logrus.Entry ConsumerLog *logrus.Entry GinLog *logrus.Entry + ProcLog *logrus.Entry ) func init() { - log = logrus.New() - log.SetReportCaller(false) - - log.Formatter = &formatter.Formatter{ - TimestampFormat: time.RFC3339Nano, - TrimMessages: true, - NoFieldsSpace: true, - HideKeys: true, - FieldsOrder: []string{"component", "category"}, - } - - AppLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "App"}) - InitLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "Init"}) - CfgLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "CFG"}) - HandlerLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "HDLR"}) - DataRepoLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "DRepo"}) - UtilLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "Util"}) - HttpLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "HTTP"}) - ConsumerLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "Consumer"}) - GinLog = log.WithFields(logrus.Fields{"component": "UDR", "category": "GIN"}) -} - -func LogFileHook(logNfPath string, log5gcPath string) error { - if fullPath, err := logger_util.CreateFree5gcLogFile(log5gcPath); err == nil { - if fullPath != "" { - free5gcLogHook, hookErr := logger_util.NewFileHook(fullPath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0o666) - if hookErr != nil { - return hookErr - } - log.Hooks.Add(free5gcLogHook) - } - } else { - return err + fieldsOrder := []string{ + logger_util.FieldNF, + logger_util.FieldCategory, } - if fullPath, err := logger_util.CreateNfLogFile(logNfPath, "udr.log"); err == nil { - selfLogHook, hookErr := logger_util.NewFileHook(fullPath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0o666) - if hookErr != nil { - return hookErr - } - log.Hooks.Add(selfLogHook) - } else { - return err - } - - return nil -} - -func SetLogLevel(level logrus.Level) { - log.SetLevel(level) -} - -func SetReportCaller(enable bool) { - log.SetReportCaller(enable) + Log = logger_util.New(fieldsOrder) + NfLog = Log.WithField(logger_util.FieldNF, "UDR") + MainLog = NfLog.WithField(logger_util.FieldCategory, "Main") + InitLog = NfLog.WithField(logger_util.FieldCategory, "Init") + CfgLog = NfLog.WithField(logger_util.FieldCategory, "CFG") + CtxLog = NfLog.WithField(logger_util.FieldCategory, "CTX") + GinLog = NfLog.WithField(logger_util.FieldCategory, "GIN") + ConsumerLog = NfLog.WithField(logger_util.FieldCategory, "Consumer") + DataRepoLog = NfLog.WithField(logger_util.FieldCategory, "DataRepo") + ProcLog = NfLog.WithField(logger_util.FieldCategory, "Proc") + HttpLog = NfLog.WithField(logger_util.FieldCategory, "HTTP") + UtilLog = NfLog.WithField(logger_util.FieldCategory, "Util") } diff --git a/internal/sbi/consumer/nf_managemant.go b/internal/sbi/consumer/nf_managemant.go index fac302a..953695e 100644 --- a/internal/sbi/consumer/nf_managemant.go +++ b/internal/sbi/consumer/nf_managemant.go @@ -106,7 +106,7 @@ func SendRegisterNFInstance(nrfUri, nfInstanceId string, profile models.NfProfil func SendDeregisterNFInstance() (problemDetails *models.ProblemDetails, err error) { logger.ConsumerLog.Infof("Send Deregister NFInstance") - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() // Set client and set url configuration := Nnrf_NFManagement.NewConfiguration() configuration.SetBasePath(udrSelf.NrfUri) diff --git a/internal/sbi/datarepository/api_default.go b/internal/sbi/datarepository/api_default.go index 3491195..a187d67 100644 --- a/internal/sbi/datarepository/api_default.go +++ b/internal/sbi/datarepository/api_default.go @@ -30,7 +30,7 @@ func sendResponse(c *gin.Context, rsp *httpwrapper.Response) { serializedBody, err := openapi.Serialize(rsp.Body, "application/json") if err != nil { logger.DataRepoLog.Errorf("Serialize Response Body error: %+v", err) - pd := util.ProblemDetailsSystemFailure(err.Error()) + pd := openapi.ProblemDetailsSystemFailure(err.Error()) c.JSON(http.StatusInternalServerError, pd) } else { c.Data(rsp.Status, "application/json", serializedBody) @@ -41,7 +41,7 @@ func getDataFromRequestBody(c *gin.Context, data interface{}) error { reqBody, err := c.GetRawData() if err != nil { logger.DataRepoLog.Errorf("Get Request Body error: %+v", err) - pd := util.ProblemDetailsSystemFailure(err.Error()) + pd := openapi.ProblemDetailsSystemFailure(err.Error()) c.JSON(http.StatusInternalServerError, pd) return err } @@ -56,94 +56,6 @@ func getDataFromRequestBody(c *gin.Context, data interface{}) error { return err } -// HTTPApplicationDataInfluenceDataGet - -func HTTPApplicationDataInfluenceDataGet(c *gin.Context) { - queryParams := c.Request.URL.Query() - rsp := producer.HandleApplicationDataInfluenceDataGet(queryParams) - sendResponse(c, rsp) -} - -// HTTPApplicationDataInfluenceDataInfluenceIdDelete - -func HTTPApplicationDataInfluenceDataInfluenceIdDelete(c *gin.Context) { - rsp := producer.HandleApplicationDataInfluenceDataInfluenceIdDelete(c.Params.ByName("influenceId")) - sendResponse(c, rsp) -} - -// HTTPApplicationDataInfluenceDataInfluenceIdPatch - -func HTTPApplicationDataInfluenceDataInfluenceIdPatch(c *gin.Context) { - var trInfluDataPatch models.TrafficInfluDataPatch - - if err := getDataFromRequestBody(c, &trInfluDataPatch); err != nil { - return - } - - rsp := producer.HandleApplicationDataInfluenceDataInfluenceIdPatch(c.Params.ByName("influenceId"), - &trInfluDataPatch) - - sendResponse(c, rsp) -} - -// HTTPApplicationDataInfluenceDataInfluenceIdPut - -func HTTPApplicationDataInfluenceDataInfluenceIdPut(c *gin.Context) { - var trInfluData models.TrafficInfluData - - if err := getDataFromRequestBody(c, &trInfluData); err != nil { - return - } - - rsp := producer.HandleApplicationDataInfluenceDataInfluenceIdPut(c.Params.ByName("influenceId"), &trInfluData) - - sendResponse(c, rsp) -} - -// HTTPApplicationDataInfluenceDataSubsToNotifyGet - -func HTTPApplicationDataInfluenceDataSubsToNotifyGet(c *gin.Context) { - queryParams := c.Request.URL.Query() - rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifyGet(queryParams) - sendResponse(c, rsp) -} - -// HTTPApplicationDataInfluenceDataSubsToNotifyPost - -func HTTPApplicationDataInfluenceDataSubsToNotifyPost(c *gin.Context) { - var trInfluSub models.TrafficInfluSub - - if err := getDataFromRequestBody(c, &trInfluSub); err != nil { - return - } - - rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifyPost(&trInfluSub) - - sendResponse(c, rsp) -} - -// HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete - -func HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete(c *gin.Context) { - rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete( - c.Params.ByName("subscriptionId")) - sendResponse(c, rsp) -} - -// HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet - -func HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet(c *gin.Context) { - rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet( - c.Params.ByName("subscriptionId")) - sendResponse(c, rsp) -} - -// HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut - -func HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut(c *gin.Context) { - var trInfluSub models.TrafficInfluSub - - if err := getDataFromRequestBody(c, &trInfluSub); err != nil { - return - } - - rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut( - c.Params.ByName("subscriptionId"), &trInfluSub) - - sendResponse(c, rsp) -} - // HTTPApplicationDataPfdsAppIdDelete - func HTTPApplicationDataPfdsAppIdDelete(c *gin.Context) { rsp := producer.HandleApplicationDataPfdsAppIdDelete(c.Params.ByName("appId")) diff --git a/internal/sbi/datarepository/api_individual_influence_data_document.go b/internal/sbi/datarepository/api_individual_influence_data_document.go new file mode 100644 index 0000000..06fc18a --- /dev/null +++ b/internal/sbi/datarepository/api_individual_influence_data_document.go @@ -0,0 +1,111 @@ +/* + * Nudr_DataRepository API OpenAPI file + * + * Unified Data Repository Service + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package datarepository + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/models" + "github.com/free5gc/udr/internal/logger" + "github.com/free5gc/udr/internal/sbi/producer" + "github.com/free5gc/util/httpwrapper" +) + +// HTTPApplicationDataInfluenceDataInfluenceIdDelete - Delete an individual Influence Data resource +func HTTPApplicationDataInfluenceDataInfluenceIdDelete(c *gin.Context) { + // New HTTP request + req := httpwrapper.NewRequest(c.Request, nil) + req.Params["influenceId"] = c.Params.ByName("influenceId") + + // Handle request + rsp := producer.HandleApplicationDataInfluenceDataInfluenceIdDelete(req) + + if rsp.Body != nil { + // Serialize response body + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.DataRepoLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else { + c.Data(rsp.Status, "application/json", responseBody) + } + } else { + c.Data(rsp.Status, "", nil) + } +} + +// HTTPApplicationDataInfluenceDataInfluenceIdPatch - +// Modify part of the properties of an individual Influence Data resource +func HTTPApplicationDataInfluenceDataInfluenceIdPatch(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{}) +} + +// HTTPApplicationDataInfluenceDataInfluenceIdPut - Create or update an individual Influence Data resource +func HTTPApplicationDataInfluenceDataInfluenceIdPut(c *gin.Context) { + // Get HTTP request body + requestBody, err := c.GetRawData() + if err != nil { + problemDetail := models.ProblemDetails{ + Title: "System failure", + Status: http.StatusInternalServerError, + Detail: err.Error(), + Cause: "SYSTEM_FAILURE", + } + logger.DataRepoLog.Errorf("Get Request Body error: %+v", err) + c.JSON(http.StatusInternalServerError, problemDetail) + return + } + + // Deserialize request body + var trafficInfluData models.TrafficInfluData + err = openapi.Deserialize(&trafficInfluData, requestBody, "application/json") + if err != nil { + problemDetail := "[Request Body] " + err.Error() + rsp := models.ProblemDetails{ + Title: "Malformed request syntax", + Status: http.StatusBadRequest, + Detail: problemDetail, + } + logger.DataRepoLog.Errorln(problemDetail) + c.JSON(http.StatusBadRequest, rsp) + return + } + + // New HTTP request + req := httpwrapper.NewRequest(c.Request, trafficInfluData) + req.Params["influenceId"] = c.Params.ByName("influenceId") + + // Handle request + rsp := producer.HandleApplicationDataInfluenceDataInfluenceIdPut(req) + + // Serialize response body + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.DataRepoLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else { + // Get the first value associated with `Location` key in the header + c.Header("Location", rsp.Header.Get("Location")) + c.Data(rsp.Status, "application/json", responseBody) + } +} diff --git a/internal/sbi/datarepository/api_individual_influence_data_subscription_document.go b/internal/sbi/datarepository/api_individual_influence_data_subscription_document.go new file mode 100644 index 0000000..4235d61 --- /dev/null +++ b/internal/sbi/datarepository/api_individual_influence_data_subscription_document.go @@ -0,0 +1,127 @@ +/* + * Nudr_DataRepository API OpenAPI file + * + * Unified Data Repository Service + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package datarepository + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/models" + "github.com/free5gc/udr/internal/logger" + "github.com/free5gc/udr/internal/sbi/producer" + "github.com/free5gc/util/httpwrapper" +) + +// HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete - +func HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete(c *gin.Context) { + // New HTTP request + req := httpwrapper.NewRequest(c.Request, nil) + req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") + + // Handle request + rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete(req) + + if rsp.Body != nil { + // Serialize response body + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.DataRepoLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else { + c.Data(rsp.Status, "application/json", responseBody) + } + } else { + c.Data(rsp.Status, "", nil) + } +} + +// HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet - +func HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet(c *gin.Context) { + // New HTTP request + req := httpwrapper.NewRequest(c.Request, nil) + req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") + + // Handle request + rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet(req) + + // Serialize response body + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.DataRepoLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else { + c.Data(rsp.Status, "application/json", responseBody) + } +} + +// HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut - +func HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut(c *gin.Context) { + // Get HTTP request body + requestBody, err := c.GetRawData() + if err != nil { + problemDetail := models.ProblemDetails{ + Title: "System failure", + Status: http.StatusInternalServerError, + Detail: err.Error(), + Cause: "SYSTEM_FAILURE", + } + logger.DataRepoLog.Errorf("Get Request Body error: %+v", err) + c.JSON(http.StatusInternalServerError, problemDetail) + return + } + + // Deserialize request body + var trafficInfluSub models.TrafficInfluSub + err = openapi.Deserialize(&trafficInfluSub, requestBody, "application/json") + if err != nil { + problemDetail := "[Request Body] " + err.Error() + rsp := models.ProblemDetails{ + Title: "Malformed request syntax", + Status: http.StatusBadRequest, + Detail: problemDetail, + } + logger.DataRepoLog.Errorln(problemDetail) + c.JSON(http.StatusBadRequest, rsp) + return + } + + // New HTTP request + req := httpwrapper.NewRequest(c.Request, trafficInfluSub) + req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") + + // Handle request + rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut(req) + + // Serialize response body + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.DataRepoLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else { + c.Data(rsp.Status, "application/json", responseBody) + } +} diff --git a/internal/sbi/datarepository/api_influence_data.go b/internal/sbi/datarepository/api_influence_data.go new file mode 100644 index 0000000..bc27e10 --- /dev/null +++ b/internal/sbi/datarepository/api_influence_data.go @@ -0,0 +1,45 @@ +/* + * Nudr_DataRepository API OpenAPI file + * + * Unified Data Repository Service + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package datarepository + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/models" + "github.com/free5gc/udr/internal/logger" + "github.com/free5gc/udr/internal/sbi/producer" + "github.com/free5gc/util/httpwrapper" +) + +// HTTPApplicationDataInfluenceDataGet - +func HTTPApplicationDataInfluenceDataGet(c *gin.Context) { + req := httpwrapper.NewRequest(c.Request, nil) + req.Query["influence-Ids"] = c.QueryArray("influence-Ids") + req.Query["dnns"] = c.QueryArray("dnns") + req.Query["snssais"] = c.QueryArray("snssais") + req.Query["internal-Group-Ids"] = c.QueryArray("internal-Group-Id") + req.Query["supis"] = c.QueryArray("supis") + rsp := producer.HandleApplicationDataInfluenceDataGet(req) + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.DataRepoLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else { + c.Data(rsp.Status, "application/json", responseBody) + } +} diff --git a/internal/sbi/datarepository/api_influence_data_subscriptions_collection.go b/internal/sbi/datarepository/api_influence_data_subscriptions_collection.go new file mode 100644 index 0000000..4e513f5 --- /dev/null +++ b/internal/sbi/datarepository/api_influence_data_subscriptions_collection.go @@ -0,0 +1,103 @@ +/* + * Nudr_DataRepository API OpenAPI file + * + * Unified Data Repository Service + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package datarepository + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/models" + "github.com/free5gc/udr/internal/logger" + "github.com/free5gc/udr/internal/sbi/producer" + "github.com/free5gc/util/httpwrapper" +) + +// HTTPApplicationDataInfluenceDataSubsToNotifyGet - +func HTTPApplicationDataInfluenceDataSubsToNotifyGet(c *gin.Context) { + // New HTTP request + req := httpwrapper.NewRequest(c.Request, nil) + req.Query["dnn"] = c.QueryArray("dnn") + req.Query["snssai"] = c.QueryArray("snssai") + req.Query["internal-Group-Id"] = c.QueryArray("internal-Group-Id") + req.Query["supi"] = c.QueryArray("supi") + + // Handle request + rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifyGet(req) + + // Serialize response body + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.DataRepoLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else { + c.Data(rsp.Status, "application/json", responseBody) + } +} + +// HTTPApplicationDataInfluenceDataSubsToNotifyPost - +func HTTPApplicationDataInfluenceDataSubsToNotifyPost(c *gin.Context) { + // Get HTTP request body + requestBody, err := c.GetRawData() + if err != nil { + problemDetail := models.ProblemDetails{ + Title: "System failure", + Status: http.StatusInternalServerError, + Detail: err.Error(), + Cause: "SYSTEM_FAILURE", + } + logger.DataRepoLog.Errorf("Get Request Body error: %+v", err) + c.JSON(http.StatusInternalServerError, problemDetail) + return + } + + // Deserialize request body + var trafficInfluSub models.TrafficInfluSub + err = openapi.Deserialize(&trafficInfluSub, requestBody, "application/json") + if err != nil { + problemDetail := "[Request Body] " + err.Error() + rsp := models.ProblemDetails{ + Title: "Malformed request syntax", + Status: http.StatusBadRequest, + Detail: problemDetail, + } + logger.DataRepoLog.Errorln(problemDetail) + c.JSON(http.StatusBadRequest, rsp) + return + } + + // New HTTP request + req := httpwrapper.NewRequest(c.Request, trafficInfluSub) + + // Handle request + rsp := producer.HandleApplicationDataInfluenceDataSubsToNotifyPost(req) + + // Serialize response body + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.DataRepoLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else { + // Get the first value associated with `Location` key in the header + c.Header("Location", rsp.Header.Get("Location")) + c.Data(rsp.Status, "application/json", responseBody) + } +} diff --git a/internal/sbi/datarepository/api_influence_data_subscriptions_test.go b/internal/sbi/datarepository/api_influence_data_subscriptions_test.go new file mode 100644 index 0000000..44e7ba3 --- /dev/null +++ b/internal/sbi/datarepository/api_influence_data_subscriptions_test.go @@ -0,0 +1,102 @@ +package datarepository_test + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/free5gc/openapi/models" + "github.com/free5gc/udr/pkg/factory" +) + +func TestUDR_GetSubs2Notify_GetBeforeCreateingOne(t *testing.T) { + server := setupHttpServer() + reqUri := factory.UdrDrResUriPrefix + "/application-data/influenceData/subs-to-notify?dnn=internet" + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, reqUri, nil) + require.Nil(t, err) + rsp := httptest.NewRecorder() + server.ServeHTTP(rsp, req) + + t.Run("UDR subs-to-notify Get before Create, dnn==internet", func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, "null", rsp.Body.String()) + }) +} + +func TestUDR_GetSubs2Notify_CreateThenGet(t *testing.T) { + server := setupHttpServer() + baseUri := factory.UdrDrResUriPrefix + "/application-data/influenceData/subs-to-notify" + reqUri := baseUri + + test := models.TrafficInfluSub{ + Dnns: []string{"internet", "outernet"}, + Snssais: []models.Snssai{{ + Sst: 1, Sd: "010203", + }, { + Sst: 1, Sd: "112233", + }}, + NotificationUri: "http://127.0.0.1/notifyMePlease", + } + bjson, err := json.Marshal(test) + require.Nil(t, err) + reqBody := bytes.NewReader(bjson) + + test.NotificationUri = "" + bjson_bad, err := json.Marshal(test) + require.Nil(t, err) + reqBody_bad := bytes.NewReader(bjson_bad) + + // Create one - w/o the mandatory NotificationUri: + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, reqUri, reqBody_bad) + require.Nil(t, err) + rsp := httptest.NewRecorder() + server.ServeHTTP(rsp, req) + t.Run("UDR subs-to-notify CreateThenGet - Create w/o mandatory notificationUri", func(t *testing.T) { + require.Equal(t, http.StatusBadRequest, rsp.Code) + }) + + // Create one - normal + req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, reqUri, reqBody) + require.Nil(t, err) + rsp = httptest.NewRecorder() + server.ServeHTTP(rsp, req) + // Linter complains not closing response body even tried to close, + // ==> remove the comments manually to test if location header is there. + // location := rsp.Result().Header.Get("Location") + // err = rsp.Result().Body.Close() + // require.Nil(t, err) + // require.NotNil(t, location) + // t.Log("location:", location) + t.Run("UDR subs-to-notify CreateThenGet - Create normal case", func(t *testing.T) { + require.Equal(t, http.StatusCreated, rsp.Code) + require.Equal(t, string(bjson), rsp.Body.String()) + // require.True(t, strings.Contains(location, baseUri+"/")) + // require.True(t, strings.HasPrefix(location, udr_context.GetSelf().GetIPv4Uri()+baseUri+"/")) + }) + + // Get success + rsp = getUri(t, baseUri, "?dnn=internet") + t.Run("UDR subs-to-notify CreateThenGet - get", func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, "["+string(bjson)+"]", rsp.Body.String()) + }) + + // Get without a filter + rsp = getUri(t, baseUri, "") + t.Run("UDR subs-to-notify CreateThenGet - get w/o a filter", func(t *testing.T) { + require.Equal(t, http.StatusBadRequest, rsp.Code) + }) + + // Get a non-exist DNN + rsp = getUri(t, baseUri, "?dnn=ThisIsABadDNN") + t.Run("UDR subs-to-notify CreateThenGet - get bad DNN", func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, "null", rsp.Body.String()) + }) +} diff --git a/internal/sbi/datarepository/api_influence_data_test.go b/internal/sbi/datarepository/api_influence_data_test.go new file mode 100644 index 0000000..a1c415a --- /dev/null +++ b/internal/sbi/datarepository/api_influence_data_test.go @@ -0,0 +1,258 @@ +package datarepository_test + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/free5gc/openapi/models" + "github.com/free5gc/udr/pkg/factory" +) + +type testdata struct { + influId string + supi string +} + +func getInfluData(supi string) *models.TrafficInfluData { + return &models.TrafficInfluData{ + Dnn: "internet", + Snssai: &models.Snssai{ + Sst: 1, Sd: "010203", + }, + Supi: supi, + TrafficFilters: []models.FlowInfo{{ + FlowId: 1, + FlowDescriptions: []string{"permit out ip from 60.60.0.1 8080 to any"}, + }}, + TrafficRoutes: []models.RouteToLocation{{ + Dnai: "edge1", RouteProfId: "1", + }, { + Dnai: "edge2", RouteProfId: "2", + }}, + NwAreaInfo: &models.NetworkAreaInfo{ + Tais: []models.Tai{{ + PlmnId: &models.PlmnId{ + Mcc: "208", Mnc: "93", + }, + Tac: "1", + }}, + }, + } +} + +func getUri(t *testing.T, baseUri, extUri string) *httptest.ResponseRecorder { + server := setupHttpServer() + reqUri := baseUri + extUri + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, reqUri, nil) + require.Nil(t, err) + rsp := httptest.NewRecorder() + server.ServeHTTP(rsp, req) + return rsp +} + +func postPutInfluData(t *testing.T, method string, baseUri, extUri string, influData *models.TrafficInfluData) ( + *httptest.ResponseRecorder, []byte, +) { + server := setupHttpServer() + reqUri := baseUri + extUri + bjson, err := json.Marshal(influData) + require.Nil(t, err) + reqBody := bytes.NewReader(bjson) + req, err := http.NewRequestWithContext(context.Background(), method, reqUri, reqBody) + require.Nil(t, err) + rsp := httptest.NewRecorder() + server.ServeHTTP(rsp, req) + return rsp, bjson +} + +func postInfluData(t *testing.T, baseUri, extUri string, influData *models.TrafficInfluData) ( + *httptest.ResponseRecorder, []byte, +) { + return postPutInfluData(t, http.MethodPost, baseUri, extUri, influData) +} + +func putInfluData(t *testing.T, baseUri, extUri string, influData *models.TrafficInfluData) ( + *httptest.ResponseRecorder, []byte, +) { + return postPutInfluData(t, http.MethodPut, baseUri, extUri, influData) +} + +func delUri(t *testing.T, baseUri, extUri string) *httptest.ResponseRecorder { + server := setupHttpServer() + reqUri := baseUri + extUri + req, err := http.NewRequestWithContext(context.Background(), http.MethodDelete, reqUri, nil) + require.Nil(t, err) + rsp := httptest.NewRecorder() + server.ServeHTTP(rsp, req) + return rsp +} + +func TestUDR_InfluData_GetBeforeCreateing(t *testing.T) { + server := setupHttpServer() + reqUri := factory.UdrDrResUriPrefix + "/application-data/influenceData" + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, reqUri, nil) + require.Nil(t, err) + rsp := httptest.NewRecorder() + server.ServeHTTP(rsp, req) + + t.Run("UDR influ-data Get before Create", + func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, "[]", rsp.Body.String()) + }) +} + +func TestUDR_InfluData_CreateThenGet(t *testing.T) { + // PUT, PATCH, DELETE + setupMongoDB(t) + server := setupHttpServer() + baseUri := factory.UdrDrResUriPrefix + "/application-data/influenceData" + td1 := testdata{"/influenceId0001", "imsi-208930000000001"} + td2 := testdata{"/influenceId0002", "imsi-208930000000002"} + + // Create one - bad method (POST not allowed) + influData := getInfluData(td1.supi) + rsp, _ := postInfluData(t, baseUri, td1.influId, influData) + t.Run("UDR influ-data CreateThenGet - Create one - bad method", + func(t *testing.T) { + require.Equal(t, http.StatusMethodNotAllowed, rsp.Code) + }) + + // Create one - normal + influData = getInfluData(td1.supi) + rsp, bjson := putInfluData(t, baseUri, td1.influId, influData) + t.Run("UDR influ-data CreateThenGet - Create normal case", + func(t *testing.T) { + require.Equal(t, http.StatusCreated, rsp.Code) + require.Equal(t, string(bjson), rsp.Body.String()) + }) + + // Create one - update existing one with identical data + influData = getInfluData(td1.supi) + rsp, bjson = putInfluData(t, baseUri, td1.influId, influData) + t.Run("UDR influ-data CreateThenGet - Create - update existing one-identical data", + func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, string(bjson), rsp.Body.String()) + }) + + // Create one - update existing one with some difference + influData = getInfluData(td1.supi) + influData.Snssai.Sst = 2 + rsp, bjson = putInfluData(t, baseUri, td1.influId, influData) + t.Run("UDR influ-data CreateThenGet - Create - update existing one-with some difference", + func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, string(bjson), rsp.Body.String()) + }) + + // Note: NOT WORING + // Patch - update existing one with some difference + // influData = &models.TrafficInfluData{ + // Snssai: &models.Snssai{ + // Sst: 1, Sd: "995995", + // }} + // bjson, err = json.Marshal(influData) + // require.Nil(t, err) + // reqBody = bytes.NewReader(bjson) + // req, err = http.NewRequestWithContext(context.Background(), http.MethodPatch, reqUri, reqBody) + // require.Nil(t, err) + // rsp = httptest.NewRecorder() + // server.ServeHTTP(rsp, req) + // t.Run("UDR influ-data CreateThenGet - Patch - update existing one-with some difference", + // func(t *testing.T) { + // require.Equal(t, http.StatusNoContent, rsp.Code) + // require.Equal(t, string(bjson), rsp.Body.String()) + // }) + + // Get success + rsp = getUri(t, baseUri, "?dnns="+influData.Dnn) + testRsp := []models.TrafficInfluData{} + err := json.Unmarshal(rsp.Body.Bytes(), &testRsp) + require.Nil(t, err) + t.Run("UDR influ-data CreateThenGet - get", + func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, influData.Dnn, testRsp[0].Dnn) + require.Equal(t, influData.Snssai, testRsp[0].Snssai) + // ResUri differs here + }) + + // Create with td2 - normal + influData = getInfluData(td2.supi) + rsp, bjson = putInfluData(t, baseUri, td2.influId, influData) + t.Run("UDR influ-data CreateThenGet - Create normal case", + func(t *testing.T) { + require.Equal(t, http.StatusCreated, rsp.Code) + require.Equal(t, string(bjson), rsp.Body.String()) + }) + + // Get - 2 influencesIds + rsp = getUri(t, baseUri, "?dnns="+influData.Dnn) + err = json.Unmarshal(rsp.Body.Bytes(), &testRsp) + t.Log(rsp.Body.String()) + require.Nil(t, err) + t.Run("UDR influ-data CreateThenGet - get - 2 influData", + func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, 2, len(testRsp)) + }) + + // Get a non-exist Supi + rsp = getUri(t, baseUri, "?Supi=BadSupi") + err = json.Unmarshal(rsp.Body.Bytes(), &testRsp) + require.Nil(t, err) + t.Run("UDR influ-data CreateThenGet - Bad DNN", + func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + // expect zero influData + require.Equal(t, 0, len(testRsp)) + }) + + // Delete td2 + reqUri := baseUri + td2.influId + req, err := http.NewRequestWithContext(context.Background(), http.MethodDelete, reqUri, nil) + require.Nil(t, err) + rsp = httptest.NewRecorder() + server.ServeHTTP(rsp, req) + t.Run("UDR influ-data CreateThenGet - Delete td2", + func(t *testing.T) { + require.Equal(t, http.StatusNoContent, rsp.Code) + }) + + // Get - 1 influenceId left + rsp = getUri(t, baseUri, "?dnns="+influData.Dnn) + err = json.Unmarshal(rsp.Body.Bytes(), &testRsp) + t.Log(rsp.Body.String()) + require.Nil(t, err) + t.Run("UDR influ-data CreateThenGet - get - 1 influData left", + func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, 1, len(testRsp)) + }) + + // Delete td1 + rsp = delUri(t, baseUri, td1.influId) + t.Run("UDR influ-data CreateThenGet - Delete td1", + func(t *testing.T) { + require.Equal(t, http.StatusNoContent, rsp.Code) + }) + + // Get without a filter - 0 influenceId + rsp = getUri(t, baseUri, "?dnns="+influData.Dnn) + err = json.Unmarshal(rsp.Body.Bytes(), &testRsp) + t.Log(rsp.Body.String()) + require.Nil(t, err) + t.Run("UDR influ-data CreateThenGet - get - all influData deleted", + func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, 0, len(testRsp)) + }) +} diff --git a/internal/sbi/datarepository/api_sanity_test.go b/internal/sbi/datarepository/api_sanity_test.go new file mode 100644 index 0000000..880ff47 --- /dev/null +++ b/internal/sbi/datarepository/api_sanity_test.go @@ -0,0 +1,50 @@ +package datarepository_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" + + "github.com/free5gc/udr/internal/logger" + "github.com/free5gc/udr/internal/sbi/datarepository" + "github.com/free5gc/udr/internal/sbi/producer" + "github.com/free5gc/udr/pkg/factory" + util_logger "github.com/free5gc/util/logger" + "github.com/free5gc/util/mongoapi" +) + +func setupHttpServer() *gin.Engine { + ginEngine := util_logger.NewGinWithLogrus(logger.GinLog) + datarepository.AddService(ginEngine) + return ginEngine +} + +func setupMongoDB(t *testing.T) { + err := mongoapi.SetMongoDB("test5gc", "mongodb://localhost:27017") + require.Nil(t, err) + err = mongoapi.Drop(producer.APPDATA_INFLUDATA_DB_COLLECTION_NAME) + require.Nil(t, err) + err = mongoapi.Drop(producer.APPDATA_INFLUDATA_SUBSC_DB_COLLECTION_NAME) + require.Nil(t, err) + err = mongoapi.Drop(producer.APPDATA_PFD_DB_COLLECTION_NAME) + require.Nil(t, err) +} + +func TestUDR_Root(t *testing.T) { + server := setupHttpServer() + reqUri := factory.UdrDrResUriPrefix + "/" + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, reqUri, nil) + require.Nil(t, err) + rsp := httptest.NewRecorder() + server.ServeHTTP(rsp, req) + + t.Run("UDR Root", func(t *testing.T) { + require.Equal(t, http.StatusOK, rsp.Code) + require.Equal(t, "Hello World!", rsp.Body.String()) + }) +} diff --git a/internal/sbi/datarepository/api_subs_to_nofify_collection.go b/internal/sbi/datarepository/api_subs_to_notify_collection.go similarity index 100% rename from internal/sbi/datarepository/api_subs_to_nofify_collection.go rename to internal/sbi/datarepository/api_subs_to_notify_collection.go diff --git a/internal/sbi/datarepository/routers.go b/internal/sbi/datarepository/routers.go index 9c92d83..645e601 100644 --- a/internal/sbi/datarepository/routers.go +++ b/internal/sbi/datarepository/routers.go @@ -16,6 +16,7 @@ import ( "github.com/gin-gonic/gin" "github.com/free5gc/udr/internal/logger" + "github.com/free5gc/udr/pkg/factory" logger_util "github.com/free5gc/util/logger" ) @@ -106,22 +107,20 @@ func eeMsgDispatchHandlerFunc(c *gin.Context) { c.String(http.StatusMethodNotAllowed, "Method Not Allowed") } -// Handler to distinguish "subs-to-notify" from ":influenceId". -func appInfluDataMsgDispatchHandlerFunc(c *gin.Context) { - influID := c.Param("influenceId") - for _, route := range appInfluDataRoutes { - if route.Method == c.Request.Method { - if influID == "subs-to-notify" { - if strings.Contains(route.Pattern, "subs-to-notify") { - route.HandlerFunc(c) - return - } - } else { - if !strings.Contains(route.Pattern, "subs-to-notify") { - route.HandlerFunc(c) - return - } - } +func appMsgDispatchHandlerFunc(c *gin.Context) { + subsToNotify := c.Param("influenceId") + for _, route := range appRoutes { + if subsToNotify == "subs-to-notify" && + strings.Contains(route.Pattern, "subs-to-notify") && + route.Method == c.Request.Method { + route.HandlerFunc(c) + return + } + if subsToNotify != "subs-to-notify" && + !strings.Contains(route.Pattern, "subs-to-notify") && + route.Method == c.Request.Method { + route.HandlerFunc(c) + return } } c.String(http.StatusMethodNotAllowed, "Method Not Allowed") @@ -146,7 +145,7 @@ func expoMsgDispatchHandlerFunc(c *gin.Context) { } func AddService(engine *gin.Engine) *gin.RouterGroup { - group := engine.Group("/nudr-dr/v1") + group := engine.Group(factory.UdrDrResUriPrefix) for _, route := range routes { switch route.Method { @@ -182,9 +181,8 @@ func AddService(engine *gin.Engine) *gin.RouterGroup { * Only can use '/application-data/influenceData/:influenceId' pattern and * use a dispatch handler to distinguish "subs-to-notify" from ":influenceId". */ - appInfluDataPattern := "/application-data/influenceData/:influenceId" - group.Any(appInfluDataPattern, appInfluDataMsgDispatchHandlerFunc) - + appPattern := "/application-data/influenceData/:influenceId" + group.Any(appPattern, appMsgDispatchHandlerFunc) expoPatternShort := "/exposure-data/:ueId/:subId" group.Any(expoPatternShort, expoMsgDispatchHandlerFunc) @@ -199,6 +197,36 @@ func Index(c *gin.Context) { c.String(http.StatusOK, "Hello World!") } +// HandleAppDataInfluDataSubsToNotifyConflictDelete filters invalid requested resource on subs-to-notify DELETE +func HandleAppDataInfluDataSubsToNotifyConflictDelete(c *gin.Context) { + influenceId := c.Param("influenceId") + if influenceId == "subs-to-notify" { + HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete(c) + return + } + c.String(http.StatusNotFound, "404 page not found") +} + +// HandleAppDataInfluDataSubsToNotifyConflictGet filters invalid requested resource on subs-to-notify GET +func HandleAppDataInfluDataSubsToNotifyConflictGet(c *gin.Context) { + influenceId := c.Param("influenceId") + if influenceId == "subs-to-notify" { + HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet(c) + return + } + c.String(http.StatusNotFound, "404 page not found") +} + +// HandleAppDataInfluDataSubsToNotifyConflictPut filters invalid requested resource on subs-to-notify PUT +func HandleAppDataInfluDataSubsToNotifyConflictPut(c *gin.Context) { + influenceId := c.Param("influenceId") + if influenceId == "subs-to-notify" { + HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut(c) + return + } + c.String(http.StatusNotFound, "404 page not found") +} + var routes = Routes{ { "Index", @@ -316,21 +344,24 @@ var routes = Routes{ "HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete", strings.ToUpper("Delete"), "/application-data/influenceData/:influenceId/:subscriptionId", - HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete, + HandleAppDataInfluDataSubsToNotifyConflictDelete, + // HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete, }, { "HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet", strings.ToUpper("Get"), "/application-data/influenceData/:influenceId/:subscriptionId", - HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet, + HandleAppDataInfluDataSubsToNotifyConflictGet, + // HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet, }, { "HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut", strings.ToUpper("Put"), "/application-data/influenceData/:influenceId/:subscriptionId", - HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut, + HandleAppDataInfluDataSubsToNotifyConflictPut, + // HTTPApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut, }, { @@ -877,7 +908,7 @@ var expoRoutes = Routes{ }, } -var appInfluDataRoutes = Routes{ +var appRoutes = Routes{ { "HTTPApplicationDataInfluenceDataSubsToNotifyGet", strings.ToUpper("Get"), @@ -912,18 +943,4 @@ var appInfluDataRoutes = Routes{ "/application-data/influenceData/:influenceId", HTTPApplicationDataInfluenceDataInfluenceIdPut, }, - - { - "HTTPApplicationDataInfluenceDataSubsToNotifyGet", - strings.ToUpper("Get"), - "/application-data/influenceData/:influenceId", - HTTPApplicationDataInfluenceDataSubsToNotifyGet, - }, - - { - "HTTPApplicationDataInfluenceDataSubsToNotifyPost", - strings.ToUpper("Post"), - "/application-data/influenceData/:influenceId", - HTTPApplicationDataInfluenceDataSubsToNotifyPost, - }, } diff --git a/internal/sbi/producer/callback.go b/internal/sbi/producer/callback.go index a2f37ee..5d5ed21 100644 --- a/internal/sbi/producer/callback.go +++ b/internal/sbi/producer/callback.go @@ -1,7 +1,10 @@ package producer import ( + "fmt" + "github.com/free5gc/openapi/models" + udr_context "github.com/free5gc/udr/internal/context" "github.com/free5gc/udr/internal/sbi/producer/callback" ) @@ -61,3 +64,10 @@ func PreHandlePolicyDataChangeNotification(ueId string, dataId string, value int go callback.SendPolicyDataChangeNotification(policyDataChangeNotification) } + +func PreHandleInfluenceDataUpdateNotification(influenceId string, original, modified *models.TrafficInfluData) { + resUri := fmt.Sprintf("%s/application-data/influenceData/%s", + udr_context.GetSelf().GetIPv4GroupUri(udr_context.NUDR_DR), influenceId) + + go callback.SendInfluenceDataUpdateNotification(resUri, original, modified) +} diff --git a/internal/sbi/producer/callback/callback.go b/internal/sbi/producer/callback/callback.go index 172e8d3..d68245c 100644 --- a/internal/sbi/producer/callback/callback.go +++ b/internal/sbi/producer/callback/callback.go @@ -8,6 +8,7 @@ import ( "github.com/free5gc/openapi/models" udr_context "github.com/free5gc/udr/internal/context" "github.com/free5gc/udr/internal/logger" + "github.com/free5gc/udr/internal/util" ) func SendOnDataChangeNotify(ueId string, notifyItems []models.NotifyItem) { @@ -18,7 +19,7 @@ func SendOnDataChangeNotify(ueId string, notifyItems []models.NotifyItem) { } }() - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() configuration := Nudr_DataRepository.NewConfiguration() client := Nudr_DataRepository.NewAPIClient(configuration) @@ -33,11 +34,9 @@ func SendOnDataChangeNotify(ueId string, notifyItems []models.NotifyItem) { httpResponse, err := client.DataChangeNotifyCallbackDocumentApi.OnDataChangeNotify(context.TODO(), onDataChangeNotifyUrl, dataChangeNotify) if err != nil { - if httpResponse == nil { - logger.HttpLog.Errorln(err.Error()) - } else if err.Error() != httpResponse.Status { - logger.HttpLog.Errorln(err.Error()) - } + logger.HttpLog.Errorln(err.Error()) + } else if httpResponse == nil { + logger.HttpLog.Errorln("Empty HTTP response") } } } @@ -51,21 +50,82 @@ func SendPolicyDataChangeNotification(policyDataChangeNotification models.Policy } }() - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() for _, policyDataSubscription := range udrSelf.PolicyDataSubscriptions { policyDataChangeNotificationUrl := policyDataSubscription.NotificationUri configuration := Nudr_DataRepository.NewConfiguration() client := Nudr_DataRepository.NewAPIClient(configuration) - httpResponse, err := client.PolicyDataChangeNotificationCallbackDocumentApi.PolicyDataChangeNotification( + httpResponse, err := client.PolicyDataChangeNotifyCallbackDocumentApi.PolicyDataChangeNotify( context.TODO(), policyDataChangeNotificationUrl, policyDataChangeNotification) if err != nil { - if httpResponse == nil { + logger.HttpLog.Errorln(err.Error()) + } else if httpResponse == nil { + logger.HttpLog.Errorln("Empty HTTP response") + } + } +} + +func SendInfluenceDataUpdateNotification(resUri string, original, modified *models.TrafficInfluData) { + udrSelf := udr_context.GetSelf() + + configuration := Nudr_DataRepository.NewConfiguration() + client := Nudr_DataRepository.NewAPIClient(configuration) + + var trafficInfluDataNotif models.TrafficInfluDataNotif + trafficInfluDataNotif.ResUri = resUri + udrSelf.InfluenceDataSubscriptions.Range(func(key, value interface{}) bool { + influenceDataSubscription, ok := value.(*models.TrafficInfluSub) + if !ok { + logger.HttpLog.Errorf("Failed to load influenceData subscription ID [%+v]", key) + return true + } + influenceDataChangeNotificationUrl := influenceDataSubscription.NotificationUri + + // Check if the modified data is subscribed + // If positive, send notification about the update + if checkInfluenceDataSubscription(modified, influenceDataSubscription) { + logger.HttpLog.Tracef("Send notification about update of influence data") + trafficInfluDataNotif.TrafficInfluData = modified + httpResponse, err := client.InfluenceDataUpdateNotifyCallbackDocumentApi.InfluenceDataChangeNotify(context.TODO(), + influenceDataChangeNotificationUrl, []models.TrafficInfluDataNotif{trafficInfluDataNotif}) + if err != nil { logger.HttpLog.Errorln(err.Error()) - } else if err.Error() != httpResponse.Status { + } else if httpResponse == nil { + logger.HttpLog.Errorln("Empty HTTP response") + } + } else if checkInfluenceDataSubscription(original, influenceDataSubscription) { + // If the modified data is not subscribed or nil, check if the original data is subscribed + // If positive, send notification about the removal + logger.HttpLog.Tracef("Send notification about removal of influence data") + trafficInfluDataNotif.TrafficInfluData = nil + httpResponse, err := client.InfluenceDataUpdateNotifyCallbackDocumentApi.InfluenceDataChangeNotify(context.TODO(), + influenceDataChangeNotificationUrl, []models.TrafficInfluDataNotif{trafficInfluDataNotif}) + if err != nil { logger.HttpLog.Errorln(err.Error()) + } else if httpResponse == nil { + logger.HttpLog.Errorln("Empty HTTP response") } } + return true + }) +} + +func checkInfluenceDataSubscription(data *models.TrafficInfluData, sub *models.TrafficInfluSub) bool { + if data == nil || sub == nil { + return false + } + if data.Dnn != "" && !util.Contain(data.Dnn, sub.Dnns) { + return false + } else if data.Snssai != nil && !util.Contain(*data.Snssai, sub.Snssais) { + return false + } else if data.InterGroupId != "AnyUE" { + if data.InterGroupId != "" && !util.Contain(data.InterGroupId, sub.InternalGroupIds) { + return false + } else if data.Supi != "" && !util.Contain(data.Supi, sub.Supis) { + return false + } } + return true } diff --git a/internal/sbi/producer/data_repository.go b/internal/sbi/producer/data_repository.go index 25cd6b7..d66ccfd 100644 --- a/internal/sbi/producer/data_repository.go +++ b/internal/sbi/producer/data_repository.go @@ -11,12 +11,13 @@ import ( jsonpatch "github.com/evanphx/json-patch" "github.com/mitchellh/mapstructure" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/free5gc/openapi" "github.com/free5gc/openapi/models" udr_context "github.com/free5gc/udr/internal/context" "github.com/free5gc/udr/internal/logger" "github.com/free5gc/udr/internal/util" + "github.com/free5gc/udr/pkg/factory" "github.com/free5gc/util/httpwrapper" "github.com/free5gc/util/mongoapi" ) @@ -32,7 +33,7 @@ var CurrentResourceUri string func getDataFromDB(collName string, filter bson.M) (map[string]interface{}, *models.ProblemDetails) { data, err := mongoapi.RestfulAPIGetOne(collName, filter) if err != nil { - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, openapi.ProblemDetailsSystemFailure(err.Error()) } if data == nil { return nil, util.ProblemDetailsNotFound("DATA_NOT_FOUND") @@ -419,388 +420,525 @@ func QueryAuthenticationStatusProcedure(collName string, ueId string) (*map[stri return &data, nil } -func HandleApplicationDataInfluenceDataGet(queryParams map[string][]string) *httpwrapper.Response { - logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataGet: queryParams=%#v", queryParams) - - influIDs := queryParams["influence-Ids"] - dnns := queryParams["dnns"] - snssais := queryParams["snssais"] - intGroupIDs := queryParams["internal-Group-Ids"] - supis := queryParams["supis"] - if len(influIDs) == 0 && len(dnns) == 0 && len(snssais) == 0 && len(intGroupIDs) == 0 && len(supis) == 0 { - pd := util.ProblemDetailsMalformedReqSyntax("No query parameters") - return httpwrapper.NewResponse(int(pd.Status), nil, pd) - } - - response := getApplicationDataInfluenceDatafromDB(influIDs, dnns, snssais, intGroupIDs, supis) - +func HandleApplicationDataInfluenceDataGet(request *httpwrapper.Request) *httpwrapper.Response { + logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataGet") + collName := "applicationData.influenceData" + var filter []bson.M + influenceIdsParam := request.Query["influence-Ids"] + dnnsParam := request.Query["dnns"] + internalGroupIdsParam := request.Query["internal-Group-Ids"] + supisParam := request.Query["supis"] + snssaisParam := request.Query["snssais"] + if len(influenceIdsParam) != 0 { + influenceIds := strings.Split(influenceIdsParam[0], ",") + filter = append(filter, bson.M{"influenceId": bson.M{"$in": influenceIds}}) + } + if len(dnnsParam) != 0 { + dnns := strings.Split(dnnsParam[0], ",") + filter = append(filter, bson.M{"dnn": bson.M{"$in": dnns}}) + } + if len(internalGroupIdsParam) != 0 { + internalGroupIds := strings.Split(internalGroupIdsParam[0], ",") + withAnyUeIndFilter := []bson.M{ + { + "interGroupId": bson.M{"$in": internalGroupIds}, + }, + { + "interGroupId": "AnyUE", + }, + } + filter = append(filter, bson.M{"$or": withAnyUeIndFilter}) + } else if len(supisParam) != 0 { + supis := strings.Split(supisParam[0], ",") + withAnyUeIndFilter := []bson.M{ + { + "supi": bson.M{"$in": supis}, + }, + { + "interGroupId": "AnyUE", + }, + } + filter = append(filter, bson.M{"$or": withAnyUeIndFilter}) + } + if len(snssaisParam) != 0 { + snssais := parseSnssaisFromQueryParam(snssaisParam[0]) + // NOTE: The following code would have bugs with several tries that return null value from Mongo DB, while most of + // tries would be correct. The errors seem to occur only when the receiving filters on Mongo DB have reverse + // orders of snssai fields, i.e. first sd then sst, even though bson.M{} is used + // matchList := buildSnssaiMatchList(snssais) + // filter = append(filter, bson.M{"snssai": bson.M{"$in": matchList}}) + matchList := buildSnssaiMatchList(snssais) + filter = append(filter, bson.M{"$or": matchList}) + } + response := ApplicationDataInfluenceDataGetProcedure(collName, filter) return httpwrapper.NewResponse(http.StatusOK, nil, response) } -func getApplicationDataInfluenceDatafromDB(influIDs, dnns, snssais, - intGroupIDs, supis []string, -) []map[string]interface{} { - filter := bson.M{} - allInfluDatas, err := mongoapi.RestfulAPIGetMany(APPDATA_INFLUDATA_DB_COLLECTION_NAME, filter) +func parseSnssaisFromQueryParam(snssaiStr string) []models.Snssai { + var snssais []models.Snssai + err := json.Unmarshal([]byte("["+snssaiStr+"]"), &snssais) if err != nil { - logger.DataRepoLog.Errorf("getApplicationDataInfluenceDatafromDB err: %+v", err) - return nil - } - var matchedInfluDatas []map[string]interface{} - matchedInfluDatas = filterDataByString("influenceId", influIDs, allInfluDatas) - matchedInfluDatas = filterDataByString("dnn", dnns, matchedInfluDatas) - matchedInfluDatas = filterDataByString("interGroupId", intGroupIDs, matchedInfluDatas) - matchedInfluDatas = filterDataByString("supi", supis, matchedInfluDatas) - matchedInfluDatas = filterDataBySnssai(snssais, matchedInfluDatas) - for i := 0; i < len(matchedInfluDatas); i++ { - // Delete "influenceId" entry which is added by us - delete(matchedInfluDatas[i], "influenceId") - } - return matchedInfluDatas -} - -func filterDataByString(filterName string, filterValues []string, - datas []map[string]interface{}, -) []map[string]interface{} { - if len(filterValues) == 0 { - return datas - } - var matchedDatas []map[string]interface{} - for _, data := range datas { - for _, v := range filterValues { - if data[filterName].(string) == v { - matchedDatas = append(matchedDatas, data) - break - } - } + logger.DataRepoLog.Warnln("Unmarshal Error in snssaiStruct", err) } - return matchedDatas + return snssais } -func filterDataBySnssai(snssaiValues []string, - datas []map[string]interface{}, -) []map[string]interface{} { - if len(snssaiValues) == 0 { - return datas - } - var matchedDatas []map[string]interface{} - for _, data := range datas { - var dataSnssai models.Snssai - if err := json.Unmarshal( - util.MapToByte(data["snssai"].(map[string]interface{})), &dataSnssai); err != nil { - logger.DataRepoLog.Warnln(err) - break - } - logger.DataRepoLog.Debugf("dataSnssai=%#v", dataSnssai) - for _, v := range snssaiValues { - var filterSnssai models.Snssai - if err := json.Unmarshal([]byte(v), &filterSnssai); err != nil { - logger.DataRepoLog.Warnln(err) - break - } - logger.DataRepoLog.Debugf("filterSnssai=%#v", filterSnssai) - if dataSnssai.Sd == filterSnssai.Sd && dataSnssai.Sst == filterSnssai.Sst { - matchedDatas = append(matchedDatas, data) - break - } - } +// func buildSnssaiMatchList(snssais []models.Snssai) (matchList []bson.M) { +// for _, v := range snssais { +// bsonData := util.ToBsonM(v) +// matchList = append(matchList, bsonData) +// } +// return +// } + +func buildSnssaiMatchList(snssais []models.Snssai) (matchList []bson.M) { + for _, v := range snssais { + matchList = append(matchList, bson.M{"snssai.sst": v.Sst, "snssai.sd": v.Sd}) } - return matchedDatas + return } -func HandleApplicationDataInfluenceDataInfluenceIdDelete(influId string) *httpwrapper.Response { - logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataInfluenceIdDelete: influId=%q", influId) - - deleteApplicationDataIndividualInfluenceDataFromDB(influId) - - return httpwrapper.NewResponse(http.StatusNoContent, nil, map[string]interface{}{}) +func ApplicationDataInfluenceDataGetProcedure(collName string, filter []bson.M) ( + response *[]map[string]interface{}, +) { + influenceDataArray := make([]map[string]interface{}, 0) + if len(filter) != 0 { + var err error + influenceDataArray, err = mongoapi.RestfulAPIGetMany(collName, bson.M{"$and": filter}) + if err != nil { + logger.DataRepoLog.Errorf("ApplicationDataInfluenceDataGetProcedure err: %+v", err) + return nil + } + } + for _, influenceData := range influenceDataArray { + groupUri := udr_context.GetSelf().GetIPv4GroupUri(udr_context.NUDR_DR) + influenceData["resUri"] = fmt.Sprintf("%s/application-data/influenceData/%s", + groupUri, influenceData["influenceId"].(string)) + delete(influenceData, "_id") + delete(influenceData, "influenceId") + } + return &influenceDataArray } -func deleteApplicationDataIndividualInfluenceDataFromDB(influId string) { - filter := bson.M{"influenceId": influId} - deleteDataFromDB(APPDATA_INFLUDATA_DB_COLLECTION_NAME, filter) -} +func HandleApplicationDataInfluenceDataInfluenceIdDelete(request *httpwrapper.Request) *httpwrapper.Response { + logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataInfluenceIdDelete") -func HandleApplicationDataInfluenceDataInfluenceIdPatch(influID string, - trInfluDataPatch *models.TrafficInfluDataPatch, -) *httpwrapper.Response { - logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataInfluenceIdPatch: influID=%q", influID) + collName := "applicationData.influenceData" + influenceId := request.Params["influenceId"] + status := ApplicationDataInfluenceDataInfluenceIdDeleteProcedure(collName, influenceId) - response, status := patchApplicationDataIndividualInfluenceDataToDB(influID, trInfluDataPatch) + if status == http.StatusNoContent { + return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) + } - return httpwrapper.NewResponse(status, nil, response) + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "UNSPECIFIED", + } + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } -func patchApplicationDataIndividualInfluenceDataToDB(influID string, - trInfluDataPatch *models.TrafficInfluDataPatch, -) (bson.M, int) { - filter := bson.M{"influenceId": influID} +func ApplicationDataInfluenceDataInfluenceIdDeleteProcedure(collName, influenceId string) int { + filter := bson.M{"influenceId": influenceId} - oldData, pd := getDataFromDB(APPDATA_INFLUDATA_DB_COLLECTION_NAME, filter) - if pd != nil { - logger.DataRepoLog.Errorf("patchApplicationDataIndividualInfluenceDataToDB err: %s", pd.Detail) - return nil, http.StatusNotFound + mapData, err := mongoapi.RestfulAPIGetOne(collName, filter) + if err != nil { + logger.DataRepoLog.Errorf("ApplicationDataInfluenceDataInfluenceIdDeleteProcedure err: %+v", err) + return http.StatusInternalServerError + } + var original *models.TrafficInfluData + if len(mapData) != 0 { + original = new(models.TrafficInfluData) + byteData, err := json.Marshal(mapData) + if err != nil { + logger.DataRepoLog.Errorf(err.Error()) + return http.StatusInternalServerError + } + err = json.Unmarshal(byteData, &original) + if err != nil { + logger.DataRepoLog.Errorf(err.Error()) + return http.StatusInternalServerError + } } - trInfluData := models.TrafficInfluData{ - UpPathChgNotifCorreId: trInfluDataPatch.UpPathChgNotifCorreId, - AppReloInd: trInfluDataPatch.AppReloInd, - AfAppId: oldData["afAppId"].(string), - Dnn: trInfluDataPatch.Dnn, - EthTrafficFilters: trInfluDataPatch.EthTrafficFilters, - Snssai: trInfluDataPatch.Snssai, - InterGroupId: trInfluDataPatch.InternalGroupId, - Supi: trInfluDataPatch.Supi, - TrafficFilters: trInfluDataPatch.TrafficFilters, - TrafficRoutes: trInfluDataPatch.TrafficRoutes, - ValidStartTime: trInfluDataPatch.ValidStartTime, - ValidEndTime: trInfluDataPatch.ValidEndTime, - NwAreaInfo: trInfluDataPatch.NwAreaInfo, - UpPathChgNotifUri: trInfluDataPatch.UpPathChgNotifUri, - } - newData := util.ToBsonM(trInfluData) - - // Add "influenceId" entry to DB - newData["influenceId"] = influID - if _, err := mongoapi.RestfulAPIPutOne(APPDATA_INFLUDATA_DB_COLLECTION_NAME, filter, newData); err != nil { - logger.DataRepoLog.Errorf("patchApplicationDataIndividualInfluenceDataToDB err: %+v", err) - return nil, http.StatusInternalServerError + if err := mongoapi.RestfulAPIDeleteOne(collName, filter); err != nil { + logger.DataRepoLog.Errorf("InfluIdDelProcedure: %+v", err) + return http.StatusInternalServerError } - // Roll back to origin data before return - delete(newData, "influenceId") - return newData, http.StatusOK + // Notify the change of influence data + PreHandleInfluenceDataUpdateNotification(influenceId, original, nil) + + return http.StatusNoContent } -func HandleApplicationDataInfluenceDataInfluenceIdPut(influID string, - trInfluData *models.TrafficInfluData, -) *httpwrapper.Response { - logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataInfluenceIdPut: influID=%q", influID) +func HandleApplicationDataInfluenceDataInfluenceIdPatch(request *httpwrapper.Request) *httpwrapper.Response { + logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataInfluenceIdPatch") - response, status := putApplicationDataIndividualInfluenceDataToDB(influID, trInfluData) + collName := "application.influenceData" + influenceId := request.Params["influenceId"] + trafficInfluDataPatch := request.Body.(models.TrafficInfluDataPatch) - return httpwrapper.NewResponse(status, nil, response) + response, status := ApplicationDataInfluenceDataInfluenceIdPatchProcedure( + collName, influenceId, &trafficInfluDataPatch) + + if status == http.StatusOK || status == http.StatusNoContent { + return httpwrapper.NewResponse(status, nil, response) + } else if status == http.StatusNotFound { + problemDetails := models.ProblemDetails{ + Status: http.StatusNotFound, + Detail: "Resource not found", + } + return httpwrapper.NewResponse(status, nil, problemDetails) + } + + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "UNSPECIFIED", + } + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } -func putApplicationDataIndividualInfluenceDataToDB(influID string, - trInfluData *models.TrafficInfluData, -) (bson.M, int) { - filter := bson.M{"influenceId": influID} - data := util.ToBsonM(*trInfluData) +func ApplicationDataInfluenceDataInfluenceIdPatchProcedure( + collName, influenceId string, request *models.TrafficInfluDataPatch) ( + *models.TrafficInfluDataPatch, int, +) { + filter := bson.M{"influenceId": influenceId} - // Add "influenceId" entry to DB - data["influenceId"] = influID - existed, err := mongoapi.RestfulAPIPutOne(APPDATA_INFLUDATA_DB_COLLECTION_NAME, filter, data) + mapData, err := mongoapi.RestfulAPIGetOne(collName, filter) if err != nil { - logger.DataRepoLog.Errorf("putApplicationDataIndividualInfluenceDataToDB err: %+v", err) + logger.DataRepoLog.Errorf(err.Error()) return nil, http.StatusInternalServerError } + var original *models.TrafficInfluData + if len(mapData) != 0 { + original = new(models.TrafficInfluData) + byteData, err := json.Marshal(mapData) + if err != nil { + logger.DataRepoLog.Errorf(err.Error()) + return nil, http.StatusInternalServerError + } + err = json.Unmarshal(byteData, &original) + if err != nil { + logger.DataRepoLog.Errorf(err.Error()) + return nil, http.StatusInternalServerError + } + } else { + return nil, http.StatusNotFound + } - // Roll back to origin data before return - delete(data, "influenceId") - - if existed { - return data, http.StatusOK + patchTrafficInfluData := models.TrafficInfluData{ + UpPathChgNotifCorreId: request.UpPathChgNotifCorreId, + AppReloInd: request.AppReloInd, + AfAppId: original.AfAppId, + Dnn: request.Dnn, + EthTrafficFilters: request.EthTrafficFilters, + Snssai: request.Snssai, + InterGroupId: request.InternalGroupId, + Supi: request.Supi, + TrafficFilters: request.TrafficFilters, + TrafficRoutes: request.TrafficRoutes, + TraffCorreInd: original.TraffCorreInd, + ValidStartTime: request.ValidStartTime, + ValidEndTime: request.ValidEndTime, + TempValidities: original.TempValidities, + NwAreaInfo: request.NwAreaInfo, + UpPathChgNotifUri: request.UpPathChgNotifUri, + SubscribedEvents: original.SubscribedEvents, + DnaiChgType: original.DnaiChgType, + AfAckInd: original.AfAckInd, + AddrPreserInd: original.AddrPreserInd, + SupportedFeatures: original.SupportedFeatures, + ResUri: original.ResUri, + } + + if reflect.DeepEqual(*original, patchTrafficInfluData) { + return nil, http.StatusNoContent + } else { + putData := util.ToBsonM(patchTrafficInfluData) + putData["influenceId"] = influenceId + if _, err := mongoapi.RestfulAPIPutOne(collName, filter, putData); err != nil { + return nil, http.StatusInternalServerError + } + // Notify the change of influence data + PreHandleInfluenceDataUpdateNotification(influenceId, original, &patchTrafficInfluData) + return request, http.StatusOK } - return data, http.StatusCreated } -func HandleApplicationDataInfluenceDataSubsToNotifyGet(queryParams map[string][]string) *httpwrapper.Response { - logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataSubsToNotifyGet: queryParams=%#v", queryParams) +func HandleApplicationDataInfluenceDataInfluenceIdPut(request *httpwrapper.Request) *httpwrapper.Response { + logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataInfluenceIdPut") - dnn := queryParams["dnn"] - snssai := queryParams["snssai"] - intGroupID := queryParams["internal-Group-Id"] - supi := queryParams["supi"] - if len(dnn) == 0 && len(snssai) == 0 && len(intGroupID) == 0 && len(supi) == 0 { - pd := util.ProblemDetailsMalformedReqSyntax("No query parameters") - return httpwrapper.NewResponse(int(pd.Status), nil, pd) - } - if len(dnn) > 1 { - pd := util.ProblemDetailsMalformedReqSyntax("Too many dnn query parameters") - return httpwrapper.NewResponse(int(pd.Status), nil, pd) - } - if len(snssai) > 1 { - pd := util.ProblemDetailsMalformedReqSyntax("Too many snssai query parameters") - return httpwrapper.NewResponse(int(pd.Status), nil, pd) - } - if len(intGroupID) > 1 { - pd := util.ProblemDetailsMalformedReqSyntax("Too many internal-Group-Id query parameters") - return httpwrapper.NewResponse(int(pd.Status), nil, pd) + collName := "applicationData.influenceData" + influenceId := request.Params["influenceId"] + trafficInfluData := request.Body.(models.TrafficInfluData) + + response, problemDetails, status := ApplicationDataInfluenceDataInfluenceIdPutProcedure( + collName, influenceId, &trafficInfluData) + if status == http.StatusCreated { + // According to 3GPP TS 29.519 V16.5.0 clause 6.2.6.3.1 + // Contain the URI of the newly created resource with `Location` key in the header + groupUri := udr_context.GetSelf().GetIPv4GroupUri(udr_context.NUDR_DR) + resourceUri := fmt.Sprintf("%s/application-data/influenceData/%s", groupUri, influenceId) + header := http.Header{ + "Location": {resourceUri}, + } + return httpwrapper.NewResponse(http.StatusCreated, header, response) + } else if status == http.StatusOK { + return httpwrapper.NewResponse(http.StatusOK, nil, response) + } else if status == http.StatusNoContent { + return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) + } else if problemDetails != nil { + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } - if len(supi) > 1 { - pd := util.ProblemDetailsMalformedReqSyntax("Too many supi query parameters") - return httpwrapper.NewResponse(int(pd.Status), nil, pd) + + problemDetails = &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "UNSPECIFIED", } + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) +} - response := getApplicationDataInfluenceDataSubsToNotifyfromDB(dnn, snssai, intGroupID, supi) +func ApplicationDataInfluenceDataInfluenceIdPutProcedure( + collName, influenceId string, request *models.TrafficInfluData) ( + *models.TrafficInfluData, *models.ProblemDetails, int, +) { + putData := util.ToBsonM(*request) + putData["influenceId"] = influenceId + filter := bson.M{"influenceId": influenceId} - return httpwrapper.NewResponse(http.StatusOK, nil, response) -} + var original *models.TrafficInfluData -func getApplicationDataInfluenceDataSubsToNotifyfromDB(dnn, snssai, intGroupID, - supi []string, -) []map[string]interface{} { - filter := bson.M{} - if len(dnn) != 0 { - filter["dnns"] = dnn[0] - } - if len(intGroupID) != 0 { - filter["internalGroupIds"] = intGroupID[0] - } - if len(supi) != 0 { - filter["supis"] = supi[0] + if mapData, err := mongoapi.RestfulAPIGetOne(collName, filter); err != nil { + logger.DataRepoLog.Errorf(err.Error()) + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Detail: err.Error(), + } + return nil, problemDetails, http.StatusInternalServerError + } else { + if len(mapData) != 0 { + original = new(models.TrafficInfluData) + byteData, err := json.Marshal(mapData) + if err != nil { + logger.DataRepoLog.Errorf(err.Error()) + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Detail: err.Error(), + } + return nil, problemDetails, http.StatusInternalServerError + } + err = json.Unmarshal(byteData, &original) + if err != nil { + logger.DataRepoLog.Errorf(err.Error()) + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Detail: err.Error(), + } + return nil, problemDetails, http.StatusInternalServerError + } + } } - matchedSubs, err := mongoapi.RestfulAPIGetMany(APPDATA_INFLUDATA_SUBSC_DB_COLLECTION_NAME, filter) + + isExisted, err := mongoapi.RestfulAPIPutOne(collName, filter, putData) if err != nil { - logger.DataRepoLog.Errorf("getApplicationDataInfluenceDataSubsToNotifyfromDB err: %+v", err) - return nil + logger.DataRepoLog.Errorf("ApplicationDataInfluenceDataInfluenceIdPutProcedure err: %+v", err) + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Detail: err.Error(), + } + return nil, problemDetails, http.StatusInternalServerError } - if len(snssai) != 0 { - matchedSubs = filterDataBySnssais(snssai[0], matchedSubs) + if original == nil || !reflect.DeepEqual(*original, *request) { + // Notify the change of influence data + PreHandleInfluenceDataUpdateNotification(influenceId, original, request) } - for i := 0; i < len(matchedSubs); i++ { - // Delete "_id" entry which is auto-inserted by MongoDB - delete(matchedSubs[i], "_id") - // Delete "subscriptionId" entry which is added by us - delete(matchedSubs[i], "subscriptionId") + + if isExisted { + return request, nil, http.StatusOK + } else { + return request, nil, http.StatusCreated } - return matchedSubs } -func filterDataBySnssais(snssaiValue string, - datas []map[string]interface{}, -) []map[string]interface{} { - var matchedDatas []map[string]interface{} - var filterSnssai models.Snssai - if err := json.Unmarshal([]byte(snssaiValue), &filterSnssai); err != nil { - logger.DataRepoLog.Warnln(err) - } - logger.DataRepoLog.Debugf("filterSnssai=%#v", filterSnssai) - for _, data := range datas { - var dataSnssais []models.Snssai - if err := json.Unmarshal( - util.PrimitiveAToByte(data["snssais"].(primitive.A)), &dataSnssais); err != nil { - logger.DataRepoLog.Warnln(err) - break - } - logger.DataRepoLog.Debugf("dataSnssais=%#v", dataSnssais) - for _, v := range dataSnssais { - if v.Sd == filterSnssai.Sd && v.Sst == filterSnssai.Sst { - matchedDatas = append(matchedDatas, data) - break +func HandleApplicationDataInfluenceDataSubsToNotifyGet(request *httpwrapper.Request) *httpwrapper.Response { + logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataSubsToNotifyGet") + + dnn := request.Query.Get("dnn") + internalGroupId := request.Query.Get("internal-Group-Id") + supi := request.Query.Get("supi") + + var snssai *models.Snssai + if request.Query.Get("snssai") != "" { + snssai = new(models.Snssai) + err := openapi.Deserialize(snssai, []byte(request.Query.Get("snssai")), "application/json") + if err != nil { + problemDetails := models.ProblemDetails{ + Status: http.StatusBadRequest, + Detail: err.Error(), } + return httpwrapper.NewResponse(http.StatusBadRequest, nil, problemDetails) + } + } + + if dnn == "" && snssai == nil && internalGroupId == "" && supi == "" { + problemDetails := models.ProblemDetails{ + Status: http.StatusBadRequest, + Detail: "At least one of DNNs, S-NSSAIs, Internal Group IDs or SUPIs shall be provided", } + return httpwrapper.NewResponse(http.StatusBadRequest, nil, problemDetails) + } + + rspData, problemDetails := ApplicationDataInfluenceDataSubsToNotifyGetProcedure(dnn, snssai, internalGroupId, supi) + + if problemDetails == nil { + return httpwrapper.NewResponse(http.StatusOK, nil, rspData) + } else { + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } - return matchedDatas } -func HandleApplicationDataInfluenceDataSubsToNotifyPost(trInfluSub *models.TrafficInfluSub) *httpwrapper.Response { +func ApplicationDataInfluenceDataSubsToNotifyGetProcedure( + dnn string, snssai *models.Snssai, internalGroupId, supi string) ( + []models.TrafficInfluSub, *models.ProblemDetails, +) { + var response []models.TrafficInfluSub + + udrSelf := udr_context.GetSelf() + udrSelf.InfluenceDataSubscriptions.Range(func(key, value interface{}) bool { + subs, ok := value.(*models.TrafficInfluSub) + if !ok { + logger.DataRepoLog.Errorf("Failed to load influence Data subscription ID [%+v]", key) + return true + } else if dnn != "" && !util.Contain(dnn, subs.Dnns) { + return true + } else if snssai != nil && !util.Contain(*snssai, subs.Snssais) { + return true + } else if internalGroupId != "" && !util.Contain(internalGroupId, subs.InternalGroupIds) { + return true + } else if supi != "" && !util.Contain(supi, subs.Supis) { + return true + } else { + response = append(response, *subs) + } + return true + }) + return response, nil +} + +func HandleApplicationDataInfluenceDataSubsToNotifyPost( + request *httpwrapper.Request, +) *httpwrapper.Response { logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataSubsToNotifyPost") - udrSelf := udr_context.UDR_Self() - newSubscID := strconv.FormatUint(udrSelf.NewAppDataInfluDataSubscriptionID(), 10) - response, status := postApplicationDataInfluenceDataSubsToNotifyToDB(newSubscID, trInfluSub) + requestMsg := request.Body.(models.TrafficInfluSub) - /* Contains the URI of the newly created resource, according - to the structure: {apiRoot}/application-data/influenceData/subs-to-notify/{subscID} */ - locationHeader := fmt.Sprintf("%s/application-data/influenceData/subs-to-notify/%s", - udrSelf.GetIPv4GroupUri(udr_context.NUDR_DR), newSubscID) - logger.DataRepoLog.Infof("locationHeader:%q", locationHeader) - headers := http.Header{} - headers.Set("Location", locationHeader) - return httpwrapper.NewResponse(status, headers, response) -} + subscriptionId := udr_context.NewInfluenceDataSubscriptionId() -func postApplicationDataInfluenceDataSubsToNotifyToDB(subscID string, - trInfluSub *models.TrafficInfluSub, -) (bson.M, int) { - filter := bson.M{"subscriptionId": subscID} - data := util.ToBsonM(*trInfluSub) + rspData, problemDetails := ApplicationDataInfluenceDataSubsToNotifySubscriptionIdPutProcedure( + subscriptionId, &requestMsg) - // Add "subscriptionId" entry to DB - data["subscriptionId"] = subscID - _, err := mongoapi.RestfulAPIPutOne(APPDATA_INFLUDATA_SUBSC_DB_COLLECTION_NAME, filter, data) - if err != nil { - logger.DataRepoLog.Errorf("postApplicationDataInfluenceDataSubsToNotifyToDB err: %+v", err) - return nil, http.StatusInternalServerError + if rspData != nil { + header := http.Header{ + "Location": { + fmt.Sprintf("%s/application-data/influenceData/subs-to-notify/%s", + udr_context.GetSelf().GetIPv4GroupUri(udr_context.NUDR_DR), subscriptionId), + }, + } + return httpwrapper.NewResponse(http.StatusCreated, header, rspData) + } else if problemDetails != nil { + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } - // Revert back to origin data before return - delete(data, "subscriptionId") - return data, http.StatusCreated + + problemDetails = &models.ProblemDetails{ + Status: http.StatusForbidden, + Cause: "UNSPECIFIED", + } + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) } -func HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete(subscID string) *httpwrapper.Response { - logger.DataRepoLog.Infof( - "Handle ApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete: subscID=%q", subscID) +func HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete( + request *httpwrapper.Request, +) *httpwrapper.Response { + logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataSubsToNotifySubscriptionIdDelete") - deleteApplicationDataIndividualInfluenceDataSubsToNotifyFromDB(subscID) + subscriptionId := request.Params["subscriptionId"] - return httpwrapper.NewResponse(http.StatusNoContent, nil, map[string]interface{}{}) -} + udrSelf := udr_context.GetSelf() -func deleteApplicationDataIndividualInfluenceDataSubsToNotifyFromDB(subscID string) { - filter := bson.M{"subscriptionId": subscID} - deleteDataFromDB(APPDATA_INFLUDATA_SUBSC_DB_COLLECTION_NAME, filter) + udrSelf.InfluenceDataSubscriptions.Delete(subscriptionId) + + return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) } -func HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet(subscID string) *httpwrapper.Response { - logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet: subscID=%q", subscID) +func HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet( + request *httpwrapper.Request, +) *httpwrapper.Response { + logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataSubsToNotifySubscriptionIdGet") - response, problemDetails := getApplicationDataIndividualInfluenceDataSubsToNotifyFromDB(subscID) + subscriptionId := request.Params["subscriptionId"] - if problemDetails != nil { - return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) - } - return httpwrapper.NewResponse(http.StatusOK, nil, response) -} + udrSelf := udr_context.GetSelf() -func getApplicationDataIndividualInfluenceDataSubsToNotifyFromDB( - subscID string, -) (map[string]interface{}, *models.ProblemDetails) { - filter := bson.M{"subscriptionId": subscID} - data, pd := getDataFromDB(APPDATA_INFLUDATA_SUBSC_DB_COLLECTION_NAME, filter) - if pd != nil { - logger.DataRepoLog.Errorf("getApplicationDataIndividualInfluenceDataSubsToNotifyFromDB err: %s", pd.Detail) - return nil, pd + if subscription, ok := udrSelf.InfluenceDataSubscriptions.Load(subscriptionId); ok { + return httpwrapper.NewResponse(http.StatusOK, nil, subscription) + } else { + problemDetails := models.ProblemDetails{ + Status: http.StatusNotFound, + Detail: "Resource not found", + } + return httpwrapper.NewResponse(http.StatusNotFound, nil, problemDetails) } - // Delete "subscriptionId" entry which is added by us - delete(data, "subscriptionId") - return data, nil } func HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut( - subscID string, trInfluSub *models.TrafficInfluSub, + request *httpwrapper.Request, ) *httpwrapper.Response { - logger.DataRepoLog.Infof( - "Handle HandleApplicationDataInfluenceDataSubsToNotifySubscriptionIdPut: subscID=%q", subscID) + logger.DataRepoLog.Infof("Handle ApplicationDataInfluenceDataSubsToNotifySubscriptiondIdPut") - response, status := putApplicationDataIndividualInfluenceDataSubsToNotifyToDB(subscID, trInfluSub) + subscriptionId := request.Params["subscriptionId"] + requestMsg := request.Body.(models.TrafficInfluSub) - return httpwrapper.NewResponse(status, nil, response) + rspData, problemDetails := ApplicationDataInfluenceDataSubsToNotifySubscriptionIdPutProcedure( + subscriptionId, &requestMsg) + + if rspData != nil { + return httpwrapper.NewResponse(http.StatusOK, nil, rspData) + } else if problemDetails == nil { + return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) + } else { + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) + } } -func putApplicationDataIndividualInfluenceDataSubsToNotifyToDB(subscID string, - trInfluSub *models.TrafficInfluSub, -) (bson.M, int) { - filter := bson.M{"subscriptionId": subscID} - newData := util.ToBsonM(*trInfluSub) +func ApplicationDataInfluenceDataSubsToNotifySubscriptionIdPutProcedure( + subscriptionId string, request *models.TrafficInfluSub) ( + *models.TrafficInfluSub, *models.ProblemDetails, +) { + if len(request.Dnns) == 0 && + len(request.Snssais) == 0 && + len(request.InternalGroupIds) == 0 && + len(request.Supis) == 0 { + return nil, &models.ProblemDetails{ + Status: http.StatusBadRequest, + Detail: "At least one of DNNs, S-NSSAIs, Internal Group IDs or SUPIs shall be provided", + } + } - _, pd := getDataFromDB(APPDATA_INFLUDATA_SUBSC_DB_COLLECTION_NAME, filter) - if pd != nil { - logger.DataRepoLog.Errorf("putApplicationDataIndividualInfluenceDataSubsToNotifyToDB err: %s", pd.Detail) - return nil, http.StatusNotFound + if request.NotificationUri == "" { + return nil, &models.ProblemDetails{ + Status: http.StatusBadRequest, + Detail: "Notification URI shall be provided", + } } - // Add "subscriptionId" entry to DB - newData["subscriptionId"] = subscID - // Modify with new data - if _, err := mongoapi.RestfulAPIPutOne(APPDATA_INFLUDATA_SUBSC_DB_COLLECTION_NAME, filter, newData); err != nil { - logger.DataRepoLog.Errorf("putApplicationDataIndividualInfluenceDataSubsToNotifyToDB err: %+v", err) - return nil, http.StatusInternalServerError + + udrSelf := udr_context.GetSelf() + if subs, ok := udrSelf.InfluenceDataSubscriptions.Load(subscriptionId); ok && reflect.DeepEqual(*request, subs) { + return nil, nil + } else { + udrSelf.InfluenceDataSubscriptions.Store(subscriptionId, request) + return request, nil } - // Roll back to origin data before return - delete(newData, "subscriptionId") - return newData, http.StatusOK } func HandleApplicationDataPfdsAppIdDelete(appID string) *httpwrapper.Response { @@ -1077,7 +1215,7 @@ func HandlePolicyDataSubsToNotifyPost(request *httpwrapper.Request) *httpwrapper } func PolicyDataSubsToNotifyPostProcedure(PolicyDataSubscription models.PolicyDataSubscription) string { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() newSubscriptionID := strconv.Itoa(udrSelf.PolicyDataSubscriptionIDGenerator) udrSelf.PolicyDataSubscriptions[newSubscriptionID] = &PolicyDataSubscription @@ -1106,7 +1244,7 @@ func HandlePolicyDataSubsToNotifySubsIdDelete(request *httpwrapper.Request) *htt } func PolicyDataSubsToNotifySubsIdDeleteProcedure(subsId string) (problemDetails *models.ProblemDetails) { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() _, ok := udrSelf.PolicyDataSubscriptions[subsId] if !ok { return util.ProblemDetailsNotFound("SUBSCRIPTION_NOT_FOUND") @@ -1134,7 +1272,7 @@ func HandlePolicyDataSubsToNotifySubsIdPut(request *httpwrapper.Request) *httpwr func PolicyDataSubsToNotifySubsIdPutProcedure(subsId string, policyDataSubscription models.PolicyDataSubscription, ) (*models.PolicyDataSubscription, *models.ProblemDetails) { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() _, ok := udrSelf.PolicyDataSubscriptions[subsId] if !ok { return nil, util.ProblemDetailsNotFound("SUBSCRIPTION_NOT_FOUND") @@ -1558,7 +1696,7 @@ func PolicyDataUesUeIdUePolicySetPatchProcedure(collName string, ueId string, } if err := json.Unmarshal(util.MapToByte(uePolicySetBsonM), &uePolicySet); err != nil { logger.DataRepoLog.Errorf("PolicyDataUesUeIdUePolicySetPatchProcedure err: %+v", err) - return util.ProblemDetailsSystemFailure(err.Error()) + return openapi.ProblemDetailsSystemFailure(err.Error()) } PreHandlePolicyDataChangeNotification(ueId, "", uePolicySet) return nil @@ -1620,7 +1758,7 @@ func HandleCreateAMFSubscriptions(request *httpwrapper.Request) *httpwrapper.Res func CreateAMFSubscriptionsProcedure(subsId string, ueId string, AmfSubscriptionInfo []models.AmfSubscriptionInfo, ) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -1652,7 +1790,7 @@ func HandleRemoveAmfSubscriptionsInfo(request *httpwrapper.Request) *httpwrapper } func RemoveAmfSubscriptionsInfoProcedure(subsId string, ueId string) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -1693,7 +1831,7 @@ func HandleModifyAmfSubscriptionInfo(request *httpwrapper.Request) *httpwrapper. func ModifyAmfSubscriptionInfoProcedure(ueId string, subsId string, patchItem []models.PatchItem, ) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -1761,7 +1899,7 @@ func HandleGetAmfSubscriptionInfo(request *httpwrapper.Request) *httpwrapper.Res func GetAmfSubscriptionInfoProcedure(subsId string, ueId string) (*[]models.AmfSubscriptionInfo, *models.ProblemDetails, ) { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { @@ -1825,7 +1963,7 @@ func HandleRemoveEeGroupSubscriptions(request *httpwrapper.Request) *httpwrapper } func RemoveEeGroupSubscriptionsProcedure(ueGroupId string, subsId string) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UEGroupCollection.Load(ueGroupId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -1861,7 +1999,7 @@ func HandleUpdateEeGroupSubscriptions(request *httpwrapper.Request) *httpwrapper func UpdateEeGroupSubscriptionsProcedure(ueGroupId string, subsId string, EeSubscription models.EeSubscription, ) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UEGroupCollection.Load(ueGroupId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -1892,7 +2030,7 @@ func HandleCreateEeGroupSubscriptions(request *httpwrapper.Request) *httpwrapper } func CreateEeGroupSubscriptionsProcedure(ueGroupId string, EeSubscription models.EeSubscription) string { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UEGroupCollection.Load(ueGroupId) if !ok { @@ -1910,7 +2048,7 @@ func CreateEeGroupSubscriptionsProcedure(ueGroupId string, EeSubscription models /* Contains the URI of the newly created resource, according to the structure: {apiRoot}/nudr-dr/v1/subscription-data/group-data/{ueGroupId}/ee-subscriptions */ - locationHeader := fmt.Sprintf("%s/nudr-dr/v1/subscription-data/group-data/%s/ee-subscriptions/%s", + locationHeader := fmt.Sprintf("%s"+factory.UdrDrResUriPrefix+"/subscription-data/group-data/%s/ee-subscriptions/%s", udrSelf.GetIPv4GroupUri(udr_context.NUDR_DR), ueGroupId, newSubscriptionID) return locationHeader @@ -1934,7 +2072,7 @@ func HandleQueryEeGroupSubscriptions(request *httpwrapper.Request) *httpwrapper. } func QueryEeGroupSubscriptionsProcedure(ueGroupId string) ([]models.EeSubscription, *models.ProblemDetails) { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UEGroupCollection.Load(ueGroupId) if !ok { @@ -1966,7 +2104,7 @@ func HandleRemoveeeSubscriptions(request *httpwrapper.Request) *httpwrapper.Resp } func RemoveeeSubscriptionsProcedure(ueId string, subsId string) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -2001,7 +2139,7 @@ func HandleUpdateEesubscriptions(request *httpwrapper.Request) *httpwrapper.Resp func UpdateEesubscriptionsProcedure(ueId string, subsId string, EeSubscription models.EeSubscription, ) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -2032,7 +2170,7 @@ func HandleCreateEeSubscriptions(request *httpwrapper.Request) *httpwrapper.Resp } func CreateEeSubscriptionsProcedure(ueId string, EeSubscription models.EeSubscription) string { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { @@ -2075,7 +2213,7 @@ func HandleQueryeesubscriptions(request *httpwrapper.Request) *httpwrapper.Respo } func QueryeesubscriptionsProcedure(ueId string) ([]models.EeSubscription, *models.ProblemDetails) { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { @@ -2223,7 +2361,7 @@ func QueryProvisionedDataProcedure(ueId string, servingPlmnId string, if err := mapstructure.Decode(accessAndMobilitySubscriptionData, &tmp); err != nil { logger.DataRepoLog.Errorf( "QueryProvisionedDataProcedure accessAndMobilitySubscriptionData decode err: %+v", err) - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, openapi.ProblemDetailsSystemFailure(err.Error()) } provisionedDataSets.AmData = &tmp } @@ -2240,7 +2378,7 @@ func QueryProvisionedDataProcedure(ueId string, servingPlmnId string, if err := mapstructure.Decode(smfSelectionSubscriptionData, &tmp); err != nil { logger.DataRepoLog.Errorf( "QueryProvisionedDataProcedure smfSelectionSubscriptionData decode err: %+v", err) - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, openapi.ProblemDetailsSystemFailure(err.Error()) } provisionedDataSets.SmfSelData = &tmp } @@ -2257,7 +2395,7 @@ func QueryProvisionedDataProcedure(ueId string, servingPlmnId string, if err := mapstructure.Decode(smsSubscriptionData, &tmp); err != nil { logger.DataRepoLog.Errorf( "QueryProvisionedDataProcedure smsSubscriptionData decode err: %+v", err) - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, openapi.ProblemDetailsSystemFailure(err.Error()) } provisionedDataSets.SmsSubsData = &tmp } @@ -2267,14 +2405,14 @@ func QueryProvisionedDataProcedure(ueId string, servingPlmnId string, sessionManagementSubscriptionDatas, err := mongoapi.RestfulAPIGetMany(collName, filter) if err != nil { logger.DataRepoLog.Errorf("QueryProvisionedDataProcedure get sessionManagementSubscriptionDatas err: %+v", err) - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, openapi.ProblemDetailsSystemFailure(err.Error()) } if sessionManagementSubscriptionDatas != nil { var tmp []models.SessionManagementSubscriptionData if err := mapstructure.Decode(sessionManagementSubscriptionDatas, &tmp); err != nil { logger.DataRepoLog.Errorf( "QueryProvisionedDataProcedure sessionManagementSubscriptionDatas decode err: %+v", err) - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, openapi.ProblemDetailsSystemFailure(err.Error()) } for _, smData := range tmp { dnnConfigurations := smData.DnnConfigurations @@ -2299,7 +2437,7 @@ func QueryProvisionedDataProcedure(ueId string, servingPlmnId string, var tmp models.TraceData if err := mapstructure.Decode(traceData, &tmp); err != nil { logger.DataRepoLog.Errorf("QueryProvisionedDataProcedure traceData decode err: %+v", err) - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, openapi.ProblemDetailsSystemFailure(err.Error()) } provisionedDataSets.TraceData = &tmp } @@ -2317,7 +2455,7 @@ func QueryProvisionedDataProcedure(ueId string, servingPlmnId string, if err := mapstructure.Decode(smsManagementSubscriptionData, &tmp); err != nil { logger.DataRepoLog.Errorf( "QueryProvisionedDataProcedure smsManagementSubscriptionData decode err: %+v", err) - return nil, util.ProblemDetailsSystemFailure(err.Error()) + return nil, openapi.ProblemDetailsSystemFailure(err.Error()) } provisionedDataSets.SmsMngData = &tmp } @@ -2470,7 +2608,7 @@ func HandleRemovesdmSubscriptions(request *httpwrapper.Request) *httpwrapper.Res } func RemovesdmSubscriptionsProcedure(ueId string, subsId string) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -2506,7 +2644,7 @@ func HandleUpdatesdmsubscriptions(request *httpwrapper.Request) *httpwrapper.Res func UpdatesdmsubscriptionsProcedure(ueId string, subsId string, SdmSubscription models.SdmSubscription, ) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { return util.ProblemDetailsNotFound("USER_NOT_FOUND") @@ -2541,7 +2679,7 @@ func HandleCreateSdmSubscriptions(request *httpwrapper.Request) *httpwrapper.Res func CreateSdmSubscriptionsProcedure(SdmSubscription models.SdmSubscription, collName string, ueId string, ) (string, models.SdmSubscription) { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { @@ -2584,7 +2722,7 @@ func HandleQuerysdmsubscriptions(request *httpwrapper.Request) *httpwrapper.Resp } func QuerysdmsubscriptionsProcedure(ueId string) (*[]models.SdmSubscription, *models.ProblemDetails) { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { @@ -3020,7 +3158,7 @@ func HandlePostSubscriptionDataSubscriptions(request *httpwrapper.Request) *http func PostSubscriptionDataSubscriptionsProcedure( SubscriptionDataSubscriptions models.SubscriptionDataSubscriptions, ) string { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() newSubscriptionID := strconv.Itoa(udrSelf.SubscriptionDataSubscriptionIDGenerator) udrSelf.SubscriptionDataSubscriptions[newSubscriptionID] = &SubscriptionDataSubscriptions @@ -3049,7 +3187,7 @@ func HandleRemovesubscriptionDataSubscriptions(request *httpwrapper.Request) *ht } func RemovesubscriptionDataSubscriptionsProcedure(subsId string) *models.ProblemDetails { - udrSelf := udr_context.UDR_Self() + udrSelf := udr_context.GetSelf() _, ok := udrSelf.SubscriptionDataSubscriptions[subsId] if !ok { return util.ProblemDetailsNotFound("SUBSCRIPTION_NOT_FOUND") diff --git a/internal/util/list.go b/internal/util/list.go new file mode 100644 index 0000000..9db1b46 --- /dev/null +++ b/internal/util/list.go @@ -0,0 +1,24 @@ +/* + * UDR Utility - List + * + * Copy from NSSF and NEF utility + */ + +package util + +import ( + "reflect" +) + +// Contain checks whether a slice contains an element +func Contain(target interface{}, slice interface{}) bool { + arr := reflect.ValueOf(slice) + if arr.Kind() == reflect.Slice { + for i := 0; i < arr.Len(); i++ { + if reflect.DeepEqual(arr.Index(i).Interface(), target) { + return true + } + } + } + return false +} diff --git a/pkg/factory/config.go b/pkg/factory/config.go index 2687828..64e7138 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -6,55 +6,48 @@ package factory import ( "fmt" + "sync" "github.com/asaskevich/govalidator" - logger_util "github.com/free5gc/util/logger" + "github.com/free5gc/udr/internal/logger" ) const ( - UDR_EXPECTED_CONFIG_VERSION = "1.0.1" + UdrDefaultTLSKeyLogPath = "./log/udrsslkey.log" + UdrDefaultCertPemPath = "./cert/udr.pem" + UdrDefaultPrivateKeyPath = "./cert/udr.key" + UdrDefaultConfigPath = "./config/udrcfg.yaml" + UdrSbiDefaultIPv4 = "127.0.0.9" + UdrSbiDefaultPort = 8000 + UdrSbiDefaultScheme = "https" + UdrDefaultNrfUri = "https://127.0.0.10:8000" + UdrDrResUriPrefix = "/nudr-dr/v1" ) type Config struct { - Info *Info `yaml:"info" valid:"required"` - Configuration *Configuration `yaml:"configuration" valid:"required"` - Logger *logger_util.Logger `yaml:"logger" valid:"optional"` + Info *Info `yaml:"info" valid:"required"` + Configuration *Configuration `yaml:"configuration" valid:"required"` + Logger *Logger `yaml:"logger" valid:"required"` + sync.RWMutex } func (c *Config) Validate() (bool, error) { - if info := c.Info; info != nil { - if result, err := info.validate(); err != nil { - return result, err - } - } - if configuration := c.Configuration; configuration != nil { if result, err := configuration.validate(); err != nil { return result, err } } - if logger := c.Logger; logger != nil { - if result, err := logger.Validate(); err != nil { - return result, err - } - } - result, err := govalidator.ValidateStruct(c) return result, appendInvalid(err) } type Info struct { - Version string `yaml:"version,omitempty" valid:"type(string),required"` + Version string `yaml:"version,omitempty" valid:"required,in(1.0.2)"` Description string `yaml:"description,omitempty" valid:"type(string),optional"` } -func (i *Info) validate() (bool, error) { - result, err := govalidator.ValidateStruct(i) - return result, appendInvalid(err) -} - const ( UDR_DEFAULT_IPV4 = "127.0.0.4" UDR_DEFAULT_PORT = "8000" @@ -67,6 +60,12 @@ type Configuration struct { NrfUri string `yaml:"nrfUri" valid:"url,required"` } +type Logger struct { + Enable bool `yaml:"enable" valid:"type(bool)"` + Level string `yaml:"level" valid:"required,in(trace|debug|info|warn|error|fatal|panic)"` + ReportCaller bool `yaml:"reportCaller" valid:"type(bool)"` +} + func (c *Configuration) validate() (bool, error) { govalidator.TagMap["scheme"] = govalidator.Validator(func(str string) bool { return str == "https" || str == "http" @@ -110,8 +109,85 @@ func appendInvalid(err error) error { } func (c *Config) GetVersion() string { - if c.Info != nil && c.Info.Version != "" { + c.RLock() + defer c.RUnlock() + + if c.Info.Version != "" { return c.Info.Version } return "" } + +func (c *Config) SetLogEnable(enable bool) { + c.Lock() + defer c.Unlock() + + if c.Logger == nil { + logger.CfgLog.Warnf("Logger should not be nil") + c.Logger = &Logger{ + Enable: enable, + Level: "info", + } + } else { + c.Logger.Enable = enable + } +} + +func (c *Config) SetLogLevel(level string) { + c.Lock() + defer c.Unlock() + + if c.Logger == nil { + logger.CfgLog.Warnf("Logger should not be nil") + c.Logger = &Logger{ + Level: level, + } + } else { + c.Logger.Level = level + } +} + +func (c *Config) SetLogReportCaller(reportCaller bool) { + c.Lock() + defer c.Unlock() + + if c.Logger == nil { + logger.CfgLog.Warnf("Logger should not be nil") + c.Logger = &Logger{ + Level: "info", + ReportCaller: reportCaller, + } + } else { + c.Logger.ReportCaller = reportCaller + } +} + +func (c *Config) GetLogEnable() bool { + c.RLock() + defer c.RUnlock() + if c.Logger == nil { + logger.CfgLog.Warnf("Logger should not be nil") + return false + } + return c.Logger.Enable +} + +func (c *Config) GetLogLevel() string { + c.RLock() + defer c.RUnlock() + if c.Logger == nil { + logger.CfgLog.Warnf("Logger should not be nil") + return "info" + } + return c.Logger.Level +} + +func (c *Config) GetLogReportCaller() bool { + c.RLock() + defer c.RUnlock() + if c.Logger == nil { + logger.CfgLog.Warnf("Logger should not be nil") + return false + } + return c.Logger.ReportCaller +} diff --git a/pkg/factory/factory.go b/pkg/factory/factory.go index 3dd4c76..cc5b838 100644 --- a/pkg/factory/factory.go +++ b/pkg/factory/factory.go @@ -8,37 +8,45 @@ import ( "fmt" "io/ioutil" + "github.com/asaskevich/govalidator" "gopkg.in/yaml.v2" "github.com/free5gc/udr/internal/logger" ) -var UdrConfig Config +var UdrConfig *Config // TODO: Support configuration update from REST api -func InitConfigFactory(f string) error { +func InitConfigFactory(f string, cfg *Config) error { + if f == "" { + // Use default config path + f = UdrDefaultConfigPath + } if content, err := ioutil.ReadFile(f); err != nil { - return err + return fmt.Errorf("[Factory] %+v", err) } else { - UdrConfig = Config{} - - if yamlErr := yaml.Unmarshal(content, &UdrConfig); yamlErr != nil { - return yamlErr + logger.CfgLog.Infof("Read config from [%s]", f) + if yamlErr := yaml.Unmarshal(content, cfg); yamlErr != nil { + return fmt.Errorf("[Factory] %+v", yamlErr) } } return nil } -func CheckConfigVersion() error { - currentVersion := UdrConfig.GetVersion() - - if currentVersion != UDR_EXPECTED_CONFIG_VERSION { - return fmt.Errorf("config version is [%s], but expected is [%s].", - currentVersion, UDR_EXPECTED_CONFIG_VERSION) +func ReadConfig(cfgPath string) (*Config, error) { + cfg := &Config{} + if err := InitConfigFactory(cfgPath, cfg); err != nil { + return nil, fmt.Errorf("ReadConfig [%s] Error: %+v", cfgPath, err) + } + if _, err := cfg.Validate(); err != nil { + validErrs := err.(govalidator.Errors).Errors() + for _, validErr := range validErrs { + logger.CfgLog.Errorf("%+v", validErr) + } + logger.CfgLog.Errorf("[-- PLEASE REFER TO SAMPLE CONFIG FILE COMMENTS --]") + return nil, fmt.Errorf("Config validate Error") } - logger.CfgLog.Infof("config version [%s]", currentVersion) - - return nil + return cfg, nil } diff --git a/pkg/service/init.go b/pkg/service/init.go index 2f0cbad..3ef1a77 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -1,128 +1,79 @@ package service import ( - "bufio" "fmt" - "io" + "io/ioutil" "os" - "os/exec" "os/signal" "runtime/debug" - "sync" "syscall" "github.com/sirupsen/logrus" - "github.com/urfave/cli" udr_context "github.com/free5gc/udr/internal/context" "github.com/free5gc/udr/internal/logger" "github.com/free5gc/udr/internal/sbi/consumer" "github.com/free5gc/udr/internal/sbi/datarepository" - "github.com/free5gc/udr/internal/util" "github.com/free5gc/udr/pkg/factory" "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" "github.com/free5gc/util/mongoapi" ) -type UDR struct { - KeyLogPath string +type UdrApp struct { + cfg *factory.Config + udrCtx *udr_context.UDRContext } -type ( - // Commands information. - Commands struct { - config string - } -) - -var commands Commands - -var cliCmd = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - cli.StringFlag{ - Name: "log, l", - Usage: "Output NF log to `FILE`", - }, - cli.StringFlag{ - Name: "log5gc, lc", - Usage: "Output free5gc log to `FILE`", - }, +func NewApp(cfg *factory.Config) (*UdrApp, error) { + udr := &UdrApp{cfg: cfg} + udr.SetLogEnable(cfg.GetLogEnable()) + udr.SetLogLevel(cfg.GetLogLevel()) + udr.SetReportCaller(cfg.GetLogReportCaller()) + udr_context.Init() + udr.udrCtx = udr_context.GetSelf() + return udr, nil } -func (*UDR) GetCliCmd() (flags []cli.Flag) { - return cliCmd -} - -func (udr *UDR) Initialize(c *cli.Context) error { - commands = Commands{ - config: c.String("config"), +func (a *UdrApp) SetLogEnable(enable bool) { + logger.MainLog.Infof("Log enable is set to [%v]", enable) + if enable && logger.Log.Out == os.Stderr { + return + } else if !enable && logger.Log.Out == ioutil.Discard { + return } - - if commands.config != "" { - if err := factory.InitConfigFactory(commands.config); err != nil { - return err - } + a.cfg.SetLogEnable(enable) + if enable { + logger.Log.SetOutput(os.Stderr) } else { - if err := factory.InitConfigFactory(util.UdrDefaultConfigPath); err != nil { - return err - } - } - - udr.SetLogLevel() - - if err := factory.CheckConfigVersion(); err != nil { - return err + logger.Log.SetOutput(ioutil.Discard) } - - if _, err := factory.UdrConfig.Validate(); err != nil { - return err - } - - return nil } -func (udr *UDR) SetLogLevel() { - if factory.UdrConfig.Logger == nil { - logger.InitLog.Warnln("UDR config without log level setting!!!") +func (a *UdrApp) SetLogLevel(level string) { + lvl, err := logrus.ParseLevel(level) + if err != nil { + logger.MainLog.Warnf("Log level [%s] is invalid", level) return } - - if factory.UdrConfig.Logger.UDR != nil { - if factory.UdrConfig.Logger.UDR.DebugLevel != "" { - if level, err := logrus.ParseLevel(factory.UdrConfig.Logger.UDR.DebugLevel); err != nil { - logger.InitLog.Warnf("UDR Log level [%s] is invalid, set to [info] level", - factory.UdrConfig.Logger.UDR.DebugLevel) - logger.SetLogLevel(logrus.InfoLevel) - } else { - logger.InitLog.Infof("UDR Log level is set to [%s] level", level) - logger.SetLogLevel(level) - } - } else { - logger.InitLog.Infoln("UDR Log level not set. Default set to [info] level") - logger.SetLogLevel(logrus.InfoLevel) - } - logger.SetReportCaller(factory.UdrConfig.Logger.UDR.ReportCaller) + logger.MainLog.Infof("Log level is set to [%s]", level) + if lvl == logger.Log.GetLevel() { + return } + a.cfg.SetLogLevel(level) + logger.Log.SetLevel(lvl) } -func (udr *UDR) FilterCli(c *cli.Context) (args []string) { - for _, flag := range udr.GetCliCmd() { - name := flag.GetName() - value := fmt.Sprint(c.Generic(name)) - if value == "" { - continue - } - - args = append(args, "--"+name, value) +func (a *UdrApp) SetReportCaller(reportCaller bool) { + logger.MainLog.Infof("Report Caller is set to [%v]", reportCaller) + if reportCaller == logger.Log.ReportCaller { + return } - return args + a.cfg.SetLogReportCaller(reportCaller) + logger.Log.SetReportCaller(reportCaller) } -func (udr *UDR) Start() { +func (a *UdrApp) Start(tlsKeyLogPath string) { // get config file info config := factory.UdrConfig mongodb := config.Configuration.Mongodb @@ -141,16 +92,16 @@ func (udr *UDR) Start() { datarepository.AddService(router) - pemPath := util.UdrDefaultPemPath - keyPath := util.UdrDefaultKeyPath + pemPath := factory.UdrDefaultCertPemPath + keyPath := factory.UdrDefaultPrivateKeyPath sbi := config.Configuration.Sbi if sbi.Tls != nil { pemPath = sbi.Tls.Pem keyPath = sbi.Tls.Key } - self := udr_context.UDR_Self() - util.InitUdrContext(self) + self := a.udrCtx + udr_context.InitUdrContext(self) addr := fmt.Sprintf("%s:%d", self.BindingIPv4, self.SBIPort) profile := consumer.BuildNFInstance(self) @@ -174,11 +125,11 @@ func (udr *UDR) Start() { }() <-signalChannel - udr.Terminate() + a.Terminate() os.Exit(0) }() - server, err := httpwrapper.NewHttp2Server(addr, udr.KeyLogPath, router) + server, err := httpwrapper.NewHttp2Server(addr, tlsKeyLogPath, router) if server == nil { logger.InitLog.Errorf("Initialize HTTP server failed: %+v", err) return @@ -200,83 +151,7 @@ func (udr *UDR) Start() { } } -func (udr *UDR) Exec(c *cli.Context) error { - // UDR.Initialize(cfgPath, c) - - logger.InitLog.Traceln("args:", c.String("udrcfg")) - args := udr.FilterCli(c) - logger.InitLog.Traceln("filter: ", args) - command := exec.Command("./udr", args...) - - if err := udr.Initialize(c); err != nil { - return err - } - - var stdout io.ReadCloser - if readCloser, err := command.StdoutPipe(); err != nil { - logger.InitLog.Fatalln(err) - } else { - stdout = readCloser - } - wg := sync.WaitGroup{} - wg.Add(3) - go func() { - defer func() { - if p := recover(); p != nil { - // Print stack for panic to log. Fatalf() will let program exit. - logger.InitLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) - } - }() - - in := bufio.NewScanner(stdout) - for in.Scan() { - fmt.Println(in.Text()) - } - wg.Done() - }() - - var stderr io.ReadCloser - if readCloser, err := command.StderrPipe(); err != nil { - logger.InitLog.Fatalln(err) - } else { - stderr = readCloser - } - go func() { - defer func() { - if p := recover(); p != nil { - // Print stack for panic to log. Fatalf() will let program exit. - logger.InitLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) - } - }() - - in := bufio.NewScanner(stderr) - for in.Scan() { - fmt.Println(in.Text()) - } - wg.Done() - }() - - var err error - go func() { - defer func() { - if p := recover(); p != nil { - // Print stack for panic to log. Fatalf() will let program exit. - logger.InitLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) - } - }() - - if errormessage := command.Start(); err != nil { - fmt.Println("command.Start Fails!") - err = errormessage - } - wg.Done() - }() - - wg.Wait() - return err -} - -func (udr *UDR) Terminate() { +func (a *UdrApp) Terminate() { logger.InitLog.Infof("Terminating UDR...") // deregister with NRF problemDetails, err := consumer.SendDeregisterNFInstance()