From 14faeca8fd2f5bf77ff14ba6301e43ca77c01abe Mon Sep 17 00:00:00 2001 From: Umair Khan <umair.anwar@xflowresearch.com> Date: Wed, 18 Dec 2024 05:59:43 +0000 Subject: [PATCH 1/9] add query parameter filtering to MEC 021 Mobility Service GET request --- go-apps/meep-ams/server/ams.go | 328 +++++++++++++++++++++++++++++++-- 1 file changed, 313 insertions(+), 15 deletions(-) diff --git a/go-apps/meep-ams/server/ams.go b/go-apps/meep-ams/server/ams.go index e6e04c7dd..fd9d98c8f 100644 --- a/go-apps/meep-ams/server/ams.go +++ b/go-apps/meep-ams/server/ams.go @@ -148,6 +148,19 @@ func notImplemented(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +type RegisterationInfoList struct { + Registrations []RegistrationInfo + Filters *FilterParameters +} + +type FilterParameters struct { + filter string + all_fields string + fields string + exclude_fields string + exclude_default string +} + // Init - App Mobility Service initialization func Init() (err error) { @@ -1205,8 +1218,10 @@ func subscriptionLinkListSubscriptionsGet(w http.ResponseWriter, r *http.Request u, _ := url.Parse(r.URL.String()) q := u.Query() validQueryParams := []string{"subscriptionType"} - if !validateQueryParams(q, validQueryParams) { - w.WriteHeader(http.StatusBadRequest) + err := validateQueryParams(q, validQueryParams) + if err != nil { + + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return } @@ -1475,18 +1490,46 @@ func appMobilityServiceByIdDELETE(w http.ResponseWriter, r *http.Request) { func appMobilityServiceGET(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") + // Validate query parameters + u, _ := url.Parse(r.URL.String()) + q := u.Query() + validParams := []string{"filter", "All_fields", "Fields", "Exclude_fields", "Exclude_default"} + err := validateQueryParams(q, validParams) + if err != nil { + print("Query Parameter error") + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } + + // Parse query parameters + filters := q.Get("filter") + allFields := q.Get("all_fields") + // field := q["fields"] + // excludeFields := q["exclude_fields"] + // excludeDefault := q.Get("exclude_default") + + regInfoList := &RegisterationInfoList{ + Filters: &FilterParameters{ + filter: filters, + all_fields: allFields, + }, + Registrations: make([]RegistrationInfo, 0), + } + // Get all AMS Registration Info - regInfoList := make([]RegistrationInfo, 0) + //regInfoList := make([]RegistrationInfo, 0) key := baseKey + "svc:*:info" - err := rc.ForEachJSONEntry(key, populateRegInfoList, ®InfoList) + + err = rc.ForEachJSONEntry(key, populateRegInfoList, regInfoList) + if err != nil { log.Error(err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) return } - // Send response - jsonResponse, err := json.Marshal(regInfoList) + // Prepare & send response + jsonResponse, err := json.Marshal(regInfoList.Registrations) if err != nil { log.Error(err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusInternalServerError) @@ -1498,21 +1541,275 @@ func appMobilityServiceGET(w http.ResponseWriter, r *http.Request) { } func populateRegInfoList(key string, jsonEntry string, response interface{}) error { - regInfoList := response.(*[]RegistrationInfo) - if regInfoList == nil { - return errors.New("Response not defined") + data := response.(*RegisterationInfoList) + if data == nil { + return errors.New("response not defined") } // Retrieve registration info from DB var regInfo RegistrationInfo err := json.Unmarshal([]byte(jsonEntry), ®Info) + println("keysss", key) if err != nil { return err } - *regInfoList = append(*regInfoList, regInfo) + + // Filter services + if data.Filters != nil { + + if data.Filters.filter != "" { + + filterField := data.Filters.filter + // Split filterField into operator, attribute, and value + filterField = strings.Trim(filterField, "()") + filterParts := strings.SplitN(filterField, ",", 3) + if len(filterParts) != 3 { + return nil + } + operator := filterParts[0] + attribute := filterParts[1] + value := filterParts[2] + + // Apply filters based on attribute + switch attribute { + case "appMobilityServiceId": + if !applyStringFilter(operator, regInfo.AppMobilityServiceId, value) { + return nil + } + + case "serviceConsumerId.appInstanceId": + if !applyStringFilter(operator, regInfo.ServiceConsumerId.AppInstanceId, value) { + return nil + } + + case "serviceConsumerId.mepId": + if !applyStringFilter(operator, regInfo.ServiceConsumerId.MepId, value) { + return nil + } + + case "deviceInformation.associateId": + matched := false + for _, deviceInfo := range regInfo.DeviceInformation { + if applyStringFilter(operator, deviceInfo.AssociateId.Value, value) { + matched = true + break + } + } + if !matched { + return nil + } + + // case "deviceInformation.contextTransferState": + // matched := false + // for _, deviceInfo := range regInfo.DeviceInformation { + // if applyEnumFilter(operator, string(deviceInfo.ContextTransferState.val), value) { + // matched = true + // break + // } + // } + // if !matched { + // return fmt.Errorf("filter mismatch: deviceInformation.contextTransferState") + // } + + case "expiryTime": + expiryTime, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return nil + } + if !applyNumericFilter(operator, uint32(regInfo.ExpiryTime), uint32(expiryTime)) { + return nil + } + + default: + return nil + } + + } + + } + data.Registrations = append(data.Registrations, regInfo) return nil } +// Helper functions for applying filters +func applyStringFilter(operator, fieldValue, filterValue string) bool { + switch operator { + case "eq": + return fieldValue == filterValue + case "neq": + return fieldValue != filterValue + case "cont": + return strings.Contains(fieldValue, filterValue) + case "ncont": + return !strings.Contains(fieldValue, filterValue) + case "in": + values := strings.Split(filterValue, ",") + for _, v := range values { + if fieldValue == v { + return true + } + } + return false + case "nin": + values := strings.Split(filterValue, ",") + for _, v := range values { + if fieldValue == v { + return false + } + } + return true + case "gt": + return fieldValue > filterValue + case "gte": + return fieldValue >= filterValue + case "lt": + return fieldValue < filterValue + case "lte": + return fieldValue <= filterValue + default: + return false + } +} + +func applyEnumFilter(operator, fieldValue, filterValue string) bool { + return applyStringFilter(operator, fieldValue, filterValue) +} + +func applyNumericFilter(operator string, fieldValue, filterValue uint32) bool { + switch operator { + case "eq": + return fieldValue == filterValue + case "neq": + return fieldValue != filterValue + case "gt": + return fieldValue > filterValue + case "gte": + return fieldValue >= filterValue + case "lt": + return fieldValue < filterValue + case "lte": + return fieldValue <= filterValue + default: + return false + } +} + +// Original +// func populateRegInfoList(key string, jsonEntry string, response interface{}) error { +// data := response.(*RegisterationInfoList) +// if data == nil { +// return errors.New("response not defined") +// } + +// // Retrieve registration info from DB +// var regInfo RegistrationInfo +// err := json.Unmarshal([]byte(jsonEntry), ®Info) +// println("keysss", key) +// if err == nil { +// return err +// } + +// // Filter services +// if data.Filters != nil { + +// if data.Filters.filter != "" { +// filterField := data.Filters.filter +// if regInfo.AppMobilityServiceId == "" || (filterField != regInfo.AppMobilityServiceId) { +// return nil +// } +// } + +// } + +// data.Registrations = append(data.Registrations, regInfo) +// return nil +// } + +// filterRegistrationInfo applies filtering based on the provided filters. +// func filterRegistrationInfo(regInfoList []RegistrationInfo, filters []string) []RegistrationInfo { +// var filteredList []RegistrationInfo + +// for _, regInfo := range regInfoList { +// match := true + +// // Iterate over filters to apply them one by one +// for _, filter := range filters { +// filterParts := strings.Split(filter, ":") +// if len(filterParts) != 2 { +// // Invalid filter format (e.g., "key:value") +// continue +// } +// key := filterParts[0] +// value := filterParts[1] + +// // Apply filter based on key-value pairs +// switch key { +// case "appMobilityServiceId": +// if regInfo.AppMobilityServiceId != value { +// match = false +// } +// case "appInstanceId": +// if regInfo.ServiceConsumerId.AppInstanceId != value { +// match = false +// } +// case "mepId": +// if regInfo.ServiceConsumerId.MepId != value { +// match = false +// } +// case "associateId": +// // Check if any device has the associateId value +// found := false +// for _, device := range regInfo.DeviceInformation { +// if device.AssociateId == value { +// found = true +// break +// } +// } +// if !found { +// match = false +// } +// case "expiryTime": +// expiryTime, err := strconv.Atoi(value) +// if err != nil || regInfo.ExpiryTime != uint32(expiryTime) { +// match = false +// } +// default: +// // If filter key is not recognized, skip it +// continue +// } + +// // If any filter condition doesn't match, we stop checking further +// if !match { +// break +// } +// } + +// // If all filters matched, add to the result list +// if match { +// filteredList = append(filteredList, regInfo) +// } +// } + +// return filteredList +// } + +// func populateRegInfoList(key string, jsonEntry string, response interface{}) error { +// regInfoList := response.(*[]RegistrationInfo) +// if regInfoList == nil { +// return errors.New("Response not defined") +// } + +// // Retrieve registration info from DB +// var regInfo RegistrationInfo +// err := json.Unmarshal([]byte(jsonEntry), ®Info) +// if err != nil { +// return err +// } + +// *regInfoList = append(*regInfoList, regInfo) +// return nil +// } + func cleanUp() { log.Info("Terminate all") @@ -2324,21 +2621,22 @@ func delTrackedDevInfo(svcId string, address string) error { return nil } -func validateQueryParams(params url.Values, validParamList []string) bool { +func validateQueryParams(params url.Values, validParams []string) error { for param := range params { found := false - for _, validParam := range validParamList { + for _, validParam := range validParams { if param == validParam { found = true break } } if !found { - log.Error("Invalid query param: ", param) - return false + err := errors.New("Invalid query param: " + param) + log.Error(err.Error()) + return err } } - return true + return nil } func validateQueryParamValue(val string, validValues []string) bool { -- GitLab From 7f7a8871210790c68c1d8f9680cb923851f465ee Mon Sep 17 00:00:00 2001 From: Umair Khan <umair.anwar@xflowresearch.com> Date: Wed, 18 Dec 2024 12:52:42 +0000 Subject: [PATCH 2/9] fix issue in query parameter filtering to MEC 021 Mobility Service GET request --- go-apps/meep-ams/server/ams.go | 333 +++++++++++++-------------------- 1 file changed, 126 insertions(+), 207 deletions(-) diff --git a/go-apps/meep-ams/server/ams.go b/go-apps/meep-ams/server/ams.go index fd9d98c8f..e6c3d2345 100644 --- a/go-apps/meep-ams/server/ams.go +++ b/go-apps/meep-ams/server/ams.go @@ -28,6 +28,7 @@ import ( "net/http" "net/url" "os" + "regexp" "sort" "strconv" "strings" @@ -1488,6 +1489,7 @@ func appMobilityServiceByIdDELETE(w http.ResponseWriter, r *http.Request) { } func appMobilityServiceGET(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") // Validate query parameters @@ -1502,16 +1504,17 @@ func appMobilityServiceGET(w http.ResponseWriter, r *http.Request) { } // Parse query parameters - filters := q.Get("filter") - allFields := q.Get("all_fields") + urlFilter := q.Get("filter") + // allFields := q.Get("all_fields") // field := q["fields"] // excludeFields := q["exclude_fields"] // excludeDefault := q.Get("exclude_default") - + if urlFilter != "" { + fmt.Printf("filter: %s", urlFilter) + } regInfoList := &RegisterationInfoList{ Filters: &FilterParameters{ - filter: filters, - all_fields: allFields, + filter: urlFilter, }, Registrations: make([]RegistrationInfo, 0), } @@ -1549,7 +1552,6 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err // Retrieve registration info from DB var regInfo RegistrationInfo err := json.Unmarshal([]byte(jsonEntry), ®Info) - println("keysss", key) if err != nil { return err } @@ -1557,18 +1559,25 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err // Filter services if data.Filters != nil { + // Filter Paramter if data.Filters.filter != "" { filterField := data.Filters.filter + fmt.Printf("Filter Field: %s", filterField) // Split filterField into operator, attribute, and value - filterField = strings.Trim(filterField, "()") - filterParts := strings.SplitN(filterField, ",", 3) - if len(filterParts) != 3 { - return nil - } - operator := filterParts[0] - attribute := filterParts[1] - value := filterParts[2] + operator, attribute, value, _ := parseFilter(string(filterField)) + + fmt.Printf("operator: %s", operator) + fmt.Printf("attribute: %s", attribute) + fmt.Printf("value: %s", value) + // filterField = strings.Trim(filterField, "()") + // filterParts := strings.SplitN(filterField, ",", 3) + // if len(filterParts) != 3 { + // return nil + // } + // operator := filterParts[0] + // attribute := filterParts[1] + // value := filterParts[2] // Apply filters based on attribute switch attribute { @@ -1577,17 +1586,17 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err return nil } - case "serviceConsumerId.appInstanceId": + case "serviceConsumerId/appInstanceId": if !applyStringFilter(operator, regInfo.ServiceConsumerId.AppInstanceId, value) { return nil } - case "serviceConsumerId.mepId": + case "serviceConsumerId/mepId": if !applyStringFilter(operator, regInfo.ServiceConsumerId.MepId, value) { return nil } - case "deviceInformation.associateId": + case "deviceInformation/associateId": matched := false for _, deviceInfo := range regInfo.DeviceInformation { if applyStringFilter(operator, deviceInfo.AssociateId.Value, value) { @@ -1599,17 +1608,17 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err return nil } - // case "deviceInformation.contextTransferState": - // matched := false - // for _, deviceInfo := range regInfo.DeviceInformation { - // if applyEnumFilter(operator, string(deviceInfo.ContextTransferState.val), value) { - // matched = true - // break - // } - // } - // if !matched { - // return fmt.Errorf("filter mismatch: deviceInformation.contextTransferState") - // } + case "deviceInformation/contextTransferState": + matched := false + for _, deviceInfo := range regInfo.DeviceInformation { + if applyEnumFilter(operator, string(*deviceInfo.ContextTransferState), value) { + matched = true + break + } + } + if !matched { + return nil + } case "expiryTime": expiryTime, err := strconv.ParseUint(value, 10, 32) @@ -1631,185 +1640,6 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err return nil } -// Helper functions for applying filters -func applyStringFilter(operator, fieldValue, filterValue string) bool { - switch operator { - case "eq": - return fieldValue == filterValue - case "neq": - return fieldValue != filterValue - case "cont": - return strings.Contains(fieldValue, filterValue) - case "ncont": - return !strings.Contains(fieldValue, filterValue) - case "in": - values := strings.Split(filterValue, ",") - for _, v := range values { - if fieldValue == v { - return true - } - } - return false - case "nin": - values := strings.Split(filterValue, ",") - for _, v := range values { - if fieldValue == v { - return false - } - } - return true - case "gt": - return fieldValue > filterValue - case "gte": - return fieldValue >= filterValue - case "lt": - return fieldValue < filterValue - case "lte": - return fieldValue <= filterValue - default: - return false - } -} - -func applyEnumFilter(operator, fieldValue, filterValue string) bool { - return applyStringFilter(operator, fieldValue, filterValue) -} - -func applyNumericFilter(operator string, fieldValue, filterValue uint32) bool { - switch operator { - case "eq": - return fieldValue == filterValue - case "neq": - return fieldValue != filterValue - case "gt": - return fieldValue > filterValue - case "gte": - return fieldValue >= filterValue - case "lt": - return fieldValue < filterValue - case "lte": - return fieldValue <= filterValue - default: - return false - } -} - -// Original -// func populateRegInfoList(key string, jsonEntry string, response interface{}) error { -// data := response.(*RegisterationInfoList) -// if data == nil { -// return errors.New("response not defined") -// } - -// // Retrieve registration info from DB -// var regInfo RegistrationInfo -// err := json.Unmarshal([]byte(jsonEntry), ®Info) -// println("keysss", key) -// if err == nil { -// return err -// } - -// // Filter services -// if data.Filters != nil { - -// if data.Filters.filter != "" { -// filterField := data.Filters.filter -// if regInfo.AppMobilityServiceId == "" || (filterField != regInfo.AppMobilityServiceId) { -// return nil -// } -// } - -// } - -// data.Registrations = append(data.Registrations, regInfo) -// return nil -// } - -// filterRegistrationInfo applies filtering based on the provided filters. -// func filterRegistrationInfo(regInfoList []RegistrationInfo, filters []string) []RegistrationInfo { -// var filteredList []RegistrationInfo - -// for _, regInfo := range regInfoList { -// match := true - -// // Iterate over filters to apply them one by one -// for _, filter := range filters { -// filterParts := strings.Split(filter, ":") -// if len(filterParts) != 2 { -// // Invalid filter format (e.g., "key:value") -// continue -// } -// key := filterParts[0] -// value := filterParts[1] - -// // Apply filter based on key-value pairs -// switch key { -// case "appMobilityServiceId": -// if regInfo.AppMobilityServiceId != value { -// match = false -// } -// case "appInstanceId": -// if regInfo.ServiceConsumerId.AppInstanceId != value { -// match = false -// } -// case "mepId": -// if regInfo.ServiceConsumerId.MepId != value { -// match = false -// } -// case "associateId": -// // Check if any device has the associateId value -// found := false -// for _, device := range regInfo.DeviceInformation { -// if device.AssociateId == value { -// found = true -// break -// } -// } -// if !found { -// match = false -// } -// case "expiryTime": -// expiryTime, err := strconv.Atoi(value) -// if err != nil || regInfo.ExpiryTime != uint32(expiryTime) { -// match = false -// } -// default: -// // If filter key is not recognized, skip it -// continue -// } - -// // If any filter condition doesn't match, we stop checking further -// if !match { -// break -// } -// } - -// // If all filters matched, add to the result list -// if match { -// filteredList = append(filteredList, regInfo) -// } -// } - -// return filteredList -// } - -// func populateRegInfoList(key string, jsonEntry string, response interface{}) error { -// regInfoList := response.(*[]RegistrationInfo) -// if regInfoList == nil { -// return errors.New("Response not defined") -// } - -// // Retrieve registration info from DB -// var regInfo RegistrationInfo -// err := json.Unmarshal([]byte(jsonEntry), ®Info) -// if err != nil { -// return err -// } - -// *regInfoList = append(*regInfoList, regInfo) -// return nil -// } - func cleanUp() { log.Info("Terminate all") @@ -2668,3 +2498,92 @@ func errHandlerProblemDetails(w http.ResponseWriter, error string, code int) { w.WriteHeader(code) fmt.Fprint(w, jsonResponse) } + +func parseFilter(filterField string) (string, string, string, error) { + // Regular expression to match the filter format + re := regexp.MustCompile(`^(eq|neq|gt|lt|gte|lte|in|nin|cont|ncont),([a-zA-Z0-9/]+),([^,]+)(?:,([^,]+))?$`) + + // Trim any surrounding parentheses + filterField = strings.Trim(filterField, "()") + + // Match the filterField against the regular expression + matches := re.FindStringSubmatch(filterField) + if len(matches) < 3 { + return "", "", "", nil + } + + // Extract the operator, attribute, and value(s) + operator := matches[1] + attribute := matches[2] + value := matches[3] + + // If there's a second value (for operators like "in" or "nin"), handle it + if len(matches) > 4 && matches[4] != "" { + value += "," + matches[4] + } + + return operator, attribute, value, nil +} + +// Helper functions for applying filters +func applyStringFilter(operator, fieldValue, filterValue string) bool { + switch operator { + case "eq": + return fieldValue == filterValue + case "neq": + return fieldValue != filterValue + case "cont": + return strings.Contains(fieldValue, filterValue) + case "ncont": + return !strings.Contains(fieldValue, filterValue) + case "in": + values := strings.Split(filterValue, ",") + for _, v := range values { + if fieldValue == v { + return true + } + } + return false + case "nin": + values := strings.Split(filterValue, ",") + for _, v := range values { + if fieldValue == v { + return false + } + } + return true + case "gt": + return fieldValue > filterValue + case "gte": + return fieldValue >= filterValue + case "lt": + return fieldValue < filterValue + case "lte": + return fieldValue <= filterValue + default: + return false + } +} + +func applyEnumFilter(operator, fieldValue, filterValue string) bool { + return applyStringFilter(operator, fieldValue, filterValue) +} + +func applyNumericFilter(operator string, fieldValue, filterValue uint32) bool { + switch operator { + // case "eq": + // return fieldValue == filterValue + // case "neq": + // return fieldValue != filterValue + case "gt": + return fieldValue > filterValue + case "gte": + return fieldValue >= filterValue + case "lt": + return fieldValue < filterValue + case "lte": + return fieldValue <= filterValue + default: + return false + } +} -- GitLab From 04c28b4aba96132e4aa5a59541b46e148cc0d526 Mon Sep 17 00:00:00 2001 From: Umair Khan <umair.anwar@xflowresearch.com> Date: Thu, 19 Dec 2024 10:48:56 +0000 Subject: [PATCH 3/9] add query paramets fields and exclude_fields to MEC 021 Mobility Service GET request --- go-apps/meep-ams/server/ams.go | 156 ++++++++++++++++++++++++++++----- 1 file changed, 133 insertions(+), 23 deletions(-) diff --git a/go-apps/meep-ams/server/ams.go b/go-apps/meep-ams/server/ams.go index e6c3d2345..0f7bd77a6 100644 --- a/go-apps/meep-ams/server/ams.go +++ b/go-apps/meep-ams/server/ams.go @@ -1495,7 +1495,7 @@ func appMobilityServiceGET(w http.ResponseWriter, r *http.Request) { // Validate query parameters u, _ := url.Parse(r.URL.String()) q := u.Query() - validParams := []string{"filter", "All_fields", "Fields", "Exclude_fields", "Exclude_default"} + validParams := []string{"filter", "all_fields", "fields", "exclude_fields", "exclude_default"} err := validateQueryParams(q, validParams) if err != nil { print("Query Parameter error") @@ -1504,17 +1504,18 @@ func appMobilityServiceGET(w http.ResponseWriter, r *http.Request) { } // Parse query parameters - urlFilter := q.Get("filter") - // allFields := q.Get("all_fields") - // field := q["fields"] - // excludeFields := q["exclude_fields"] - // excludeDefault := q.Get("exclude_default") - if urlFilter != "" { - fmt.Printf("filter: %s", urlFilter) - } + urlFilter := q.Get("Filter") + urlAllFields := q.Get("all_fields") + urlfields := q.Get("fields") + urlExcludeFields := q.Get("exclude_fields") + urlExcludeDefault := q.Get("exclude_default") regInfoList := &RegisterationInfoList{ Filters: &FilterParameters{ - filter: urlFilter, + filter: urlFilter, + all_fields: urlAllFields, + fields: urlfields, + exclude_fields: urlExcludeFields, + exclude_default: urlExcludeDefault, }, Registrations: make([]RegistrationInfo, 0), } @@ -1565,19 +1566,7 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err filterField := data.Filters.filter fmt.Printf("Filter Field: %s", filterField) // Split filterField into operator, attribute, and value - operator, attribute, value, _ := parseFilter(string(filterField)) - - fmt.Printf("operator: %s", operator) - fmt.Printf("attribute: %s", attribute) - fmt.Printf("value: %s", value) - // filterField = strings.Trim(filterField, "()") - // filterParts := strings.SplitN(filterField, ",", 3) - // if len(filterParts) != 3 { - // return nil - // } - // operator := filterParts[0] - // attribute := filterParts[1] - // value := filterParts[2] + operator, attribute, value, _ := parseFilter(filterField) // Apply filters based on attribute switch attribute { @@ -1608,6 +1597,18 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err return nil } + case "deviceInformation/appMobilityServiceLevel": + matched := false + for _, deviceInfo := range regInfo.DeviceInformation { + if applyStringFilter(operator, string(*deviceInfo.AppMobilityServiceLevel), value) { + matched = true + break + } + } + if !matched { + return nil + } + case "deviceInformation/contextTransferState": matched := false for _, deviceInfo := range regInfo.DeviceInformation { @@ -1635,7 +1636,116 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err } + // Handle Fields Parameter + if data.Filters.fields != "" { + fields := strings.Split(data.Filters.fields, ",") + filteredRegInfo := RegistrationInfo{} + + for _, field := range fields { + switch field { + case "appMobilityServiceId": + filteredRegInfo.AppMobilityServiceId = regInfo.AppMobilityServiceId + + case "serviceConsumerId/appInstanceId": + if filteredRegInfo.ServiceConsumerId == nil { + filteredRegInfo.ServiceConsumerId = &RegistrationInfoServiceConsumerId{} + } + if regInfo.ServiceConsumerId.AppInstanceId != "" { + filteredRegInfo.ServiceConsumerId.AppInstanceId = regInfo.ServiceConsumerId.AppInstanceId + } + + case "serviceConsumerId/mepId": + if filteredRegInfo.ServiceConsumerId == nil { + filteredRegInfo.ServiceConsumerId = &RegistrationInfoServiceConsumerId{} + } + if regInfo.ServiceConsumerId.MepId != "" { + filteredRegInfo.ServiceConsumerId.MepId = regInfo.ServiceConsumerId.MepId + } + + case "deviceInformation/associateId": + for _, deviceInfo := range regInfo.DeviceInformation { + if deviceInfo.AssociateId.Value != "" { + filteredDeviceInfo := RegistrationInfoDeviceInformation{ + AssociateId: deviceInfo.AssociateId, + } + filteredRegInfo.DeviceInformation = append(filteredRegInfo.DeviceInformation, filteredDeviceInfo) + } + } + + case "deviceInformation/appMobilityServiceLevel": + for _, deviceInfo := range regInfo.DeviceInformation { + if *deviceInfo.AppMobilityServiceLevel != "" { + filteredDeviceInfo := RegistrationInfoDeviceInformation{ + AppMobilityServiceLevel: deviceInfo.AppMobilityServiceLevel, + } + filteredRegInfo.DeviceInformation = append(filteredRegInfo.DeviceInformation, filteredDeviceInfo) + } + } + + case "deviceInformation/contextTransferState": + for _, deviceInfo := range regInfo.DeviceInformation { + if *deviceInfo.ContextTransferState != "" { + filteredDeviceInfo := RegistrationInfoDeviceInformation{ + ContextTransferState: deviceInfo.ContextTransferState, + } + filteredRegInfo.DeviceInformation = append(filteredRegInfo.DeviceInformation, filteredDeviceInfo) + } + } + + case "expiryTime": + //Logic + if string(regInfo.ExpiryTime) != "" { + filteredRegInfo.ExpiryTime = regInfo.ExpiryTime + } + + } + // Replace regInfo with the filtered version + + } + regInfo = filteredRegInfo + } + + // Handle Exclude Fields Parameter (Exclude specified fields) + if data.Filters.exclude_fields != "" { + excludeFields := strings.Split(data.Filters.exclude_fields, ",") + filteredRegInfo := regInfo + + // Exclude the listed fields + for _, field := range excludeFields { + switch field { + case "appMobilityServiceId": + filteredRegInfo.AppMobilityServiceId = "" // Exclude this field + case "serviceConsumerId/appInstanceId": + if filteredRegInfo.ServiceConsumerId != nil { + filteredRegInfo.ServiceConsumerId.AppInstanceId = "" // Exclude this field + } + case "serviceConsumerId/mepId": + if filteredRegInfo.ServiceConsumerId != nil { + filteredRegInfo.ServiceConsumerId.MepId = "" // Exclude this field + } + case "deviceInformation/associateId": + for i := range filteredRegInfo.DeviceInformation { + filteredRegInfo.DeviceInformation[i].AssociateId = nil // Exclude this field + } + case "deviceInformation/appMobilityServiceLevel": + for i := range filteredRegInfo.DeviceInformation { + filteredRegInfo.DeviceInformation[i].AppMobilityServiceLevel = nil // Exclude this field + } + case "deviceInformation/contextTransferState": + for i := range filteredRegInfo.DeviceInformation { + filteredRegInfo.DeviceInformation[i].ContextTransferState = nil // Exclude this field + } + case "expiryTime": + filteredRegInfo.ExpiryTime = 0 // Exclude this field + } + } + + // Replace regInfo with the filtered version based on exclude_fields parameter + regInfo = filteredRegInfo + } + } + // Returning Data data.Registrations = append(data.Registrations, regInfo) return nil } -- GitLab From 7fbc85c5811e0d6f2ed098e110f512201414514e Mon Sep 17 00:00:00 2001 From: Umair Khan <umair.anwar@xflowresearch.com> Date: Fri, 20 Dec 2024 11:52:57 +0000 Subject: [PATCH 4/9] update etsi mec sandbox mec 021 AMS back-end as per 3.1.1 AMS document version --- go-apps/meep-ams/api/swagger.yaml | 36 +++++++++---------- go-apps/meep-ams/server/ams.go | 33 +++++++++-------- go-apps/meep-ams/server/api_amsi.go | 2 +- go-apps/meep-ams/server/api_unsupported.go | 2 +- go-apps/meep-ams/server/backup.go | 1 + go-apps/meep-ams/server/logger.go | 2 +- .../model_adjacent_app_info_notification.go | 2 +- ...app_info_notification_adjacent_app_info.go | 2 +- .../model_adjacent_app_info_subscription.go | 2 +- ...t_app_info_subscription_filter_criteria.go | 2 +- ...el_adjacent_app_info_subscription_links.go | 2 +- .../model_adjacent_app_instance_info.go | 2 +- .../model_app_mobility_service_level.go | 2 +- .../model_app_termination_notification.go | 2 +- ...del_app_termination_notification__links.go | 2 +- go-apps/meep-ams/server/model_associate_id.go | 2 +- .../server/model_associate_id_type.go | 2 +- .../server/model_communication_interface.go | 2 +- ...el_communication_interface_ip_addresses.go | 2 +- .../server/model_context_transfer_state.go | 2 +- .../server/model_expiry_notification.go | 2 +- .../server/model_inline_notification.go | 2 +- .../server/model_inline_subscription.go | 2 +- go-apps/meep-ams/server/model_link.go | 2 +- go-apps/meep-ams/server/model_link_type.go | 2 +- .../server/model_mec_host_information.go | 2 +- .../model_mobility_procedure_notification.go | 2 +- ..._procedure_notification_target_app_info.go | 2 +- .../model_mobility_procedure_subscription.go | 2 +- ..._procedure_subscription_filter_criteria.go | 2 +- ...l_mobility_procedure_subscription_links.go | 2 +- .../meep-ams/server/model_mobility_status.go | 2 +- .../model_one_of_inline_notification.go | 2 +- .../model_one_of_inline_subscription.go | 2 +- .../server/model_operation_action_type.go | 2 +- .../meep-ams/server/model_problem_details.go | 2 +- .../server/model_registration_info.go | 2 +- ...el_registration_info_device_information.go | 2 +- ...l_registration_info_service_consumer_id.go | 2 +- .../server/model_subscription_link_list.go | 2 +- .../model_subscription_link_list_links.go | 2 +- ...del_subscription_link_list_subscription.go | 2 +- .../server/model_subscription_type.go | 2 +- .../server/model_test_notification.go | 2 +- .../server/model_test_notification__links.go | 2 +- go-apps/meep-ams/server/model_time_stamp.go | 2 +- .../server/model_websock_notif_config.go | 2 +- go-apps/meep-ams/server/routers.go | 2 +- 48 files changed, 83 insertions(+), 77 deletions(-) create mode 100644 go-apps/meep-ams/server/backup.go diff --git a/go-apps/meep-ams/api/swagger.yaml b/go-apps/meep-ams/api/swagger.yaml index dc17eb3c6..ab52abf35 100644 --- a/go-apps/meep-ams/api/swagger.yaml +++ b/go-apps/meep-ams/api/swagger.yaml @@ -1,9 +1,9 @@ openapi: 3.0.0 info: title: AdvantEDGE Application Mobility API - version: '2.2.1' + version: '3.1.1' description: Application Mobility Service is AdvantEDGE's implementation of [ETSI - MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) + MEC ISG MEC021 Application Mobility API](https://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/03.01.01_60/gs_mec021v030101p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get @@ -16,8 +16,8 @@ info: name: InterDigital AdvantEDGE Support email: AdvantEDGE@InterDigital.com externalDocs: - description: ETSI GS MEC 021 Application Mobility Service API, v2.2.1 - url: https://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_mec021v020201p.pdf + description: ETSI GS MEC 021 Application Mobility Service API, v3.1.1 + url: https://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/03.01.01_60/gs_mec021v030101p.pdf servers: - url: https://localhost/sandboxname/amsi/v1 variables: {} @@ -40,30 +40,30 @@ paths: explode: true schema: type: string - - name: all_fields + - name: All_fields in: query description: Include all complex attributes in the response. style: form explode: true schema: type: string - - name: fields + - name: Fields in: query description: Complex attributes to be included into the response. See clause 6.18 in ETSI GS MEC 009 style: form explode: true schema: type: string - - name: exclude_fields + - name: Exclude_fields in: query description: Complex attributes to be excluded from the response.See clause 6.18 in ETSI GS MEC 009 style: form explode: true schema: type: string - - name: exclude_default + - name: Exclude_default in: query - description: Indicates to exclude the following complex attributes from the response See clause 6.18 in ETSI GS MEC 011 for details. + description: Indicates to exclude the following complex attributes from the response See clause 6.18 in ETSI GS MEC 009 for details. style: form explode: true schema: @@ -133,35 +133,35 @@ paths: parameters: - name: filter in: query - description: Attribute-based filtering parameters according to ETSI GS MEC 011 + description: Attribute-based filtering parameters, according to ETSI GS MEC 009, use the format (op,attr,value) style: form explode: true schema: type: string - - name: all_fields + - name: All_fields in: query - description: Include all complex attributes in the response. + description: Include all complex attributes in the response. e.g., All_Fields. style: form explode: true schema: type: string - - name: fields + - name: Fields in: query - description: Complex attributes to be included into the response. See clause 6.18 in ETSI GS MEC 011 + description: Complex attributes to be included in the response (see Clause 6.18 in ETSI GS MEC 009), e.g., att or att/subatt. style: form explode: true schema: type: string - - name: exclude_fields + - name: Exclude_fields in: query - description: Complex attributes to be excluded from the response.See clause 6.18 in ETSI GS MEC 011 + description: Complex attributes to be excluded in the response (see Clause 6.18 in ETSI GS MEC 009), e.g., att or att/subatt. style: form explode: true schema: type: string - - name: exclude_default + - name: Exclude_default in: query - description: Indicates to exclude the following complex attributes from the response See clause 6.18 in ETSI GS MEC 011 for details. + description: Indicates to exclude the following complex attributes from the response See clause 6.18 in ETSI GS MEC 009 for details. style: form explode: true schema: diff --git a/go-apps/meep-ams/server/ams.go b/go-apps/meep-ams/server/ams.go index 0f7bd77a6..0123bd8b8 100644 --- a/go-apps/meep-ams/server/ams.go +++ b/go-apps/meep-ams/server/ams.go @@ -1495,7 +1495,7 @@ func appMobilityServiceGET(w http.ResponseWriter, r *http.Request) { // Validate query parameters u, _ := url.Parse(r.URL.String()) q := u.Query() - validParams := []string{"filter", "all_fields", "fields", "exclude_fields", "exclude_default"} + validParams := []string{"filter", "All_fields", "Fields", "Exclude_fields", "Exclude_default"} err := validateQueryParams(q, validParams) if err != nil { print("Query Parameter error") @@ -1504,11 +1504,11 @@ func appMobilityServiceGET(w http.ResponseWriter, r *http.Request) { } // Parse query parameters - urlFilter := q.Get("Filter") - urlAllFields := q.Get("all_fields") - urlfields := q.Get("fields") - urlExcludeFields := q.Get("exclude_fields") - urlExcludeDefault := q.Get("exclude_default") + urlFilter := q.Get("filter") + urlAllFields := q.Get("All_fields") + urlfields := q.Get("Fields") + urlExcludeFields := q.Get("Exclude_fields") + urlExcludeDefault := q.Get("Exclude_default") regInfoList := &RegisterationInfoList{ Filters: &FilterParameters{ filter: urlFilter, @@ -1562,9 +1562,7 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err // Filter Paramter if data.Filters.filter != "" { - filterField := data.Filters.filter - fmt.Printf("Filter Field: %s", filterField) // Split filterField into operator, attribute, and value operator, attribute, value, _ := parseFilter(filterField) @@ -1636,6 +1634,11 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err } + // Handle Fields Parameter (Include ALL fields) + if data.Filters.all_fields != "" && data.Filters.all_fields != "All_fields" { + return nil + } + // Handle Fields Parameter if data.Filters.fields != "" { fields := strings.Split(data.Filters.fields, ",") @@ -1647,17 +1650,17 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err filteredRegInfo.AppMobilityServiceId = regInfo.AppMobilityServiceId case "serviceConsumerId/appInstanceId": - if filteredRegInfo.ServiceConsumerId == nil { - filteredRegInfo.ServiceConsumerId = &RegistrationInfoServiceConsumerId{} - } + // if filteredRegInfo.ServiceConsumerId == nil { + // filteredRegInfo.ServiceConsumerId = &RegistrationInfoServiceConsumerId{} + // } if regInfo.ServiceConsumerId.AppInstanceId != "" { filteredRegInfo.ServiceConsumerId.AppInstanceId = regInfo.ServiceConsumerId.AppInstanceId } case "serviceConsumerId/mepId": - if filteredRegInfo.ServiceConsumerId == nil { - filteredRegInfo.ServiceConsumerId = &RegistrationInfoServiceConsumerId{} - } + // if filteredRegInfo.ServiceConsumerId == nil { + // filteredRegInfo.ServiceConsumerId = &RegistrationInfoServiceConsumerId{} + // } if regInfo.ServiceConsumerId.MepId != "" { filteredRegInfo.ServiceConsumerId.MepId = regInfo.ServiceConsumerId.MepId } @@ -1744,6 +1747,8 @@ func populateRegInfoList(key string, jsonEntry string, response interface{}) err regInfo = filteredRegInfo } + // Handle Exclude Fields default Parameter (Exclude specified fields) + } // Returning Data data.Registrations = append(data.Registrations, regInfo) diff --git a/go-apps/meep-ams/server/api_amsi.go b/go-apps/meep-ams/server/api_amsi.go index 6031d67cd..e4ba29ead 100644 --- a/go-apps/meep-ams/server/api_amsi.go +++ b/go-apps/meep-ams/server/api_amsi.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/api_unsupported.go b/go-apps/meep-ams/server/api_unsupported.go index 8ec5a84cb..e506857b2 100644 --- a/go-apps/meep-ams/server/api_unsupported.go +++ b/go-apps/meep-ams/server/api_unsupported.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/backup.go b/go-apps/meep-ams/server/backup.go new file mode 100644 index 000000000..abb4e431a --- /dev/null +++ b/go-apps/meep-ams/server/backup.go @@ -0,0 +1 @@ +package server diff --git a/go-apps/meep-ams/server/logger.go b/go-apps/meep-ams/server/logger.go index c5a73ff34..38c32dbca 100644 --- a/go-apps/meep-ams/server/logger.go +++ b/go-apps/meep-ams/server/logger.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_adjacent_app_info_notification.go b/go-apps/meep-ams/server/model_adjacent_app_info_notification.go index 0a48637f4..7bacaf62d 100644 --- a/go-apps/meep-ams/server/model_adjacent_app_info_notification.go +++ b/go-apps/meep-ams/server/model_adjacent_app_info_notification.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_adjacent_app_info_notification_adjacent_app_info.go b/go-apps/meep-ams/server/model_adjacent_app_info_notification_adjacent_app_info.go index ce78d8249..26325a4e8 100644 --- a/go-apps/meep-ams/server/model_adjacent_app_info_notification_adjacent_app_info.go +++ b/go-apps/meep-ams/server/model_adjacent_app_info_notification_adjacent_app_info.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_adjacent_app_info_subscription.go b/go-apps/meep-ams/server/model_adjacent_app_info_subscription.go index 81b575be6..b97798845 100644 --- a/go-apps/meep-ams/server/model_adjacent_app_info_subscription.go +++ b/go-apps/meep-ams/server/model_adjacent_app_info_subscription.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_adjacent_app_info_subscription_filter_criteria.go b/go-apps/meep-ams/server/model_adjacent_app_info_subscription_filter_criteria.go index 71c90e00d..ee4a1abc4 100644 --- a/go-apps/meep-ams/server/model_adjacent_app_info_subscription_filter_criteria.go +++ b/go-apps/meep-ams/server/model_adjacent_app_info_subscription_filter_criteria.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_adjacent_app_info_subscription_links.go b/go-apps/meep-ams/server/model_adjacent_app_info_subscription_links.go index 123c35744..7a1f49382 100644 --- a/go-apps/meep-ams/server/model_adjacent_app_info_subscription_links.go +++ b/go-apps/meep-ams/server/model_adjacent_app_info_subscription_links.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_adjacent_app_instance_info.go b/go-apps/meep-ams/server/model_adjacent_app_instance_info.go index 607c4702a..ec7a4ad9a 100644 --- a/go-apps/meep-ams/server/model_adjacent_app_instance_info.go +++ b/go-apps/meep-ams/server/model_adjacent_app_instance_info.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_app_mobility_service_level.go b/go-apps/meep-ams/server/model_app_mobility_service_level.go index 3bdf477dc..a59b7a275 100644 --- a/go-apps/meep-ams/server/model_app_mobility_service_level.go +++ b/go-apps/meep-ams/server/model_app_mobility_service_level.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_app_termination_notification.go b/go-apps/meep-ams/server/model_app_termination_notification.go index 9f3da6625..96b5afc52 100644 --- a/go-apps/meep-ams/server/model_app_termination_notification.go +++ b/go-apps/meep-ams/server/model_app_termination_notification.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_app_termination_notification__links.go b/go-apps/meep-ams/server/model_app_termination_notification__links.go index 7287931a3..3dbe215e0 100644 --- a/go-apps/meep-ams/server/model_app_termination_notification__links.go +++ b/go-apps/meep-ams/server/model_app_termination_notification__links.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_associate_id.go b/go-apps/meep-ams/server/model_associate_id.go index 89d553ef6..12b49c577 100644 --- a/go-apps/meep-ams/server/model_associate_id.go +++ b/go-apps/meep-ams/server/model_associate_id.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_associate_id_type.go b/go-apps/meep-ams/server/model_associate_id_type.go index 329ad9bb5..92700ce4d 100644 --- a/go-apps/meep-ams/server/model_associate_id_type.go +++ b/go-apps/meep-ams/server/model_associate_id_type.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_communication_interface.go b/go-apps/meep-ams/server/model_communication_interface.go index 028c8e138..e842c2627 100644 --- a/go-apps/meep-ams/server/model_communication_interface.go +++ b/go-apps/meep-ams/server/model_communication_interface.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_communication_interface_ip_addresses.go b/go-apps/meep-ams/server/model_communication_interface_ip_addresses.go index 38e94df10..fe0959c21 100644 --- a/go-apps/meep-ams/server/model_communication_interface_ip_addresses.go +++ b/go-apps/meep-ams/server/model_communication_interface_ip_addresses.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_context_transfer_state.go b/go-apps/meep-ams/server/model_context_transfer_state.go index 2d80ed20a..7bc47d626 100644 --- a/go-apps/meep-ams/server/model_context_transfer_state.go +++ b/go-apps/meep-ams/server/model_context_transfer_state.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_expiry_notification.go b/go-apps/meep-ams/server/model_expiry_notification.go index 11cb2d4a8..ced3ec82b 100644 --- a/go-apps/meep-ams/server/model_expiry_notification.go +++ b/go-apps/meep-ams/server/model_expiry_notification.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_inline_notification.go b/go-apps/meep-ams/server/model_inline_notification.go index efa3570bb..eb6cadfb6 100644 --- a/go-apps/meep-ams/server/model_inline_notification.go +++ b/go-apps/meep-ams/server/model_inline_notification.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_inline_subscription.go b/go-apps/meep-ams/server/model_inline_subscription.go index 95d96d07f..83b523c25 100644 --- a/go-apps/meep-ams/server/model_inline_subscription.go +++ b/go-apps/meep-ams/server/model_inline_subscription.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_link.go b/go-apps/meep-ams/server/model_link.go index e864c1989..14af44fd7 100644 --- a/go-apps/meep-ams/server/model_link.go +++ b/go-apps/meep-ams/server/model_link.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_link_type.go b/go-apps/meep-ams/server/model_link_type.go index d812ac816..a0e9f5a6f 100644 --- a/go-apps/meep-ams/server/model_link_type.go +++ b/go-apps/meep-ams/server/model_link_type.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_mec_host_information.go b/go-apps/meep-ams/server/model_mec_host_information.go index e9326dc34..a87ff97bb 100644 --- a/go-apps/meep-ams/server/model_mec_host_information.go +++ b/go-apps/meep-ams/server/model_mec_host_information.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_mobility_procedure_notification.go b/go-apps/meep-ams/server/model_mobility_procedure_notification.go index ff90a8a11..258577475 100644 --- a/go-apps/meep-ams/server/model_mobility_procedure_notification.go +++ b/go-apps/meep-ams/server/model_mobility_procedure_notification.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_mobility_procedure_notification_target_app_info.go b/go-apps/meep-ams/server/model_mobility_procedure_notification_target_app_info.go index 7515c2eef..7b19b4b10 100644 --- a/go-apps/meep-ams/server/model_mobility_procedure_notification_target_app_info.go +++ b/go-apps/meep-ams/server/model_mobility_procedure_notification_target_app_info.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_mobility_procedure_subscription.go b/go-apps/meep-ams/server/model_mobility_procedure_subscription.go index ec7828ded..5492d708b 100644 --- a/go-apps/meep-ams/server/model_mobility_procedure_subscription.go +++ b/go-apps/meep-ams/server/model_mobility_procedure_subscription.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_mobility_procedure_subscription_filter_criteria.go b/go-apps/meep-ams/server/model_mobility_procedure_subscription_filter_criteria.go index a24757e3e..3bf738c19 100644 --- a/go-apps/meep-ams/server/model_mobility_procedure_subscription_filter_criteria.go +++ b/go-apps/meep-ams/server/model_mobility_procedure_subscription_filter_criteria.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_mobility_procedure_subscription_links.go b/go-apps/meep-ams/server/model_mobility_procedure_subscription_links.go index 8cce93b4e..8d54fc712 100644 --- a/go-apps/meep-ams/server/model_mobility_procedure_subscription_links.go +++ b/go-apps/meep-ams/server/model_mobility_procedure_subscription_links.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_mobility_status.go b/go-apps/meep-ams/server/model_mobility_status.go index f78702402..9da7212f1 100644 --- a/go-apps/meep-ams/server/model_mobility_status.go +++ b/go-apps/meep-ams/server/model_mobility_status.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_one_of_inline_notification.go b/go-apps/meep-ams/server/model_one_of_inline_notification.go index c8b870189..98744a2c5 100644 --- a/go-apps/meep-ams/server/model_one_of_inline_notification.go +++ b/go-apps/meep-ams/server/model_one_of_inline_notification.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_one_of_inline_subscription.go b/go-apps/meep-ams/server/model_one_of_inline_subscription.go index a2486df2b..ac4925f2b 100644 --- a/go-apps/meep-ams/server/model_one_of_inline_subscription.go +++ b/go-apps/meep-ams/server/model_one_of_inline_subscription.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_operation_action_type.go b/go-apps/meep-ams/server/model_operation_action_type.go index 4b1880c2c..c95c033ab 100644 --- a/go-apps/meep-ams/server/model_operation_action_type.go +++ b/go-apps/meep-ams/server/model_operation_action_type.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_problem_details.go b/go-apps/meep-ams/server/model_problem_details.go index 10ca51558..f451c100a 100644 --- a/go-apps/meep-ams/server/model_problem_details.go +++ b/go-apps/meep-ams/server/model_problem_details.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_registration_info.go b/go-apps/meep-ams/server/model_registration_info.go index a172f9256..fda8ed3f0 100644 --- a/go-apps/meep-ams/server/model_registration_info.go +++ b/go-apps/meep-ams/server/model_registration_info.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_registration_info_device_information.go b/go-apps/meep-ams/server/model_registration_info_device_information.go index 2bd5c0183..3507a2aeb 100644 --- a/go-apps/meep-ams/server/model_registration_info_device_information.go +++ b/go-apps/meep-ams/server/model_registration_info_device_information.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_registration_info_service_consumer_id.go b/go-apps/meep-ams/server/model_registration_info_service_consumer_id.go index 2924164ef..eb485a72d 100644 --- a/go-apps/meep-ams/server/model_registration_info_service_consumer_id.go +++ b/go-apps/meep-ams/server/model_registration_info_service_consumer_id.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_subscription_link_list.go b/go-apps/meep-ams/server/model_subscription_link_list.go index 30c277c09..018516898 100644 --- a/go-apps/meep-ams/server/model_subscription_link_list.go +++ b/go-apps/meep-ams/server/model_subscription_link_list.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_subscription_link_list_links.go b/go-apps/meep-ams/server/model_subscription_link_list_links.go index 576ae04c8..666a5bfe2 100644 --- a/go-apps/meep-ams/server/model_subscription_link_list_links.go +++ b/go-apps/meep-ams/server/model_subscription_link_list_links.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_subscription_link_list_subscription.go b/go-apps/meep-ams/server/model_subscription_link_list_subscription.go index bd29fc4fb..89b80580e 100644 --- a/go-apps/meep-ams/server/model_subscription_link_list_subscription.go +++ b/go-apps/meep-ams/server/model_subscription_link_list_subscription.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_subscription_type.go b/go-apps/meep-ams/server/model_subscription_type.go index aa34777c8..c0c996e1a 100644 --- a/go-apps/meep-ams/server/model_subscription_type.go +++ b/go-apps/meep-ams/server/model_subscription_type.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_test_notification.go b/go-apps/meep-ams/server/model_test_notification.go index 55749ad09..3c5962328 100644 --- a/go-apps/meep-ams/server/model_test_notification.go +++ b/go-apps/meep-ams/server/model_test_notification.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_test_notification__links.go b/go-apps/meep-ams/server/model_test_notification__links.go index cef23f442..852dab996 100644 --- a/go-apps/meep-ams/server/model_test_notification__links.go +++ b/go-apps/meep-ams/server/model_test_notification__links.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_time_stamp.go b/go-apps/meep-ams/server/model_time_stamp.go index 46e31b7eb..a3a6e5f92 100644 --- a/go-apps/meep-ams/server/model_time_stamp.go +++ b/go-apps/meep-ams/server/model_time_stamp.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/model_websock_notif_config.go b/go-apps/meep-ams/server/model_websock_notif_config.go index 403bb1015..b5b30991d 100644 --- a/go-apps/meep-ams/server/model_websock_notif_config.go +++ b/go-apps/meep-ams/server/model_websock_notif_config.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ diff --git a/go-apps/meep-ams/server/routers.go b/go-apps/meep-ams/server/routers.go index ce1eb3d9e..e99757f7b 100644 --- a/go-apps/meep-ams/server/routers.go +++ b/go-apps/meep-ams/server/routers.go @@ -17,7 +17,7 @@ * * Application Mobility Service is AdvantEDGE's implementation of [ETSI MEC ISG MEC021 Application Mobility API](http://www.etsi.org/deliver/etsi_gs/MEC/001_099/021/02.02.01_60/gs_MEC021v020201p.pdf) <p>[Copyright (c) ETSI 2017](https://forge.etsi.org/etsi-forge-copyright-notice.txt) <p>**Micro-service**<br>[meep-ams](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-ams) <p>**Type & Usage**<br>Edge Service used by edge applications that want to get information about application mobility in the network <p>**Note**<br>AdvantEDGE supports a selected subset of Application Mobility API endpoints (see below). * - * API version: 2.2.1 + * API version: 3.1.1 * Contact: AdvantEDGE@InterDigital.com * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) */ -- GitLab From c33305ab044e77e908ff7ae13da830372635ada0 Mon Sep 17 00:00:00 2001 From: Ikram Ul Haq <ikram.haq@xflowresearch.com> Date: Tue, 4 Feb 2025 12:26:09 +0000 Subject: [PATCH 5/9] Implement Service Provisioning, EEC Registration, and ECS Discovery --- .../meep-app-enablement/values-template.yaml | 13 + .../api/app-support/swagger.yaml | 1357 ++++++++++++++++- .../server/app-support/api_mec_app_support.go | 23 + .../server/app-support/app-support.go | 685 ++++++++- .../server/app-support/convert.go | 39 + .../app-support/model_ac_characteristics.go | 15 + .../server/app-support/model_ac_profile.go | 24 + .../server/app-support/model_acr_scenario.go | 23 + .../app-support/model_app_group_profile.go | 18 + .../server/app-support/model_app_info.go | 8 +- .../app-support/model_application_info.go | 17 + .../server/app-support/model_device_type.go | 18 + .../app-support/model_discovered_eas.go | 23 + .../server/app-support/model_eas_detail.go | 16 + .../app-support/model_eas_discovery_filter.go | 16 + .../app-support/model_eas_discovery_req.go | 39 + .../app-support/model_eas_discovery_resp.go | 16 + .../server/app-support/model_eas_profile.go | 41 + .../app-support/model_ecs_serv_prov_req.go | 30 + .../server/app-support/model_edn_con_info.go | 15 + .../app-support/model_edn_config_info.go | 23 + .../app-support/model_eec_registration.go | 40 + .../server/app-support/model_ees_info.go | 22 + .../server/app-support/model_end_point.go | 21 + .../app-support/model_geographic_area.go | 15 + .../model_geographical_coordinates.go | 17 + .../app-support/model_inline_response_201.go | 27 + .../server/app-support/model_location_info.go | 15 + .../server/app-support/model_point.go | 17 + ...odel_registrations_registration_id_body.go | 23 + .../server/app-support/model_requestor_id.go | 20 + .../app-support/model_supported_gad_shapes.go | 29 + go-apps/meep-app-enablement/server/routers.go | 42 + 33 files changed, 2710 insertions(+), 37 deletions(-) create mode 100644 go-apps/meep-app-enablement/server/app-support/model_ac_characteristics.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_ac_profile.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_acr_scenario.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_app_group_profile.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_application_info.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_device_type.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_discovered_eas.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_eas_detail.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_eas_discovery_filter.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_eas_discovery_req.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_eas_discovery_resp.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_eas_profile.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_ecs_serv_prov_req.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_edn_con_info.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_edn_config_info.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_eec_registration.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_ees_info.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_end_point.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_geographic_area.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_geographical_coordinates.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_inline_response_201.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_location_info.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_point.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_registrations_registration_id_body.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_requestor_id.go create mode 100644 go-apps/meep-app-enablement/server/app-support/model_supported_gad_shapes.go diff --git a/charts/meep-app-enablement/values-template.yaml b/charts/meep-app-enablement/values-template.yaml index f279333ac..3ab14ee01 100644 --- a/charts/meep-app-enablement/values-template.yaml +++ b/charts/meep-app-enablement/values-template.yaml @@ -49,12 +49,19 @@ ingress: - /{{.SandboxName}}/{{.MepName}}/service-apis - /{{.SandboxName}}/{{.MepName}}/published-apis - /{{.SandboxName}}/{{.MepName}}/capif-events + - /{{.SandboxName}}/{{.MepName}}/eecs-serviceprovisioning + - /{{.SandboxName}}/{{.MepName}}/eees-eecregistration + - /{{.SandboxName}}/{{.MepName}}/eees-easdiscovery + {{- else }} - /{{.SandboxName}}/mec_app_support - /{{.SandboxName}}/mec_service_mgmt - /{{.SandboxName}}/service-apis - /{{.SandboxName}}/published-apis - /{{.SandboxName}}/capif-events + - /{{.SandboxName}}/eecs-serviceprovisioning + - /{{.SandboxName}}/eees-eecregistration + - /{{.SandboxName}}/eees-easdiscovery {{- end }} annotations: kubernetes.io/ingress.class: nginx @@ -66,6 +73,9 @@ ingress: rewrite ^/{{ .SandboxName }}/{{.MepName}}/service-apis(/|$)(.*)$ /service-apis/$2 break; rewrite ^/{{ .SandboxName }}/{{.MepName}}/published-apis(/|$)(.*)$ /published-apis/$2 break; rewrite ^/{{ .SandboxName }}/{{.MepName}}/capif-events(/|$)(.*)$ /capif-events/$2 break; + rewrite ^/{{ .SandboxName }}/{{.MepName}}/eecs-serviceprovisioning(/|$)(.*)$ /eecs-serviceprovisioning/$2 break; + rewrite ^/{{ .SandboxName }}/{{.MepName}}/eees-eecregistration(/|$)(.*)$ /eees-eecregistration/$2 break; + rewrite ^/{{ .SandboxName }}/{{.MepName}}/eees-easdiscovery(/|$)(.*)$ /eees-easdiscovery/$2 break; {{- else }} nginx.ingress.kubernetes.io/configuration-snippet: | rewrite ^/{{ .SandboxName }}/mec_app_support(/|$)(.*)$ /mec_app_support/$2 break; @@ -73,6 +83,9 @@ ingress: rewrite ^/{{ .SandboxName }}/service-apis(/|$)(.*)$ /service-apis/$2 break; rewrite ^/{{ .SandboxName }}/published-apis(/|$)(.*)$ /published-apis/$2 break; rewrite ^/{{ .SandboxName }}/capif-events(/|$)(.*)$ /capif-events/$2 break; + rewrite ^/{{ .SandboxName }}/eecs-serviceprovisioning(/|$)(.*)$ /eecs-serviceprovisioning/$2 break; + rewrite ^/{{ .SandboxName }}/eees-eecregistration(/|$)(.*)$ /eees-eecregistration/$2 break; + rewrite ^/{{ .SandboxName }}/eees-easdiscovery(/|$)(.*)$ /eees-easdiscovery/$2 break; {{- end }} {{- if .AuthEnabled }} nginx.ingress.kubernetes.io/auth-url: https://$http_host/auth/v1/authenticate?svc=meep-app-enablement&sbox={{.SandboxName}}&mep={{.MepName}} diff --git a/go-apps/meep-app-enablement/api/app-support/swagger.yaml b/go-apps/meep-app-enablement/api/app-support/swagger.yaml index fb7bb25dd..a359f5ed5 100644 --- a/go-apps/meep-app-enablement/api/app-support/swagger.yaml +++ b/go-apps/meep-app-enablement/api/app-support/swagger.yaml @@ -23,6 +23,7 @@ servers: tags: - name: mec_app_support - name: unsupported + - name: Service Provisioning, EEC Registration, and ECS Discovery - name: callbacks paths: /applications/{appInstanceId}/traffic_rules: @@ -1067,9 +1068,9 @@ paths: - mec_app_support summary: Register the MEC application instance to the MEC platform description: >- - The POST method may be used by the MEC application instance to request its registration to the MEC platform. + The POST method may be used by the MEC application instance to request its registration to the MEC platform. operationId: ApplicationsRegistrations_POST - parameters: [] + parameters: [] requestBody: description: >- The message content in the request contains the profile of the MEC application instance, calling the MEC platform to register the MEC application instance. @@ -1077,7 +1078,96 @@ paths: application/json: schema: $ref: '#/components/schemas/AppInfo' - required: false + example: + appName: "appName" + appProvider: "appProvider1" + appCategory: + href: "/example/catalogue1" + id: "id12345" + name: "RNI" + version: "version1" + appDId: "TODO" + appInstanceId: "ID1" + endpoint: + uris: + - "mecAppSuptApi/example" + appServiceRequired: + - requestedPermissions: "string" + serCategory: + href: "/example/catalogue1" + id: "id12345" + name: "RNI" + version: "version1" + serName: "string" + serTransportDependencies: + - labels: + - "string" + serializers: + - "JSON" + transport: + protocol: "string" + security: + oAuth2Info: + grantTypes: + - "OAUTH2_CLIENT_CREDENTIALS" + tokenEndpoint: "/mecSerMgmtApi/security/TokenEndPoint" + type: "REST_HTTP" + version: "string" + version: "string" + appServiceOptional: + - requestedPermissions: "string" + serCategory: + href: "/example/catalogue1" + id: "id12345" + name: "RNI" + version: "version1" + serName: "string" + serTransportDependencies: + - labels: + - "string" + serializers: + - "JSON" + transport: + protocol: "string" + security: + oAuth2Info: + grantTypes: + - "OAUTH2_CLIENT_CREDENTIALS" + tokenEndpoint: "/mecSerMgmtApi/security/TokenEndPoint" + type: "REST_HTTP" + version: "string" + version: "string" + appFeatureRequired: + - featureName: "string" + version: "string" + appFeatureOptional: + - featureName: "string" + version: "string" + isInsByMec: false + appProfile: + easId: "appName" + endPt: + uris: + - "string" + acIds: + - "string" + provId: "appProvider1" + type: "string" + scheds: + - "string" + svcArea: "string" + svcKpi: "string" + permLvl: + - "string" + easFeats: + - "string" + svcContSupp: + - "string" + appLocs: + - "string" + avlRep: 1577836800 + status: "string" + required: true responses: '201': description: >- @@ -1097,7 +1187,6 @@ paths: '400': description: >- Bad Request. It is used to indicate that incorrect parameters were passed to the request. - headers: {} content: application/problem+json: schema: @@ -1109,7 +1198,6 @@ paths: '401': description: >- Unauthorized. It is used when the client did not submit the appropriate credentials. - headers: {} content: application/problem+json: schema: @@ -1121,15 +1209,13 @@ paths: '403': description: >- Forbidden. The operation is not allowed given the current status of the resource. - headers: {} content: application/problem+json: schema: $ref: '#/components/schemas/ProblemDetails' '404': description: >- - Not Found. It is used when a client provided a URI that cannot be mapped to a valid resource URI. - headers: {} + Not Found. It is used when a client provided a URI that cannot be mapped to a valid resource URI. content: application/problem+json: schema: @@ -1138,8 +1224,7 @@ paths: schema: type: object description: Empty schema - deprecated: false - parameters: [] + deprecated: false /registrations/{appInstanceId}: get: tags: @@ -1222,6 +1307,95 @@ paths: application/json: schema: $ref: '#/components/schemas/AppInfo' + example: + appName: "appName" + appProvider: "appProvider1" + appCategory: + href: "/example/catalogue1" + id: "id12345" + name: "RNI" + version: "version1" + appDId: "TODO" + appInstanceId: "ID1" + endpoint: + uris: + - "mecAppSuptApi/example" + appServiceRequired: + - requestedPermissions: "string" + serCategory: + href: "/example/catalogue1" + id: "id12345" + name: "RNI" + version: "version1" + serName: "string" + serTransportDependencies: + - labels: + - "string" + serializers: + - "JSON" + transport: + protocol: "string" + security: + oAuth2Info: + grantTypes: + - "OAUTH2_CLIENT_CREDENTIALS" + tokenEndpoint: "/mecSerMgmtApi/security/TokenEndPoint" + type: "REST_HTTP" + version: "string" + version: "string" + appServiceOptional: + - requestedPermissions: "string" + serCategory: + href: "/example/catalogue1" + id: "id12345" + name: "RNI" + version: "version1" + serName: "string" + serTransportDependencies: + - labels: + - "string" + serializers: + - "JSON" + transport: + protocol: "string" + security: + oAuth2Info: + grantTypes: + - "OAUTH2_CLIENT_CREDENTIALS" + tokenEndpoint: "/mecSerMgmtApi/security/TokenEndPoint" + type: "REST_HTTP" + version: "string" + version: "string" + appFeatureRequired: + - featureName: "string" + version: "string" + appFeatureOptional: + - featureName: "string" + version: "string" + isInsByMec: false + appProfile: + easId: "appName" + endPt: + uris: + - "string" + acIds: + - "string" + provId: "appProvider1" + type: "string" + scheds: + - "string" + svcArea: "string" + svcKpi: "string" + permLvl: + - "string" + easFeats: + - "string" + svcContSupp: + - "string" + appLocs: + - "string" + avlRep: 1577836800 + status: "string" required: true responses: '204': @@ -1340,6 +1514,490 @@ paths: description: Empty schema deprecated: false parameters: [] + + /request: + post: + summary: Request service provisioning information. + operationId: RequestServProv + tags: + - Service Provisioning, EEC Registration, and ECS Discovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + eecId: + type: string + example: "string" + ueId: + type: string + example: "string" + acProfs: + type: array + items: + type: object + properties: + acId: + type: string + example: "string" + acType: + type: string + example: "string" + eecSvcContSupp: + type: array + items: + type: string + enum: + - EEC_INITIATED + - EEC_EXECUTED_VIA_SOURCE_EES + example: ["EEC_INITIATED", "EEC_EXECUTED_VIA_SOURCE_EES"] + locInf: + type: object + properties: + geographicArea: + type: object + properties: + point: + type: object + properties: + point: + type: object + properties: + lon: + type: number + format: float + example: 7.4200 + lat: + type: number + format: float + example: 43.7356 + shape: + type: string + example: "POINT" + required: + - eecId + responses: + '200': + description: > + OK (The requested service provisioning information was returned successfully). + content: + application/json: + schema: + $ref: '#/components/schemas/ECSServProvResp' + "204": + description: Successful response sent when there is no need to provide a + new liveness interval value to the service Instance. + content: {} + "400": + description: "It is used to indicate that incorrect parameters were passed\ + \ to the request. In the returned ProblemDetails structure, the \"detail\"\ + \ attribute should convey more information about the error." + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "403": + description: The operation is not allowed given the current status of the + resource. More information shall be provided in the "detail" attribute + of the "ProblemDetails" structure. + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "404": + description: "It is used when a client provided a URI that cannot be mapped\ + \ to a valid resource URI. In the returned ProblemDetails structure, the\ + \ \"detail\" attribute should convey more information about the error." + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "409": + description: The operation is not allowed due to a conflict with the state + of the resource. The MEC platform shall respond with this code if the + service instance is in "INACTIVE" state. More information shall be provided + in the "detail" attribute of the "ProblemDetails" structure. + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "412": + description: "It is used when a condition has failed during conditional\ + \ requests, e.g. when using ETags to avoid write conflicts. In the returned\ + \ ProblemDetails structure, the \"detail\" attribute should convey more\ + \ information about the error." + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + servers: + - url: https://localhost/sandboxname/eecs-serviceprovisioning/v1 + + /registration: + post: + operationId: CreateEECReg + tags: + - Service Provisioning, EEC Registration, and ECS Discovery + description: Create a new EEC registration at the EES. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + eecId: + type: string + example: "string" + ueId: + type: string + example: "ue-67890" + acProfs: + type: array + items: + type: object + properties: + acId: + type: string + example: "string" + acType: + type: string + example: "string" + expTime: + type: string + format: date-time + example: "2025-01-31T15:04:05Z" + eecSvcContSupp: + type: array + items: + type: string + enum: + - EEC_INITIATED + - SOURCE_EAS_DECIDED + example: ["EEC_INITIATED", "SOURCE_EAS_DECIDED"] + eecCntxId: + type: string + example: "string" + srcEesId: + type: string + example: "mep1" + endPt: + type: object + properties: + uri: + type: string + example: "http://172.30.225.7/sbx2cmq8bn/mep1" + ueMobilityReq: + type: boolean + example: true + easSelReqInd: + type: boolean + example: false + ueType: + type: string + example: "NORMAL_UE" + required: + - eecId + responses: + '201': + description: Created (EEC information is registered successfully at EES). + content: + application/json: + schema: + type: object + properties: + RegistrationID: + type: string + description: Identifier of the EEC registration. + ExpirationTime: + type: string + format: date-time + description: Expiration time of the registration. + EECContextID: + type: string + description: Identifier of the EEC context information available at the EES. + EECContextRelocationStatus: + type: boolean + description: Indicates whether the EEC context retrieval from the source EES was successful. + DiscoveredEASList: + type: array + items: + $ref: '#/components/schemas/EASProfile' + "204": + description: Successful response sent when there is no need to provide a + new liveness interval value to the service Instance. + content: {} + "400": + description: "It is used to indicate that incorrect parameters were passed\ + \ to the request. In the returned ProblemDetails structure, the \"detail\"\ + \ attribute should convey more information about the error." + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "403": + description: The operation is not allowed given the current status of the + resource. More information shall be provided in the "detail" attribute + of the "ProblemDetails" structure. + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "404": + description: "It is used when a client provided a URI that cannot be mapped\ + \ to a valid resource URI. In the returned ProblemDetails structure, the\ + \ \"detail\" attribute should convey more information about the error." + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "409": + description: The operation is not allowed due to a conflict with the state + of the resource. The MEC platform shall respond with this code if the + service instance is in "INACTIVE" state. More information shall be provided + in the "detail" attribute of the "ProblemDetails" structure. + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "412": + description: "It is used when a condition has failed during conditional\ + \ requests, e.g. when using ETags to avoid write conflicts. In the returned\ + \ ProblemDetails structure, the \"detail\" attribute should convey more\ + \ information about the error." + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + servers: + - url: https://localhost/sandboxname/eees-eecregistration/v1 + + /registrations/{registrationId}: + get: + summary: Retrieve EEC Registration Details + operationId: getRegistrationDetails + tags: + - Service Provisioning, EEC Registration, and ECS Discovery + parameters: + - name: registrationId + in: path + description: Identifies an individual EEC registration. + required: true + schema: + type: string + responses: + '200': + description: Successful retrieval of EEC registration details. + content: + application/json: + schema: + type: object + properties: + RegistrationID: + type: string + description: Identifier of the EEC registration. + ExpirationTime: + type: string + format: date-time + description: Expiration time of the registration. + EECContextID: + type: string + description: Identifier of the EEC context information available at the EES. + EECContextRelocationStatus: + type: boolean + description: Indicates whether the EEC context retrieval from the source EES was successful. + DiscoveredEASList: + type: array + items: + $ref: '#/components/schemas/EASProfile' + '404': + description: EEC registration not found. + '400': + description: Invalid registration ID supplied. + '500': + description: Internal server error. + put: + summary: EEC Registration Update Request via PUT + operationId: updateRegistrationPut + tags: + - Service Provisioning, EEC Registration, and ECS Discovery + parameters: + - name: registrationId + in: path + description: Identifies an individual EEC registration. + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + acProfs: + type: array + items: + $ref: '#/components/schemas/ACProfile' + description: Profiles of ACs for which the EEC provides edge enabling services. + expTime: + $ref: '#/components/schemas/DateTime' + ueMobilityReq: + type: boolean + description: > + Set to true to indicate that UE Mobility support is required. + Set to false to indicate that UE mobility support is not required. + The default value when omitted is false. + responses: + "200": + description: Successful registration update response via PUT + content: + application/json: + schema: + type: object + properties: + expirationTime: + type: string + format: date-time + description: Expiration time of the registration. + unfulfilledACInfo: + type: array + description: List of unfulfilled AC information. + items: + type: object + properties: + acid: + type: string + description: Application Identifier. + reason: + type: string + description: Reason indicating the cause (e.g., EAS not available). + "204": + description: Successful response sent when there is no need to provide a + new liveness interval value to the service Instance. + content: {} + "400": + description: "It is used to indicate that incorrect parameters were passed\ + \ to the request. In the returned ProblemDetails structure, the \"detail\"\ + \ attribute should convey more information about the error." + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "403": + description: The operation is not allowed given the current status of the + resource. More information shall be provided in the "detail" attribute + of the "ProblemDetails" structure. + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + "404": + description: "It is used when a client provided a URI that cannot be mapped\ + \ to a valid resource URI. In the returned ProblemDetails structure, the\ + \ \"detail\" attribute should convey more information about the error." + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + + delete: + operationId: DeleteIndEECReg + tags: + - Service Provisioning, EEC Registration, and ECS Discovery + description: Remove an existing EEC registration at EES. + parameters: + - name: registrationId + in: path + description: Identifies an individual EEC registration. + required: true + schema: + type: string + responses: + '204': + description: > + No Content (An individual EEC registration resource deleted successfully). + servers: + - url: https://localhost/sandboxname/eees-eecregistration/v1 + + /eas-profiles/request-discovery: + post: + description: > + Provides EAS information requested by the service consumer (i.e. EEC, EAS or EES). + operationId: GetEASDiscInfo + tags: + - Service Provisioning, EEC Registration, and ECS Discovery + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + requestorId: + type: object + properties: + eesId: + type: string + example: "string" + easId: + type: string + example: "string" + eecId: + type: string + example: "string" + oneOf: + - required: ["eesId"] + - required: ["easId"] + - required: ["eecId"] + ueId: + type: string + example: "string" + easDiscoveryFilter: + type: object + properties: + acChars: + type: array + items: + type: object + properties: + acProf: + type: object + properties: + acId: + type: string + example: "string" + acType: + type: string + example: "string" + eecSvcContinuity: + type: array + items: + type: string + example: ["EEC_INITIATED", "string"] + locInf: + type: object + description: "Location information. Define properties as required." + example: {} + predictExpTime: + type: string + format: date-time + example: "2025-02-04T09:49:01.348Z" + required: + - requestorId + responses: + '200': + description: > + OK (The requested EAS discovery information was returned successfully). + content: + application/json: + schema: + $ref: '#/components/schemas/EasDiscoveryResp' + + servers: + - url: https://localhost/sandboxname/eees-easdiscovery/v1 /timing/timing_caps: get: tags: @@ -1446,6 +2104,685 @@ paths: parameters: [] components: schemas: + EasDiscoveryResp: + description: EAS discovery response. + type: object + properties: + discoveredEas: + type: array + items: + $ref: '#/components/schemas/DiscoveredEas' + description: List of EAS discovery information. + required: + - discoveredEas + DiscoveredEas: + description: Represents an EAS discovery information. + type: object + properties: + eas: + $ref: '#/components/schemas/EASProfile' + eesEndPt: + $ref: '#/components/schemas/EndPoint' + lifeTime: + $ref: '#/components/schemas/DateTime' + required: + - eas + EasDiscoveryReq: + description: EAS discovery request information. + type: object + properties: + requestorId: + $ref: '#/components/schemas/RequestorId' + ueId: + $ref: '#/components/schemas/Gpsi' + easDiscoveryFilter: + $ref: '#/components/schemas/EasDiscoveryFilter' + eecSvcContinuity: + type: array + items: + $ref: '#/components/schemas/ACRScenario' + description: > + Indicates if the EEC supports service continuity or not, also indicates which ACR + scenarios are supported by the EEC. + eesSvcContinuity: + type: array + items: + $ref: '#/components/schemas/ACRScenario' + description: > + Indicates if the EES supports service continuity or not, also indicates which ACR + scenarios are supported by the EES. + easSvcContinuity: + type: array + items: + $ref: '#/components/schemas/ACRScenario' + description: > + Indicates if the EAS supports service continuity or not, also indicates which ACR + scenarios are supported by the EAS. + locInf: + $ref: '#/components/schemas/LocationInfo' + easSelSupInd: + type: boolean + description: > + Indicates if the EEC requires the EAS selection support from the EES (e.g., for + constrained device). The default value false indicates the EAS selection is not + required from the EES. + suppFeat: + $ref: '#/components/schemas/SupportedFeatures' + easIntTrigSup: + type: boolean + description: > + Indicates to the EES whether the EAS instantiation triggering should be performed for + the current request. The default value false indicates the EAS instantiation triggering + should not be performed. The true value indicate the EAS instantiation triggering should + be performed. + predictExpTime: + $ref: '#/components/schemas/DateTime' + required: + - requestorId + + EasDiscoveryFilter: + description: Represents the EAS characteristics. + type: object + properties: + acChars: + type: array + items: + $ref: '#/components/schemas/ACCharacteristics' + minItems: 1 + description: AC description for which an EAS is needed. + ACCharacteristics: + description: Represents EAS dynamic information changes filter. + type: object + properties: + acProf: + $ref: '#/components/schemas/ACProfile' + required: + - acProf + RequestorId: + description: Represents identifier of the requestor. + type: object + properties: + eesId: + type: string + description: The identifier of the EES (e.g. S-EES). + easId: + type: string + description: The application identifier of the EAS (e.g. S-EAS), e.g. FQDN, URI. + eecId: + type: string + description: The identifier of the EEC. + oneOf: + - required: [eesId] + - required: [easId] + - required: [eecId] + + EASProfile: + type: object + required: + - easId + - endPt + - acIds + - scheds + - permLvl + - easFeats + - svcContSupp + - appLocs + properties: + easId: + description: The identifier of the EAS + type: string + example: + appName + endPt: + $ref: '#/components/schemas/EndPoint' + acIds: + type: array + items: + type: string + minItems: 1 + description: Identities of the Application Clients that can be served by the EAS + provId: + type: string + description: Identifier of the ASP that provides the EAS. + example: + appProvider1 + type: + description: The category or type of EAS. + type: string + scheds: + type: array + items: + type: string + minItems: 1 + description: The availability schedule of the EAS. + svcArea: + type: string + description: >- + The list of geographical and topological areas that the EAS serves. ACs in the UE that are outside the area will not be served. + svcKpi: + type: string + description: Service characteristics provided by the EAS. + permLvl: + type: array + items: + type: string + minItems: 1 + description: level of service permissions supported by the EAS. + easFeats: + type: array + items: + type: string + minItems: 1 + description: Service features supported by the EAS. + svcContSupp: + type: array + items: + type: string + minItems: 1 + description: The ACR scenarios supported by the EAS for service continuity. + appLocs: + type: array + items: + type: string + minItems: 1 + description: >- + List of DNAI(s) and the N6 traffic information associated with the EAS. + avlRep: + type: integer + description: >- + The period indicating to the EES, how often the EES needs to check the EAS's availability after a successful registration. + example: + 1577836800 + status: + type: string + description: 'EAS status information. ' + + EECRegistration: + description: Describes the parameters to perform EEC Registration related operations. + type: object + properties: + eecId: + type: string + description: Represents a unique identifier of the EEC. + ueId: + $ref: '#/components/schemas/Gpsi' + acProfs: + type: array + items: + $ref: '#/components/schemas/ACProfile' + description: Profiles of ACs for which the EEC provides edge enabling services. + expTime: + $ref: '#/components/schemas/DateTime' + eecSvcContSupp: + type: array + items: + $ref: '#/components/schemas/ACRScenario' + description: Profiles of ACs for which the EEC provides edge enabling services. + eecCntxId: + type: string + description: Identifier of the EEC context obtained from a previous registration. + srcEesId: + type: string + description: Identifier of the EES that provided EEC context ID. + endPt: + $ref: '#/components/schemas/EndPoint' + ueMobilityReq: + type: boolean + description: > + Set to true to indicate that UE Mobility support is required. + Set to false to indicate that UE mobility support is not required. + The default value when omitted is false. + easSelReqInd: + type: boolean + description: > + Set to true to indicate the EES support for EAS selection. + Set to false to indicate the EES shall not select the EAS. + The default value when omitted is false. + ueType: + $ref: '#/components/schemas/DeviceType' + required: + - eecId + DeviceType: + anyOf: + - type: string + enum: + - CONSTRAINED_UE + - NORMAL_UE + - type: string + description: > + This string provides forward-compatibility with future + extensions to the enumeration and is not used to encode + content defined in the present version of this API. + description: > + Represents the UE type. + Possible values are: + - CONSTRAINED_UE: Indicates UE is constrained with resources like power, processor etc. + - NORMAL_UE: Indicates UE is not constrained with resources. + ECSServProvReq: + description: ECS service provisioning request information. + type: object + properties: + eecId: + type: string + description: Represents a unique identifier of the EEC. + ueId: + $ref: '#/components/schemas/Gpsi' + acProfs: + type: array + items: + $ref: '#/components/schemas/ACProfile' + description: Information about services the EEC wants to connect to. + appInfo: + type: array + items: + $ref: '#/components/schemas/ApplicationInfo' + minItems: 1 + description: Information about the list of services the EEC wants to connect. + eecSvcContSupp: + type: array + items: + $ref: '#/components/schemas/ACRScenario' + description: > + Indicates if the EEC supports service continuity or not, also indicates which + ACR scenarios are supported by the EEC. + locInf: + $ref: '#/components/schemas/LocationInfo' + ecspIds: + type: array + items: + type: string + minItems: 1 + description: Indicates to the ECS which EES providers are preferred by the EEC. + suppFeat: + $ref: '#/components/schemas/SupportedFeatures' + required: + - eecId + + ECSServProvResp: + description: ECS service provisioning response information. + type: object + properties: + ednCnfgInfo: + type: array + items: + $ref: '#/components/schemas/EDNConfigInfo' + minItems: 1 + description: List of EDN configuration information. + required: + - ednCnfgInfo + + SupportedFeatures: + type: string + pattern: '^[A-Fa-f0-9]*$' + description: > + A string used to indicate the features supported by an API that is used as defined in clause + 6.6 in 3GPP TS 29.500. The string shall contain a bitmask indicating supported features in + hexadecimal representation Each character in the string shall take a value of "0" to "9", + "a" to "f" or "A" to "F" and shall represent the support of 4 features as described in + table 5.2.2-3. The most significant character representing the highest-numbered features + shall appear first in the string, and the character representing features 1 to 4 + shall appear last in the string. The list of features and their numbering (starting with 1) + are defined separately for each API. If the string contains a lower number of characters + than there are defined features for an API, all features that would be represented by + characters that are not present in the string are not supported. + + LocationInfo: + description: Represents the user location information. + type: object + properties: + geographicArea: + $ref: '#/components/schemas/GeographicArea' + + GeographicArea: + description: Geographic area specified by different shape. + anyOf: + - $ref: '#/components/schemas/Point' + Point: + description: Ellipsoid Point. + allOf: + - $ref: '#/components/schemas/GADShape' + - type: object + required: + - point + properties: + point: + $ref: '#/components/schemas/GeographicalCoordinates' + GeographicalCoordinates: + description: Geographical coordinates. + type: object + required: + - lon + - lat + properties: + lon: + type: number + format: double + minimum: -180 + maximum: 180 + lat: + type: number + format: double + minimum: -90 + maximum: 90 + GADShape: + description: Common base type for GAD shapes. + type: object + required: + - shape + properties: + shape: + $ref: '#/components/schemas/SupportedGADShapes' + discriminator: + propertyName: shape + mapping: + POINT: '#/components/schemas/Point' + POINT_UNCERTAINTY_CIRCLE: '#/components/schemas/PointUncertaintyCircle' + POINT_UNCERTAINTY_ELLIPSE: '#/components/schemas/PointUncertaintyEllipse' + POLYGON: '#/components/schemas/Polygon' + POINT_ALTITUDE: '#/components/schemas/PointAltitude' + POINT_ALTITUDE_UNCERTAINTY: '#/components/schemas/PointAltitudeUncertainty' + ELLIPSOID_ARC: '#/components/schemas/EllipsoidArc' + LOCAL_2D_POINT_UNCERTAINTY_ELLIPSE: '#/components/schemas/Local2dPointUncertaintyEllipse' + LOCAL_3D_POINT_UNCERTAINTY_ELLIPSOID: '#/components/schemas/Local3dPointUncertaintyEllipsoid' + SupportedGADShapes: + description: Indicates supported GAD shapes. + anyOf: + - type: string + enum: + - POINT + - POINT_UNCERTAINTY_CIRCLE + - POINT_UNCERTAINTY_ELLIPSE + - POLYGON + - POINT_ALTITUDE + - POINT_ALTITUDE_UNCERTAINTY + - ELLIPSOID_ARC + - LOCAL_2D_POINT_UNCERTAINTY_ELLIPSE + - LOCAL_3D_POINT_UNCERTAINTY_ELLIPSOID + - DISTANCE_DIRECTION + - RELATIVE_2D_LOCATION_UNCERTAINTY_ELLIPSE + - RELATIVE_3D_LOCATION_UNCERTAINTY_ELLIPSOID + - type: string + + Gpsi: + type: string + pattern: '^(msisdn-[0-9]{5,15}|extid-[^@]+@[^@]+|.+)$' + description: > + String identifying a Gpsi shall contain either an External Id or an MSISDN. + It shall be formatted as follows -External Identifier= "extid-'extid', where 'extid' + shall be formatted according to clause 19.7.2 of 3GPP TS 23.003 that describes an + External Identifier. + + + ACProfile: + description: AC information indicating required services and service characteristics. + type: object + properties: + acId: + type: string + description: Identity of the AC. + acType: + type: string + description: The category or type of AC. + prefEcsps: + type: array + items: + type: string + description: Indicates to the ECS which ECSPs are preferred for the AC. + simInactTime: + $ref: '#/components/schemas/DurationSec' + eass: + type: array + items: + $ref: '#/components/schemas/EasDetail' + minItems: 1 + description: List of EAS information. + # easBundleInfos: + # type: array + # items: + # $ref: 'TS29558_Eees_EASRegistration.yaml#/components/schemas/EASBundleInfo' + # minItems: 1 + # description: > + # List of EAS bundles to which the EAS (identified via the "easId" attribute) belongs. + required: + - acId + + EasDetail: + description: EAS details. + type: object + properties: + easId: + type: string + description: Application identifier of the EAS. + required: + - easId + + DurationSec: + type: integer + minimum: 0 + description: Unsigned integer identifying a period of time in units of seconds. + + + ApplicationInfo: + description: Represents the services the EEC wants to connect. + type: object + properties: + acProf: + $ref: '#/components/schemas/ACProfile' + appGroupProfile: + $ref: '#/components/schemas/AppGroupProfile' + required: + - acProf + + ACRScenario: + anyOf: + - type: string + enum: + - EEC_INITIATED + - EEC_EXECUTED_VIA_SOURCE_EES + - EEC_EXECUTED_VIA_TARGET_EES + - SOURCE_EAS_DECIDED + - SOURCE_EES_EXECUTED + - EEL_MANAGED_ACR + - type: string + description: > + This string provides forward-compatibility with future + extensions to the enumeration but is not used to encode + content defined in the present version of this API. + description: | + Represents the ACR scenarios supported by EES. + Possible values are: + - EEC_INITIATED: Represents the EEC initiated ACR scenario. + - EEC_EXECUTED_VIA_SOURCE_EES: Represents the EEC ACR scenario executed via the S-EES. + - EEC_EXECUTED_VIA_TARGET_EES: Represents the EEC ACR scenario executed via the T-EES. + - SOURCE_EAS_DECIDED: Represents the EEC ACR scenario where the S-EAS decides to perform + ACR. + - SOURCE_EES_EXECUTED: Represents the EEC ACR scenario where S-EES executes the ACR. + - EEL_MANAGED_ACR: Represents the EEC ACR scenario where the ACR is managed by the + Edge Enabler Layer. + AppGroupProfile: + description: Represents the application group profile for common EAS. + type: object + properties: + appGrpId: + type: string + description: Represents the application group that uniquely identifies + the group of UEs using the same application. + easId: + type: string + description: Represents the application identifier of the EAS. + required: + - appGrpId + - easId + + EDNConfigInfo: + description: Represents the EDN configuration information. + type: object + properties: + ednConInfo: + $ref: '#/components/schemas/EDNConInfo' + eess: + type: array + items: + $ref: '#/components/schemas/EESInfo' + minItems: 1 + description: Contains the list of EESs of the EDN. + lifeTime: + $ref: '#/components/schemas/DateTime' + required: + - ednConInfo + - eess + + + EDNConInfo: + description: Represents an EDN connection information. + type: object + properties: + dnn: + $ref: '#/components/schemas/Dnn' + # snssai: + # $ref: 'TS29571_CommonData.yaml#/components/schemas/Snssai' + # ednTopoSrvArea: + # $ref: 'TS29122_CommonData.yaml#/components/schemas/LocationArea5G' + + Dnn: + type: string + description: > + String representing a Data Network as defined in clause 9A of 3GPP TS 23.003; + it shall contain either a DNN Network Identifier, or a full DNN with both the Network + Identifier and Operator Identifier, as specified in 3GPP TS 23.003 clause 9.1.1 and 9.1.2. + It shall be coded as string in which the labels are separated by dots + (e.g. "Label1.Label2.Label3"). + + DateTime: + format: date-time + type: string + description: string with format "date-time" as defined in OpenAPI. + + + EESInfo: + description: Represents EES information. + type: object + properties: + eesId: + type: string + description: Identity of the EES. + endPt: + $ref: '#/components/schemas/EndPoint' + easIds: + type: array + items: + type: string + description: > + Application identities of the Edge Application Servers registered + with the EES. + eecRegConf: + type: boolean + description: > + Indicates whether the EEC is required to register on the EES to use edge services + or not. + required: + - eesId + - eecRegConf + Fqdn: + description: Fully Qualified Domain Name + type: string + pattern: '^([0-9A-Za-z]([-0-9A-Za-z]{0,61}[0-9A-Za-z])?\.)+[A-Za-z]{2,63}\.?$' + minLength: 4 + maxLength: 253 + + Ipv6Addr: + type: string + description: > + string identifying a Ipv6 address formatted according to clause 4 in IETF RFC 5952. + The mixed Ipv4 Ipv6 notation according to clause 5 of IETF RFC 5952 shall not be used. + Uri: + type: string + description: string providing an URI formatted according to IETF RFC 3986. + Ipv4Addr: + type: string + description: > + string identifying a Ipv4 address formatted in the "dotted decimal" notation as defined in + IETF RFC 1166. + EndPoint: + type: object + description: The end point information to reach EAS. + properties: + fqdn: + $ref: '#/components/schemas/Fqdn' + ipv4Addrs: + type: array + items: + $ref: '#/components/schemas/Ipv4Addr' + minItems: 1 + description: IPv4 addresses of the edge server. + ipv6Addrs: + type: array + items: + $ref: '#/components/schemas/Ipv6Addr' + minItems: 1 + description: IPv6 addresses of the edge server. + uri: + $ref: '#/components/schemas/Uri' + oneOf: + - required: [uri] + - required: [fqdn] + - required: [ipv4Addrs] + - required: [ipv6Addrs] + + InvalidParam: + description: > + Represents the description of invalid parameters, for a request rejected due to invalid + parameters. + type: object + properties: + param: + type: string + description: Attribute's name encoded as a JSON Pointer, or header's name. + reason: + type: string + description: A human-readable reason, e.g. "must be a positive integer". + required: + - param + + # EASBundleDetail: + # description: Represents details of EAS Bundle. + # type: object + # properties: + # easId: + # type: string + # description: > + # Application identity of the Edge Application Servers registered with the EES. + # easBundleInfos: + # type: array + # items: + # $ref: 'TS29558_Eees_EASRegistration.yaml#/components/schemas/EASBundleInfo' + # minItems: 1 + # description: List of EAS bundles to which the EAS belongs. + # required: + # - easId + # - easBundleInfos + EesAuthMethod: + anyOf: + - type: string + enum: + - TLS_CLIENT_SERVER_CERTIFICATE + - TLS_WITH_AKMA + - TLS_WITH_GBA + - SERVER_SIDE_CERTIFICATE_BASED + - type: string + description: > + This string provides forward-compatibility with future + extensions to the enumeration and is not used to encode + content defined in the present version of this API. + description: | + Represents the Authentication methods supported by EES. + Possible values are: + - TLS_CLIENT_SERVER_CERTIFICATE: Represents TLS with client server certificate + authentication. + - TLS_WITH_AKMA: Represents TLS with AKMA authentication. + - TLS_WITH_GBA: Represents TLS with GBA authentication. + - SERVER_SIDE_CERTIFICATE_BASED: Represents server side certification only. + AppReadyConfirmation: title: AppReadyConfirmation required: diff --git a/go-apps/meep-app-enablement/server/app-support/api_mec_app_support.go b/go-apps/meep-app-enablement/server/app-support/api_mec_app_support.go index 1480ebf7b..ee3afe971 100644 --- a/go-apps/meep-app-enablement/server/app-support/api_mec_app_support.go +++ b/go-apps/meep-app-enablement/server/app-support/api_mec_app_support.go @@ -59,6 +59,29 @@ func TimingCurrentTimeGET(w http.ResponseWriter, r *http.Request) { timingCurrentTimeGET(w, r) } +func GetEASDiscInfo(w http.ResponseWriter, r *http.Request) { + getEASDiscInfo(w, r) +} + +func RequestServProv(w http.ResponseWriter, r *http.Request) { + requestServProv(w, r) +} + +func DeleteIndEECReg(w http.ResponseWriter, r *http.Request) { + deleteIndEECReg(w, r) +} + +func CreateEECReg(w http.ResponseWriter, r *http.Request) { + createEECReg(w, r) +} + +func UpdateRegistrationPut(w http.ResponseWriter, r *http.Request) { + updateRegistrationPut(w, r) +} +func GetRegistration(w http.ResponseWriter, r *http.Request) { + getRegistration(w, r) +} + func AppRegistrationPOST(w http.ResponseWriter, r *http.Request) { appRegistrationPOST(w, r) } diff --git a/go-apps/meep-app-enablement/server/app-support/app-support.go b/go-apps/meep-app-enablement/server/app-support/app-support.go index e4f2ca46d..a67c0b508 100644 --- a/go-apps/meep-app-enablement/server/app-support/app-support.go +++ b/go-apps/meep-app-enablement/server/app-support/app-support.go @@ -24,6 +24,7 @@ import ( "net/url" "reflect" "strconv" + "strings" "sync" "time" @@ -34,6 +35,7 @@ import ( mq "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-mq" redis "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis" subs "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-subscriptions" + "github.com/google/uuid" "github.com/gorilla/mux" ) @@ -127,7 +129,12 @@ func Init(sandbox string, mep string, host *url.URL, msgQueue *mq.MsgQueue, redi return err } log.Info("Connected to Application Store") - + // Populate ECS Configuration + err = setupECSConfiguration() + if err != nil { + log.Error("Failed to set up ECS configuration: ", err) + return err + } // Create Subscription Manager subMgrCfg := &subs.SubscriptionMgrCfg{ Module: moduleName, @@ -647,6 +654,633 @@ func timingCurrentTimeGET(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, string(jsonResponse)) } +func setupECSConfiguration() error { + // Mandatory ECS Configuration (only ECS Address) + ecsConfig := map[string]interface{}{ + "ECSAddress": hostUrl.String(), // MEC Sandbox URL as ECS Address + } + + // Convert ECS Config to JSON + ecsConfigJson, err := json.Marshal(ecsConfig) + if err != nil { + return fmt.Errorf("failed to marshal ECS configuration: %v", err) + } + + // Convert []byte to string before passing to JSONSetEntry + ecsConfigJsonStr := string(ecsConfigJson) + // Store in Redis + ecsKey := baseKey + "ecs:config" + err = rc.JSONSetEntry(ecsKey, ".", ecsConfigJsonStr) + if err != nil { + return fmt.Errorf("failed to set ECS configuration in Redis: %v", err) + } + + log.Info("ECS configuration stored successfully") + return nil +} + +func getECSConfig() (map[string]interface{}, error) { + ecsKey := baseKey + "ecs:config" + ecsConfigJson, err := rc.JSONGetEntry(ecsKey, ".") + if err != nil { + return nil, fmt.Errorf("failed to get ECS configuration: %v", err) + } + + var ecsConfig map[string]interface{} + err = json.Unmarshal([]byte(ecsConfigJson), &ecsConfig) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal ECS configuration: %v", err) + } + + return ecsConfig, nil +} + +func getRegistration(w http.ResponseWriter, r *http.Request) { + log.Info("Get EEC Registration by RegistrationId") + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + vars := mux.Vars(r) + registrationId := vars["registrationId"] + keyName := baseKey + "app:" + registrationId + // Find reginfo entry in redis DB + eecPrevReg, err := rc.JSONGetEntry(keyName, ".") + if err != nil { + err = errors.New("eecRegistration not found against the provided RegistrationId") + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + return + } + if eecPrevReg == "" { + log.Error("RegistrationId %s not found in Redis", registrationId) + errHandlerProblemDetails(w, "Registration not found", http.StatusNotFound) + return + } + sInfoJson := convertEecPrevRegReqInfoToJson(eecPrevReg) + jsonResponse, err := json.Marshal(sInfoJson) + if err != nil { + log.Error("Failed to marshal the response:", err.Error()) + errHandlerProblemDetails(w, "Internal server error", http.StatusInternalServerError) + return + } + + // Send the response + w.WriteHeader(http.StatusOK) + w.Write(jsonResponse) +} + +func updateRegistrationPut(w http.ResponseWriter, r *http.Request) { + log.Info("Update EEC Registration by RegistrationId") + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + vars := mux.Vars(r) + registrationId := vars["registrationId"] + var ecsRegUpdateReq RegistrationsRegistrationIdBody + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&ecsRegUpdateReq) + if err != nil { + log.Error("Failed to decode the request body:", err.Error()) + errHandlerProblemDetails(w, "Invalid request format", http.StatusBadRequest) + return + } + keyName := baseKey + "app:" + registrationId + + // Find reginfo entry in redis DB + eecPrevReg, err := rc.JSONGetEntry(keyName, ".") + if err != nil { + err = errors.New("eecRegistration not found against the provided RegistrationId") + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + return + } + if eecPrevReg == "" { + log.Error("RegistrationId %s not found in Redis", registrationId) + errHandlerProblemDetails(w, "Registration not found", http.StatusNotFound) + return + } + sInfoJson := convertEecPrevRegReqInfoToJson(eecPrevReg) + + // Helper function to check if AcId is valid + isValidAcId := func(acId string) bool { + return acId != "" && acId != "string" + } + + // Check if either valid AcId or LocationInfo is provided + hasAcId := false + for _, acProf := range ecsRegUpdateReq.AcProfs { + if isValidAcId(acProf.AcId) { + hasAcId = true + break + } + } + // Process AcId if provided + if hasAcId { + for _, acProf := range ecsRegUpdateReq.AcProfs { + if isValidAcId(acProf.AcId) { + appId := acProf.AcId + appInfo, err := getAppInfo(appId) + if err != nil { + errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + return + } + + code, problemDetails, err := validateAppInfo(appInfo) + if err != nil { + log.Error(err.Error()) + if problemDetails != "" { + w.WriteHeader(code) + fmt.Fprint(w, problemDetails) + } else { + errHandlerProblemDetails(w, err.Error(), code) + } + return + } + } + } + } + sInfoJson.AcProfs = ecsRegUpdateReq.AcProfs + sInfoJson.ExpTime = ecsRegUpdateReq.ExpTime + sInfoJson.UeMobilityReq = ecsRegUpdateReq.UeMobilityReq + + sInfoJson_ := convertEecRegReqInfoToJson(sInfoJson) + key := baseKey + "app:" + registrationId + err = rc.JSONSetEntry(key, ".", sInfoJson_) + if err != nil { + log.Error("Failed to set json entry in redis db") + errHandlerProblemDetails(w, "Failed to set json entry in redis db", http.StatusInternalServerError) + return + } + + response := InlineResponse201{ + ExpirationTime: time.Now(), + } + + jsonResponse, err := json.Marshal(response) + if err != nil { + log.Error("Failed to marshal the response:", err.Error()) + errHandlerProblemDetails(w, "Internal server error", http.StatusInternalServerError) + return + } + + // Send the response + w.WriteHeader(http.StatusOK) + w.Write(jsonResponse) +} + +func createEECReg(w http.ResponseWriter, r *http.Request) { + log.Info("requestServProv") + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + mutex.Lock() + defer mutex.Unlock() + + if r.Body == nil { + err := errors.New("Request body is missing") + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } + var ecsRegReq EecRegistration + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&ecsRegReq) + if err != nil { + log.Error("Failed to decode the request body:", err.Error()) + errHandlerProblemDetails(w, "Invalid request format", http.StatusBadRequest) + return + } + if ecsRegReq.EecId == "" || ecsRegReq.EecId == "string" { + log.Error("Invalid request: Please enter the unique EecId") + errHandlerProblemDetails(w, "Please enter the unique EecId", http.StatusBadRequest) + return + } + key := baseKey + "app:" + ecsRegReq.EecId + sInfoJson1, err := rc.JSONGetEntry(key, ".") + if err != nil { + log.Error("Failed to get json entry from redis db") + errHandlerProblemDetails(w, "Failed to get json entry from redis db", http.StatusInternalServerError) + return + } + if sInfoJson1 == "" { + errHandlerProblemDetails(w, "Request is not stored in redis db", http.StatusInternalServerError) + return + } + // Helper function to check if AcId is valid + isValidAcId := func(acId string) bool { + return acId != "" && acId != "string" + } + + // Check if either valid AcId or LocationInfo is provided + hasAcId := false + for _, acProf := range ecsRegReq.AcProfs { + if isValidAcId(acProf.AcId) { + hasAcId = true + break + } + } + // Process AcId if provided + if hasAcId { + for _, acProf := range ecsRegReq.AcProfs { + if isValidAcId(acProf.AcId) { + appId := acProf.AcId + appInfo, err := getAppInfo(appId) + if err != nil { + errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + return + } + + code, problemDetails, err := validateAppInfo(appInfo) + if err != nil { + log.Error(err.Error()) + if problemDetails != "" { + w.WriteHeader(code) + fmt.Fprint(w, problemDetails) + } else { + errHandlerProblemDetails(w, err.Error(), code) + } + return + } + } + } + } + + // Get platform details dynamically and prepare response + ednConfig, err := getDynamicPlatformDetails(basePath) + if err != nil { + errHandlerProblemDetails(w, "Failed to get the ednconfig information that contain MEC plateform information", http.StatusBadRequest) + return + } + // Compare EEC registration endpoint with EDN config endpoint + if ecsRegReq.EndPt == nil || ecsRegReq.EndPt.Uri != ednConfig.Eess[0].EndPt.Uri { + log.Error("Endpoint mismatch: EEC registration endpoint does not match EDN config endpoint") + errHandlerProblemDetails(w, "Endpoint mismatch: EEC registration endpoint does not match EDN config endpoint", http.StatusBadRequest) + return + } + if ecsRegReq.EndPt == nil || ecsRegReq.SrcEesId != ednConfig.Eess[0].EesId { + log.Error("Endpoint mismatch: SrcEesId does not match EDN config MEC Plateform ID") + errHandlerProblemDetails(w, "Endpoint mismatch: SrcEesId does not match EDN config MEC Plateform ID", http.StatusBadRequest) + return + } + registrationId := uuid.New().String() + sInfoJson := convertEecRegReqInfoToJson(&ecsRegReq) + key = baseKey + "app:" + registrationId + err = rc.JSONSetEntry(key, ".", sInfoJson) + if err != nil { + log.Error("Failed to set json entry in redis db") + errHandlerProblemDetails(w, "Failed to set json entry in redis db", http.StatusInternalServerError) + return + } + + // if ecsRegReq.EndPt == nil || ecsRegReq.SrcEesId != + response := InlineResponse201{ + RegistrationID: registrationId, + ExpirationTime: time.Now(), + EECContextID: "example-context-id", + EECContextRelocationStatus: true, + DiscoveredEASList: []EasProfile{}, // Add appropriate EasProfile values if needed + } + + jsonResponse, err := json.Marshal(response) + if err != nil { + log.Error("Failed to marshal the response:", err.Error()) + errHandlerProblemDetails(w, "Internal server error", http.StatusInternalServerError) + return + } + + // Send the response + w.WriteHeader(http.StatusOK) + w.Write(jsonResponse) +} + +func deleteIndEECReg(w http.ResponseWriter, r *http.Request) { + log.Info("Delete EEC Registration by RegistrationId") + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + vars := mux.Vars(r) + registrationId := vars["registrationId"] + + keyName := baseKey + "app:" + registrationId + + // Find appInfo entry in redis DB + _, err := rc.JSONGetEntry(keyName, ".") + if err != nil { + err = errors.New("eecRegistration not found against the provided RegistrationId") + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + return + } + + // Delete appInfo entry from redis DB + err = rc.JSONDelEntry(keyName, ".") + if err != nil { + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + return + } + + // Send response on successful deletion of registration + w.WriteHeader(http.StatusNoContent) +} + +func requestServProv(w http.ResponseWriter, r *http.Request) { + log.Info("requestServProv") + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + mutex.Lock() + defer mutex.Unlock() + + if r.Body == nil { + err := errors.New("Request body is missing") + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } + + var ecsServReq EcsServProvReq + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&ecsServReq) + if err != nil { + log.Error("Failed to decode the request body:", err.Error()) + errHandlerProblemDetails(w, "Invalid request format", http.StatusBadRequest) + return + } + if ecsServReq.EecId == "" || ecsServReq.EecId == "string" { + log.Error("Invalid request: Please enter the unique EecId") + errHandlerProblemDetails(w, "Please enter the unique EecId", http.StatusBadRequest) + return + } + + // Helper function to check if AcId is valid + isValidAcId := func(acId string) bool { + return acId != "" && acId != "string" + } + + // Check if either valid AcId or LocationInfo is provided + hasAcId := false + for _, acProf := range ecsServReq.AcProfs { + if isValidAcId(acProf.AcId) { + hasAcId = true + break + } + } + + if !hasAcId && ecsServReq.LocInf == nil { + log.Error("Invalid request: Both AcId and LocationInfo are missing") + errHandlerProblemDetails(w, "Either a valid AcId or LocationInfo must be provided", http.StatusBadRequest) + return + } + + // Process AcId if provided + if hasAcId { + for _, acProf := range ecsServReq.AcProfs { + if isValidAcId(acProf.AcId) { + appId := acProf.AcId + appInfo, err := getAppInfo(appId) + if err != nil { + errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + return + } + + code, problemDetails, err := validateAppInfo(appInfo) + if err != nil { + log.Error(err.Error()) + if problemDetails != "" { + w.WriteHeader(code) + fmt.Fprint(w, problemDetails) + } else { + errHandlerProblemDetails(w, err.Error(), code) + } + return + } + } + } + } + + // Process LocationInfo if provided + if ecsServReq.LocInf != nil { + lat := ecsServReq.LocInf.GeographicArea.Point.Point.Lat + lon := ecsServReq.LocInf.GeographicArea.Point.Point.Lon + log.Info("Received coordinates: Lat:", lat, " Lon:", lon) + + if !isValidCoordinates(lat, lon) { + log.Error("Invalid location: MEC platform not found for this location") + errHandlerProblemDetails(w, "MEC platform not found for this location", http.StatusNotFound) + return + } + } + + sInfoJson := convertEcsServProvReqInfoToJson(&ecsServReq) + key := baseKey + "app:" + ecsServReq.EecId + err = rc.JSONSetEntry(key, ".", sInfoJson) + if err != nil { + log.Error("Failed to set json entry in redis db") + errHandlerProblemDetails(w, "Failed to set json entry in redis db", http.StatusInternalServerError) + return + } + sInfoJson1, err := rc.JSONGetEntry(key, ".") + if err != nil { + log.Error("Failed to get json entry from redis db") + errHandlerProblemDetails(w, "Failed to get json entry from redis db", http.StatusInternalServerError) + return + } + if sInfoJson1 == "" { + errHandlerProblemDetails(w, "Request is not stored in redis db", http.StatusInternalServerError) + return + } + + // Get platform details dynamically and prepare response + ednConfig, err := getDynamicPlatformDetails(basePath) + if err != nil { + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } + + // Send the response + jsonResponse, err := json.Marshal(ednConfig) + if err != nil { + log.Error("Failed to marshal the response:", err.Error()) + errHandlerProblemDetails(w, "Internal server error", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(jsonResponse) +} + +// Function to get dynamic platform details +func getDynamicPlatformDetails(basePath string) (*EdnConfigInfo, error) { + platformDetails, err := getPlatformDetailsFromBasePath(basePath) + if err != nil { + return nil, err + } + + eesInfo := EesInfo{ + EesId: platformDetails.EesId, + EndPt: platformDetails.EndPt, + } + + return &EdnConfigInfo{ + Eess: []EesInfo{eesInfo}, + }, nil +} + +// Function to validate coordinates +func isValidCoordinates(lat, lon float64) bool { + const tolerance = 0.0001 + return lat >= (43.7244-tolerance) && lat <= (43.7515+tolerance) && lon >= (7.4090-tolerance) && lon <= (7.4390+tolerance) +} + +// Function to extract platform details dynamically from basePath +func getPlatformDetailsFromBasePath(basePath string) (*EesInfo, error) { + mepIndex := strings.Index(basePath, "/mep") + if mepIndex == -1 { + return nil, errors.New("Invalid base path: /mep not found") + } + + namespace := basePath[:mepIndex] + platformPart := basePath[mepIndex+1:] + nextSlashIndex := strings.Index(platformPart, "/") + var platformIdentifier string + if nextSlashIndex != -1 { + platformIdentifier = platformPart[:nextSlashIndex] + } else { + platformIdentifier = platformPart + } + + mecPlatformUrl := strings.TrimSuffix(hostUrl.String(), "/") + "/" + strings.TrimPrefix(namespace, "/") + "/" + platformIdentifier + + return &EesInfo{ + EesId: platformIdentifier, + EndPt: &EndPoint{ + Uri: mecPlatformUrl, + }, + }, nil +} + +///////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + +func getEASDiscInfo(w http.ResponseWriter, r *http.Request) { + log.Debug(">>> getEASDiscInfo:", r) + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + var discReq EasDiscoveryReq + if err := json.NewDecoder(r.Body).Decode(&discReq); err != nil { + log.Error("Error decoding request body:", err) + errHandlerProblemDetails(w, "Invalid request body.", http.StatusBadRequest) + return + } + log.Info("getEASDiscInfo: Received reqInfo:", discReq) + // Immediately after decoding the request body + if discReq.RequestorId.EasId != "" { + log.Error("EasId support is currently disabled") + errHandlerProblemDetails(w, "EasId is not supported in this implementation", http.StatusBadRequest) + return + } + + // Enforce oneOf between EecId and EesId + idCount := 0 + if discReq.RequestorId.EecId != "" { + idCount++ + } + if discReq.RequestorId.EesId != "" { + idCount++ + } + + if idCount != 1 { + log.Error("Request must contain exactly one identifier (EecId or EesId)") + errHandlerProblemDetails(w, "Exactly one of eecId or eesId must be provided", http.StatusBadRequest) + return + } + + // Existing EecId validation + if discReq.RequestorId.EecId != "" { + key := baseKey + "app:" + discReq.RequestorId.EecId + sInfoJson1, err := rc.JSONGetEntry(key, ".") + if err != nil { + log.Error("Invalid EecId: ", discReq.RequestorId.EecId) + errHandlerProblemDetails(w, "Invalid EecId - not generated through serviceProvisioning", http.StatusBadRequest) + return + } + if sInfoJson1 == "" { + errHandlerProblemDetails(w, "No data found for EecId", http.StatusNotFound) + return + } + } else { + // EesId validation + ednConfig, err := getDynamicPlatformDetails(basePath) + if err != nil { + errHandlerProblemDetails(w, "Platform configuration error: "+err.Error(), http.StatusInternalServerError) + return + } + + if len(ednConfig.Eess) == 0 || ednConfig.Eess[0].EesId == "" { + log.Error("Missing EES configuration") + errHandlerProblemDetails(w, "Server configuration error", http.StatusInternalServerError) + return + } + + expectedEesId := ednConfig.Eess[0].EesId + if discReq.RequestorId.EesId != expectedEesId { + log.Error("EesId mismatch") + errHandlerProblemDetails(w, "Invalid EesId", http.StatusBadRequest) + return + } + } + // Initialize appInstanceId + var appInstanceId string + + // Check if EasDiscoveryFilter and AcChars exist and are not empty + if discReq.EasDiscoveryFilter != nil && len(discReq.EasDiscoveryFilter.AcChars) > 0 { + // Take the first AcCharacteristics entry + acChar := discReq.EasDiscoveryFilter.AcChars[0] + if acChar.AcProf != nil { + appInstanceId = acChar.AcProf.AcId + } + } + + // Check if appInstanceId was successfully retrieved + if appInstanceId == "" { + err := errors.New("acId not found in the request") + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) + return + } + keyName := baseKey + "appInfo:" + appInstanceId + log.Info("appRegistrationGET: keyName: ", keyName) + jsonAppInfo, err := rc.JSONGetEntry(keyName, ".") + if err != nil { + err = errors.New("appInfo not found against the provided appInstanceId") + log.Error(err.Error()) + errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + return + } + // Unmarshal retrieved JSON into AppInfo struct + var appInfo AppInfo + if err := json.Unmarshal([]byte(jsonAppInfo), &appInfo); err != nil { + log.Error("Error unmarshaling appInfo:", err) + errHandlerProblemDetails(w, "Internal server error.", http.StatusInternalServerError) + return + } + // Map AppInfo attributes to EASProfile response format + // Map to EasProfile + easProfile := EasProfile{ + EasId: appInfo.AppName, + EndPt: appInfo.Endpoint, + ProvId: appInfo.AppProvider, + Type_: appInfo.AppCategory, + Scheds: appInfo.Scheds, + SvcArea: appInfo.SvcArea, + SvcKpi: appInfo.SvcKpi, + PermLvl: appInfo.PermLvl, + AcIds: []string{appInfo.AppInstanceId}, + } + // Construct response + resp := EasDiscoveryResp{ + DiscoveredEas: []DiscoveredEas{ + { + Eas: &easProfile, + }, + }, + } + // Send JSON response with status 201 Created + jsonResponse := convertEasDiscoveryRespToJson(&resp) + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, jsonResponse) +} + /* * appRegistrationPOST handles the registration of applications. * It decodes the request body into an AppInfo struct, validates mandatory parameters, retrieves app instance information, validates the app info, and stores it in Redis. @@ -686,34 +1320,35 @@ func appRegistrationPOST(w http.ResponseWriter, r *http.Request) { errHandlerProblemDetails(w, "Endpoint is required when IsInsByMec is FALSE.", http.StatusBadRequest) return } + if appInfo.IsInsByMec { + // Process appProfile if provided + if appInfo.AppProfile != nil { + // Validate appProvider and other fields mapped to EASProfile + if appInfo.AppProvider != appInfo.AppProfile.ProvId { + log.Error("Mismatch between appProvider in AppInfo and provId in appProfile") + errHandlerProblemDetails(w, "appProvider and provId must match.", http.StatusBadRequest) + return + } - // Process appProfile if provided - if appInfo.AppProfile != nil { - // Validate appProvider and other fields mapped to EASProfile - if appInfo.AppProvider != appInfo.AppProfile.ProvId { - log.Error("Mismatch between appProvider in AppInfo and provId in appProfile") - errHandlerProblemDetails(w, "appProvider and provId must match.", http.StatusBadRequest) - return - } - - if !reflect.DeepEqual(getEndpointUris(appInfo.Endpoint), getProfileEndpointUris(appInfo.AppProfile.EndPt)) { - log.Error("Mismatch between endpoint in AppInfo and endPt in appProfile") - errHandlerProblemDetails(w, "Endpoint and endPt must match.", http.StatusBadRequest) - return - } + if !reflect.DeepEqual(getEndpointUris(appInfo.Endpoint), getProfileEndpointUris(appInfo.AppProfile.EndPt)) { + log.Error("Mismatch between endpoint in AppInfo and endPt in appProfile") + errHandlerProblemDetails(w, "Endpoint and endPt must match.", http.StatusBadRequest) + return + } - if appInfo.AppProfile.EasId == "" { - log.Error("Missing mandatory parameter: easId") - errHandlerProblemDetails(w, "Mandatory attribute easId is missing.", http.StatusBadRequest) - return - } + if appInfo.AppProfile.EasId == "" { + log.Error("Missing mandatory parameter: easId") + errHandlerProblemDetails(w, "Mandatory attribute easId is missing.", http.StatusBadRequest) + return + } - if appInfo.AppName != appInfo.AppProfile.EasId { - log.Error("Mismatch between AppName in AppInfo and EasId in appProfile") - errHandlerProblemDetails(w, "AppName and EasId must match.", http.StatusBadRequest) - return + if appInfo.AppName != appInfo.AppProfile.EasId { + log.Error("Mismatch between AppName in AppInfo and EasId in appProfile") + errHandlerProblemDetails(w, "AppName and EasId must match.", http.StatusBadRequest) + return + } + // Additional checks for attributes such as scheds, svcArea, etc., as required. } - // Additional checks for attributes such as scheds, svcArea, etc., as required. } // Retrieve App instance information diff --git a/go-apps/meep-app-enablement/server/app-support/convert.go b/go-apps/meep-app-enablement/server/app-support/convert.go index c352c580c..072da21a4 100644 --- a/go-apps/meep-app-enablement/server/app-support/convert.go +++ b/go-apps/meep-app-enablement/server/app-support/convert.go @@ -77,3 +77,42 @@ func convertAppInfoToJson(obj *AppInfo) string { return string(jsonInfo) } + +func convertEcsServProvReqInfoToJson(obj *EcsServProvReq) string { + jsonInfo, err := json.Marshal(*obj) + if err != nil { + log.Error(err.Error()) + return "" + } + return string(jsonInfo) +} + +func convertEecRegReqInfoToJson(obj *EecRegistration) string { + jsonInfo, err := json.Marshal(*obj) + if err != nil { + log.Error(err.Error()) + return "" + } + return string(jsonInfo) +} + +func convertEecPrevRegReqInfoToJson(jsonInfo string) *EecRegistration { + var obj EecRegistration + err := json.Unmarshal([]byte(jsonInfo), &obj) + if err != nil { + log.Error(err.Error()) + return nil + } + return &obj +} + +func convertEasDiscoveryRespToJson(obj *EasDiscoveryResp) string { + + jsonInfo, err := json.Marshal(*obj) + if err != nil { + log.Error(err.Error()) + return "" + } + + return string(jsonInfo) +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_ac_characteristics.go b/go-apps/meep-app-enablement/server/app-support/model_ac_characteristics.go new file mode 100644 index 000000000..27d4b86dc --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_ac_characteristics.go @@ -0,0 +1,15 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents EAS dynamic information changes filter. +type AcCharacteristics struct { + AcProf *AcProfile `json:"acProf"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_ac_profile.go b/go-apps/meep-app-enablement/server/app-support/model_ac_profile.go new file mode 100644 index 000000000..6a960070e --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_ac_profile.go @@ -0,0 +1,24 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// AC information indicating required services and service characteristics. +type AcProfile struct { + // Identity of the AC. + AcId string `json:"acId"` + // The category or type of AC. + AcType string `json:"acType,omitempty"` + // Indicates to the ECS which ECSPs are preferred for the AC. + PrefEcsps []string `json:"prefEcsps,omitempty"` + + SimInactTime int32 `json:"simInactTime,omitempty"` + // List of EAS information. + Eass []EasDetail `json:"eass,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_acr_scenario.go b/go-apps/meep-app-enablement/server/app-support/model_acr_scenario.go new file mode 100644 index 000000000..c6a8bb65f --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_acr_scenario.go @@ -0,0 +1,23 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents the ACR scenarios supported by EES. Possible values are: - EEC_INITIATED: Represents the EEC initiated ACR scenario. - EEC_EXECUTED_VIA_SOURCE_EES: Represents the EEC ACR scenario executed via the S-EES. - EEC_EXECUTED_VIA_TARGET_EES: Represents the EEC ACR scenario executed via the T-EES. - SOURCE_EAS_DECIDED: Represents the EEC ACR scenario where the S-EAS decides to perform ACR. - SOURCE_EES_EXECUTED: Represents the EEC ACR scenario where S-EES executes the ACR. - EEL_MANAGED_ACR: Represents the EEC ACR scenario where the ACR is managed by the Edge Enabler Layer. +type AcrScenario string + +// Enum values for ACRScenarioEnum. +const ( + EEC_INITIATED AcrScenario = "EEC_INITIATED" + EEC_EXECUTED_VIA_SOURCE_EES AcrScenario = "EEC_EXECUTED_VIA_SOURCE_EES" + EEC_EXECUTED_VIA_TARGET_EES AcrScenario = "EEC_EXECUTED_VIA_TARGET_EES" + SOURCE_EAS_DECIDED AcrScenario = "SOURCE_EAS_DECIDED" + SOURCE_EES_EXECUTED AcrScenario = "SOURCE_EES_EXECUTED" + EEL_MANAGED_ACR AcrScenario = "EEL_MANAGED_ACR" +) diff --git a/go-apps/meep-app-enablement/server/app-support/model_app_group_profile.go b/go-apps/meep-app-enablement/server/app-support/model_app_group_profile.go new file mode 100644 index 000000000..2a4efd0e6 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_app_group_profile.go @@ -0,0 +1,18 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents the application group profile for common EAS. +type AppGroupProfile struct { + // Represents the application group that uniquely identifies the group of UEs using the same application. + AppGrpId string `json:"appGrpId"` + // Represents the application identifier of the EAS. + EasId string `json:"easId"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_app_info.go b/go-apps/meep-app-enablement/server/app-support/model_app_info.go index 836049ee3..958aa082c 100644 --- a/go-apps/meep-app-enablement/server/app-support/model_app_info.go +++ b/go-apps/meep-app-enablement/server/app-support/model_app_info.go @@ -46,7 +46,11 @@ type AppInfo struct { // Describes features a MEC application may use if available. FeatureDependency is defined in ETSI GS MEC 010-2 [4]. It shall not be provided if an AppD is available. AppFeatureOptional []FeatureDependency `json:"appFeatureOptional,omitempty"` // Indicate whether the application instance is instantiated by the MEC Management. Default to FALSE if absent. - IsInsByMec bool `json:"isInsByMec,omitempty"` - + IsInsByMec bool `json:"isInsByMec,omitempty"` + Scheds []string `json:"scheds"` + SvcArea string `json:"svcArea,omitempty"` AppProfile *AppProfile `json:"appProfile,omitempty"` + // Service characteristics provided by the EAS. + SvcKpi string `json:"svcKpi,omitempty"` + PermLvl []string `json:"permLvl"` } diff --git a/go-apps/meep-app-enablement/server/app-support/model_application_info.go b/go-apps/meep-app-enablement/server/app-support/model_application_info.go new file mode 100644 index 000000000..1962b4b72 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_application_info.go @@ -0,0 +1,17 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents the services the EEC wants to connect. +type ApplicationInfo struct { + AcProf *AcProfile `json:"acProf"` + + AppGroupProfile *AppGroupProfile `json:"appGroupProfile,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_device_type.go b/go-apps/meep-app-enablement/server/app-support/model_device_type.go new file mode 100644 index 000000000..24a47c771 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_device_type.go @@ -0,0 +1,18 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents the UE type. Possible values are: - CONSTRAINED_UE: Indicates UE is constrained with resources like power, processor etc. - NORMAL_UE: Indicates UE is not constrained with resources. +type DeviceType string + +const ( + ConstrainedUE = "CONSTRAINED_UE" + NormalUE = "NORMAL_UE" +) diff --git a/go-apps/meep-app-enablement/server/app-support/model_discovered_eas.go b/go-apps/meep-app-enablement/server/app-support/model_discovered_eas.go new file mode 100644 index 000000000..97141c366 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_discovered_eas.go @@ -0,0 +1,23 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +import ( + "time" +) + +// Represents an EAS discovery information. +type DiscoveredEas struct { + Eas *EasProfile `json:"eas"` + + EesEndPt *EndPoint `json:"eesEndPt,omitempty"` + + LifeTime *time.Time `json:"lifeTime,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_eas_detail.go b/go-apps/meep-app-enablement/server/app-support/model_eas_detail.go new file mode 100644 index 000000000..f93b04308 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_eas_detail.go @@ -0,0 +1,16 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// EAS details. +type EasDetail struct { + // Application identifier of the EAS. + EasId string `json:"easId"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_filter.go b/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_filter.go new file mode 100644 index 000000000..6c27737cf --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_filter.go @@ -0,0 +1,16 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents the EAS characteristics. +type EasDiscoveryFilter struct { + // AC description for which an EAS is needed. + AcChars []AcCharacteristics `json:"acChars,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_req.go b/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_req.go new file mode 100644 index 000000000..4d6af9722 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_req.go @@ -0,0 +1,39 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +import ( + "time" +) + +// EAS discovery request information. +type EasDiscoveryReq struct { + RequestorId *RequestorId `json:"requestorId"` + + UeId string `json:"ueId,omitempty"` + + EasDiscoveryFilter *EasDiscoveryFilter `json:"easDiscoveryFilter,omitempty"` + // Indicates if the EEC supports service continuity or not, also indicates which ACR scenarios are supported by the EEC. + EecSvcContinuity []AcrScenario `json:"eecSvcContinuity,omitempty"` + // Indicates if the EES supports service continuity or not, also indicates which ACR scenarios are supported by the EES. + EesSvcContinuity []AcrScenario `json:"eesSvcContinuity,omitempty"` + // Indicates if the EAS supports service continuity or not, also indicates which ACR scenarios are supported by the EAS. + EasSvcContinuity []AcrScenario `json:"easSvcContinuity,omitempty"` + + LocInf *LocationInfo `json:"locInf,omitempty"` + // Indicates if the EEC requires the EAS selection support from the EES (e.g., for constrained device). The default value false indicates the EAS selection is not required from the EES. + EasSelSupInd bool `json:"easSelSupInd,omitempty"` + + SuppFeat string `json:"suppFeat,omitempty"` + // Indicates to the EES whether the EAS instantiation triggering should be performed for the current request. The default value false indicates the EAS instantiation triggering should not be performed. The true value indicate the EAS instantiation triggering should be performed. + EasIntTrigSup bool `json:"easIntTrigSup,omitempty"` + + PredictExpTime *time.Time `json:"predictExpTime,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_resp.go b/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_resp.go new file mode 100644 index 000000000..6c6a76a88 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_eas_discovery_resp.go @@ -0,0 +1,16 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// EAS discovery response. +type EasDiscoveryResp struct { + // List of EAS discovery information. + DiscoveredEas []DiscoveredEas `json:"discoveredEas"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_eas_profile.go b/go-apps/meep-app-enablement/server/app-support/model_eas_profile.go new file mode 100644 index 000000000..c26789af5 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_eas_profile.go @@ -0,0 +1,41 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +type EasProfile struct { + // The identifier of the EAS + EasId string `json:"easId"` + + EndPt *OneOfAppInfoEndpoint `json:"endPt"` + // Identities of the Application Clients that can be served by the EAS + AcIds []string `json:"acIds"` + // Identifier of the ASP that provides the EAS. + ProvId string `json:"provId,omitempty"` + // The category or type of EAS. + Type_ *CategoryRef `json:"type,omitempty"` + // The availability schedule of the EAS. + Scheds []string `json:"scheds"` + // The list of geographical and topological areas that the EAS serves. ACs in the UE that are outside the area will not be served. + SvcArea string `json:"svcArea,omitempty"` + // Service characteristics provided by the EAS. + SvcKpi string `json:"svcKpi,omitempty"` + // level of service permissions supported by the EAS. + PermLvl []string `json:"permLvl"` + // Service features supported by the EAS. + EasFeats []string `json:"easFeats"` + // The ACR scenarios supported by the EAS for service continuity. + SvcContSupp []string `json:"svcContSupp"` + // List of DNAI(s) and the N6 traffic information associated with the EAS. + AppLocs []string `json:"appLocs"` + // The period indicating to the EES, how often the EES needs to check the EAS's availability after a successful registration. + AvlRep int32 `json:"avlRep,omitempty"` + // EAS status information. + Status string `json:"status,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_ecs_serv_prov_req.go b/go-apps/meep-app-enablement/server/app-support/model_ecs_serv_prov_req.go new file mode 100644 index 000000000..8afeff3ad --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_ecs_serv_prov_req.go @@ -0,0 +1,30 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// ECS service provisioning request information. +type EcsServProvReq struct { + // Represents a unique identifier of the EEC. + EecId string `json:"eecId"` + + UeId string `json:"ueId,omitempty"` + // Information about services the EEC wants to connect to. + AcProfs []AcProfile `json:"acProfs,omitempty"` + // Information about the list of services the EEC wants to connect. + AppInfo []ApplicationInfo `json:"appInfo,omitempty"` + // Indicates if the EEC supports service continuity or not, also indicates which ACR scenarios are supported by the EEC. + EecSvcContSupp []AcrScenario `json:"eecSvcContSupp,omitempty"` + + LocInf *LocationInfo `json:"locInf,omitempty"` + // Indicates to the ECS which EES providers are preferred by the EEC. + EcspIds []string `json:"ecspIds,omitempty"` + + SuppFeat string `json:"suppFeat,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_edn_con_info.go b/go-apps/meep-app-enablement/server/app-support/model_edn_con_info.go new file mode 100644 index 000000000..8abe61ed0 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_edn_con_info.go @@ -0,0 +1,15 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents an EDN connection information. +type EdnConInfo struct { + Dnn string `json:"dnn,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_edn_config_info.go b/go-apps/meep-app-enablement/server/app-support/model_edn_config_info.go new file mode 100644 index 000000000..8f10cf311 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_edn_config_info.go @@ -0,0 +1,23 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +import ( + "time" +) + +// Represents the EDN configuration information. +type EdnConfigInfo struct { + EdnConInfo *EdnConInfo `json:"ednConInfo"` + // Contains the list of EESs of the EDN. + Eess []EesInfo `json:"eess"` + + LifeTime *time.Time `json:"lifeTime,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_eec_registration.go b/go-apps/meep-app-enablement/server/app-support/model_eec_registration.go new file mode 100644 index 000000000..8b610d3ca --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_eec_registration.go @@ -0,0 +1,40 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +import ( + "time" +) + +// Describes the parameters to perform EEC Registration related operations. +type EecRegistration struct { + // Represents a unique identifier of the EEC. + EecId string `json:"eecId"` + + UeId string `json:"ueId,omitempty"` + // Profiles of ACs for which the EEC provides edge enabling services. + AcProfs []AcProfile `json:"acProfs,omitempty"` + + ExpTime *time.Time `json:"expTime,omitempty"` + // Profiles of ACs for which the EEC provides edge enabling services. + EecSvcContSupp []AcrScenario `json:"eecSvcContSupp,omitempty"` + // Identifier of the EEC context obtained from a previous registration. + EecCntxId string `json:"eecCntxId,omitempty"` + // Identifier of the EES that provided EEC context ID. + SrcEesId string `json:"srcEesId,omitempty"` + + EndPt *EndPoint `json:"endPt,omitempty"` + // Set to true to indicate that UE Mobility support is required. Set to false to indicate that UE mobility support is not required. The default value when omitted is false. + UeMobilityReq bool `json:"ueMobilityReq,omitempty"` + // Set to true to indicate the EES support for EAS selection. Set to false to indicate the EES shall not select the EAS. The default value when omitted is false. + EasSelReqInd bool `json:"easSelReqInd,omitempty"` + + UeType *DeviceType `json:"ueType,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_ees_info.go b/go-apps/meep-app-enablement/server/app-support/model_ees_info.go new file mode 100644 index 000000000..1fadd0541 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_ees_info.go @@ -0,0 +1,22 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents EES information. +type EesInfo struct { + // Identity of the EES. + EesId string `json:"eesId"` + + EndPt *EndPoint `json:"endPt,omitempty"` + // Application identities of the Edge Application Servers registered with the EES. + EasIds []string `json:"easIds,omitempty"` + // Indicates whether the EEC is required to register on the EES to use edge services or not. + EecRegConf bool `json:"eecRegConf"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_end_point.go b/go-apps/meep-app-enablement/server/app-support/model_end_point.go new file mode 100644 index 000000000..c871e1f9e --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_end_point.go @@ -0,0 +1,21 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// The end point information to reach EAS. +type EndPoint struct { + Fqdn string `json:"fqdn,omitempty"` + // IPv4 addresses of the edge server. + Ipv4Addrs []string `json:"ipv4Addrs,omitempty"` + // IPv6 addresses of the edge server. + Ipv6Addrs []string `json:"ipv6Addrs,omitempty"` + + Uri string `json:"uri,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_geographic_area.go b/go-apps/meep-app-enablement/server/app-support/model_geographic_area.go new file mode 100644 index 000000000..1ca1b111b --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_geographic_area.go @@ -0,0 +1,15 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Geographic area specified by different shape. +type GeographicArea struct { + Point *Point `json:"point,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_geographical_coordinates.go b/go-apps/meep-app-enablement/server/app-support/model_geographical_coordinates.go new file mode 100644 index 000000000..2916c05e1 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_geographical_coordinates.go @@ -0,0 +1,17 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Geographical coordinates. +type GeographicalCoordinates struct { + Lon float64 `json:"lon"` + + Lat float64 `json:"lat"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_inline_response_201.go b/go-apps/meep-app-enablement/server/app-support/model_inline_response_201.go new file mode 100644 index 000000000..c8beccfa2 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_inline_response_201.go @@ -0,0 +1,27 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +import ( + "time" +) + +type InlineResponse201 struct { + // Identifier of the EEC registration. + RegistrationID string `json:"RegistrationID,omitempty"` + // Expiration time of the registration. + ExpirationTime time.Time `json:"ExpirationTime,omitempty"` + // Identifier of the EEC context information available at the EES. + EECContextID string `json:"EECContextID,omitempty"` + // Indicates whether the EEC context retrieval from the source EES was successful. + EECContextRelocationStatus bool `json:"EECContextRelocationStatus,omitempty"` + + DiscoveredEASList []EasProfile `json:"DiscoveredEASList,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_location_info.go b/go-apps/meep-app-enablement/server/app-support/model_location_info.go new file mode 100644 index 000000000..43b39d40a --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_location_info.go @@ -0,0 +1,15 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents the user location information. +type LocationInfo struct { + GeographicArea *GeographicArea `json:"geographicArea,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_point.go b/go-apps/meep-app-enablement/server/app-support/model_point.go new file mode 100644 index 000000000..8883debf9 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_point.go @@ -0,0 +1,17 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Ellipsoid Point. +type Point struct { + Point *GeographicalCoordinates `json:"point"` + + Shape *SupportedGadShapes `json:"shape"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_registrations_registration_id_body.go b/go-apps/meep-app-enablement/server/app-support/model_registrations_registration_id_body.go new file mode 100644 index 000000000..b19f20de6 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_registrations_registration_id_body.go @@ -0,0 +1,23 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +import ( + "time" +) + +type RegistrationsRegistrationIdBody struct { + // Profiles of ACs for which the EEC provides edge enabling services. + AcProfs []AcProfile `json:"acProfs,omitempty"` + + ExpTime *time.Time `json:"expTime,omitempty"` + // Set to true to indicate that UE Mobility support is required. Set to false to indicate that UE mobility support is not required. The default value when omitted is false. + UeMobilityReq bool `json:"ueMobilityReq,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_requestor_id.go b/go-apps/meep-app-enablement/server/app-support/model_requestor_id.go new file mode 100644 index 000000000..e20f925d2 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_requestor_id.go @@ -0,0 +1,20 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Represents identifier of the requestor. +type RequestorId struct { + // The identifier of the EES (e.g. S-EES). + EesId string `json:"eesId,omitempty"` + // The application identifier of the EAS (e.g. S-EAS), e.g. FQDN, URI. + EasId string `json:"easId,omitempty"` + // The identifier of the EEC. + EecId string `json:"eecId,omitempty"` +} diff --git a/go-apps/meep-app-enablement/server/app-support/model_supported_gad_shapes.go b/go-apps/meep-app-enablement/server/app-support/model_supported_gad_shapes.go new file mode 100644 index 000000000..109dc3630 --- /dev/null +++ b/go-apps/meep-app-enablement/server/app-support/model_supported_gad_shapes.go @@ -0,0 +1,29 @@ +/* + * MEC Service Management API + * + * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI + * + * API version: 3.1.1 + * Contact: cti_support@etsi.org + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package server + +// Indicates supported GAD shapes. +type SupportedGadShapes string + +// Enum values for SupportedGADShapes. +const ( + POINT SupportedGadShapes = "POINT" + POINT_UNCERTAINTY_CIRCLE SupportedGadShapes = "POINT_UNCERTAINTY_CIRCLE" + POINT_UNCERTAINTY_ELLIPSE SupportedGadShapes = "POINT_UNCERTAINTY_ELLIPSE" + POLYGON SupportedGadShapes = "POLYGON" + POINT_ALTITUDE SupportedGadShapes = "POINT_ALTITUDE" + POINT_ALTITUDE_UNCERTAINTY SupportedGadShapes = "POINT_ALTITUDE_UNCERTAINTY" + ELLIPSOID_ARC SupportedGadShapes = "ELLIPSOID_ARC" + LOCAL_2D_POINT_UNCERTAINTY_ELLIPSE SupportedGadShapes = "LOCAL_2D_POINT_UNCERTAINTY_ELLIPSE" + LOCAL_3D_POINT_UNCERTAINTY_ELLIPSOID SupportedGadShapes = "LOCAL_3D_POINT_UNCERTAINTY_ELLIPSOID" + DISTANCE_DIRECTION SupportedGadShapes = "DISTANCE_DIRECTION" + RELATIVE_2D_LOCATION_UNCERTAINTY_ELLIPSE SupportedGadShapes = "RELATIVE_2D_LOCATION_UNCERTAINTY_ELLIPSE" + RELATIVE_3D_LOCATION_UNCERTAINTY_ELLIPSOID SupportedGadShapes = "RELATIVE_3D_LOCATION_UNCERTAINTY_ELLIPSOID" +) diff --git a/go-apps/meep-app-enablement/server/routers.go b/go-apps/meep-app-enablement/server/routers.go index 1ad8f96b9..8a59112dc 100644 --- a/go-apps/meep-app-enablement/server/routers.go +++ b/go-apps/meep-app-enablement/server/routers.go @@ -182,6 +182,13 @@ var routes = Routes{ appSupport.TimingCapsGET, }, + Route{ + "GetEASDiscInfo", + strings.ToUpper("Post"), + "/eees-easdiscovery/v1/eas-profiles/request-discovery", + appSupport.GetEASDiscInfo, + }, + Route{ "TimingCurrentTimeGET", strings.ToUpper("Get"), @@ -322,6 +329,41 @@ var routes = Routes{ svcMgmt.TransportsGET, }, + Route{ + "RequestServProv", + strings.ToUpper("Post"), + "/eecs-serviceprovisioning/v1/request", + appSupport.RequestServProv, + }, + + Route{ + "DeleteEECReg", + strings.ToUpper("Delete"), + "/eees-eecregistration/v1/registration/{registrationId}", + appSupport.DeleteIndEECReg, + }, + + Route{ + "UpdateRegistrationPut", + strings.ToUpper("Put"), + "/eees-eecregistration/v1/registration/{registrationId}", + appSupport.UpdateRegistrationPut, + }, + + Route{ + "GetRegistration", + strings.ToUpper("Get"), + "/eees-eecregistration/v1/registration/{registrationId}", + appSupport.GetRegistration, + }, + + Route{ + "CreateEECReg", + strings.ToUpper("Post"), + "/eees-eecregistration/v1/registration", + appSupport.CreateEECReg, + }, + Route{ "Index", "GET", -- GitLab From 475f626c3a3de24cf8537a06109755ef89e17b88 Mon Sep 17 00:00:00 2001 From: Ikram Ul Haq <ikram.haq@xflowresearch.com> Date: Wed, 5 Feb 2025 09:14:27 +0000 Subject: [PATCH 6/9] Refactored ECS configuration and EEC registration handling for improved validation and error handling --- .../server/app-support/app-support.go | 374 +++++++++++------- 1 file changed, 240 insertions(+), 134 deletions(-) diff --git a/go-apps/meep-app-enablement/server/app-support/app-support.go b/go-apps/meep-app-enablement/server/app-support/app-support.go index a67c0b508..6b047a5bf 100644 --- a/go-apps/meep-app-enablement/server/app-support/app-support.go +++ b/go-apps/meep-app-enablement/server/app-support/app-support.go @@ -654,115 +654,144 @@ func timingCurrentTimeGET(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, string(jsonResponse)) } +// setupECSConfiguration stores the mandatory ECS configuration in Redis. +// It uses the hostUrl as the ECS address and returns an error if any step fails. func setupECSConfiguration() error { - // Mandatory ECS Configuration (only ECS Address) + // Create a map containing the mandatory ECS configuration ecsConfig := map[string]interface{}{ - "ECSAddress": hostUrl.String(), // MEC Sandbox URL as ECS Address + "ECSAddress": hostUrl.String(), // Use the MEC Sandbox URL as the ECS Address } - // Convert ECS Config to JSON + // Convert the ECS configuration to JSON ecsConfigJson, err := json.Marshal(ecsConfig) if err != nil { + log.Error("setupECSConfiguration: failed to marshal ECS configuration", "error", err.Error()) return fmt.Errorf("failed to marshal ECS configuration: %v", err) } - // Convert []byte to string before passing to JSONSetEntry + // Convert JSON bytes to a string for storage ecsConfigJsonStr := string(ecsConfigJson) - // Store in Redis + + // Define the Redis key and store the JSON configuration in Redis ecsKey := baseKey + "ecs:config" err = rc.JSONSetEntry(ecsKey, ".", ecsConfigJsonStr) if err != nil { + log.Error("setupECSConfiguration: failed to set ECS configuration in Redis", "error", err.Error()) return fmt.Errorf("failed to set ECS configuration in Redis: %v", err) } - log.Info("ECS configuration stored successfully") + log.Info("setupECSConfiguration: ECS configuration stored successfully in Redis", "key", ecsKey) return nil } +// getECSConfig retrieves the ECS configuration from Redis and returns it as a map. +// An error is returned if the configuration is missing or cannot be unmarshaled. func getECSConfig() (map[string]interface{}, error) { + // Define the Redis key for the ECS configuration ecsKey := baseKey + "ecs:config" ecsConfigJson, err := rc.JSONGetEntry(ecsKey, ".") if err != nil { + log.Error("getECSConfig: failed to get ECS configuration from Redis", "error", err.Error()) return nil, fmt.Errorf("failed to get ECS configuration: %v", err) } + // Unmarshal the JSON configuration into a map var ecsConfig map[string]interface{} err = json.Unmarshal([]byte(ecsConfigJson), &ecsConfig) if err != nil { + log.Error("getECSConfig: failed to unmarshal ECS configuration", "error", err.Error()) return nil, fmt.Errorf("failed to unmarshal ECS configuration: %v", err) } + log.Info("getECSConfig: successfully retrieved ECS configuration from Redis", "key", ecsKey) return ecsConfig, nil } +// getRegistration retrieves the EEC registration details for a given RegistrationId. +// It extracts the registrationId from the request URL, looks up the corresponding entry in Redis, +// and sends a JSON response with the registration information. func getRegistration(w http.ResponseWriter, r *http.Request) { - log.Info("Get EEC Registration by RegistrationId") + log.Info("getRegistration: Get EEC Registration by RegistrationId") w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + // Extract the registrationId from the URL variables vars := mux.Vars(r) registrationId := vars["registrationId"] keyName := baseKey + "app:" + registrationId - // Find reginfo entry in redis DB + + // Retrieve the registration information from Redis eecPrevReg, err := rc.JSONGetEntry(keyName, ".") if err != nil { - err = errors.New("eecRegistration not found against the provided RegistrationId") - log.Error(err.Error()) - errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + errMsg := "getRegistration: eecRegistration not found for the provided RegistrationId" + log.Error(errMsg, "registrationId", registrationId, "error", err.Error()) + errHandlerProblemDetails(w, errMsg, http.StatusNotFound) return } if eecPrevReg == "" { - log.Error("RegistrationId %s not found in Redis", registrationId) + log.Error("getRegistration: RegistrationId not found in Redis", "registrationId", registrationId) errHandlerProblemDetails(w, "Registration not found", http.StatusNotFound) return } + + // Convert the previously stored registration information to JSON format for the response sInfoJson := convertEecPrevRegReqInfoToJson(eecPrevReg) jsonResponse, err := json.Marshal(sInfoJson) if err != nil { - log.Error("Failed to marshal the response:", err.Error()) + log.Error("getRegistration: failed to marshal the response", "error", err.Error()) errHandlerProblemDetails(w, "Internal server error", http.StatusInternalServerError) return } - // Send the response + // Send the JSON response with a 200 OK status w.WriteHeader(http.StatusOK) w.Write(jsonResponse) + log.Info("getRegistration: successfully retrieved registration", "registrationId", registrationId) } +// updateRegistrationPut updates an existing EEC registration based on the RegistrationId provided in the URL. +// It decodes the update request, validates AcId fields, and updates the stored registration data in Redis. func updateRegistrationPut(w http.ResponseWriter, r *http.Request) { - log.Info("Update EEC Registration by RegistrationId") + log.Info("updateRegistrationPut: Update EEC Registration by RegistrationId") w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + // Extract registrationId from URL vars := mux.Vars(r) registrationId := vars["registrationId"] + + // Decode the update request body into ecsRegUpdateReq var ecsRegUpdateReq RegistrationsRegistrationIdBody decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&ecsRegUpdateReq) - if err != nil { - log.Error("Failed to decode the request body:", err.Error()) + if err := decoder.Decode(&ecsRegUpdateReq); err != nil { + log.Error("updateRegistrationPut: failed to decode the request body", "error", err.Error()) errHandlerProblemDetails(w, "Invalid request format", http.StatusBadRequest) return } + keyName := baseKey + "app:" + registrationId - // Find reginfo entry in redis DB + // Retrieve the current registration entry from Redis eecPrevReg, err := rc.JSONGetEntry(keyName, ".") if err != nil { - err = errors.New("eecRegistration not found against the provided RegistrationId") - log.Error(err.Error()) - errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + errMsg := "updateRegistrationPut: eecRegistration not found for the provided RegistrationId" + log.Error(errMsg, "registrationId", registrationId, "error", err.Error()) + errHandlerProblemDetails(w, errMsg, http.StatusNotFound) return } if eecPrevReg == "" { - log.Error("RegistrationId %s not found in Redis", registrationId) + log.Error("updateRegistrationPut: RegistrationId not found in Redis", "registrationId", registrationId) errHandlerProblemDetails(w, "Registration not found", http.StatusNotFound) return } + + // Convert the current registration info to a modifiable JSON structure sInfoJson := convertEecPrevRegReqInfoToJson(eecPrevReg) - // Helper function to check if AcId is valid + // Helper function to check if an AcId is valid isValidAcId := func(acId string) bool { return acId != "" && acId != "string" } - // Check if either valid AcId or LocationInfo is provided + // Validate that at least one valid AcId is provided in the update request hasAcId := false for _, acProf := range ecsRegUpdateReq.AcProfs { if isValidAcId(acProf.AcId) { @@ -770,20 +799,23 @@ func updateRegistrationPut(w http.ResponseWriter, r *http.Request) { break } } - // Process AcId if provided + + // Process and validate each valid AcId in the update request if hasAcId { for _, acProf := range ecsRegUpdateReq.AcProfs { if isValidAcId(acProf.AcId) { appId := acProf.AcId + log.Debug("updateRegistrationPut: processing AcId", "appId", appId) appInfo, err := getAppInfo(appId) if err != nil { + log.Error("updateRegistrationPut: getAppInfo failed", "appId", appId, "error", err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) return } code, problemDetails, err := validateAppInfo(appInfo) if err != nil { - log.Error(err.Error()) + log.Error("updateRegistrationPut: validateAppInfo error", "appId", appId, "error", err.Error()) if problemDetails != "" { w.WriteHeader(code) fmt.Fprint(w, problemDetails) @@ -795,76 +827,90 @@ func updateRegistrationPut(w http.ResponseWriter, r *http.Request) { } } } + + // Update the registration JSON with new AcProfs, ExpTime, and UeMobilityReq values sInfoJson.AcProfs = ecsRegUpdateReq.AcProfs sInfoJson.ExpTime = ecsRegUpdateReq.ExpTime sInfoJson.UeMobilityReq = ecsRegUpdateReq.UeMobilityReq + // Convert the updated registration structure back to JSON for storage sInfoJson_ := convertEecRegReqInfoToJson(sInfoJson) - key := baseKey + "app:" + registrationId - err = rc.JSONSetEntry(key, ".", sInfoJson_) + err = rc.JSONSetEntry(keyName, ".", sInfoJson_) if err != nil { - log.Error("Failed to set json entry in redis db") - errHandlerProblemDetails(w, "Failed to set json entry in redis db", http.StatusInternalServerError) + log.Error("updateRegistrationPut: failed to set JSON entry in Redis DB", "registrationId", registrationId, "error", err.Error()) + errHandlerProblemDetails(w, "Failed to set JSON entry in Redis DB", http.StatusInternalServerError) return } + // Prepare and send a response indicating a successful update response := InlineResponse201{ ExpirationTime: time.Now(), } - jsonResponse, err := json.Marshal(response) if err != nil { - log.Error("Failed to marshal the response:", err.Error()) + log.Error("updateRegistrationPut: failed to marshal the response", "error", err.Error()) errHandlerProblemDetails(w, "Internal server error", http.StatusInternalServerError) return } - // Send the response w.WriteHeader(http.StatusOK) w.Write(jsonResponse) + log.Info("updateRegistrationPut: registration updated successfully", "registrationId", registrationId) } +// createEECReg creates a new EEC registration. +// It validates the incoming request, verifies that the request is stored in Redis, +// validates AcId fields, and finally stores the new registration while returning the registration details. func createEECReg(w http.ResponseWriter, r *http.Request) { - log.Info("requestServProv") + log.Info("createEECReg: Request to create EEC Registration") w.Header().Set("Content-Type", "application/json; charset=UTF-8") mutex.Lock() defer mutex.Unlock() + // Check if the request body is provided if r.Body == nil { - err := errors.New("Request body is missing") + err := errors.New("createEECReg: request body is missing") + log.Error(err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return } + + // Decode the request body into an EecRegistration structure var ecsRegReq EecRegistration decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&ecsRegReq) - if err != nil { - log.Error("Failed to decode the request body:", err.Error()) + if err := decoder.Decode(&ecsRegReq); err != nil { + log.Error("createEECReg: failed to decode the request body", "error", err.Error()) errHandlerProblemDetails(w, "Invalid request format", http.StatusBadRequest) return } + + // Validate that a unique EecId is provided if ecsRegReq.EecId == "" || ecsRegReq.EecId == "string" { - log.Error("Invalid request: Please enter the unique EecId") + log.Error("createEECReg: invalid request - unique EecId missing or default value provided") errHandlerProblemDetails(w, "Please enter the unique EecId", http.StatusBadRequest) return } + + // Verify that the registration request is already stored in Redis key := baseKey + "app:" + ecsRegReq.EecId sInfoJson1, err := rc.JSONGetEntry(key, ".") if err != nil { - log.Error("Failed to get json entry from redis db") - errHandlerProblemDetails(w, "Failed to get json entry from redis db", http.StatusInternalServerError) + log.Error("createEECReg: failed to get JSON entry from Redis DB", "error", err.Error()) + errHandlerProblemDetails(w, "Failed to get JSON entry from Redis DB", http.StatusInternalServerError) return } if sInfoJson1 == "" { - errHandlerProblemDetails(w, "Request is not stored in redis db", http.StatusInternalServerError) + log.Error("createEECReg: registration request is not stored in Redis", "EecId", ecsRegReq.EecId) + errHandlerProblemDetails(w, "Request is not stored in Redis DB", http.StatusInternalServerError) return } - // Helper function to check if AcId is valid + + // Helper function to check if an AcId is valid isValidAcId := func(acId string) bool { return acId != "" && acId != "string" } - // Check if either valid AcId or LocationInfo is provided + // Ensure that at least one valid AcId is provided in the registration request hasAcId := false for _, acProf := range ecsRegReq.AcProfs { if isValidAcId(acProf.AcId) { @@ -872,20 +918,23 @@ func createEECReg(w http.ResponseWriter, r *http.Request) { break } } - // Process AcId if provided + + // Validate each valid AcId by retrieving and validating associated application info if hasAcId { for _, acProf := range ecsRegReq.AcProfs { if isValidAcId(acProf.AcId) { appId := acProf.AcId + log.Debug("createEECReg: processing AcId", "appId", appId) appInfo, err := getAppInfo(appId) if err != nil { + log.Error("createEECReg: getAppInfo failed", "appId", appId, "error", err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) return } code, problemDetails, err := validateAppInfo(appInfo) if err != nil { - log.Error(err.Error()) + log.Error("createEECReg: validateAppInfo error", "appId", appId, "error", err.Error()) if problemDetails != "" { w.WriteHeader(code) fmt.Fprint(w, problemDetails) @@ -898,116 +947,137 @@ func createEECReg(w http.ResponseWriter, r *http.Request) { } } - // Get platform details dynamically and prepare response + // Dynamically retrieve platform details to verify the registration endpoint and MEC platform ID ednConfig, err := getDynamicPlatformDetails(basePath) if err != nil { - errHandlerProblemDetails(w, "Failed to get the ednconfig information that contain MEC plateform information", http.StatusBadRequest) + log.Error("createEECReg: failed to get EDN config information", "error", err.Error()) + errHandlerProblemDetails(w, "Failed to get the EDN config information", http.StatusBadRequest) return } - // Compare EEC registration endpoint with EDN config endpoint + + // Validate that the registration endpoint and source EES ID match the expected EDN config values if ecsRegReq.EndPt == nil || ecsRegReq.EndPt.Uri != ednConfig.Eess[0].EndPt.Uri { - log.Error("Endpoint mismatch: EEC registration endpoint does not match EDN config endpoint") + log.Error("createEECReg: endpoint mismatch - EEC registration endpoint does not match EDN config endpoint", + "provided", ecsRegReq.EndPt, "expected", ednConfig.Eess[0].EndPt) errHandlerProblemDetails(w, "Endpoint mismatch: EEC registration endpoint does not match EDN config endpoint", http.StatusBadRequest) return } if ecsRegReq.EndPt == nil || ecsRegReq.SrcEesId != ednConfig.Eess[0].EesId { - log.Error("Endpoint mismatch: SrcEesId does not match EDN config MEC Plateform ID") - errHandlerProblemDetails(w, "Endpoint mismatch: SrcEesId does not match EDN config MEC Plateform ID", http.StatusBadRequest) + log.Error("createEECReg: endpoint mismatch - SrcEesId does not match EDN config MEC Platform ID", + "provided", ecsRegReq.SrcEesId, "expected", ednConfig.Eess[0].EesId) + errHandlerProblemDetails(w, "Endpoint mismatch: SrcEesId does not match EDN config MEC Platform ID", http.StatusBadRequest) return } + + // Generate a new unique registrationId for the new registration registrationId := uuid.New().String() sInfoJson := convertEecRegReqInfoToJson(&ecsRegReq) key = baseKey + "app:" + registrationId err = rc.JSONSetEntry(key, ".", sInfoJson) if err != nil { - log.Error("Failed to set json entry in redis db") - errHandlerProblemDetails(w, "Failed to set json entry in redis db", http.StatusInternalServerError) + log.Error("createEECReg: failed to set JSON entry in Redis DB", "registrationId", registrationId, "error", err.Error()) + errHandlerProblemDetails(w, "Failed to set JSON entry in Redis DB", http.StatusInternalServerError) return } - // if ecsRegReq.EndPt == nil || ecsRegReq.SrcEesId != + // Prepare the response with registration details response := InlineResponse201{ RegistrationID: registrationId, ExpirationTime: time.Now(), - EECContextID: "example-context-id", + EECContextID: "example-context-id", // Replace with actual context ID if available EECContextRelocationStatus: true, - DiscoveredEASList: []EasProfile{}, // Add appropriate EasProfile values if needed + DiscoveredEASList: []EasProfile{}, // Populate with actual EasProfile values if needed } jsonResponse, err := json.Marshal(response) if err != nil { - log.Error("Failed to marshal the response:", err.Error()) + log.Error("createEECReg: failed to marshal the response", "error", err.Error()) errHandlerProblemDetails(w, "Internal server error", http.StatusInternalServerError) return } - // Send the response + // Send the successful creation response w.WriteHeader(http.StatusOK) w.Write(jsonResponse) + log.Info("createEECReg: registration created successfully", "registrationId", registrationId) } +// deleteIndEECReg deletes an individual EEC registration identified by RegistrationId. +// It removes the corresponding entry from Redis and returns a 204 No Content response on success. func deleteIndEECReg(w http.ResponseWriter, r *http.Request) { - log.Info("Delete EEC Registration by RegistrationId") - + log.Info("deleteIndEECReg: Delete EEC Registration by RegistrationId") w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + // Extract registrationId from URL vars := mux.Vars(r) registrationId := vars["registrationId"] - keyName := baseKey + "app:" + registrationId - // Find appInfo entry in redis DB + // Check if the registration exists in Redis _, err := rc.JSONGetEntry(keyName, ".") if err != nil { - err = errors.New("eecRegistration not found against the provided RegistrationId") - log.Error(err.Error()) - errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + errMsg := "deleteIndEECReg: eecRegistration not found for the provided RegistrationId" + log.Error(errMsg, "registrationId", registrationId, "error", err.Error()) + errHandlerProblemDetails(w, errMsg, http.StatusNotFound) return } - // Delete appInfo entry from redis DB + // Delete the registration entry from Redis err = rc.JSONDelEntry(keyName, ".") if err != nil { - log.Error(err.Error()) + log.Error("deleteIndEECReg: failed to delete JSON entry from Redis DB", "registrationId", registrationId, "error", err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) return } - // Send response on successful deletion of registration + log.Info("deleteIndEECReg: registration deleted successfully", "registrationId", registrationId) + // Send a 204 No Content response on successful deletion w.WriteHeader(http.StatusNoContent) } +// requestServProv handles requests for service provisioning. It validates the request payload, +// ensures proper synchronization, checks required fields, and finally stores the request in Redis +// before sending the platform configuration as a response. func requestServProv(w http.ResponseWriter, r *http.Request) { - log.Info("requestServProv") + log.Info("requestServProv: Received request to provide service") + + // Set response header to JSON w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + // Ensure thread safety when accessing shared resources mutex.Lock() defer mutex.Unlock() + // Check if the request body is present if r.Body == nil { - err := errors.New("Request body is missing") + err := errors.New("requestServProv: request body is missing") + log.Error(err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return } + // Decode the incoming JSON request into an EcsServProvReq structure var ecsServReq EcsServProvReq decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&ecsServReq) - if err != nil { - log.Error("Failed to decode the request body:", err.Error()) + if err := decoder.Decode(&ecsServReq); err != nil { + log.Error("requestServProv: failed to decode request body", "error", err.Error()) errHandlerProblemDetails(w, "Invalid request format", http.StatusBadRequest) return } + + // Validate that the EecId is provided and is not a placeholder value if ecsServReq.EecId == "" || ecsServReq.EecId == "string" { - log.Error("Invalid request: Please enter the unique EecId") + log.Error("requestServProv: invalid request - unique EecId missing or default value provided") errHandlerProblemDetails(w, "Please enter the unique EecId", http.StatusBadRequest) return } - // Helper function to check if AcId is valid + // Helper function to check if a provided AcId is valid (i.e. non-empty and not the default "string") isValidAcId := func(acId string) bool { return acId != "" && acId != "string" } - // Check if either valid AcId or LocationInfo is provided + // Verify that either a valid AcId is provided within AcProfs or LocationInfo is present. hasAcId := false for _, acProf := range ecsServReq.AcProfs { if isValidAcId(acProf.AcId) { @@ -1015,27 +1085,30 @@ func requestServProv(w http.ResponseWriter, r *http.Request) { break } } - if !hasAcId && ecsServReq.LocInf == nil { - log.Error("Invalid request: Both AcId and LocationInfo are missing") + log.Error("requestServProv: invalid request - both AcId and LocationInfo are missing") errHandlerProblemDetails(w, "Either a valid AcId or LocationInfo must be provided", http.StatusBadRequest) return } - // Process AcId if provided + // Process each valid AcId in the AcProfs slice if hasAcId { for _, acProf := range ecsServReq.AcProfs { if isValidAcId(acProf.AcId) { appId := acProf.AcId + log.Debug("requestServProv: processing AcId", "appId", appId) + // Retrieve application info for the given appId appInfo, err := getAppInfo(appId) if err != nil { + log.Error("requestServProv: getAppInfo failed", "appId", appId, "error", err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) return } + // Validate the retrieved appInfo; if invalid, send an appropriate response. code, problemDetails, err := validateAppInfo(appInfo) if err != nil { - log.Error(err.Error()) + log.Error("requestServProv: validateAppInfo error", "appId", appId, "error", err.Error()) if problemDetails != "" { w.WriteHeader(code) fmt.Fprint(w, problemDetails) @@ -1052,60 +1125,73 @@ func requestServProv(w http.ResponseWriter, r *http.Request) { if ecsServReq.LocInf != nil { lat := ecsServReq.LocInf.GeographicArea.Point.Point.Lat lon := ecsServReq.LocInf.GeographicArea.Point.Point.Lon - log.Info("Received coordinates: Lat:", lat, " Lon:", lon) + log.Info("requestServProv: received coordinates", "latitude", lat, "longitude", lon) + // Validate the geographic coordinates to ensure they are within acceptable boundaries. if !isValidCoordinates(lat, lon) { - log.Error("Invalid location: MEC platform not found for this location") + log.Error("requestServProv: invalid location - MEC platform not found for provided coordinates", + "latitude", lat, "longitude", lon) errHandlerProblemDetails(w, "MEC platform not found for this location", http.StatusNotFound) return } } + // Convert the ECS service provisioning request info into JSON format for storage. sInfoJson := convertEcsServProvReqInfoToJson(&ecsServReq) key := baseKey + "app:" + ecsServReq.EecId - err = rc.JSONSetEntry(key, ".", sInfoJson) - if err != nil { - log.Error("Failed to set json entry in redis db") - errHandlerProblemDetails(w, "Failed to set json entry in redis db", http.StatusInternalServerError) + + // Store the request in Redis DB. + if err := rc.JSONSetEntry(key, ".", sInfoJson); err != nil { + log.Error("requestServProv: failed to set JSON entry in Redis DB", "error", err.Error()) + errHandlerProblemDetails(w, "Failed to set JSON entry in Redis DB", http.StatusInternalServerError) return } + + // Verify that the entry was successfully stored by retrieving it. sInfoJson1, err := rc.JSONGetEntry(key, ".") if err != nil { - log.Error("Failed to get json entry from redis db") - errHandlerProblemDetails(w, "Failed to get json entry from redis db", http.StatusInternalServerError) + log.Error("requestServProv: failed to get JSON entry from Redis DB", "error", err.Error()) + errHandlerProblemDetails(w, "Failed to get JSON entry from Redis DB", http.StatusInternalServerError) return } if sInfoJson1 == "" { - errHandlerProblemDetails(w, "Request is not stored in redis db", http.StatusInternalServerError) + log.Error("requestServProv: request not stored in Redis DB", "key", key) + errHandlerProblemDetails(w, "Request is not stored in Redis DB", http.StatusInternalServerError) return } - // Get platform details dynamically and prepare response + // Dynamically retrieve platform configuration details using the basePath. ednConfig, err := getDynamicPlatformDetails(basePath) if err != nil { + log.Error("requestServProv: failed to get dynamic platform details", "error", err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return } - // Send the response + // Marshal the platform configuration to JSON for the response. jsonResponse, err := json.Marshal(ednConfig) if err != nil { - log.Error("Failed to marshal the response:", err.Error()) + log.Error("requestServProv: failed to marshal response", "error", err.Error()) errHandlerProblemDetails(w, "Internal server error", http.StatusInternalServerError) return } + // Send the successful response with HTTP 200 OK. w.WriteHeader(http.StatusOK) w.Write(jsonResponse) + log.Info("requestServProv: successfully processed service provisioning request", "EecId", ecsServReq.EecId) } -// Function to get dynamic platform details +// getDynamicPlatformDetails extracts platform configuration details dynamically based on the provided basePath. +// It returns an EdnConfigInfo structure containing the list of EES (Edge Services) configurations or an error if any occurs. func getDynamicPlatformDetails(basePath string) (*EdnConfigInfo, error) { platformDetails, err := getPlatformDetailsFromBasePath(basePath) if err != nil { + log.Error("getDynamicPlatformDetails: error retrieving platform details from basePath", "error", err) return nil, err } + // Build EES information from the retrieved platform details. eesInfo := EesInfo{ EesId: platformDetails.EesId, EndPt: platformDetails.EndPt, @@ -1116,20 +1202,28 @@ func getDynamicPlatformDetails(basePath string) (*EdnConfigInfo, error) { }, nil } -// Function to validate coordinates +// isValidCoordinates checks whether the given latitude and longitude fall within the expected geographic boundaries, +// allowing a small tolerance for minor discrepancies. func isValidCoordinates(lat, lon float64) bool { const tolerance = 0.0001 - return lat >= (43.7244-tolerance) && lat <= (43.7515+tolerance) && lon >= (7.4090-tolerance) && lon <= (7.4390+tolerance) + return lat >= (43.7244-tolerance) && lat <= (43.7515+tolerance) && + lon >= (7.4090-tolerance) && lon <= (7.4390+tolerance) } -// Function to extract platform details dynamically from basePath +// getPlatformDetailsFromBasePath parses the basePath to extract the platform identifier and constructs the corresponding URL. +// It returns an EesInfo structure containing the platform's EES identifier and endpoint or an error if the parsing fails. func getPlatformDetailsFromBasePath(basePath string) (*EesInfo, error) { + // Locate the "/mep" segment in the path. This marks the beginning of the platform-specific portion. mepIndex := strings.Index(basePath, "/mep") if mepIndex == -1 { - return nil, errors.New("Invalid base path: /mep not found") + err := errors.New("getPlatformDetailsFromBasePath: invalid base path, '/mep' not found") + log.Error(err.Error()) + return nil, err } + // The namespace is the part of the basePath before "/mep". namespace := basePath[:mepIndex] + // Extract the platform-specific segment after "/mep". platformPart := basePath[mepIndex+1:] nextSlashIndex := strings.Index(platformPart, "/") var platformIdentifier string @@ -1139,7 +1233,11 @@ func getPlatformDetailsFromBasePath(basePath string) (*EesInfo, error) { platformIdentifier = platformPart } - mecPlatformUrl := strings.TrimSuffix(hostUrl.String(), "/") + "/" + strings.TrimPrefix(namespace, "/") + "/" + platformIdentifier + // Construct the full URL for the MEC platform by combining hostUrl with the namespace and platform identifier. + mecPlatformUrl := strings.TrimSuffix(hostUrl.String(), "/") + "/" + + strings.TrimPrefix(namespace, "/") + "/" + platformIdentifier + + log.Debug("getPlatformDetailsFromBasePath: constructed MEC platform URL", "url", mecPlatformUrl) return &EesInfo{ EesId: platformIdentifier, @@ -1149,28 +1247,31 @@ func getPlatformDetailsFromBasePath(basePath string) (*EesInfo, error) { }, nil } -///////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// - +// getEASDiscInfo handles HTTP requests to retrieve EAS (Edge Application Service) discovery information. +// It validates the request, retrieves application configuration from the datastore, and returns a JSON response. func getEASDiscInfo(w http.ResponseWriter, r *http.Request) { - log.Debug(">>> getEASDiscInfo:", r) + log.Debug("getEASDiscInfo: request received", "method", r.Method, "url", r.URL) + + // Set the response content type to JSON. w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + // Decode the incoming JSON request into an EasDiscoveryReq structure. var discReq EasDiscoveryReq if err := json.NewDecoder(r.Body).Decode(&discReq); err != nil { - log.Error("Error decoding request body:", err) + log.Error("getEASDiscInfo: error decoding request body", "error", err) errHandlerProblemDetails(w, "Invalid request body.", http.StatusBadRequest) return } - log.Info("getEASDiscInfo: Received reqInfo:", discReq) - // Immediately after decoding the request body + log.Info("getEASDiscInfo: decoded request info", "discReq", discReq) + + // Immediately reject requests that include an unsupported EasId. if discReq.RequestorId.EasId != "" { - log.Error("EasId support is currently disabled") + log.Error("getEASDiscInfo: unsupported EasId provided", "EasId", discReq.RequestorId.EasId) errHandlerProblemDetails(w, "EasId is not supported in this implementation", http.StatusBadRequest) return } - // Enforce oneOf between EecId and EesId + // Validate that exactly one of EecId or EesId is provided in the request. idCount := 0 if discReq.RequestorId.EecId != "" { idCount++ @@ -1178,84 +1279,86 @@ func getEASDiscInfo(w http.ResponseWriter, r *http.Request) { if discReq.RequestorId.EesId != "" { idCount++ } - if idCount != 1 { - log.Error("Request must contain exactly one identifier (EecId or EesId)") + log.Error("getEASDiscInfo: request validation failed - exactly one identifier required", "EecId", discReq.RequestorId.EecId, "EesId", discReq.RequestorId.EesId) errHandlerProblemDetails(w, "Exactly one of eecId or eesId must be provided", http.StatusBadRequest) return } - // Existing EecId validation + // Process EecId if provided. if discReq.RequestorId.EecId != "" { key := baseKey + "app:" + discReq.RequestorId.EecId sInfoJson1, err := rc.JSONGetEntry(key, ".") if err != nil { - log.Error("Invalid EecId: ", discReq.RequestorId.EecId) - errHandlerProblemDetails(w, "Invalid EecId - not generated through serviceProvisioning", http.StatusBadRequest) + log.Error("getEASDiscInfo: invalid EecId", "EecId", discReq.RequestorId.EecId, "error", err) + errHandlerProblemDetails(w, "Invalid EecId", http.StatusBadRequest) return } if sInfoJson1 == "" { + log.Error("getEASDiscInfo: no data found for provided EecId", "EecId", discReq.RequestorId.EecId) errHandlerProblemDetails(w, "No data found for EecId", http.StatusNotFound) return } } else { - // EesId validation + // Validate the provided EesId against the dynamically generated platform configuration. ednConfig, err := getDynamicPlatformDetails(basePath) if err != nil { + log.Error("getEASDiscInfo: error retrieving dynamic platform details", "error", err) errHandlerProblemDetails(w, "Platform configuration error: "+err.Error(), http.StatusInternalServerError) return } if len(ednConfig.Eess) == 0 || ednConfig.Eess[0].EesId == "" { - log.Error("Missing EES configuration") + log.Error("getEASDiscInfo: missing EES configuration in server settings") errHandlerProblemDetails(w, "Server configuration error", http.StatusInternalServerError) return } expectedEesId := ednConfig.Eess[0].EesId if discReq.RequestorId.EesId != expectedEesId { - log.Error("EesId mismatch") + log.Error("getEASDiscInfo: provided EesId does not match expected configuration", "providedEesId", discReq.RequestorId.EesId, "expectedEesId", expectedEesId) errHandlerProblemDetails(w, "Invalid EesId", http.StatusBadRequest) return } } - // Initialize appInstanceId - var appInstanceId string - // Check if EasDiscoveryFilter and AcChars exist and are not empty + // Retrieve the appInstanceId from the first AcCharacteristics entry if present. + var appInstanceId string if discReq.EasDiscoveryFilter != nil && len(discReq.EasDiscoveryFilter.AcChars) > 0 { - // Take the first AcCharacteristics entry acChar := discReq.EasDiscoveryFilter.AcChars[0] if acChar.AcProf != nil { appInstanceId = acChar.AcProf.AcId } } - // Check if appInstanceId was successfully retrieved + // Ensure that appInstanceId is present in the request. if appInstanceId == "" { - err := errors.New("acId not found in the request") + err := errors.New("getEASDiscInfo: acId not found in the request") log.Error(err.Error()) errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest) return } + + // Construct the datastore key for retrieving app information. keyName := baseKey + "appInfo:" + appInstanceId - log.Info("appRegistrationGET: keyName: ", keyName) + log.Info("getEASDiscInfo: retrieving app information", "keyName", keyName) jsonAppInfo, err := rc.JSONGetEntry(keyName, ".") if err != nil { - err = errors.New("appInfo not found against the provided appInstanceId") - log.Error(err.Error()) - errHandlerProblemDetails(w, err.Error(), http.StatusNotFound) + errMsg := "appInfo not found for the provided appInstanceId" + log.Error("getEASDiscInfo:", errMsg, "appInstanceId", appInstanceId, "error", err) + errHandlerProblemDetails(w, errMsg, http.StatusNotFound) return } - // Unmarshal retrieved JSON into AppInfo struct + + // Unmarshal the retrieved JSON data into the AppInfo structure. var appInfo AppInfo if err := json.Unmarshal([]byte(jsonAppInfo), &appInfo); err != nil { - log.Error("Error unmarshaling appInfo:", err) + log.Error("getEASDiscInfo: error unmarshaling appInfo", "error", err) errHandlerProblemDetails(w, "Internal server error.", http.StatusInternalServerError) return } - // Map AppInfo attributes to EASProfile response format - // Map to EasProfile + + // Map the AppInfo data to the EASProfile response format. easProfile := EasProfile{ EasId: appInfo.AppName, EndPt: appInfo.Endpoint, @@ -1267,7 +1370,8 @@ func getEASDiscInfo(w http.ResponseWriter, r *http.Request) { PermLvl: appInfo.PermLvl, AcIds: []string{appInfo.AppInstanceId}, } - // Construct response + + // Build the full discovery response with the mapped EAS profile. resp := EasDiscoveryResp{ DiscoveredEas: []DiscoveredEas{ { @@ -1275,9 +1379,11 @@ func getEASDiscInfo(w http.ResponseWriter, r *http.Request) { }, }, } - // Send JSON response with status 201 Created + + // Convert the response to JSON and send it with an HTTP 200 OK status. jsonResponse := convertEasDiscoveryRespToJson(&resp) w.WriteHeader(http.StatusOK) + log.Info("getEASDiscInfo: successfully processed request", "appInstanceId", appInstanceId) fmt.Fprint(w, jsonResponse) } -- GitLab From b56b6df77d4b8c9b89581c041bbcb247b344cae7 Mon Sep 17 00:00:00 2001 From: garciay <yann.garcia@fscom.fr> Date: Mon, 10 Feb 2025 14:49:41 +0100 Subject: [PATCH 7/9] Editorial changes & fixes --- .../python/notebook/MEC application.ipynb | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/demo6/python/notebook/MEC application.ipynb b/examples/demo6/python/notebook/MEC application.ipynb index c19b9722f..6d3e3877c 100644 --- a/examples/demo6/python/notebook/MEC application.ipynb +++ b/examples/demo6/python/notebook/MEC application.ipynb @@ -5,7 +5,7 @@ "metadata": {}, "source": [ "# How to develop a MEC application using the MEC Sandbox HTTP REST API\n", - "This tutorial introduces the step by step procedure to create a basic MEC appcation following ETSI MEC standards.\n", + "This tutorial introduces the step by step procedure to create a basic MEC application following ETSI MEC standards.\n", "It uses the ETSI MEC Sandbox simulator.\n", "\n", "<div class=\"alert alert-block alert-danger\">\n", @@ -334,8 +334,8 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", - "# Uncomment the ;line above to skip execution of this cell\n", + "%%script echo skipping\n", + "# Uncomment the line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", " This is the skeleton of our MEC application:\n", @@ -441,7 +441,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -621,7 +621,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -841,7 +841,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -1233,7 +1233,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -1502,7 +1502,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -1663,7 +1663,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -1749,13 +1749,13 @@ " logger.info('do_GET: ' + ctype)\n", "\n", " message = ''\n", - " if self.path == '/statistic/v1/quantity':\n", + " if self.path == '/sandbox/v1/statistic/v1/quantity':\n", " logger.info('do_GET: Computing statistic quantities for application MEC service')\n", " # TODO Add logit to our MEC service\n", " message = '{\"time\":20180124,\"avg\": 0.0,\"max\": 0.0,\"min\": 0.0,\"stddev\": 0.0 }'\n", " else:\n", - " # Send message back to client\n", - " message = bytes(str(self.headers) + \"\\n\" +self.requestline +\"\\n\", 'utf8')\n", + " # Send error message\n", + " message = '{\"title\":\"Unknown URI\",\"type\":\"do_GET.parser\",\"status\":404 }'\n", " logger.info('do_GET: message: ' + message)\n", " \n", " # Send response status code\n", @@ -1767,7 +1767,7 @@ " self.end_headers()\n", "\n", " # Write content as utf-8 data\n", - " self.wfile.write(message)\n", + " self.wfile.write(message.encode('utf8'))\n", " return\n", " # End of function do_GET\n", "\n", @@ -1929,7 +1929,7 @@ }, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -2077,7 +2077,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -2545,7 +2545,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -2614,7 +2614,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -3485,7 +3485,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", @@ -4511,7 +4511,7 @@ " data = json.loads(result.data)\n", " logger.info('data: %s', str(data))\n", " logger.info('=============> Execute the command: curl --request GET %s/statistic/v1/quantity --header \"Accept: application/json\" --data \\'{\"time\":20180124,\"data1\":\"[1516752000,11590.6,11616.9,11590.4,11616.9,0.25202387,1516752060,11622.4,11651.7,11622.4,11644.6,1.03977764]\"}\\'', CALLBACK_URI)\n", - " time.sleep(45)\n", + " time.sleep(60)\n", "\n", " # Stop notification server\n", " stop_notification_server(httpd)\n", @@ -4667,7 +4667,7 @@ "metadata": {}, "outputs": [], "source": [ - "#%%script echo skipping\n", + "%%script echo skipping\n", "# Uncomment the ;line above to skip execution of this cell\n", "def process_main():\n", " \"\"\"\n", -- GitLab From d2a07e910d2da5f7cdab2b13d960f76f2b02c334 Mon Sep 17 00:00:00 2001 From: garciay <yann.garcia@fscom.fr> Date: Sat, 15 Mar 2025 08:06:53 +0100 Subject: [PATCH 8/9] Add CAPIF_And_ETSI_MEC_Tutorial.ipynb --- .../CAPIF_And_ETSI_MEC_Tutorial.ipynb | 2384 +++++++++++++++++ 1 file changed, 2384 insertions(+) create mode 100644 examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb diff --git a/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb b/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb new file mode 100644 index 000000000..22c03d22f --- /dev/null +++ b/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb @@ -0,0 +1,2384 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "44TomlvPCGTe" + }, + "source": [ + "# Using ETSI MEC profile of CAPIF in CAPIF application\n", + "\n", + "## Introduction\n", + "\n", + "3GPP CAPIF (Common API Framework) is a standardized API management framework designed to enable a unified northbound API approach across 3GPP network functions (see 3GPP TS 23.222 version 18.6.0 Release 18/ETSI TS 123 222 V18.6.0 (2024-06) and 3GPP TS 29.222 version 18.6.0 Release 18/ETSI TS 129 222 V18.6.0 (2022-06)).\n", + "\n", + "This tutorial introduces the step by step procedure to create a basic CAPIF application to exploit the ETSI MEC CAPIF profile as described in ETSI GS MEC 011 (V3.2.1) Clause 9.\n", + "It uses the ETSI MEC Sandbox simulator.\n", + "\n", + "<div class=\"alert alert-block alert-danger\">\n", + " <b>Note:</b> These source code examples are simplified and ignore return codes and error checks to a large extent. We do this to highlight how to use the MEC Sandbox API and the different MEC satndards and reduce unrelated code.\n", + "A real-world application will of course properly check every return value and exit correctly at the first serious error.\n", + "</div>\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4DpxwmiomELg" + }, + "source": [ + "## The basics of developing a MEC application\n", + "\n", + "\n", + "<div class=\"alert alert-warning\" role=\"alert\">\n", + " <b>Note:</b> The sub-paragraph 'Putting everything together' is a specific paragraph where all the newly features introduced in the main paragraph are put together to create an executable block of code. It is possible to skip this block of code by removing the comment character (#) on first line of this block of code.\n", + "</div>\n", + "\n", + "Before going to create our CAPIF application skeleton, the following steps shall be done:\n", + "\n", + "1) Apply the python imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "1gjo-NM6hD1k" + }, + "outputs": [], + "source": [ + "from __future__ import division # Import floating-point division (1/4=0.25) instead of Euclidian division (1/4=0)\n", + "\n", + "import os\n", + "import sys\n", + "import re\n", + "import logging\n", + "import threading\n", + "import time\n", + "import json\n", + "import uuid\n", + "import base64\n", + "\n", + "import pprint\n", + "\n", + "import requests\n", + "\n", + "from http import HTTPStatus\n", + "from http.server import BaseHTTPRequestHandler, HTTPServer\n", + "\n", + "try:\n", + " import urllib3\n", + "except ImportError:\n", + " raise ImportError('Swagger python client requires urllib3.')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "j9wDIe9IEUQz" + }, + "source": [ + "The following imports are required to support the security aspects such as certificates management, signatures..." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "xb4ReBZZEVLB" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pyOpenSSL==25.0.0 in /opt/conda/lib/python3.11/site-packages (25.0.0)\n", + "Requirement already satisfied: cryptography<45,>=41.0.5 in /opt/conda/lib/python3.11/site-packages (from pyOpenSSL==25.0.0) (43.0.1)\n", + "Requirement already satisfied: typing-extensions>=4.9 in /opt/conda/lib/python3.11/site-packages (from pyOpenSSL==25.0.0) (4.12.2)\n", + "Requirement already satisfied: cffi>=1.12 in /opt/conda/lib/python3.11/site-packages (from cryptography<45,>=41.0.5->pyOpenSSL==25.0.0) (1.17.1)\n", + "Requirement already satisfied: pycparser in /opt/conda/lib/python3.11/site-packages (from cffi>=1.12->cryptography<45,>=41.0.5->pyOpenSSL==25.0.0) (2.22)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_3714172/2658395025.py:5: DeprecationWarning: CSR support in pyOpenSSL is deprecated. You should use the APIs in cryptography.\n", + " from OpenSSL.crypto import (dump_certificate_request, dump_privatekey, load_publickey, PKey, TYPE_RSA, X509Req, dump_publickey)\n" + ] + } + ], + "source": [ + "!pip3 install pyOpenSSL==25.0.0\n", + "\n", + "try:\n", + " from OpenSSL.SSL import FILETYPE_PEM\n", + " from OpenSSL.crypto import (dump_certificate_request, dump_privatekey, load_publickey, PKey, TYPE_RSA, X509Req, dump_publickey)\n", + "except ImportError:\n", + " raise ImportError('OpenSSL package not found.')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DrPJzD14nLas" + }, + "source": [ + "2) Initialize of the global constants (cell 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "id": "rNibZWiBitPE" + }, + "outputs": [], + "source": [ + "REGISTER_HOSTNAME = 'lab-oai.etsi.org' # capif-prev.mobilesandbox.cloud\n", + "REGISTER_PORT = 31120 # 31120\n", + "REGISTER_USER = 'admin' # Basic AUTH for registration\n", + "REGISTER_PASSWORD = 'password123' # Basic AUTH for registration\n", + "\n", + "CAPIF_HOSTNAME = 'lab-oai.etsi.org' # capif-prev.mobilesandbox.cloud\n", + "CAPIF_PORT = 443\n", + "\n", + "USER_PASSWORD = 'password123'\n", + "\n", + "TRY_MEC_URL = 'try-mec.etsi.org' # MEC Sandbox URL\n", + "TRY_MEC_SESSION_ID = 'sbxx3i4jhr' # MEC Sandbox identifier\n", + "TRY_MEC_PLTF = 'mep1' # MEC Platform identifier (depending of the network scenario loaded)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MOa9g-NMnpod" + }, + "source": [ + "3) Setup the logger instance and the HTTP REST API (cell 4)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "-cuxWhfantSw" + }, + "outputs": [], + "source": [ + "# Initialize the logger\n", + "logger = logging.getLogger(__name__)\n", + "logger.setLevel(logging.DEBUG)\n", + "logging.basicConfig(filename='/tmp/' + time.strftime('%Y%m%d-%H%M%S') + '.log')\n", + "l = logging.StreamHandler()\n", + "l.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))\n", + "logger.addHandler(l)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "D67Aq0vujB0q" + }, + "source": [ + "4) Setup the global variables (cell 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "7RC7UY-0oACq" + }, + "outputs": [], + "source": [ + "# Initialize the global variables\n", + "ca_root = \"\" # The CAPIF root certificate\n", + "ccf_api_onboarding_url = \"\" #\n", + "ccf_publish_url = \"\" # The CAPIF publish API endpoint\n", + "ccf_discover_url = \"\" # The CAPIF discovery endpoint\n", + "ccf_security_url = \"\" # The CAPIF security endpoint\n", + "ccf_onboarding_url = \"\" # The CAPIF onboarding endpoint\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "2YvSVMClhPJT" + }, + "source": [ + "To enable the Automatic Debugger Calling, uncomment the code bellow." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "OQjYWHgnYM4G" + }, + "outputs": [], + "source": [ + "#!pip3 install ipdb\n", + "#import ipdb\n", + "#%pdb on\n", + "# Use the command ipdb.set_trace() to set a breakpoint" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1fMmXWk9jLDX" + }, + "source": [ + "## Create our first CAPIF application\n", + "\n", + "The first step to develop a MEC application is to create the application skeleton which contains the minimum steps below:\n", + "\n", + "- Login to instanciate a MEC Sandbox\n", + "- Logout to delete a existing MEC Sandbox" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rtAVXZayoQRx" + }, + "source": [ + "#### Login\n", + "\n", + "The login operation is required by ETSI TS 123 222 V18.6.0 (2024-06) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.6.0 (2022-06)." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "Ad8g1no-pH7i" + }, + "outputs": [], + "source": [ + "def process_login() -> tuple:\n", + " \"\"\"\n", + " Logs in to the CAPIF server.\n", + " :return A dictionary containing the login response, or None if login fails\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> process_login')\n", + "\n", + " try:\n", + " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/login'\n", + " logger.debug('process_login: url=' + url)\n", + " auth_string = f\"{REGISTER_USER}:{REGISTER_PASSWORD}\"\n", + " encoded_auth = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8')\n", + " headers = {'Content-Type': 'application/json', 'Authorization': f'Basic {encoded_auth}'}\n", + " logger.debug('process_login (step1): headers: ' + str(headers))\n", + " response = requests.post(url, headers=headers, verify=False)\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('process_login (step2): result: ' + str(response.json()))\n", + " if response.status_code != 200:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return None, None\n", + " tokens = json.loads(response.text)\n", + " return tokens['refresh_token'], tokens['access_token']\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"Login failed: {e}\")\n", + "\n", + " return None, None\n", + " # End of function process_login" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8Cw5MBc-st1e" + }, + "source": [ + "### Logout\n", + "\n", + "The logout operation is required by ETSI TS 123 222 V18.6.0 (2024-06) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.6.0 (2022-06)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "id": "XmyLOuFasuvU" + }, + "outputs": [], + "source": [ + "def process_logout():\n", + " \"\"\"\n", + " Logs out from the CAPIF server\n", + " Nothing to do\n", + " \"\"\"\n", + " pass\n", + " # End of function process_logout" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mCKT-ntspnsM" + }, + "source": [ + "### Putting everything together\n", + "Now, it is time now to create the our first iteration of our CAPIF/MEC application. Here the logic is:\n", + "* Login\n", + "* Print obtained tokens\n", + "* Logout\n", + "* Check that logout is effective" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "id": "XYC8PnDUpvui" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "skipping\n" + ] + } + ], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the first sprint of our CAPIF application:\n", + " - Login\n", + " - Print obtained tokens\n", + " - Logout\n", + " - Check that logout is effective\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " return\n", + "\n", + " # Print obtained tokens\n", + " logger.debug(\"Login successful: admin_token=\" + admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " # Check that logout is effective\n", + " logger.debug('To check that logout is effective')\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rTcvGY5T1pZJ" + }, + "source": [ + "## Create the API Provider\n", + "\n", + "The next step is to create a new user associated to our CAPIF application to obtain a user UUID. It will be used to genereate ceertificates to be used during TLS mutual authentication and API onboarding and offboarding for instance.\n", + "\n", + "**Note:** It is required by ETSI TS 123 222 V18.6.0 (2024-06) Clause 4.5 Operations, Administration and Maintenance but is out of the scope of ETSI TS 129 222 V18.6.0 (2022-06).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ysxZ8sIiLLgw" + }, + "source": [ + "### Creating a new user\n", + "\n", + "The cell below provides an implementation for this user creation.\n", + "\n", + "**Note:** To improve this code, the user profile shlould be fully parametrized." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "id": "Jq-9_sLI8WgW" + }, + "outputs": [], + "source": [ + "def create_user(p_admin_token: str) -> tuple:\n", + " \"\"\"\n", + " Creates a new user.\n", + " :return: The user UUID on success, None otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> create_user')\n", + "\n", + " try:\n", + " user_name = str(uuid.uuid1())\n", + " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/createUser'\n", + " logger.debug('create_user: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_admin_token}\n", + " logger.debug('create_user (step1): headers: ' + str(headers))\n", + " data = {\n", + " 'username': user_name,\n", + " 'password': USER_PASSWORD,\n", + " 'enterprise': 'ETSI',\n", + " 'country': 'France',\n", + " 'email': 'ocf@etsi.org',\n", + " 'purpose': 'Tutorial on MEC/OpenCAPIF',\n", + " 'phone_number': \"+330405060708\",\n", + " 'company_web': 'www.etsi.org',\n", + " 'description': 'A step by step procedure to create a basic CAPIF application to exploit the ETSI MEC CAPIF profile'\n", + " }\n", + " response = requests.post(url, headers=headers, data=json.dumps(data), verify=False)\n", + " logger.debug('create_user (step2): response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " if response.status_code != 201:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return ()\n", + " tokens = json.loads(response.text)\n", + " return (user_name, tokens['uuid'])\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"Error creating user: {e}\")\n", + "\n", + " return ()\n", + " # End of function create_user" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ut3CLrRUFT5o" + }, + "source": [ + "### Deleting an existing User\n", + "\n", + "Before to terminate our CAPIF application, we have to clean up the resources. So, a function to delete a created user is required." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "id": "WRIdwNMNFrdC" + }, + "outputs": [], + "source": [ + "def delete_user(p_user_uuid: str, p_admin_token: str) -> int:\n", + " \"\"\"\n", + " Deletes a user.\n", + " :param p_user_uuid: The user UUID\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> delete_user')\n", + "\n", + " try:\n", + " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/deleteUser/' + p_user_uuid\n", + " logger.debug('delete_user: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_admin_token}\n", + " response = requests.delete(url, headers=headers, verify=False)\n", + " logger.debug('delete_user: response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " return 0\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"Error creating user: {e}\")\n", + "\n", + " return -1\n", + " # End of function delete_user" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IAh9tN25-82V" + }, + "source": [ + "### Putting everything together\n", + "It is time now to create the our second iteration of our CAPIF/MEC application.\n", + "\n", + "The sequence is the following:\n", + "* Login\n", + "* Print obtained tokens\n", + "* Create a new user\n", + "* Print the user UUID\n", + "* Delete the newly created user\n", + "* Logout\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "id": "1M_x2I1B_Crp" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "skipping\n" + ] + } + ], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the second sprint of our CAPIF/MEC application:\n", + " - Login\n", + " - Print obtained tokens\n", + " - Create a new user\n", + " - Print the user UUID\n", + " - Delete the newly created user\n", + " - Logout\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " return\n", + "\n", + " # Print obtained tokens\n", + " logger.debug(\"Login successful: admin_token=\" + admin_token)\n", + "\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + "\n", + " # Print User UUID\n", + " logger.debug(\"User successfully created: user_uuid=\" + user_uuid)\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Delete the newly created user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f896qBJOjMuz" + }, + "source": [ + "## Getting security materials\n", + "\n", + "The purpose is to retrieves peer certificates for the TLS mutual authentication purpose and the JWT token to onboarding and offboarding APIs.\n", + "The following information is retrived:\n", + "- The root certificate\n", + "- An access token which will be used for onboarding and offboarding APIs\n", + "- The URLs for the different CAPIF endpoints:\n", + " * API onbording endpoint\n", + " * API discovery endpoint\n", + " * API publish endpoint\n", + " * Security endpoint\n", + "\n", + "This operation needs the user name and the user password used in previous [chapter](#create_the_invoker_/_provider).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lC2JAah7LWLp" + }, + "source": [ + "### Getting certificates" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "id": "1glmqNSRK1cH" + }, + "outputs": [], + "source": [ + "def get_auth(p_user_name: str, p_user_password: str) -> dict:\n", + " \"\"\"\n", + " Gets the authentication information.\n", + " :param The user name\n", + " :param The user password\n", + " :return A dictionary containing the authentication information on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> get_auth')\n", + "\n", + " try:\n", + " url = 'https://' + REGISTER_HOSTNAME + ':' + str(REGISTER_PORT) + '/getauth'\n", + " logger.debug('get_auth: url=' + url)\n", + " auth_string = f\"{p_user_name}:{p_user_password}\"\n", + " encoded_auth = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8')\n", + " headers = {'Content-Type': 'application/json', 'Authorization': f'Basic {encoded_auth}'}\n", + " logger.debug('get_auth (step1): headers: ' + str(headers))\n", + " response = requests.get(url, headers=headers, verify=False)\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('get_auth (step2): result: ' + str(response.json()))\n", + " if response.status_code != 200:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " auth = json.loads(response.text)\n", + " return auth\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"get_auth failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function get_auth" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BUw-VS1WLb7i" + }, + "source": [ + "### Putting everything together\n", + "\n", + "Now, it is time now to create the our third iteration of our MEC application. Here the logic is:\n", + "\n", + "- Login\n", + "- Create the user\n", + "- Get the information to use CAPIF (security materials & URLs)\n", + "- Print the information to use CAPI\n", + "- Delete the user\n", + "- Logout\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "id": "J002Vuz2OIKl" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "skipping\n" + ] + } + ], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the first sprint of our skeleton of our CAPIF application:\n", + " - Login\n", + " - Print obtained tokens\n", + " - Create a new user\n", + " - Get the information to use CAPIF (security materials & URLs)\n", + " - Print the information to use CAPI\n", + " - Delete the newly created user\n", + " - Logout\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_publish_url, ccf_discover_url, ccf_security_url, ccf_onboarding_url\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " return\n", + "\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + "\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " return\n", + "\n", + " # Print the authentication information\n", + " logger.debug(\"Authentication information=\" + str(auth))\n", + " access_token = auth['access_token']\n", + " ca_root = auth['ca_root']\n", + " ccf_api_onboarding_url = auth['ccf_api_onboarding_url']\n", + " ccf_discover_url = auth['ccf_discover_url']\n", + " ccf_onboarding_url = auth['ccf_onboarding_url']\n", + " ccf_publish_url = auth['ccf_publish_url']\n", + " ccf_security_url = auth['ccf_security_url']\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Delete the newly created user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oNhnnDhjjOd7" + }, + "source": [ + "## Onboarding and offboarding APIs\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "K6i4ktfM1xFQ" + }, + "source": [ + "### Generate certificates\n", + "\n", + "Until now, all HTTPS exchanges were done with the the 'verify' attribute of the HTTP reques set to False. It means that the TLS mutual authentication was disabled.\n", + "\n", + "Fo the process of onboarding and offboarding APIs, the TLS mutual authentication is required. We already got the peer certificate to verify peer but we need to generate our own certificate to be verified by the CAPIF server. This is the purpose of the following functions to generate the public/private keys and generate a CSR and request certificates for each of the three functions AMF, AEF and APF.\n", + "\n", + "**Refer to:** ETSI TS 129 222 V18.6.0 (2022-06) Clauses 5.11 CAPIF_API_Provider_Management and 8.9 CAPIF_API_Provider_Management_API\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "id": "gEIS3iAH2D4t" + }, + "outputs": [], + "source": [ + "def generate_csr(p_cn: str, p_org: str, p_country: str) -> tuple:\n", + " \"\"\"\n", + " To generate the CSR and generate the dumps\n", + " :param p_cn: The common name\n", + " :param p_org: The organization\n", + " :param p_country: The country\n", + " :return: The CSR and the private keys on success, None otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> generate_csr')\n", + "\n", + " # Generate the public/private key\n", + " key = PKey()\n", + " key.generate_key(TYPE_RSA, 2048)\n", + "\n", + " # Generate the CSR\n", + " req = X509Req()\n", + " req.get_subject().CN = p_cn\n", + " req.get_subject().O = p_org\n", + " req.get_subject().C = p_country\n", + " req.set_pubkey(key)\n", + " req.sign(key, 'sha256')\n", + "\n", + " # Generate the dumps\n", + " csr_request = dump_certificate_request(FILETYPE_PEM, req)\n", + " private_key = dump_privatekey(FILETYPE_PEM, key)\n", + " logger.debug('generate_csr: PrivKey: ' + str(private_key))\n", + "\n", + " return (csr_request, private_key)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "F2-W0a5S3snI" + }, + "source": [ + "**Note:** The function above can be improved using parameter for the SHA, and the signature/encryption algorithm." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1HyqrdUz-uzn" + }, + "source": [ + "### Onboard the API provider\n", + "\n", + "The purpose here is to get certificates from CAPIF in order to export our APIs for the different functions:\n", + "- AMF: API Management Function\n", + "- AEF: API Exposing Function\n", + "- APF: API Publishing Function" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "id": "6cCn1vKLGe0k" + }, + "outputs": [], + "source": [ + "def onboard_provider(p_name: str, p_access_token: str) -> dict:\n", + " \"\"\"\n", + " To onboard the provider.\n", + " :param p_name: The name of the provider\n", + " :return: A dictionary containing security material for each CAPIF endpoint on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, ccf_api_onboarding_url, access_token\n", + "\n", + " logger.debug('>>> onboard_provider')\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_api_onboarding_url\n", + " logger.debug('onboard_provider: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_access_token}\n", + " logger.debug('onboard_provider (step1): headers: ' + str(headers))\n", + " # Build the list of certificate request for the three endpoints\n", + " l = []\n", + " amf_csr_request, amf_private_key = generate_csr(\"AMF\", \"ETSI\", \"Fr\")\n", + " amf_entry = {\n", + " 'regInfo': {\n", + " 'apiProvPubKey': amf_csr_request.decode(\"utf-8\")\n", + " },\n", + " 'apiProvFuncRole': 'AMF'\n", + " }\n", + " l.append(amf_entry)\n", + " aef_csr_request, aef_private_key = generate_csr(\"AEF\", \"ETSI\", \"Fr\")\n", + " aef_entry = {\n", + " 'regInfo': {\n", + " 'apiProvPubKey': aef_csr_request.decode(\"utf-8\")\n", + " },\n", + " 'apiProvFuncRole': 'AEF'\n", + " }\n", + " l.append(aef_entry)\n", + " apf_csr_request, apf_private_key = generate_csr(\"APF\", \"ETSI\", \"Fr\")\n", + " apf_entry = {\n", + " 'regInfo': {\n", + " 'apiProvPubKey': apf_csr_request.decode(\"utf-8\")\n", + " },\n", + " 'apiProvFuncRole': 'APF'\n", + " }\n", + " l.append(apf_entry)\n", + " # Build the request body\n", + " data = {\n", + " 'apiProvFuncs': l,\n", + " 'apiProvDomInfo': p_name,\n", + " 'suppFeat': 'fff',\n", + " 'failReason': 'string',\n", + " 'regSec': p_access_token\n", + " }\n", + " logger.debug('onboard_provider (step2): body: ' + str(data))\n", + " response = requests.post(url, headers=headers, data=json.dumps(data), verify=False)\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('onboard_provider (step3): result: ' + str(response.json()))\n", + " if response.status_code != 201:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " res = json.loads(response.text)\n", + " # Add an entry for CSRs and private keys for future usage\n", + " res['csr'] = {\n", + " 'amf': [amf_csr_request, amf_private_key],\n", + " 'aef': [aef_csr_request, aef_private_key],\n", + " 'apf': [apf_csr_request, apf_private_key]\n", + " }\n", + " return res\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"onboard_provider failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function onboard_provider" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yP6ZytijFxKG" + }, + "source": [ + "### Offboard the API provider\n", + "\n", + "The purpose is to offboard the API provider from the CAPIF server. Here, the certificate and the private key of the AMF endpoint are required (TLS mutual authentication)." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "id": "rbpNr26tF2gr" + }, + "outputs": [], + "source": [ + "def offboard_provider(p_api_provider_id: str, p_bundle: tuple) -> list:\n", + " \"\"\"\n", + " To offboard the API provider.\n", + " :param p_api_provider_id: The identifier of the API provider\n", + " :param p_bundle: The bundle of certificates and keys for the TLS mutual authentication operations\n", + " :return: A list containing the files created for the TLS mutual authentication operations on success, or an empty list otherwise\n", + " \"\"\"\n", + " global logger, ccf_api_onboarding_url, ca_root\n", + "\n", + " logger.debug('>>> offboard_provider')\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_api_onboarding_url + '/' + p_api_provider_id\n", + " logger.debug('offboard_provider: url=' + url)\n", + " headers = {'Content-Type': 'application/json'}\n", + " logger.debug('offboard_provider (step1): headers: ' + str(headers))\n", + " bundle = store_certificate_2_files(p_bundle[0], p_bundle[1], ca_root) # Use CA certificate for verif\n", + " if len(bundle) != 3:\n", + " logger.error(f\"Error converting in-memory bundle into files\")\n", + " return []\n", + " logger.debug('offboard_provider (step2): bundle: ' + str(bundle))\n", + " response = requests.delete(url, headers=headers, cert=(bundle[0], bundle[1]), verify=bundle[2])\n", + " logger.debug('offboard_provider (step3): response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " if response.status_code != 204:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return []\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"offboard_provider failed: {e}\")\n", + " return []\n", + "\n", + " return bundle\n", + " # End of function offboard_provider\n", + "\n", + "def store_certificate_2_files(p_certificate, p_private_key, p_ca_root) -> list:\n", + " \"\"\"\n", + " Save certificate and key into files\n", + " :param p_certificate:\n", + " :param p_private_key:\n", + " :param p_ca_root:\n", + " :return: A list of file paths on success, an empty list otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> store_certificate_2_files')\n", + " try:\n", + " with open(\"p_crt.crt\", \"w\") as f:\n", + " f.write(p_certificate)\n", + " with open(\"p_key.key\", \"w\") as f:\n", + " f.write(p_private_key.decode('utf-8'))\n", + " with open(\"ca_root.pem\", \"w\") as f:\n", + " f.write(p_ca_root)\n", + " return [\"p_crt.crt\", \"p_key.key\", \"ca_root.pem\"]\n", + " except Exception as e:\n", + " logger.error(f\"An error occurred: {e}\")\n", + "\n", + " return []\n", + " # End of function store_certificate_2_files\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wmvJSK8I13XD" + }, + "source": [ + "### Putting everything together\n", + "\n", + "Now, it is time now to create the our third iteration of our CAPIF/MEC application. Here the logic is:\n", + "\n", + "- Login\n", + "- Create the user\n", + "- Get the information to use CAPIF (security materials & URLs)\n", + "- Onboard the provider\n", + "- Print certificates for each function\n", + "- Delete the user\n", + "- Logout\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "id": "EDcPUuNEM26H" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "skipping\n" + ] + } + ], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the third sprint of our CAPIF/MEC application:\n", + " - Login\n", + " - Print obtained tokens\n", + " - Create a new user\n", + " - Get the information to use CAPIF (security materials & URLs)\n", + " - Onboard the provider\n", + " - Print certificates for each function\n", + " - Delete the newly created user\n", + " - Logout\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_publish_url, ccf_discover_url, ccf_security_url, ccf_onboarding_url\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " return\n", + "\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return\n", + "\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " return\n", + "\n", + " # Set the CAPIF access information\n", + " access_token = auth['access_token']\n", + " ca_root = auth['ca_root']\n", + " ccf_api_onboarding_url = auth['ccf_api_onboarding_url']\n", + " ccf_discover_url = auth['ccf_discover_url']\n", + " ccf_onboarding_url = auth['ccf_onboarding_url']\n", + " ccf_publish_url = auth['ccf_publish_url']\n", + " ccf_security_url = auth['ccf_security_url']\n", + " logger.debug(\"ccf_api_onboarding_url:\" + ccf_api_onboarding_url)\n", + " logger.debug(\"ccf_discover_url:\" + ccf_discover_url)\n", + " logger.debug(\"ccf_publish_url:\" + ccf_publish_url)\n", + " logger.debug(\"ccf_security_url:\" + ccf_security_url)\n", + "\n", + " # Onboard the provider\n", + " prov = onboard_provider(\"MECSandbox_to_CAPIF_Provider\", access_token)\n", + " if len(prov) == 0:\n", + " return\n", + "\n", + " # Print certificates for each function\n", + " logger.debug(\"API Provider Id:\" + prov['apiProvDomId'])\n", + " logger.debug(\"AMF: \" + prov['apiProvFuncs'][0]['regInfo']['apiProvCert'])\n", + " logger.debug(\"AEF: \" + prov['apiProvFuncs'][1]['regInfo']['apiProvCert'])\n", + " logger.debug(\"APF: \" + prov['apiProvFuncs'][2]['regInfo']['apiProvCert'])\n", + " logger.debug(\"csr: \" + str(prov['csr']))\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Offboard the API profider\n", + " certs_bundle = (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]) # Use AMF certificate and AMF private key\n", + " file_bundle = offboard_provider(prov['apiProvDomId'], certs_bundle)\n", + " if len(file_bundle) == 0:\n", + " for file in file_bundle:\n", + " os.remove(file)\n", + "\n", + " # Delete the newly created user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0wHI1ooMbCy3" + }, + "source": [ + "## Using ETSI MEC profile for CAPIF\n", + "\n", + "The purpose is to export the MEC Profile for CAPIF API into our CAPIF application. To achieve it, we need to fulfill the following requirements:\n", + "1. Create an instance of a MEC Sandbox using the '4g-5g-macri-v2x' network scenario\n", + "2. Set TRY_MEC_URL, TRY_MEC_SESSION_ID, TRY_MEC_PLTF constants accordingly\n", + "3. Build the ServiceAPIDescription as described in ETSI TS 129 222 V18.6.0 (2022-06) Table 8.2.4.2.2-1: Definition of type ServiceAPIDescription. This is the role of the function below" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "id": "S7InJDD1_g-v" + }, + "outputs": [], + "source": [ + "def build_publish_api_from_mec_services(p_aefId: str) -> dict:\n", + " \"\"\"\n", + " This function builds the Publish API request body data structure which will be used todo the request for publish API\n", + " :param p_aefId: The AEF ID\n", + " :return The request body data structure on success, an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, TRY_MEC_URL, TRY_MEC_SESSION_ID, TRY_MEC_PLTF\n", + "\n", + " logger.debug('>>> build_publish_api_from_mec_services: p_aefId=' + p_aefId)\n", + "\n", + " # Sanity checks\n", + " if len(p_aefId) == 0:\n", + " logger.error('build_publish_api_from_mec_services: p_aefId is empty')\n", + " return dict()\n", + "\n", + " # Build the service-apis data structure\n", + " publish_api_req_body = {\n", + " \"apiName\": \"MEC Profile for CAPIF\",\n", + " \"aefProfiles\": [\n", + " {\n", + " \"aefId\": p_aefId,\n", + " \"versions\": [\n", + " {\n", + " \"apiVersion\": \"v1\",\n", + " \"expiry\": \"2025-11-30T10:32:02.004Z\",\n", + " \"resources\": [\n", + " {\n", + " \"resourceName\": \"MEC Profile of CAPIF\",\n", + " \"commType\": \"REQUEST_RESPONSE\",\n", + " \"uri\": f\"/{TRY_MEC_SESSION_ID}/{TRY_MEC_PLTF}/service-apis/v1/allServiceAPIs\",\n", + " \"custOpName\": \"string\",\n", + " \"operations\": [\n", + " \"GET\"\n", + " ],\n", + " \"description\": \"Endpoint to access MEC services\"\n", + " }\n", + " ],\n", + " \"custOperations\": [\n", + " {\n", + " \"commType\": \"REQUEST_RESPONSE\",\n", + " \"custOpName\": \"string\",\n", + " \"operations\": [\n", + " \"GET\"\n", + " ],\n", + " \"description\": \"string\"\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"protocol\": \"HTTP_1_1\",\n", + " \"dataFormat\": \"JSON\",\n", + " \"securityMethods\": [\"OAUTH\"],\n", + " \"interfaceDescriptions\": [\n", + " {\n", + " \"ipv4Addr\": TRY_MEC_URL,\n", + " \"securityMethods\": [\"OAUTH\"]\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"description\": \"MEC Profile of CAPIF\",\n", + " \"supportedFeatures\": \"fffff\",\n", + " \"shareableInfo\": {\n", + " \"isShareable\": True,\n", + " \"capifProvDoms\": [\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"serviceAPICategory\": \"string\",\n", + " \"apiSuppFeats\": \"fffff\",\n", + " \"pubApiPath\": {\n", + " \"ccfIds\": [\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"ccfId\": \"string\",\n", + " \"apiStatus\":{\n", + " \"aefIds\": [\n", + " p_aefId\n", + " ]\n", + " }\n", + " }\n", + "\n", + " logger.debug('<<< build_publish_api_from_mec_services: ' + str(publish_api_req_body))\n", + " return publish_api_req_body\n", + " # End of build_publish_api_from_mec_services function" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PRAie110_r8P" + }, + "source": [ + "Having built the ServiceAPIDescription data structure, the next step is to implement the CAPIF publish API.\n", + "\n", + "To proceed, we need to enable the TLS mutual authentication using the security material obtained during the onboarding APIs operation ([Onboarding APIs](#onboarding_apis)), i.e. the AEF certificate and the AEF private key ([Generate certificates](#Generate_certificates)).\n", + "\n", + "\n", + "**Refer to:** ETSI TS 129 222 V18.6.0 (2022-06) Clauses 5.3 CAPIF_Publish_Service_API and 8.2 CAPIF_Publish_Service_API\n", + "\n", + "Before to proceed with the steps above, let's create 2 helper functions to simpily the implemantation of the CAPIF publish API. These helper functions cover the following operations:\n", + "- Onboarding operations\n", + "- Offboarding operations" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IreHiSXs2U65" + }, + "source": [ + "#### Onboarding operations\n", + "\n", + "The Onboarding operations include th following steps:\n", + "- login\n", + "- create a new user\n", + "- Get the information to use CAPIF (security materials & URLs)\n", + "- onboard the provider\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "id": "nu-tEA6n2TpI" + }, + "outputs": [], + "source": [ + "def onboarding_provider() -> dict:\n", + " \"\"\"\n", + " To onboard the provider using CAPIF endpoint. It includes:\n", + " - login\n", + " - create a new user\n", + " - Get the information to use CAPIF (security materials & URLs)\n", + " - onboard the provider\n", + " :return: A dictionary containing security material and additional context information on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_publish_url, ccf_discover_url, ccf_security_url, ccf_onboarding_url\n", + "\n", + " # Login\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " return dict()\n", + "\n", + " # Create a new user\n", + " user_name, user_uuid = create_user(admin_token)\n", + " if len(user_uuid) == 0:\n", + " return dict()\n", + "\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " return dict()\n", + "\n", + " # Set the CAPIF access information\n", + " access_token = auth['access_token']\n", + " ca_root = auth['ca_root']\n", + " ccf_api_onboarding_url = auth['ccf_api_onboarding_url']\n", + " ccf_discover_url = auth['ccf_discover_url']\n", + " ccf_onboarding_url = auth['ccf_onboarding_url']\n", + " ccf_publish_url = auth['ccf_publish_url']\n", + " ccf_security_url = auth['ccf_security_url']\n", + "\n", + " # Onboard the provider\n", + " prov = onboard_provider(\"MECSandbox_to_CAPIF_Provider\", access_token)\n", + " if len(prov) == 0:\n", + " return dict()\n", + "\n", + " # Add context data\n", + " prov['refresh_token'] = refresh_token\n", + " prov['admin_token'] = admin_token\n", + " prov['user_uuid'] = user_uuid\n", + " prov['access_token'] = access_token\n", + "\n", + " return prov\n", + " # End of onboarding_provider function" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e940bUcf2deu" + }, + "source": [ + "#### Offboarding operations\n", + "\n", + "The Offboarding operations include th following steps:\n", + "- Offboard the API provide\n", + "- Delete the user\n", + "- Logout\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "id": "hEnFLfPI2hms" + }, + "outputs": [], + "source": [ + "def offboarding_provider(p_user_uuid: str, p_api_provider_id: str, p_bundle: tuple, p_admin_token: str) -> int:\n", + " \"\"\"\n", + " To offboard the provider. It includes:\n", + " - Offboard the API provider\n", + " - Delete the user\n", + " - Logout\n", + " :return: 0 on success, or -1 otherwise\n", + " \"\"\"\n", + " global logger, ccf_api_onboarding_url, access_token\n", + "\n", + " logger.debug('>>> offboarding_provider: ' + p_user_uuid)\n", + "\n", + " # Offboard the API profider\n", + " file_bundle = offboard_provider(p_api_provider_id, p_bundle)\n", + " if len(file_bundle) == 0: # Remove cert files if any\n", + " for file in file_bundle:\n", + " os.remove(file)\n", + "\n", + " # Delete the newly created user\n", + " delete_user(p_user_uuid, p_admin_token)\n", + "\n", + " # Logout\n", + " process_logout()\n", + "\n", + " return 0\n", + " # End of offboarding_provider function" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9TSYztWMcaOA" + }, + "source": [ + "#### Publish CAPIF API function\n", + "\n", + "As mentionned above , the prupose of this function is to publish an API, using the TLS mutual authentication. To do so, we need the APF certificate (public keys) and its private key for the signature and the encription processes, and the CA certificate for the verification of the peer certifcate.\n", + "\n", + "**Note**: The http.request function required taht the cerficates and the keys are stored on files, not in memory. So, when our CAPIF applicate terminates, these files shall be removed (freeing resource step). This is the reason the publish_capif_api() function return a tuple containing the files created for the TLS mutual authentication operation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "id": "z_Cwazjl_xGJ" + }, + "outputs": [], + "source": [ + "def publish_capif_api(p_apfId: str, p_body: dict, p_bundle: tuple) -> list:\n", + " \"\"\"\n", + " This function is to publish an API on CAPIF server\n", + " :param p_apfId: The APF identifier\n", + " :param p_body: The request body\n", + " :param p_bundle: The bundle of certificates and keys for the TLS mutual authentication operations\n", + " :return: A list containing the files created for the TLS mutual authentication operations on success, or an empty list otherwise\n", + " \"\"\"\n", + " global logger, ccf_publish_url, ca_root\n", + "\n", + " logger.debug('>>> publish_capif_api')\n", + "\n", + " # Sanity checks\n", + " if len(p_bundle) != 2:\n", + " logger.error('publish_capif_api: p_bundle is malformed')\n", + " return []\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_publish_url.replace('<apfId>', p_apfId)\n", + " logger.debug('publish_capif_api: url=' + url)\n", + " headers = {'Content-Type': 'application/json'}\n", + " logger.debug('publish_capif_api (step1): headers: ' + str(headers))\n", + " logger.debug('publish_capif_api (step2): body: ' + str(p_body))\n", + " bundle = store_certificate_2_files(p_bundle[0], p_bundle[1], ca_root) # Use CA certificate for verif\n", + " if len(bundle) != 3:\n", + " logger.error(f\"Error converting in-memory bundle into files\")\n", + " return []\n", + " logger.debug('publish_capif_api (step3): bundle: ' + str(bundle))\n", + " response = requests.post(url, headers=headers, data=json.dumps(p_body), cert=(bundle[0], bundle[1]), verify=bundle[2])\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('publish_capif_api (step4): result: ' + str(response.json()))\n", + " if response.status_code != 201:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return []\n", + " res = json.loads(response.text)\n", + " logger.debug('publish_capif_api (step5): res: ' + str(res))\n", + " api_id = res['apiId']\n", + " return bundle\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"publish_capif_api failed: {e}\")\n", + "\n", + " return []\n", + " # End of function publish_capif_api\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-TzvBVLM1fIc" + }, + "source": [ + "### Putting everything together\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "id": "CRtfJ6cm3V6b" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "skipping\n" + ] + } + ], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the fourth sprint of our CAPIF/MEC application:\n", + " - Onboarding operations\n", + " - Offboarding operations\n", + " \"\"\"\n", + " global logger, ca_root\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " prov = onboarding_provider()\n", + " if len(prov) == 0:\n", + " return\n", + " user_uuid = prov['user_uuid']\n", + "\n", + " # Build the publish_api body request\n", + " aefId = prov['apiProvFuncs'][1]['apiProvFuncId']\n", + " apfId = prov['apiProvFuncs'][2]['apiProvFuncId']\n", + " publish_api_req_body = build_publish_api_from_mec_services(aefId)\n", + " if len(publish_api_req_body) == 0:\n", + " offboarding_provider(user_uuid, prov['apiProvDomId'], (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]), prov['admin_token']) # Use AMF certificate and AMF private key\n", + " return\n", + " logger.debug(\"publish_api_req_body: \" + str(publish_api_req_body))\n", + " certs_bundle = (prov['apiProvFuncs'][2]['regInfo']['apiProvCert'], prov['csr']['apf'][1]) # Use APF certificate and APF private key\n", + "\n", + " # Publish the APIs\n", + " #ipdb.set_trace()\n", + " files_bundle = publish_capif_api(apfId, publish_api_req_body, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + " offboarding_provider(user_uuid, prov['apiProvDomId'], (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]), prov['admin_token']) # Use AMF certificate and AMF private key\n", + " return\n", + "\n", + " # Terminate the application\n", + " offboarding_provider(\n", + " user_uuid,\n", + " prov['apiProvDomId'],\n", + " (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]), # Use AMF certificate and AMF private key\n", + " prov['admin_token']\n", + " )\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aABBc4Hizy88" + }, + "source": [ + "## Build an helper function to publish the ETSI MEC profile for CAPIF API\n", + "\n", + "To simply the API invoker process, let's create two helpers functions:\n", + "- One to publish the ETSI MEC profile for CAPIF API\n", + "- One to remove the previously published API" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "r-gZe6mQ4yHH" + }, + "source": [ + "### Helper to publish API" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "id": "ozCMG8jh0UMd" + }, + "outputs": [], + "source": [ + "def publish_api() -> tuple:\n", + " \"\"\"\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> publish_api')\n", + "\n", + " prov = onboarding_provider()\n", + " if len(prov) == 0:\n", + " return ()\n", + " amf_cert_bundle = (prov['apiProvFuncs'][0]['regInfo']['apiProvCert'], prov['csr']['amf'][1]) # Use AMF certificate and AMF private key\n", + "\n", + " # Build the publish_api body request\n", + " aefId = prov['apiProvFuncs'][1]['apiProvFuncId']\n", + " apfId = prov['apiProvFuncs'][2]['apiProvFuncId']\n", + " apiId = prov['apiProvDomId']\n", + " publish_api_req_body = build_publish_api_from_mec_services(aefId)\n", + " if len(publish_api_req_body) == 0:\n", + " offboarding_provider(prov['user_uuid'], apiId, amf_cert_bundle, prov['admin_token'])\n", + " return ()\n", + " logger.debug(\"publish_api_req_body: \" + str(publish_api_req_body))\n", + " certs_bundle = (prov['apiProvFuncs'][2]['regInfo']['apiProvCert'], prov['csr']['apf'][1]) # Use APF certificate and APF private key\n", + "\n", + " # Publish the APIs\n", + " files_bundle = publish_capif_api(apfId, publish_api_req_body, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + " offboarding_provider(prov['user_uuid'], apiId, amf_cert_bundle, prov['admin_token']) # Use AMF certificate and AMF private key\n", + " return ()\n", + "\n", + " logger.debug('publish_api: ' + str((apiId, amf_cert_bundle)))\n", + " return (apiId, amf_cert_bundle, prov['user_uuid'], prov['admin_token'])\n", + " # End of function publish_api\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lrnfAlrZ1-TB" + }, + "source": [ + "### Helper to remove published API" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "id": "Ql9dC3P41_nO" + }, + "outputs": [], + "source": [ + "def remove_publish_api(p_user_uuid: str, p_api_id: str, p_bundle: tuple, p_admin_token: str):\n", + " \"\"\"\n", + " To remove published API.\n", + " :param p_user_uuid: The user identifier\n", + " :param p_api_id: The API identifier\n", + " :param p_bundle: The bundle of certificates and keys for the TLS mutual authentication operations\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> remove_publish_api ')\n", + "\n", + " # Terminate the application\n", + " offboarding_provider(p_user_uuid, p_api_id, p_bundle, p_admin_token)\n", + "\n", + " return\n", + " # End of function reove_publish_api\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UNN73-Zg4WZ-" + }, + "source": [ + "### Putting everything together\n", + "\n", + "Let's test these two helpers functions before to go ahead" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "id": "bVYS13iV4-s8" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "skipping\n" + ] + } + ], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " To test both helpers functions:\n", + " - publish_api\n", + " - remove_publish_api\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Publish the MEC profile for CAPIF API\n", + " res = publish_api()\n", + " if len(res) == 0:\n", + " return\n", + "\n", + " api_id, bundle, user_uuid, admin_token = res\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Remove the MEC profile for CAPIF API\n", + " remove_publish_api(user_uuid, api_id, bundle, admin_token)\n", + "\n", + " logger.debug('Stopped at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " # End of function process_main\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9W2SvVdx6fxk" + }, + "source": [ + "## Using the published API: The CAPIF Invoker side\n", + "\n", + "Now that we are able to publish an API on the CAPIF server, the next step is to use it in order to invoke some MEC Services API. To achieve this goal, we have to implement the CAPI Invoker, following these steps:\n", + "- Onboard an API invoker\n", + "- Discover the published APIs\n", + "- Get a MEC Service API (this step requires that a MEC Sandox platform is already running)\n", + "- Offboard an API invoker" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YasCvixW7E4o" + }, + "source": [ + "### Onboard an API invoker\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "id": "f11_uMS67I9J" + }, + "outputs": [], + "source": [ + "def onboard_invoker(p_name: str, p_access_token: str) -> dict:\n", + " \"\"\"\n", + " To onboard the API invoker.\n", + " :param p_name: The name of the invoker\n", + " :return: A dictionary containing security material for each CAPIF endpoint on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, ccf_api_onboarding_url, ccf_onboarding_url\n", + "\n", + " logger.debug('>>> onboard_invoker: ' + p_name)\n", + " logger.debug('>>> onboard_invoker: ' + p_access_token)\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_onboarding_url\n", + " logger.debug('onboard_invoker: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_access_token}\n", + " logger.debug('onboard_invoker (step1): headers: ' + str(headers))\n", + " # Request body for onboarding the invoker\n", + " invoker_csr_request, invoker_private_key = generate_csr(\"API Invoker\", \"ETSI\", \"Fr\")\n", + " data = {\n", + " \"notificationDestination\" : \"http://host.docker.internal:8086/netapp_callback\",\n", + " \"supportedFeatures\" : \"fffffff\",\n", + " \"apiInvokerInformation\" : \"dummy\",\n", + " \"websockNotifConfig\" : {\n", + " \"requestWebsocketUri\" : True,\n", + " \"websocketUri\" : \"websocketUri\"\n", + " },\n", + " \"onboardingInformation\" : {\n", + " \"apiInvokerPublicKey\" : invoker_csr_request.decode(\"utf-8\"),\n", + " },\n", + " \"requestTestNotification\" : True\n", + " }\n", + " logger.debug('onboard_invoker (step2): body: ' + str(data))\n", + " response = requests.post(url, headers=headers, data=json.dumps(data), verify=\"ca_root.pem\")\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('onboard_invoker (step3): result: ' + str(response.json()))\n", + " if response.status_code != 201:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " res = json.loads(response.text)\n", + " # Add an entry for CSRs and private keys for future usage\n", + " res['csr'] = [invoker_csr_request, invoker_private_key]\n", + " return res\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"onboard_invoker failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function onboard_invoker" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kQmJW-d99cGo" + }, + "source": [ + "### Offboard an API invoker" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "id": "KRC_xkGO9hEY" + }, + "outputs": [], + "source": [ + "def offboard_invoker(p_invoker_id: str, p_bundle: tuple) -> list:\n", + " \"\"\"\n", + " To offboard the API invoker.\n", + " :param p_invoker_id: The API invoker identifier\n", + " :param p_bundle: The bundle of certificates and keys for the TLS mutual authentication operations\n", + " :return: 0 on success, -1 otherwise\n", + " \"\"\"\n", + " global logger, ccf_onboarding_url, ca_root\n", + "\n", + " logger.debug('>>> offboard_invoker: ' + p_invoker_id)\n", + "\n", + " try:\n", + " # Delete the newly created user\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_onboarding_url + '/' + p_invoker_id\n", + " logger.debug('offboard_invoker: url=' + url)\n", + " headers = {'Content-Type': 'application/json'}\n", + " logger.debug('offboard_invoker (step1): headers: ' + str(headers))\n", + " bundle = store_certificate_2_files(p_bundle[0], p_bundle[1], ca_root) # Use CA certificate for verif\n", + " if len(bundle) != 3:\n", + " logger.error(f\"Error converting in-memory bundle into files\")\n", + " return []\n", + " logger.debug('offboard_invoker (step2): bundle: ' + str(bundle))\n", + " response = requests.delete(url, headers=headers, cert=(bundle[0], bundle[1]), verify=bundle[2])\n", + " logger.debug('offboard_invoker (step3): response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " if response.status_code != 204:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return []\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"offboard_invoker failed: {e}\")\n", + " return []\n", + "\n", + " return bundle\n", + " # End of function offboard_invoker" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-h0zz7ocxtyv" + }, + "source": [ + "### Discover published APIs" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "id": "ofUuploUxuhn" + }, + "outputs": [], + "source": [ + "def discover(p_invoker_id: str, p_bundle: tuple, p_access_token: str) -> dict:\n", + " \"\"\"\n", + " To discover the APIs published by capif core.\n", + " :param p_invoker_id: The API invoker identifier\n", + " :return: A dictionary containing the APIs published by capif core on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger, ca_root, ccf_discover_url\n", + "\n", + " logger.debug('>>> Discover APIs published by capif core')\n", + "\n", + " try:\n", + " url = 'https://' + CAPIF_HOSTNAME + ':' + str(CAPIF_PORT) + '/' + ccf_discover_url + p_invoker_id\n", + " logger.debug('Discover: url=' + url)\n", + " headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + p_access_token}\n", + " logger.debug('Discover (step1): headers: ' + str(headers))\n", + " bundle = store_certificate_2_files(p_bundle[0], p_bundle[1], ca_root) # Use CA certificate for verif\n", + " if len(bundle) != 3:\n", + " logger.error(f\"Error converting in-memory bundle into files\")\n", + " return dict()\n", + " logger.debug('Discover (step2): bundle: ' + str(bundle))\n", + " response = requests.get(url, headers=headers, cert=(bundle[0], bundle[1]), verify=bundle[2])\n", + " logger.debug('Discover (step3): response=' + str(response))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " logger.debug('Discover : result: ' + str(response.json()))\n", + " if response.status_code != 200:\n", + " logger.error(f\"Discovery failed: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " res = json.loads(response.text)\n", + " return res\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"Discovery failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function discover\n", + "\n", + "def extract_ipv4_and_uri(p_response_data: json.loads) -> dict:\n", + " # Extract ipv4Addr using a list comprehension\n", + " ipv4_addrs = [\n", + " desc.get(\"ipv4Addr\")\n", + " for profile in p_response_data.get(\"serviceAPIDescriptions\", [])\n", + " for aef in profile.get(\"aefProfiles\", [])\n", + " for desc in aef.get(\"interfaceDescriptions\", [])\n", + " ]\n", + "\n", + " # Extract uri using a list comprehension\n", + " uris = [\n", + " resource.get(\"uri\")\n", + " for profile in p_response_data.get(\"serviceAPIDescriptions\", [])\n", + " for aef in profile.get(\"aefProfiles\", [])\n", + " for version in aef.get(\"versions\", [])\n", + " for resource in version.get(\"resources\", [])\n", + " ]\n", + "\n", + " return {\"ipv4Addr\": ipv4_addrs, \"uri\": uris}\n", + " # End of function extract_ipv4_and_uri" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RElS9XFZ9hvQ" + }, + "source": [ + "### Putting everything together" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "id": "QPZPYJZM9mNr" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "skipping\n" + ] + } + ], + "source": [ + "%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the fiveth sprint of our CAPIF/MEC application:\n", + " - Publish the MEC profile for CAPIF API\n", + " - Create a new user for the invoker\n", + " - Get certificates\n", + " - Onboad the API invoker\n", + " - Do the discovery\n", + " - Offboard the API invoker\n", + " - Delete the\n", + " - Logout the invoker user\n", + " - Remove the MEC profile for CAPIF API\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_discover_url, ccf_onboarding_url, ccf_publish_url, ccf_security_url\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Publish the MEC profile for CAPIF API\n", + " res = publish_api()\n", + " if len(res) == 0:\n", + " return\n", + " api_id, bundle, prov_user_uuid, prov_admin_token = res\n", + "\n", + " # Login for the new user for the invoker\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Create a new user for the invoker\n", + " res = create_user(admin_token)\n", + " if len(res) == 0:\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + " user_name, user_uuid = res\n", + "\n", + " # Get certificates\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " delete_user(user_name, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Sanity checks\n", + " if auth['ca_root'] != ca_root:\n", + " raise Exception('CA root mismatch')\n", + " if auth['ccf_api_onboarding_url'] != ccf_api_onboarding_url:\n", + " raise Exception('CCF API onboarding URL mismatch')\n", + " if auth['ccf_discover_url'] != ccf_discover_url:\n", + " raise Exception('CCF discover URL mismatch')\n", + " if auth['ccf_onboarding_url'] != ccf_onboarding_url:\n", + " raise Exception('CCF onboarding URL mismatch')\n", + " if auth['ccf_publish_url'] != ccf_publish_url:\n", + " raise Exception('CCF publish URL mismatch')\n", + " if auth['ccf_security_url'] != ccf_security_url:\n", + " raise Exception('CCF security URL mismatch')\n", + " access_token = auth['access_token']\n", + "\n", + " # Onboad the API invoker\n", + " res = onboard_invoker('API Invoker', access_token)\n", + " if len(res) == 0:\n", + " delete_user(user_uuid, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Do the discovery\n", + " invoker_id = res['apiInvokerId']\n", + " certs_bundle = (res['onboardingInformation']['apiInvokerCertificate'], res['csr'][1])\n", + " mec_api = discover(invoker_id, certs_bundle, access_token)\n", + " if len(mec_api) == 0:\n", + " files_bundle = offboard_invoker(invoker_id, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + " delete_user(user_uuid, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Extract the URL to access to the MEC Sandbox platform\n", + " addrs = extract_ipv4_and_uri(mec_api)\n", + " logger.debug('addrs: ' + str(addrs))\n", + "\n", + " time.sleep(5) # Sleep for 5 seconds\n", + "\n", + " # Offboard the API invoker\n", + " files_bundle = offboard_invoker(invoker_id, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + "\n", + " # Delete the invoker user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout the invoker user\n", + " process_logout()\n", + "\n", + " # Remove the MEC profile for CAPIF API\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6tJWDz4woyz1" + }, + "source": [ + "## Discoverig MEC services\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Uy-XNKA9pN5h" + }, + "source": [ + "### Invoking the MEC profile for CAPIF\n", + "\n", + "After discovering the published API, we have the information (see content of addrs data structure in previous execution) to do a request to an existing MEC Sandbox platform to get the list of the MEC services exposed (see TRY_MEC_SESSION_ID)." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "id": "ZL8Gyo0Ao2_u" + }, + "outputs": [], + "source": [ + "def discovering_mec_services(p_url: str) -> dict:\n", + " \"\"\"\n", + " To discover MEC services API\n", + " :param p_url: The URL to access to the MEC Sandbox platform\n", + " :return: A dictionary containing the MEC services on success, or an empty dictionary otherwise\n", + " \"\"\"\n", + " global logger\n", + "\n", + " logger.debug('>>> discovering_mec_services: ' + p_url)\n", + "\n", + " try:\n", + " headers = {'Content-Type': 'application/json', 'accept': 'application/json',}\n", + " logger.debug('discovering_mec_services (step1): headers: ' + str(headers))\n", + " response = requests.get(p_url, headers=headers)\n", + " logger.debug('discovering_mec_services (step2): result: ' + str(response.json()))\n", + " response.raise_for_status() # Raise an exception for bad status codes\n", + " if response.status_code != 200:\n", + " logger.error(f\"Error creating user: {response.status_code} - {response.text}\")\n", + " return dict()\n", + " res = json.loads(response.text)\n", + " return res\n", + " except requests.exceptions.RequestException as e:\n", + " logger.error(f\"discovering_mec_services failed: {e}\")\n", + "\n", + " return dict()\n", + " # End of function discovering_mec_services" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Wa_8khiGpTAa" + }, + "source": [ + "### Putting everything together\n", + "\n", + "Here is the last and complete version of our CAPIF application achieving the main objective of this tutorial: __Retrieve the MEC services exposed by an existing MEC Sandbox platform.__\n", + "\n", + "The process involves following steps:\n", + "- Create new User (API Provider)\n", + "- Onboard API Provider\n", + "- Publish MEC APIs on CCF\n", + "- Create new User (API Invoker)\n", + "- Onboard API Invoker\n", + "- Discovery of APIs by API Invoker\n", + "- Request Discovered API (Get MEC Services)\n", + "- Offboard API Invoker/API Provider\n", + "- Delete Users" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "id": "aTllbmoUpXKx", + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-03-14 06:31:39,150 - __main__ - DEBUG - Starting at 20250314-063139\n", + "2025-03-14 06:31:39,151 - __main__ - DEBUG - \t pwd= /home/jovyan/work/notebook\n", + "2025-03-14 06:31:39,152 - __main__ - DEBUG - >>> publish_api\n", + "2025-03-14 06:31:39,153 - __main__ - DEBUG - >>> process_login\n", + "2025-03-14 06:31:39,154 - __main__ - DEBUG - process_login: url=https://lab-oai.etsi.org:31120/login\n", + "2025-03-14 06:31:39,154 - __main__ - DEBUG - process_login (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Basic YWRtaW46cGFzc3dvcmQxMjM='}\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:39,208 - __main__ - DEBUG - process_login (step2): result: {'access_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQxOTM0NDk5fQ.wNjsuLgh61j0G_6rtN6vxqfbJGMgtaetJd1xPUOoBb4', 'refresh_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQ0NTI1ODk5fQ.lgD4IZCAVJ3-smu1nFBjTtdhTW_trraua1OppvQd4rc'}\n", + "2025-03-14 06:31:39,209 - __main__ - DEBUG - >>> create_user\n", + "2025-03-14 06:31:39,210 - __main__ - DEBUG - create_user: url=https://lab-oai.etsi.org:31120/createUser\n", + "2025-03-14 06:31:39,210 - __main__ - DEBUG - create_user (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQxOTM0NDk5fQ.wNjsuLgh61j0G_6rtN6vxqfbJGMgtaetJd1xPUOoBb4'}\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:39,245 - __main__ - DEBUG - create_user (step2): response=<Response [201]>\n", + "2025-03-14 06:31:39,246 - __main__ - DEBUG - >>> get_auth\n", + "2025-03-14 06:31:39,247 - __main__ - DEBUG - get_auth: url=https://lab-oai.etsi.org:31120/getauth\n", + "2025-03-14 06:31:39,247 - __main__ - DEBUG - get_auth (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Basic ZmM1OWU4YWUtMDA5ZC0xMWYwLWI3MDYtMDI0MmFjMTEwMDAyOnBhc3N3b3JkMTIz'}\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:39,331 - __main__ - DEBUG - get_auth (step2): result: {'access_token': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiYjI0N2YyNzQtMDIxMC00ZDY4LTgwYzktMTAxNjliNTdiNzcyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjNTllOGFlLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA1ODg3NzE2YS0zNWVmLTVmOTktYmQ5NC0zOGQxZmEzZDAzMTciLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiJjNzNjNDJkYy0zOTJhLTQzNzQtOTQyNS1hODY0OTIzNjE1OTciLCJleHAiOjE3NDE5MzQ3OTl9.cQFJ7UfYr_8Fm7eG2hrazmBFqHn7z0WVtvEPmZxLqjUs27MiUmMhd-la89EZ9y_CL4ncZr68N6TCvLfhW6ZReEzdzUCivPJ6iOmw2UTsr1McAnaqf_bmExEIwz3746kYzO2BOPjja15JvaC4d8UiJSzHSW4PNTc3GrtEe2OuuOgsi1mmUH5zCYluyyBzykwZuM7N-mclarQbnmsacSCsMSlFeWVsa-fR560C8uXe0nEMdP9VBBq5O5zwol2ZNGOn3tTYTTibZTv9zZ4Qmay4KXCpQxbIKA_N5fhqSR6P6uQVJ9ihWB0lYXfmnEVlGu4H_IR4oZNs0TxNFNAZkjgROQ', 'ca_root': '-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n', 'ccf_api_onboarding_url': 'api-provider-management/v1/registrations', 'ccf_discover_url': 'service-apis/v1/allServiceAPIs?api-invoker-id=', 'ccf_onboarding_url': 'api-invoker-management/v1/onboardedInvokers', 'ccf_publish_url': 'published-apis/v1/<apfId>/service-apis', 'ccf_security_url': 'capif-security/v1/trustedInvokers/<apiInvokerId>', 'message': 'Token and CA root returned successfully'}\n", + "2025-03-14 06:31:39,332 - __main__ - DEBUG - >>> onboard_provider\n", + "2025-03-14 06:31:39,333 - __main__ - DEBUG - onboard_provider: url=https://lab-oai.etsi.org:443/api-provider-management/v1/registrations\n", + "2025-03-14 06:31:39,333 - __main__ - DEBUG - onboard_provider (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiYjI0N2YyNzQtMDIxMC00ZDY4LTgwYzktMTAxNjliNTdiNzcyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjNTllOGFlLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA1ODg3NzE2YS0zNWVmLTVmOTktYmQ5NC0zOGQxZmEzZDAzMTciLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiJjNzNjNDJkYy0zOTJhLTQzNzQtOTQyNS1hODY0OTIzNjE1OTciLCJleHAiOjE3NDE5MzQ3OTl9.cQFJ7UfYr_8Fm7eG2hrazmBFqHn7z0WVtvEPmZxLqjUs27MiUmMhd-la89EZ9y_CL4ncZr68N6TCvLfhW6ZReEzdzUCivPJ6iOmw2UTsr1McAnaqf_bmExEIwz3746kYzO2BOPjja15JvaC4d8UiJSzHSW4PNTc3GrtEe2OuuOgsi1mmUH5zCYluyyBzykwZuM7N-mclarQbnmsacSCsMSlFeWVsa-fR560C8uXe0nEMdP9VBBq5O5zwol2ZNGOn3tTYTTibZTv9zZ4Qmay4KXCpQxbIKA_N5fhqSR6P6uQVJ9ihWB0lYXfmnEVlGu4H_IR4oZNs0TxNFNAZkjgROQ'}\n", + "2025-03-14 06:31:39,334 - __main__ - DEBUG - >>> generate_csr\n", + "/tmp/ipykernel_3714172/1257919564.py:18: DeprecationWarning: CSR support in pyOpenSSL is deprecated. You should use the APIs in cryptography.\n", + " req = X509Req()\n", + "2025-03-14 06:31:39,373 - __main__ - DEBUG - generate_csr: PrivKey: b'-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCwGYlsHsmrt0wN\\nZ+0Tn7kVnf+ovdqnS07Yasvj3eAgoJWjl0pmMfX3Grqb5hAeF3DoaHZe5yMZq9eN\\nPGyMhtK7PozWWv7HQVaPQLRAW9yJ4vzzjYgyEOWXape3rGH1xhCGOvlXHyYIztMl\\npEWVmIPtOHx978WrD8i4oQrjVNO0ivYW4fCLFqv7L6uLT1rY/wns+7REtaTZopWI\\nNswZm2Upc/CBp8+mP8PCk0LWnUNeeG9J/tZNSQour5eRaadwCxPD58af2oq8fZ4L\\nHJ8W2UGYBVfLV0Gn9vzn0GGinu3qvdwKeKO+/MBlc4wcQFU2EBEBWpVFpjD09PvA\\n0dqQ+t6hAgMBAAECggEARIx8QPkvGcfNM/67e4MWQgLx7RaSUcLv2zCfU4Ef6LN5\\n7GdND8Ds9RaTg3In3f7T9bQMN98Te7+3tYPK4VzuFPNBUYO0X43givzt7YuUqpwQ\\nSSJT3OFU7f8RNe9FZq2F6ypzuuUmIhGYgbOTXqsy15nAZCl8sZ0ATlZp7Zosmr9C\\nxgHNJtBlieTsb40unETw097OKqvJV4JEWQQsvLgnsrIuKFC21d7efid09uy7O/Dm\\nYOAQI+zupQrtBWCZlxNdB0KDAFX1xjisVnunq4fPmp1uQQ+CUgDHZaTlRJ6awKjM\\ns20SP2eg9YYiygwV/6luWsB6OZOKyQe2x6JbGEnejQKBgQDs8ZyBtvSDomENiD1t\\niSq/DwExoaAB5+2a2kCwDPqIAITswgZqXTgQUK+LkFCZqj6brZ+TQaDRQCLhR+WU\\n23XET3OdywMex/fwH8URaOAdAXtsgQXqi92ZSDb9Y0h5mPE5QHFR8S4Z2rBpTjvc\\nDaHvRQW6LpTG9U/XpN8Nsa6Z8wKBgQC+Qzg3I43Wo9LCIgBaspZLwxlJdBdz4zsE\\nQpAROQYXds47bVsPQqaerCR9a259KZUhfmxwAxEH86lmsot6iPktMIWtg6O7G8jB\\nyWyVxA4f4J2t2g9RIkUG4HOkJ4lG/9QwRM7GtzMqlu8BXd5rp0wF5aWToZJmG/+o\\nWv8LqgNWGwKBgA8wObES8j+R5BjC9/USitvucUwmKSSWImP/w4/FdTXMmri2g0TE\\nLvjUwzv2B0SiZhsCmS0OUum268V3H19YZgcsdKPTxKJvfRQ2ZSKFj4AsfSXqa1+q\\nkYzm1SeO+rFYvXXHDLyM8kCUBSTq7+leMlgtG33gyIasaO7Q0b+F+URDAoGAJQeK\\nIlkeGt8pfrUFYqGNVNN00XHxupFIBC08Qs3ZXKm3u6Mt7MtpCEqyk2JcT6nPJ4Sm\\ncCp4nxaSKrWnJcMQ0/W0nq+XJyxiJy0foApXQWcC6Toebwj/bDY064byVcomrvpF\\nUDGJmMllXNu7FTKrPh6S2ifBCXR7jnWfW9LL8W8CgYAkItXeo5t/cbXjtp87WdIV\\nU+AXb4hbXdm++WQjV+m3PxkPAukHYaxZzHSxVv64urW5YdA9Xv9seJE++KRlpD+a\\nXvELsyJ/lScLp5YQoWcuNpisqkKn72dJWcGZHhfvd8GaFtIaxJ/Krtn2FO39lxjX\\nLdIp77vh3SB7KlsV7AKqwQ==\\n-----END PRIVATE KEY-----\\n'\n", + "2025-03-14 06:31:39,374 - __main__ - DEBUG - >>> generate_csr\n", + "2025-03-14 06:31:39,442 - __main__ - DEBUG - generate_csr: PrivKey: b'-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDN1nWrOlD9eyG+\\nx150iAbwsfVOGeRW0bgkZD935jI+ydF5mNpiJnRAfmdGflGDqDWDhDaCcZSjwH1t\\nX+ga0/FtUs/zHdUBGrNj1bwxFUZxL8LRMrqrkrJhH7JfcxmSSSNe7qJbLrTE3Ykg\\nldHjcZRMEkuK5zlQLycdTZQvPuBorUeMesuU+pG5+9F00QsCNVzZl/zqTQEAgkxe\\nLRBkyttqwSCENt9V9rycDCP9Q1m5uWwtE/zYyu9C36gvSGSGiM7xTv9HNTnts80j\\ngL/jsVzOyqJKPnT7ILs1TPA8rD8YrLWOL3p0mzTDBH3gFAzqCXOiOcv13WfEBom7\\nOFWMxH3vAgMBAAECggEAGs+y/w03DHVz9U4xyvsG8GY1vikYOYjStAbMz5jP9abD\\nHtQeqgc5QEdWu3Nrubr40XswwHf79QJ3M/54lLoDqgpN5Ox/WMf6Euh9vWT4ini+\\npxI1B3Exw/3ldZJgA9J8BXsO8dKZIEV8/Z4WO0qJlwkRBvRMXlnt/d6jjS3i/C9O\\nFLd/YhzUjDwwYdaSiMcQ/sgcOI5an/LouIPnbOAZFw6294bZu/MaEdr0OdeUCyjK\\nrPT2IBlP9RdEwTfZBnfCRcZ4nLHAyEKhcKB8/LLXNg2Cm6YnB3lN+2sUaaDChupy\\ndChtYsKz4mhKdjdWsO9S5CACT6T7w/GocxOGGYJbNQKBgQDv/JDffe9pTrKJm98b\\n20lLXUIenRIhOgmcQOIGnK8eTn0dugMU+TyDzKmyA97+ET/GGdAaDuV7O/f/tKkO\\nMImX/DTzoYqHpMoANo+TWMl8Jqaqu/LSp5G+y75tUlyDWR6iGIulq/Cfoo2mCzYS\\nc9exLafahogluWZTLMBdklcyFQKBgQDbkpC/UDYB1iEm6zmBpvTkT9+uoR8hBaCC\\nEZvUe6qAdAixMqi1kihTUlC0UNDBmSA2PUKvEi+0OJb2c1Ep+RFhdzN+kxPl6QbH\\nWtp75w0/FHf53Da2JS+MN5+UgzRdKHjJrwrPyPqDdcb+Bao8P8OdnZU2vAz8zKQh\\nPg3ki2Ek8wKBgQChHiCpWyRDwAkPZ+1nB1by2P6ODQfh9NQE6m6U39aV8z3+miZ9\\ni0L8fYgkMoMgcbYuKqBTDlM05DMAomqpUx7dQf7O3lJh9NmFQRwtYXuL9WsJzzsc\\nAFJSFPmY7aWcrVVqoC4JISFgG0McCgTYaJuToUJC7PU01n8DMlVHvPr7bQKBgERM\\n6R25s7MKirFyhibgalkCx4+oqug4ud773z9PYJAoh/Om0Hf6iPSyLEyKvwUZvr1g\\nfPe8bn5fBAaRhHPL+C4bSDddRNlFjUhB6KiWDLbMhS2B5LrwAkRZoRgvYACCcGA8\\nRN6xo8t0vXuTA4tOQmkq9ZlbEacpePymMugeuw8lAoGANKB5VXA1/pt2nSV1Bt0u\\nTDd8VdcXE+1oxnQ0Rt4+jHhSSVNZd7OBH4qxBkmaQSgkKC+IOqjfFYs6PTbgUI9Y\\npXyc6dMwAqvNZyP88unbooEZYQwm0ynK878pav2wiKOAZaypGkNmiRgzYKuYdZ1n\\nLSUBxB62CticCewzFLBkMnY=\\n-----END PRIVATE KEY-----\\n'\n", + "2025-03-14 06:31:39,443 - __main__ - DEBUG - >>> generate_csr\n", + "2025-03-14 06:31:39,498 - __main__ - DEBUG - generate_csr: PrivKey: b'-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1u+4cipJsRh7M\\nT5psEHv/2mCDanU7/EyqMfGah/GX3rb66XQiqNuqktihif0x5qULo3aEm6nZQ1Ed\\n2W3iO+TMo+KYnHanQZig+wXDyHyh3aJfKwuu3/aNolc1PXlcoM6qxY5PAppZoTmb\\nlEcDTPvNG8ZatYkyTQZgsfDRnL3owuiM3XTBQ0hNoBNXFHHs+cEpPzqr5y+LL5LU\\nyKLGNNNmtYpggjhWns9koCgYbJrkowhSsQOTCiCiIP+nBZ472GeEyxr4I2AM85qK\\niYHVgQ3JkaJbJmg+Ompz65uW587EDaiK3ssePj59gF0KiSraTFlzg2H0mTsoJsB7\\nlsQqQsULAgMBAAECggEALikmkaRXDd5/uyirEjDbtkC7TBYR4iMzO/XEpxpJsOg2\\ntSPwRk96wGhdUybQI8kwefTSyVsauN9i7sCIKzNIafxktZvkfZZpVNZ9/91gXuMD\\nd8XgyVIE97PZD+Jl/bOw2uqkO4hvOT38+noe0YVP3ijkaKz8xMSQzqXUBAIasNDH\\nOXw4Zep5appuTtzllhxlt79c6lywtfjWg5NWAg7Gkutm3ZGYV1KjmCcphpqVwoAH\\nxgU4PODA41k2eio99iMmIZxYGPrddTD77slTGlVzCsZCpOqoL9Ho5YKLhidpug1Q\\nmUc54eaZncTnRmW7OQMm3/sppbEGG/Ac2XLcVW0SXQKBgQDqamhnxZQKeM+jCiqe\\n8LQ6nFTgUOEM5Wwt0OYxuhGA9O6+d0gVt2DXYUAb28xj1qoRM7LRlhdVg+zv4V3O\\nf/afJW5wJGBWPmn1yv6Rpca1KrW3xk0PQ3MwrF1pIjmla1E1kO7m4q1RqaBnBO7U\\nBBfxz1zeZM5yKFfNImIItsOwvwKBgQDGd7mKiDFydKlsrJx+XVGL7kS7A6v+FRaX\\n09Ita+9FaHcVtqPHrJlD4wHIKMkltsGHZwLV7Cuk4KQosevgD2bHzKxq5LG9A0mF\\nQaBcwckStrD1GvoCmSsw5kxWckRP+HUstcQyzM9FDI1qaQaGnzxdcXJUFxUyluBy\\nb7Of3X6ytQKBgC2Hl1m4dWWHS4T2P4r5Y3gSzyV2cA+qK9XGQj+cTTQH3qsdzeFx\\n6ZxLpkEC8vLdSdDngq7UgRm58vYwhqDKF+OXSJj9Z7y4iKoV0FYHpc2gSwUzvdne\\nFux4PfhijmHDs1U9Hjm4A0PeN2pq+dwyI1hzFy2W1MY1ccBFEldw8BdPAoGBAMGD\\naWEfDH+aKrxwzaIUoXd8Gn3yxZfXvhDKE4wASuv1QO/mBmmh7EsaI9mjkwV33dmF\\nYmltftyjwMyBNwlgWwoDkjYjyP4QH3aUF6V5ufHKOl6zMASqjkd+tf4wKlWDX9T5\\nYlVaB9s+swCHaTqINtax0BUX8K5EGJLcQVtmH6xlAoGBALu15n4ueD2x/QBwTYWX\\nYwawwPHckuuXxc1c2qJcJ1VAULrcJl0FCYWza4hIqmtdAZ6uzLsiRb/Qoh66tufP\\n2bCxvKO4aSjXQcGLbY4n4dmFjp0X/vl/isb/xGWk4GqVev3CCEsVzseVXCIiWax5\\nC5F9a0df0nPSTuCSn5cAlkjl\\n-----END PRIVATE KEY-----\\n'\n", + "2025-03-14 06:31:39,499 - __main__ - DEBUG - onboard_provider (step2): body: {'apiProvFuncs': [{'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQU1GMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAZiWweyau3\\nTA1n7ROfuRWd/6i92qdLTthqy+Pd4CCglaOXSmYx9fcaupvmEB4XcOhodl7nIxmr\\n1408bIyG0rs+jNZa/sdBVo9AtEBb3Ini/PONiDIQ5Zdql7esYfXGEIY6+VcfJgjO\\n0yWkRZWYg+04fH3vxasPyLihCuNU07SK9hbh8IsWq/svq4tPWtj/Cez7tES1pNmi\\nlYg2zBmbZSlz8IGnz6Y/w8KTQtadQ154b0n+1k1JCi6vl5Fpp3ALE8Pnxp/airx9\\nngscnxbZQZgFV8tXQaf2/OfQYaKe7eq93Ap4o778wGVzjBxAVTYQEQFalUWmMPT0\\n+8DR2pD63qECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQChwyyD3Rcxidm0SK9f\\nJwyrEcTwr5an1H5EuDWpvQnKFGiePEcQF1vVzRotMazWHHT6sgJNVZO+bJqkugvS\\nC9Pc/TnocB4lFYB3iZBT7rhrC8JnjufnZfkBeaJFx1AUJnu/DxEBc9lij5acpcJB\\nD+TMOfDNy/VVa7/2RJakwmHdlwAc8WKGaSUvJw9rFpMEYGQC+HljXtByvLyBne1H\\nOZTuayTZtDHGqM7tKOFsCP0ajLEW4BuVioMh8Ge+NozUl+zLbD8CA89M/PAotANV\\nKZl+EexnS/+c3N8p+7x8Lk/8A0AsW98Ktf+MQAA20Tab5FmWSPrg/X+XjSHvHdSw\\n1qgA\\n-----END CERTIFICATE REQUEST-----\\n'}, 'apiProvFuncRole': 'AMF'}, {'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQUVGMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM3Wdas6UP17\\nIb7HXnSIBvCx9U4Z5FbRuCRkP3fmMj7J0XmY2mImdEB+Z0Z+UYOoNYOENoJxlKPA\\nfW1f6BrT8W1Sz/Md1QEas2PVvDEVRnEvwtEyuquSsmEfsl9zGZJJI17uolsutMTd\\niSCV0eNxlEwSS4rnOVAvJx1NlC8+4GitR4x6y5T6kbn70XTRCwI1XNmX/OpNAQCC\\nTF4tEGTK22rBIIQ231X2vJwMI/1DWbm5bC0T/NjK70LfqC9IZIaIzvFO/0c1Oe2z\\nzSOAv+OxXM7Koko+dPsguzVM8DysPxistY4venSbNMMEfeAUDOoJc6I5y/XdZ8QG\\nibs4VYzEfe8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBDGmtyvcLOgeMKH5yK\\nKXIAhV3gkWT8PP/cM49jh7wsy5CJ1Sa++tki8xo1OHmwG8WZkp8AhlCmRCAhqa4d\\nWKNJMR/2baFbdj3CIPsKciOacoe+0fwA+rPqT4JKzCiZ0LtAtf4+YZn5tOMQcVhA\\nd/o16jugqLU5Cvdyf5iz42p67bCKInaR+JQNkJQv99cjJbLxHkOdke9HGL3YcZYY\\nqCJ4YY/UQbfUZGQr6gXxuYIqyIwHUOhVRdlakFQESVOz8lbgvkFhs5xKETgg/6A1\\nYQSyCgYt/r6W0YarDGW9TTTnOT1/bioGxaMs4jHtCF6quUpJ2lWiY25MXlNvKa9v\\nCvdM\\n-----END CERTIFICATE REQUEST-----\\n'}, 'apiProvFuncRole': 'AEF'}, {'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQVBGMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALW77hyKkmxG\\nHsxPmmwQe//aYINqdTv8TKox8ZqH8ZfetvrpdCKo26qS2KGJ/THmpQujdoSbqdlD\\nUR3ZbeI75Myj4picdqdBmKD7BcPIfKHdol8rC67f9o2iVzU9eVygzqrFjk8Cmlmh\\nOZuURwNM+80bxlq1iTJNBmCx8NGcvejC6IzddMFDSE2gE1cUcez5wSk/OqvnL4sv\\nktTIosY002a1imCCOFaez2SgKBhsmuSjCFKxA5MKIKIg/6cFnjvYZ4TLGvgjYAzz\\nmoqJgdWBDcmRolsmaD46anPrm5bnzsQNqIreyx4+Pn2AXQqJKtpMWXODYfSZOygm\\nwHuWxCpCxQsCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQApUoo3ZXsqvfPVeGFz\\nwPuG8oUTmcPzRT9jt27zsxPl02wVNU+ah5r6gENKUWF1YNHus1YOCEwkypFlTje2\\nlM1XnrGZ16kGmJOHs/K+CUCaRBOvQNJNCdhF6O56u0cT99p6E1MBtNARdITrC1Pf\\no2UE81ixqNmbYvUUTf6HfvLwMj7y9/ZflW777dY8/X4JqP8b7EcBQj26LcnfZSo8\\nbyVj9Dqm1aFAhqKg5z4/DNnTVWKo+hioK6/ML37aQqwCjHGtYhv5onB/tV5UVAHj\\n/XSjBDEkZuGntuQHJfGy82S2W+8sWQlY/DY+UbuLehPpDJQljj0AO4P6FIltbupo\\nYAyl\\n-----END CERTIFICATE REQUEST-----\\n'}, 'apiProvFuncRole': 'APF'}], 'apiProvDomInfo': 'MECSandbox_to_CAPIF_Provider', 'suppFeat': 'fff', 'failReason': 'string', 'regSec': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiYjI0N2YyNzQtMDIxMC00ZDY4LTgwYzktMTAxNjliNTdiNzcyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjNTllOGFlLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA1ODg3NzE2YS0zNWVmLTVmOTktYmQ5NC0zOGQxZmEzZDAzMTciLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiJjNzNjNDJkYy0zOTJhLTQzNzQtOTQyNS1hODY0OTIzNjE1OTciLCJleHAiOjE3NDE5MzQ3OTl9.cQFJ7UfYr_8Fm7eG2hrazmBFqHn7z0WVtvEPmZxLqjUs27MiUmMhd-la89EZ9y_CL4ncZr68N6TCvLfhW6ZReEzdzUCivPJ6iOmw2UTsr1McAnaqf_bmExEIwz3746kYzO2BOPjja15JvaC4d8UiJSzHSW4PNTc3GrtEe2OuuOgsi1mmUH5zCYluyyBzykwZuM7N-mclarQbnmsacSCsMSlFeWVsa-fR560C8uXe0nEMdP9VBBq5O5zwol2ZNGOn3tTYTTibZTv9zZ4Qmay4KXCpQxbIKA_N5fhqSR6P6uQVJ9ihWB0lYXfmnEVlGu4H_IR4oZNs0TxNFNAZkjgROQ'}\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:39,556 - __main__ - DEBUG - onboard_provider (step3): result: {'apiProvDomId': '7d1e7acfc6bb19a31614da0968a8f8', 'regSec': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiYjI0N2YyNzQtMDIxMC00ZDY4LTgwYzktMTAxNjliNTdiNzcyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjNTllOGFlLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA1ODg3NzE2YS0zNWVmLTVmOTktYmQ5NC0zOGQxZmEzZDAzMTciLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiJjNzNjNDJkYy0zOTJhLTQzNzQtOTQyNS1hODY0OTIzNjE1OTciLCJleHAiOjE3NDE5MzQ3OTl9.cQFJ7UfYr_8Fm7eG2hrazmBFqHn7z0WVtvEPmZxLqjUs27MiUmMhd-la89EZ9y_CL4ncZr68N6TCvLfhW6ZReEzdzUCivPJ6iOmw2UTsr1McAnaqf_bmExEIwz3746kYzO2BOPjja15JvaC4d8UiJSzHSW4PNTc3GrtEe2OuuOgsi1mmUH5zCYluyyBzykwZuM7N-mclarQbnmsacSCsMSlFeWVsa-fR560C8uXe0nEMdP9VBBq5O5zwol2ZNGOn3tTYTTibZTv9zZ4Qmay4KXCpQxbIKA_N5fhqSR6P6uQVJ9ihWB0lYXfmnEVlGu4H_IR4oZNs0TxNFNAZkjgROQ', 'apiProvFuncs': [{'apiProvFuncId': 'AMF4b2ceb2ce84148aa56a88a07d9ab9c', 'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQU1GMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAZiWweyau3\\nTA1n7ROfuRWd/6i92qdLTthqy+Pd4CCglaOXSmYx9fcaupvmEB4XcOhodl7nIxmr\\n1408bIyG0rs+jNZa/sdBVo9AtEBb3Ini/PONiDIQ5Zdql7esYfXGEIY6+VcfJgjO\\n0yWkRZWYg+04fH3vxasPyLihCuNU07SK9hbh8IsWq/svq4tPWtj/Cez7tES1pNmi\\nlYg2zBmbZSlz8IGnz6Y/w8KTQtadQ154b0n+1k1JCi6vl5Fpp3ALE8Pnxp/airx9\\nngscnxbZQZgFV8tXQaf2/OfQYaKe7eq93Ap4o778wGVzjBxAVTYQEQFalUWmMPT0\\n+8DR2pD63qECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQChwyyD3Rcxidm0SK9f\\nJwyrEcTwr5an1H5EuDWpvQnKFGiePEcQF1vVzRotMazWHHT6sgJNVZO+bJqkugvS\\nC9Pc/TnocB4lFYB3iZBT7rhrC8JnjufnZfkBeaJFx1AUJnu/DxEBc9lij5acpcJB\\nD+TMOfDNy/VVa7/2RJakwmHdlwAc8WKGaSUvJw9rFpMEYGQC+HljXtByvLyBne1H\\nOZTuayTZtDHGqM7tKOFsCP0ajLEW4BuVioMh8Ge+NozUl+zLbD8CA89M/PAotANV\\nKZl+EexnS/+c3N8p+7x8Lk/8A0AsW98Ktf+MQAA20Tab5FmWSPrg/X+XjSHvHdSw\\n1qgA\\n-----END CERTIFICATE REQUEST-----\\n', 'apiProvCert': '-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIUXWNSwZhcDK6gbO6bers6UlaK+kkwDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUFNRjRiMmNl\\nYjJjZTg0MTQ4YWE1NmE4OGEwN2Q5YWI5YzCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBALAZiWweyau3TA1n7ROfuRWd/6i92qdLTthqy+Pd4CCglaOXSmYx\\n9fcaupvmEB4XcOhodl7nIxmr1408bIyG0rs+jNZa/sdBVo9AtEBb3Ini/PONiDIQ\\n5Zdql7esYfXGEIY6+VcfJgjO0yWkRZWYg+04fH3vxasPyLihCuNU07SK9hbh8IsW\\nq/svq4tPWtj/Cez7tES1pNmilYg2zBmbZSlz8IGnz6Y/w8KTQtadQ154b0n+1k1J\\nCi6vl5Fpp3ALE8Pnxp/airx9ngscnxbZQZgFV8tXQaf2/OfQYaKe7eq93Ap4o778\\nwGVzjBxAVTYQEQFalUWmMPT0+8DR2pD63qECAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQf\\n6A3W+VuniqRJYpMK89aTSeVPVDAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFBTUY0YjJjZWIyY2U4NDE0OGFhNTZhODhhMDdkOWFi\\nOWMwDQYJKoZIhvcNAQELBQADggEBAKxQV3StdM8orcqILKtomgcot9ieq+4ijyRm\\nD86Y7pKLSPRAtVgR5IAfQHmGH1/OSOskXL2JW+4EL3qHAmLuka3pnCj8d8etgxKb\\nSKD4yUTRZ/yiIcd2gB5/ykUIOeTdghzzf2yI0x5SYHtpYupmi+FP6twhMxnP8IFh\\nZ3qaYMq1rx4g8riBQz7i9yVby5kc4Na6eUG7V/J/qBL/ONrsvVYW9YBRn6uaFNJJ\\nEAHsRZWAcBy+6NaMToqzp7lC4Pslwfv9YtaythQCguGGz5P7QTc2CrAFO/MDDoM/\\nDvkdUs0skrY+VBjInWpML6e+DJ2sRRC8PJTIjAk1Qgeoxlqnaqo=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----'}, 'apiProvFuncRole': 'AMF'}, {'apiProvFuncId': 'AEFb7a696201b577f7c0233923e4b5839', 'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQUVGMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM3Wdas6UP17\\nIb7HXnSIBvCx9U4Z5FbRuCRkP3fmMj7J0XmY2mImdEB+Z0Z+UYOoNYOENoJxlKPA\\nfW1f6BrT8W1Sz/Md1QEas2PVvDEVRnEvwtEyuquSsmEfsl9zGZJJI17uolsutMTd\\niSCV0eNxlEwSS4rnOVAvJx1NlC8+4GitR4x6y5T6kbn70XTRCwI1XNmX/OpNAQCC\\nTF4tEGTK22rBIIQ231X2vJwMI/1DWbm5bC0T/NjK70LfqC9IZIaIzvFO/0c1Oe2z\\nzSOAv+OxXM7Koko+dPsguzVM8DysPxistY4venSbNMMEfeAUDOoJc6I5y/XdZ8QG\\nibs4VYzEfe8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBDGmtyvcLOgeMKH5yK\\nKXIAhV3gkWT8PP/cM49jh7wsy5CJ1Sa++tki8xo1OHmwG8WZkp8AhlCmRCAhqa4d\\nWKNJMR/2baFbdj3CIPsKciOacoe+0fwA+rPqT4JKzCiZ0LtAtf4+YZn5tOMQcVhA\\nd/o16jugqLU5Cvdyf5iz42p67bCKInaR+JQNkJQv99cjJbLxHkOdke9HGL3YcZYY\\nqCJ4YY/UQbfUZGQr6gXxuYIqyIwHUOhVRdlakFQESVOz8lbgvkFhs5xKETgg/6A1\\nYQSyCgYt/r6W0YarDGW9TTTnOT1/bioGxaMs4jHtCF6quUpJ2lWiY25MXlNvKa9v\\nCvdM\\n-----END CERTIFICATE REQUEST-----\\n', 'apiProvCert': '-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIULl5w9Xdg+aoq0YvbrhTXrpB6pIIwDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUFFRmI3YTY5\\nNjIwMWI1NzdmN2MwMjMzOTIzZTRiNTgzOTCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBAM3Wdas6UP17Ib7HXnSIBvCx9U4Z5FbRuCRkP3fmMj7J0XmY2mIm\\ndEB+Z0Z+UYOoNYOENoJxlKPAfW1f6BrT8W1Sz/Md1QEas2PVvDEVRnEvwtEyuquS\\nsmEfsl9zGZJJI17uolsutMTdiSCV0eNxlEwSS4rnOVAvJx1NlC8+4GitR4x6y5T6\\nkbn70XTRCwI1XNmX/OpNAQCCTF4tEGTK22rBIIQ231X2vJwMI/1DWbm5bC0T/NjK\\n70LfqC9IZIaIzvFO/0c1Oe2zzSOAv+OxXM7Koko+dPsguzVM8DysPxistY4venSb\\nNMMEfeAUDOoJc6I5y/XdZ8QGibs4VYzEfe8CAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSj\\nU/uJ2mE3Vy2+cTPwR2uLYsQvkzAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFBRUZiN2E2OTYyMDFiNTc3ZjdjMDIzMzkyM2U0YjU4\\nMzkwDQYJKoZIhvcNAQELBQADggEBAG2+v/AB4L8ruUwo7jrb5l7ska4DlXWstx2O\\n8VOECWa7fXuqvptIW3I/ny4BVS0365SPbr4HdimH4b4G9Im2DBNHzgOgvptPOeUy\\nWBkn9tcdUkISiwdiqlLT4jDgbD+1/ptTVEfdg8Q5xr51NnNPz+XLZjnuAr85/7me\\nxZroCJ4flALdJIJIcBxusQCiQf2TOes3IKK94bTrrq4bAwgzUsQESONwgTinzGa/\\nsQvPD84LtD1RsCmXVbNZsbEHPU9aEiZMHyV7OtObQbrG6GVqEL9lfAexTG3U3um6\\n3lm5L5LxrPXM4CFcgIFUWWd2z7fhuUvOJ1zLY0mV77m3bagw0hA=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----'}, 'apiProvFuncRole': 'AEF'}, {'apiProvFuncId': 'APF5590794dcb278ebcf3d9325f547bee', 'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQVBGMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALW77hyKkmxG\\nHsxPmmwQe//aYINqdTv8TKox8ZqH8ZfetvrpdCKo26qS2KGJ/THmpQujdoSbqdlD\\nUR3ZbeI75Myj4picdqdBmKD7BcPIfKHdol8rC67f9o2iVzU9eVygzqrFjk8Cmlmh\\nOZuURwNM+80bxlq1iTJNBmCx8NGcvejC6IzddMFDSE2gE1cUcez5wSk/OqvnL4sv\\nktTIosY002a1imCCOFaez2SgKBhsmuSjCFKxA5MKIKIg/6cFnjvYZ4TLGvgjYAzz\\nmoqJgdWBDcmRolsmaD46anPrm5bnzsQNqIreyx4+Pn2AXQqJKtpMWXODYfSZOygm\\nwHuWxCpCxQsCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQApUoo3ZXsqvfPVeGFz\\nwPuG8oUTmcPzRT9jt27zsxPl02wVNU+ah5r6gENKUWF1YNHus1YOCEwkypFlTje2\\nlM1XnrGZ16kGmJOHs/K+CUCaRBOvQNJNCdhF6O56u0cT99p6E1MBtNARdITrC1Pf\\no2UE81ixqNmbYvUUTf6HfvLwMj7y9/ZflW777dY8/X4JqP8b7EcBQj26LcnfZSo8\\nbyVj9Dqm1aFAhqKg5z4/DNnTVWKo+hioK6/ML37aQqwCjHGtYhv5onB/tV5UVAHj\\n/XSjBDEkZuGntuQHJfGy82S2W+8sWQlY/DY+UbuLehPpDJQljj0AO4P6FIltbupo\\nYAyl\\n-----END CERTIFICATE REQUEST-----\\n', 'apiProvCert': '-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIUVIf6wTIfDvMffnpLaSGeVqXrujAwDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUFQRjU1OTA3\\nOTRkY2IyNzhlYmNmM2Q5MzI1ZjU0N2JlZTCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBALW77hyKkmxGHsxPmmwQe//aYINqdTv8TKox8ZqH8ZfetvrpdCKo\\n26qS2KGJ/THmpQujdoSbqdlDUR3ZbeI75Myj4picdqdBmKD7BcPIfKHdol8rC67f\\n9o2iVzU9eVygzqrFjk8CmlmhOZuURwNM+80bxlq1iTJNBmCx8NGcvejC6IzddMFD\\nSE2gE1cUcez5wSk/OqvnL4svktTIosY002a1imCCOFaez2SgKBhsmuSjCFKxA5MK\\nIKIg/6cFnjvYZ4TLGvgjYAzzmoqJgdWBDcmRolsmaD46anPrm5bnzsQNqIreyx4+\\nPn2AXQqJKtpMWXODYfSZOygmwHuWxCpCxQsCAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRB\\nOpj37DyD3EoqrOLAqK5ti6avPjAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFBUEY1NTkwNzk0ZGNiMjc4ZWJjZjNkOTMyNWY1NDdi\\nZWUwDQYJKoZIhvcNAQELBQADggEBAGFhndkgIC2WYjbOpXI83HYtz+DaOCN1b2FQ\\nCfTYPU7+UlMqY7gokEZXaQPsABpV+T/iZDuAGyDbhJLQ8qtk4e6Zpa+d/J83AhyU\\n3b8qauPSWpTVh6TWH9+Io2WlAhLSFqREHCrBWdcbij0nTfT4MmkTHRj6uTf45RQb\\nKrnA5vYNtauJ9Pt/lLUHQd5YcVxGK2vCGTEzmoZn5L28SQLZB6dnA6mEZd/hdfYC\\nbEDGOxfq5Mhoxygzu1N+cF0F6ykI2fTpwM2xKkOyvmaEAoHRRXSgpZFXN7NZxCWM\\nxSFNtGN3UwveVmJfIQENQAxX7YVOKVYarrtnUt9Xn/Da4HiZVis=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----'}, 'apiProvFuncRole': 'APF'}], 'apiProvDomInfo': 'MECSandbox_to_CAPIF_Provider', 'suppFeat': 'fff', 'failReason': 'string'}\n", + "2025-03-14 06:31:39,558 - __main__ - DEBUG - >>> build_publish_api_from_mec_services: p_aefId=AEFb7a696201b577f7c0233923e4b5839\n", + "2025-03-14 06:31:39,559 - __main__ - DEBUG - <<< build_publish_api_from_mec_services: {'apiName': 'MEC Profile for CAPIF', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004Z', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string', 'apiStatus': {'aefIds': ['AEFb7a696201b577f7c0233923e4b5839']}}\n", + "2025-03-14 06:31:39,559 - __main__ - DEBUG - publish_api_req_body: {'apiName': 'MEC Profile for CAPIF', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004Z', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string', 'apiStatus': {'aefIds': ['AEFb7a696201b577f7c0233923e4b5839']}}\n", + "2025-03-14 06:31:39,560 - __main__ - DEBUG - >>> publish_capif_api\n", + "2025-03-14 06:31:39,560 - __main__ - DEBUG - publish_capif_api: url=https://lab-oai.etsi.org:443/published-apis/v1/APF5590794dcb278ebcf3d9325f547bee/service-apis\n", + "2025-03-14 06:31:39,561 - __main__ - DEBUG - publish_capif_api (step1): headers: {'Content-Type': 'application/json'}\n", + "2025-03-14 06:31:39,561 - __main__ - DEBUG - publish_capif_api (step2): body: {'apiName': 'MEC Profile for CAPIF', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004Z', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string', 'apiStatus': {'aefIds': ['AEFb7a696201b577f7c0233923e4b5839']}}\n", + "2025-03-14 06:31:39,561 - __main__ - DEBUG - >>> store_certificate_2_files\n", + "2025-03-14 06:31:39,562 - __main__ - DEBUG - publish_capif_api (step3): bundle: ['p_crt.crt', 'p_key.key', 'ca_root.pem']\n", + "2025-03-14 06:31:39,592 - __main__ - DEBUG - publish_capif_api (step4): result: {'apiName': 'MEC Profile for CAPIF', 'apiId': 'a55b2ecc0ee7a2ff0d5b7da3a63dd2', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004000+00:00', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string'}\n", + "2025-03-14 06:31:39,593 - __main__ - DEBUG - publish_capif_api (step5): res: {'apiName': 'MEC Profile for CAPIF', 'apiId': 'a55b2ecc0ee7a2ff0d5b7da3a63dd2', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004000+00:00', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string'}\n", + "2025-03-14 06:31:39,594 - __main__ - DEBUG - publish_api: ('7d1e7acfc6bb19a31614da0968a8f8', ('-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIUXWNSwZhcDK6gbO6bers6UlaK+kkwDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUFNRjRiMmNl\\nYjJjZTg0MTQ4YWE1NmE4OGEwN2Q5YWI5YzCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBALAZiWweyau3TA1n7ROfuRWd/6i92qdLTthqy+Pd4CCglaOXSmYx\\n9fcaupvmEB4XcOhodl7nIxmr1408bIyG0rs+jNZa/sdBVo9AtEBb3Ini/PONiDIQ\\n5Zdql7esYfXGEIY6+VcfJgjO0yWkRZWYg+04fH3vxasPyLihCuNU07SK9hbh8IsW\\nq/svq4tPWtj/Cez7tES1pNmilYg2zBmbZSlz8IGnz6Y/w8KTQtadQ154b0n+1k1J\\nCi6vl5Fpp3ALE8Pnxp/airx9ngscnxbZQZgFV8tXQaf2/OfQYaKe7eq93Ap4o778\\nwGVzjBxAVTYQEQFalUWmMPT0+8DR2pD63qECAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQf\\n6A3W+VuniqRJYpMK89aTSeVPVDAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFBTUY0YjJjZWIyY2U4NDE0OGFhNTZhODhhMDdkOWFi\\nOWMwDQYJKoZIhvcNAQELBQADggEBAKxQV3StdM8orcqILKtomgcot9ieq+4ijyRm\\nD86Y7pKLSPRAtVgR5IAfQHmGH1/OSOskXL2JW+4EL3qHAmLuka3pnCj8d8etgxKb\\nSKD4yUTRZ/yiIcd2gB5/ykUIOeTdghzzf2yI0x5SYHtpYupmi+FP6twhMxnP8IFh\\nZ3qaYMq1rx4g8riBQz7i9yVby5kc4Na6eUG7V/J/qBL/ONrsvVYW9YBRn6uaFNJJ\\nEAHsRZWAcBy+6NaMToqzp7lC4Pslwfv9YtaythQCguGGz5P7QTc2CrAFO/MDDoM/\\nDvkdUs0skrY+VBjInWpML6e+DJ2sRRC8PJTIjAk1Qgeoxlqnaqo=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----', b'-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCwGYlsHsmrt0wN\\nZ+0Tn7kVnf+ovdqnS07Yasvj3eAgoJWjl0pmMfX3Grqb5hAeF3DoaHZe5yMZq9eN\\nPGyMhtK7PozWWv7HQVaPQLRAW9yJ4vzzjYgyEOWXape3rGH1xhCGOvlXHyYIztMl\\npEWVmIPtOHx978WrD8i4oQrjVNO0ivYW4fCLFqv7L6uLT1rY/wns+7REtaTZopWI\\nNswZm2Upc/CBp8+mP8PCk0LWnUNeeG9J/tZNSQour5eRaadwCxPD58af2oq8fZ4L\\nHJ8W2UGYBVfLV0Gn9vzn0GGinu3qvdwKeKO+/MBlc4wcQFU2EBEBWpVFpjD09PvA\\n0dqQ+t6hAgMBAAECggEARIx8QPkvGcfNM/67e4MWQgLx7RaSUcLv2zCfU4Ef6LN5\\n7GdND8Ds9RaTg3In3f7T9bQMN98Te7+3tYPK4VzuFPNBUYO0X43givzt7YuUqpwQ\\nSSJT3OFU7f8RNe9FZq2F6ypzuuUmIhGYgbOTXqsy15nAZCl8sZ0ATlZp7Zosmr9C\\nxgHNJtBlieTsb40unETw097OKqvJV4JEWQQsvLgnsrIuKFC21d7efid09uy7O/Dm\\nYOAQI+zupQrtBWCZlxNdB0KDAFX1xjisVnunq4fPmp1uQQ+CUgDHZaTlRJ6awKjM\\ns20SP2eg9YYiygwV/6luWsB6OZOKyQe2x6JbGEnejQKBgQDs8ZyBtvSDomENiD1t\\niSq/DwExoaAB5+2a2kCwDPqIAITswgZqXTgQUK+LkFCZqj6brZ+TQaDRQCLhR+WU\\n23XET3OdywMex/fwH8URaOAdAXtsgQXqi92ZSDb9Y0h5mPE5QHFR8S4Z2rBpTjvc\\nDaHvRQW6LpTG9U/XpN8Nsa6Z8wKBgQC+Qzg3I43Wo9LCIgBaspZLwxlJdBdz4zsE\\nQpAROQYXds47bVsPQqaerCR9a259KZUhfmxwAxEH86lmsot6iPktMIWtg6O7G8jB\\nyWyVxA4f4J2t2g9RIkUG4HOkJ4lG/9QwRM7GtzMqlu8BXd5rp0wF5aWToZJmG/+o\\nWv8LqgNWGwKBgA8wObES8j+R5BjC9/USitvucUwmKSSWImP/w4/FdTXMmri2g0TE\\nLvjUwzv2B0SiZhsCmS0OUum268V3H19YZgcsdKPTxKJvfRQ2ZSKFj4AsfSXqa1+q\\nkYzm1SeO+rFYvXXHDLyM8kCUBSTq7+leMlgtG33gyIasaO7Q0b+F+URDAoGAJQeK\\nIlkeGt8pfrUFYqGNVNN00XHxupFIBC08Qs3ZXKm3u6Mt7MtpCEqyk2JcT6nPJ4Sm\\ncCp4nxaSKrWnJcMQ0/W0nq+XJyxiJy0foApXQWcC6Toebwj/bDY064byVcomrvpF\\nUDGJmMllXNu7FTKrPh6S2ifBCXR7jnWfW9LL8W8CgYAkItXeo5t/cbXjtp87WdIV\\nU+AXb4hbXdm++WQjV+m3PxkPAukHYaxZzHSxVv64urW5YdA9Xv9seJE++KRlpD+a\\nXvELsyJ/lScLp5YQoWcuNpisqkKn72dJWcGZHhfvd8GaFtIaxJ/Krtn2FO39lxjX\\nLdIp77vh3SB7KlsV7AKqwQ==\\n-----END PRIVATE KEY-----\\n'))\n", + "2025-03-14 06:31:39,594 - __main__ - DEBUG - >>> process_login\n", + "2025-03-14 06:31:39,595 - __main__ - DEBUG - process_login: url=https://lab-oai.etsi.org:31120/login\n", + "2025-03-14 06:31:39,595 - __main__ - DEBUG - process_login (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Basic YWRtaW46cGFzc3dvcmQxMjM='}\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:39,639 - __main__ - DEBUG - process_login (step2): result: {'access_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQxOTM0NDk5fQ.wNjsuLgh61j0G_6rtN6vxqfbJGMgtaetJd1xPUOoBb4', 'refresh_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQ0NTI1ODk5fQ.lgD4IZCAVJ3-smu1nFBjTtdhTW_trraua1OppvQd4rc'}\n", + "2025-03-14 06:31:39,640 - __main__ - DEBUG - >>> create_user\n", + "2025-03-14 06:31:39,640 - __main__ - DEBUG - create_user: url=https://lab-oai.etsi.org:31120/createUser\n", + "2025-03-14 06:31:39,641 - __main__ - DEBUG - create_user (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQxOTM0NDk5fQ.wNjsuLgh61j0G_6rtN6vxqfbJGMgtaetJd1xPUOoBb4'}\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:39,673 - __main__ - DEBUG - create_user (step2): response=<Response [201]>\n", + "2025-03-14 06:31:39,674 - __main__ - DEBUG - >>> get_auth\n", + "2025-03-14 06:31:39,674 - __main__ - DEBUG - get_auth: url=https://lab-oai.etsi.org:31120/getauth\n", + "2025-03-14 06:31:39,675 - __main__ - DEBUG - get_auth (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Basic ZmM5YjlhZWMtMDA5ZC0xMWYwLWI3MDYtMDI0MmFjMTEwMDAyOnBhc3N3b3JkMTIz'}\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:39,760 - __main__ - DEBUG - get_auth (step2): result: {'access_token': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiNmEwYzAwMGMtODM1OS00Zjk2LTg2YmYtZWM0NmU0MWI4ZWVlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjOWI5YWVjLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA0MTFiOWM0MC1mZTUwLTUyN2EtOWU5OS1kMjU1ZWVkODg1OWIiLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiIxMzkyN2UyMC1mYzA3LTQwYTgtYTM1Ni1lNjBmOTRmN2NmYzgiLCJleHAiOjE3NDE5MzQ3OTl9.ajpnFHBQdSMKmy1s5NZVFaYP6ST3e54MpG_6-ExKIvuZ3vnbqae7Y4kRMOOh4wO6NEEoZcAkb7et4spws16q_r4uuonyh5lmXFJIp63wcF1x94Yw4P51y6Qu9lSpAhZuznF-AhgVoFhtK7BI5z37iqiiTywI9HgrEHfbpll2AD5HaB5MVbmjCiFsADdixK4gisf8FRpxdBUdaCpX1pDJsEAIPhRtWt-6ZYBZhrXffNVSoRyA_Pda8fglLiKMk8lWMPYgePYQ4LntE5Hr3K-_OdxstjLyMkHtnmFHlU02fOLSQxbqJEcCBBcTZfxPHLlGbltcXkt8iHeqFKMuK5-h7w', 'ca_root': '-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n', 'ccf_api_onboarding_url': 'api-provider-management/v1/registrations', 'ccf_discover_url': 'service-apis/v1/allServiceAPIs?api-invoker-id=', 'ccf_onboarding_url': 'api-invoker-management/v1/onboardedInvokers', 'ccf_publish_url': 'published-apis/v1/<apfId>/service-apis', 'ccf_security_url': 'capif-security/v1/trustedInvokers/<apiInvokerId>', 'message': 'Token and CA root returned successfully'}\n", + "2025-03-14 06:31:39,761 - __main__ - DEBUG - >>> onboard_invoker: API Invoker\n", + "2025-03-14 06:31:39,761 - __main__ - DEBUG - >>> onboard_invoker: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiNmEwYzAwMGMtODM1OS00Zjk2LTg2YmYtZWM0NmU0MWI4ZWVlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjOWI5YWVjLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA0MTFiOWM0MC1mZTUwLTUyN2EtOWU5OS1kMjU1ZWVkODg1OWIiLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiIxMzkyN2UyMC1mYzA3LTQwYTgtYTM1Ni1lNjBmOTRmN2NmYzgiLCJleHAiOjE3NDE5MzQ3OTl9.ajpnFHBQdSMKmy1s5NZVFaYP6ST3e54MpG_6-ExKIvuZ3vnbqae7Y4kRMOOh4wO6NEEoZcAkb7et4spws16q_r4uuonyh5lmXFJIp63wcF1x94Yw4P51y6Qu9lSpAhZuznF-AhgVoFhtK7BI5z37iqiiTywI9HgrEHfbpll2AD5HaB5MVbmjCiFsADdixK4gisf8FRpxdBUdaCpX1pDJsEAIPhRtWt-6ZYBZhrXffNVSoRyA_Pda8fglLiKMk8lWMPYgePYQ4LntE5Hr3K-_OdxstjLyMkHtnmFHlU02fOLSQxbqJEcCBBcTZfxPHLlGbltcXkt8iHeqFKMuK5-h7w\n", + "2025-03-14 06:31:39,762 - __main__ - DEBUG - onboard_invoker: url=https://lab-oai.etsi.org:443/api-invoker-management/v1/onboardedInvokers\n", + "2025-03-14 06:31:39,763 - __main__ - DEBUG - onboard_invoker (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiNmEwYzAwMGMtODM1OS00Zjk2LTg2YmYtZWM0NmU0MWI4ZWVlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjOWI5YWVjLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA0MTFiOWM0MC1mZTUwLTUyN2EtOWU5OS1kMjU1ZWVkODg1OWIiLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiIxMzkyN2UyMC1mYzA3LTQwYTgtYTM1Ni1lNjBmOTRmN2NmYzgiLCJleHAiOjE3NDE5MzQ3OTl9.ajpnFHBQdSMKmy1s5NZVFaYP6ST3e54MpG_6-ExKIvuZ3vnbqae7Y4kRMOOh4wO6NEEoZcAkb7et4spws16q_r4uuonyh5lmXFJIp63wcF1x94Yw4P51y6Qu9lSpAhZuznF-AhgVoFhtK7BI5z37iqiiTywI9HgrEHfbpll2AD5HaB5MVbmjCiFsADdixK4gisf8FRpxdBUdaCpX1pDJsEAIPhRtWt-6ZYBZhrXffNVSoRyA_Pda8fglLiKMk8lWMPYgePYQ4LntE5Hr3K-_OdxstjLyMkHtnmFHlU02fOLSQxbqJEcCBBcTZfxPHLlGbltcXkt8iHeqFKMuK5-h7w'}\n", + "2025-03-14 06:31:39,763 - __main__ - DEBUG - >>> generate_csr\n", + "2025-03-14 06:31:39,833 - __main__ - DEBUG - generate_csr: PrivKey: b'-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGj82/J4u6FSpP\\nSVh1FyO5gjjWJOlT3C9f+z1ZrC3QDSILgHiQlVOBVpaBnw4QWpIwFRnX+nHlL5N0\\njfDWvtqfxp1m3y4PGMv4DV9MRVXSITEnLA/V6hV7FzQkabYdaS4FDGWh7U5Gc2KJ\\n1Sddo5RlYxaH+NIiV9rCvu2frWJ8Ol+2EJwRJgXoBHNtEKkGm+P7IeT+UuPPUiF/\\nRRwgYDd530stLMqYCMTpxFwnxHuLpB1ylPgh7Qdvfg9qylTFndETKpUKkFbn4rvE\\nV939ekBor/Q0nAYXgkhOrbJqLk7LGspFNqC4fFBKgEueeVScIsWll57EDNb/UVAF\\nSYvsvS7NAgMBAAECggEAMEM83cUlc834MjxgKVm9/7W0zew74HvI8hn1sAmnOwYu\\nK8CGzZHWMb5Tp976wqPZG6HNXc9Mhsn5G9/yVagfKK3UjXAa2GWNElZhvv6tXP1f\\nXqeZk8Opg/lAXcdqEh0CKAz62RB+saYTIfBy+JItnDUpAVTL8rgK/sH8rQ6uPQZf\\ngUjXUKpLYrb79IrdYq1qrR6wfNDjru+OrFum/2HpQMDg4abo1whwWsp5nlK/tryj\\nQHN8oieZghg02SpiMwuYkAtAPuQFgN6ENSva08iddxUavO5pHf5BqyUhW2bjaVoo\\nYLT7sr5gx8vhn9h7/+s3H33/Lw+RCvKwdVYueGBOywKBgQDt8DBEIQhHZbSf81Mz\\nCvJpS5DioBQIiw3fmsIToE33bLD19LJNP1IqEilYvnUvC5ctXB9rM3WJ4Fx7uHo2\\ndQMq+/HyGKJNz38pK03/k6ABb+ZNbdI3Qq+4t5SDDwtEui3tGdTGzFFTvNrWuYGg\\n4yIkv0w9gr3STTn4+0MGELf3kwKBgQDVomsnTr1HP1StPh7JcCeMkGIYyW6J4wrZ\\nx3uUI5XR2/xyaaqjToUZu9BUZ0l261mGAsru8fNMIVT46Z3vekVeBuVzBxMg8CW3\\nANKmRqJfLr6MpyBdUyIRkpHUGvx9CnazGSbx+hWB7BNoBqyjRIiow05uNx2hjfnw\\noKONzCl8HwKBgQCJ8rNJDI2sNz8dbQlTkokwmusJOR3kRhppBWR31Hzfcli2gIPP\\nXWLZmWX3WZS/Dc08MyjUEiWXJkj4QeA5KmYHycJgRf/zdNWYnM6/2mrt6l5vjbhO\\n6Y7PXT/xLAuwcPCngk5mY5bTIa8OxsZs7MKi43XkQ6SiBLwTqjkVjyPZmQKBgHJ1\\nr+WCiWTn6I1dcA9LONVV8kkHe2MDMygVef+XxUiIDcybEqKmiieMegUOxcyiMffb\\n/TBij5EldqpaOJU7NHk5RqwHiVcnc32GQlZ2F77Zg5xGWs/Fn8Y8ekdjIg44kfpJ\\nKpWRAP74JjmhAdQD/xg0dAwXGZgaQmSLHLX3Qe7PAoGBANvuaZcN0umxqEYfq8mA\\nhmQ0WJuxaJw42Y2qLnx492kKA4aTODAVzUfkmuWFoIrw+0ivmXS6GUpz1iejSXce\\n3CNw/7Z8b40lnbZN9gVD3YiWLhFZMV7FUtYk5uNuOuXAeoKP9ydFtWANShrtIqFJ\\nyW94au/ArID/+5GvRJt7Taz/\\n-----END PRIVATE KEY-----\\n'\n", + "2025-03-14 06:31:39,834 - __main__ - DEBUG - onboard_invoker (step2): body: {'notificationDestination': 'http://host.docker.internal:8086/netapp_callback', 'supportedFeatures': 'fffffff', 'apiInvokerInformation': 'dummy', 'websockNotifConfig': {'requestWebsocketUri': True, 'websocketUri': 'websocketUri'}, 'onboardingInformation': {'apiInvokerPublicKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICdzCCAV8CAQAwMjEUMBIGA1UEAwwLQVBJIEludm9rZXIxDTALBgNVBAoMBEVU\\nU0kxCzAJBgNVBAYTAkZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\\nxo/NvyeLuhUqT0lYdRcjuYI41iTpU9wvX/s9Wawt0A0iC4B4kJVTgVaWgZ8OEFqS\\nMBUZ1/px5S+TdI3w1r7an8adZt8uDxjL+A1fTEVV0iExJywP1eoVexc0JGm2HWku\\nBQxloe1ORnNiidUnXaOUZWMWh/jSIlfawr7tn61ifDpfthCcESYF6ARzbRCpBpvj\\n+yHk/lLjz1Ihf0UcIGA3ed9LLSzKmAjE6cRcJ8R7i6QdcpT4Ie0Hb34PaspUxZ3R\\nEyqVCpBW5+K7xFfd/XpAaK/0NJwGF4JITq2yai5OyxrKRTaguHxQSoBLnnlUnCLF\\npZeexAzW/1FQBUmL7L0uzQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAAnTqKJS\\n5JCzYIHQ4J31iZzwq5Omg4z4SbowzQl41AnQmPDt5zRkZzhwM1HjAus4Qt0v8M/7\\nwe8BPlYQyRZbHF9HN0Gvqe61E7IAVtftfGdmEMwZigRUJ3wfwxF92a66JMg2IcV0\\nsHzsApr8XxEy7slzjINEDWHUmVEBrLsO9HTE7otGBPdbUQ+NheOYTQKxXUkcDKf5\\nmC1OG4xHWqm1vuxcnbXi1Bc7PHNbiSBckPyPgrGwMYrmSimVeSIJA6YzKGbSozdG\\n73b3ZPO6mCV3qXElgeq+igBC7pCo5EbsSOD0lyJX1Tu3EMo2Rvix5xwI5yx3MpQ2\\nC1iHUqsOd0yNbic=\\n-----END CERTIFICATE REQUEST-----\\n'}, 'requestTestNotification': True}\n", + "2025-03-14 06:31:39,867 - __main__ - DEBUG - onboard_invoker (step3): result: {'apiInvokerId': 'INV87e7a6dbc97d1fd9478862f6bd2ce5', 'onboardingInformation': {'apiInvokerPublicKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICdzCCAV8CAQAwMjEUMBIGA1UEAwwLQVBJIEludm9rZXIxDTALBgNVBAoMBEVU\\nU0kxCzAJBgNVBAYTAkZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\\nxo/NvyeLuhUqT0lYdRcjuYI41iTpU9wvX/s9Wawt0A0iC4B4kJVTgVaWgZ8OEFqS\\nMBUZ1/px5S+TdI3w1r7an8adZt8uDxjL+A1fTEVV0iExJywP1eoVexc0JGm2HWku\\nBQxloe1ORnNiidUnXaOUZWMWh/jSIlfawr7tn61ifDpfthCcESYF6ARzbRCpBpvj\\n+yHk/lLjz1Ihf0UcIGA3ed9LLSzKmAjE6cRcJ8R7i6QdcpT4Ie0Hb34PaspUxZ3R\\nEyqVCpBW5+K7xFfd/XpAaK/0NJwGF4JITq2yai5OyxrKRTaguHxQSoBLnnlUnCLF\\npZeexAzW/1FQBUmL7L0uzQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAAnTqKJS\\n5JCzYIHQ4J31iZzwq5Omg4z4SbowzQl41AnQmPDt5zRkZzhwM1HjAus4Qt0v8M/7\\nwe8BPlYQyRZbHF9HN0Gvqe61E7IAVtftfGdmEMwZigRUJ3wfwxF92a66JMg2IcV0\\nsHzsApr8XxEy7slzjINEDWHUmVEBrLsO9HTE7otGBPdbUQ+NheOYTQKxXUkcDKf5\\nmC1OG4xHWqm1vuxcnbXi1Bc7PHNbiSBckPyPgrGwMYrmSimVeSIJA6YzKGbSozdG\\n73b3ZPO6mCV3qXElgeq+igBC7pCo5EbsSOD0lyJX1Tu3EMo2Rvix5xwI5yx3MpQ2\\nC1iHUqsOd0yNbic=\\n-----END CERTIFICATE REQUEST-----\\n', 'apiInvokerCertificate': '-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIUbRfvEE4B2gIGKB4FwwQWAKNT/rowDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUlOVjg3ZTdh\\nNmRiYzk3ZDFmZDk0Nzg4NjJmNmJkMmNlNTCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBAMaPzb8ni7oVKk9JWHUXI7mCONYk6VPcL1/7PVmsLdANIguAeJCV\\nU4FWloGfDhBakjAVGdf6ceUvk3SN8Na+2p/GnWbfLg8Yy/gNX0xFVdIhMScsD9Xq\\nFXsXNCRpth1pLgUMZaHtTkZzYonVJ12jlGVjFof40iJX2sK+7Z+tYnw6X7YQnBEm\\nBegEc20QqQab4/sh5P5S489SIX9FHCBgN3nfSy0sypgIxOnEXCfEe4ukHXKU+CHt\\nB29+D2rKVMWd0RMqlQqQVufiu8RX3f16QGiv9DScBheCSE6tsmouTssaykU2oLh8\\nUEqAS555VJwixaWXnsQM1v9RUAVJi+y9Ls0CAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQ2\\nOXQyNZNJpml3Xyv+rWt333ETmTAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFJTlY4N2U3YTZkYmM5N2QxZmQ5NDc4ODYyZjZiZDJj\\nZTUwDQYJKoZIhvcNAQELBQADggEBAEAL/HW76QNK6eQsomekoYQA2mxlZQ+k216s\\nSxuE6wl+WEgi6SEZyAx3/ZhELLF2SUtURQvfm+RuDbgUMK1kWX4RBRm/8hXgwKyu\\n0H450FF+FwKhE08qMpfbDf4uv5yrGpTdtuLt+g90iXV6Dq2IzJeWiQ7dzXhqVxfX\\nfX/f81S0mbjbnaTSum/OZjJDf+p1lacTdcNLVKgP1YjJkt8qtLJz5UO4rNza4mka\\nSDwyVKyIEgz0OEnc5SuA6A99L4YMI0oZ4vpicvWEERzybyfOHn4M1TG1yHFM+dUJ\\nBv6pbo4mQhNsJ0AIUOgQmoVO2gnW/zNclmLmN3dh8Vknz4gWWRE=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----'}, 'notificationDestination': 'http://host.docker.internal:8086/netapp_callback', 'requestTestNotification': True, 'websockNotifConfig': {'websocketUri': 'websocketUri', 'requestWebsocketUri': True}, 'apiInvokerInformation': 'dummy', 'supportedFeatures': 'fffffff'}\n", + "2025-03-14 06:31:39,868 - __main__ - DEBUG - >>> Discover APIs published by capif core\n", + "2025-03-14 06:31:39,869 - __main__ - DEBUG - Discover: url=https://lab-oai.etsi.org:443/service-apis/v1/allServiceAPIs?api-invoker-id=INV87e7a6dbc97d1fd9478862f6bd2ce5\n", + "2025-03-14 06:31:39,869 - __main__ - DEBUG - Discover (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiNmEwYzAwMGMtODM1OS00Zjk2LTg2YmYtZWM0NmU0MWI4ZWVlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjOWI5YWVjLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA0MTFiOWM0MC1mZTUwLTUyN2EtOWU5OS1kMjU1ZWVkODg1OWIiLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiIxMzkyN2UyMC1mYzA3LTQwYTgtYTM1Ni1lNjBmOTRmN2NmYzgiLCJleHAiOjE3NDE5MzQ3OTl9.ajpnFHBQdSMKmy1s5NZVFaYP6ST3e54MpG_6-ExKIvuZ3vnbqae7Y4kRMOOh4wO6NEEoZcAkb7et4spws16q_r4uuonyh5lmXFJIp63wcF1x94Yw4P51y6Qu9lSpAhZuznF-AhgVoFhtK7BI5z37iqiiTywI9HgrEHfbpll2AD5HaB5MVbmjCiFsADdixK4gisf8FRpxdBUdaCpX1pDJsEAIPhRtWt-6ZYBZhrXffNVSoRyA_Pda8fglLiKMk8lWMPYgePYQ4LntE5Hr3K-_OdxstjLyMkHtnmFHlU02fOLSQxbqJEcCBBcTZfxPHLlGbltcXkt8iHeqFKMuK5-h7w'}\n", + "2025-03-14 06:31:39,870 - __main__ - DEBUG - >>> store_certificate_2_files\n", + "2025-03-14 06:31:39,871 - __main__ - DEBUG - Discover (step2): bundle: ['p_crt.crt', 'p_key.key', 'ca_root.pem']\n", + "2025-03-14 06:31:39,900 - __main__ - DEBUG - Discover (step3): response=<Response [200]>\n", + "2025-03-14 06:31:39,901 - __main__ - DEBUG - Discover : result: {'serviceAPIDescriptions': [{'apiName': 'MEC Profile for CAPIF', 'apiId': 'a55b2ecc0ee7a2ff0d5b7da3a63dd2', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004000+00:00', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string'}]}\n", + "2025-03-14 06:31:39,902 - __main__ - DEBUG - addrs: {'ipv4Addr': ['try-mec.etsi.org'], 'uri': ['/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs']}\n", + "2025-03-14 06:31:39,902 - __main__ - DEBUG - >>> discovering_mec_services: https://try-mec.etsi.org/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs\n", + "2025-03-14 06:31:39,903 - __main__ - DEBUG - discovering_mec_services (step1): headers: {'Content-Type': 'application/json', 'accept': 'application/json'}\n", + "2025-03-14 06:31:39,930 - __main__ - DEBUG - discovering_mec_services (step2): result: {'serviceAPIDescriptions': [{'apiName': 'mec016-1', 'apiId': 'fa5b0b09-c2e6-4bf6-b7f2-8853132a50da', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/dev_app/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'daiId', 'name': 'DAI', 'version': 'v1'}}}, {'apiName': 'mec015-1', 'apiId': '70ff5514-1479-4746-a867-406b904962fe', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/bwm/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'bwmId', 'name': 'BWM', 'version': 'v1'}}}, {'apiName': 'mec013-1', 'apiId': 'fb18ea66-b93c-4b4d-9d6f-9ed197355ad6', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['3.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/location/v3/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'locationId', 'name': 'Location', 'version': 'v2'}}}, {'apiName': 'mec028-1', 'apiId': '9c69f4b0-77e8-4cb3-ad8f-b666f38f2560', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/wai/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'waiId', 'name': 'WAI', 'version': 'v2'}}}, {'apiName': 'mec012-1', 'apiId': '89e1b639-0780-4fd1-9c12-fe771e0cbafb', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/rni/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'rniId', 'name': 'RNI', 'version': 'v2'}}}, {'apiName': 'mec015-1', 'apiId': '9ee651cc-2eb5-4db8-8c57-40036889835f', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/mts/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'mtsId', 'name': 'MTS', 'version': 'v1'}}}, {'apiName': 'mec030-1', 'apiId': 'f304467b-a269-4647-9aca-67df18b3b5b2', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['3.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/vis/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'visId', 'name': 'V2XI', 'version': 'v2'}}}]}\n", + "2025-03-14 06:31:39,932 - __main__ - DEBUG - ===> The list of the MEC services exposed by try-mec.etsi.org is: {'serviceAPIDescriptions': [{'apiName': 'mec016-1', 'apiId': 'fa5b0b09-c2e6-4bf6-b7f2-8853132a50da', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/dev_app/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'daiId', 'name': 'DAI', 'version': 'v1'}}}, {'apiName': 'mec015-1', 'apiId': '70ff5514-1479-4746-a867-406b904962fe', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/bwm/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'bwmId', 'name': 'BWM', 'version': 'v1'}}}, {'apiName': 'mec013-1', 'apiId': 'fb18ea66-b93c-4b4d-9d6f-9ed197355ad6', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['3.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/location/v3/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'locationId', 'name': 'Location', 'version': 'v2'}}}, {'apiName': 'mec028-1', 'apiId': '9c69f4b0-77e8-4cb3-ad8f-b666f38f2560', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/wai/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'waiId', 'name': 'WAI', 'version': 'v2'}}}, {'apiName': 'mec012-1', 'apiId': '89e1b639-0780-4fd1-9c12-fe771e0cbafb', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/rni/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'rniId', 'name': 'RNI', 'version': 'v2'}}}, {'apiName': 'mec015-1', 'apiId': '9ee651cc-2eb5-4db8-8c57-40036889835f', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/mts/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'mtsId', 'name': 'MTS', 'version': 'v1'}}}, {'apiName': 'mec030-1', 'apiId': 'f304467b-a269-4647-9aca-67df18b3b5b2', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['3.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/vis/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'visId', 'name': 'V2XI', 'version': 'v2'}}}]}\n", + "2025-03-14 06:31:39,932 - __main__ - DEBUG - >>> offboard_invoker: INV87e7a6dbc97d1fd9478862f6bd2ce5\n", + "2025-03-14 06:31:39,933 - __main__ - DEBUG - offboard_invoker: url=https://lab-oai.etsi.org:443/api-invoker-management/v1/onboardedInvokers/INV87e7a6dbc97d1fd9478862f6bd2ce5\n", + "2025-03-14 06:31:39,933 - __main__ - DEBUG - offboard_invoker (step1): headers: {'Content-Type': 'application/json'}\n", + "2025-03-14 06:31:39,933 - __main__ - DEBUG - >>> store_certificate_2_files\n", + "2025-03-14 06:31:39,934 - __main__ - DEBUG - offboard_invoker (step2): bundle: ['p_crt.crt', 'p_key.key', 'ca_root.pem']\n", + "2025-03-14 06:31:39,967 - __main__ - DEBUG - offboard_invoker (step3): response=<Response [204]>\n", + "2025-03-14 06:31:39,968 - __main__ - DEBUG - >>> delete_user\n", + "2025-03-14 06:31:39,969 - __main__ - DEBUG - delete_user: url=https://lab-oai.etsi.org:31120/deleteUser/411b9c40-fe50-527a-9e99-d255eed8859b\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:40,020 - __main__ - DEBUG - delete_user: response=<Response [204]>\n", + "2025-03-14 06:31:40,021 - __main__ - DEBUG - >>> remove_publish_api \n", + "2025-03-14 06:31:40,021 - __main__ - DEBUG - >>> offboarding_provider: 5887716a-35ef-5f99-bd94-38d1fa3d0317\n", + "2025-03-14 06:31:40,022 - __main__ - DEBUG - >>> offboard_provider\n", + "2025-03-14 06:31:40,022 - __main__ - DEBUG - offboard_provider: url=https://lab-oai.etsi.org:443/api-provider-management/v1/registrations/7d1e7acfc6bb19a31614da0968a8f8\n", + "2025-03-14 06:31:40,022 - __main__ - DEBUG - offboard_provider (step1): headers: {'Content-Type': 'application/json'}\n", + "2025-03-14 06:31:40,023 - __main__ - DEBUG - >>> store_certificate_2_files\n", + "2025-03-14 06:31:40,024 - __main__ - DEBUG - offboard_provider (step2): bundle: ['p_crt.crt', 'p_key.key', 'ca_root.pem']\n", + "2025-03-14 06:31:40,050 - __main__ - DEBUG - offboard_provider (step3): response=<Response [204]>\n", + "2025-03-14 06:31:40,052 - __main__ - DEBUG - >>> delete_user\n", + "2025-03-14 06:31:40,053 - __main__ - DEBUG - delete_user: url=https://lab-oai.etsi.org:31120/deleteUser/5887716a-35ef-5f99-bd94-38d1fa3d0317\n", + "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "2025-03-14 06:31:40,089 - __main__ - DEBUG - delete_user: response=<Response [204]>\n" + ] + } + ], + "source": [ + "#%%script echo skipping\n", + "# Comment the line above to execute this cell\n", + "def process_main():\n", + " \"\"\"\n", + " This is the fiveth sprint of our CAPIF/MEC application:\n", + " - Publish the MEC profile for CAPIF API\n", + " - Create a new user for the invoker\n", + " - Get certificates\n", + " - Onboad the API invoker\n", + " - Do the discovery\n", + " - Offboard the API invoker\n", + " - Delete the\n", + " - Logout the invoker user\n", + " - Remove the MEC profile for CAPIF API\n", + " \"\"\"\n", + " global logger, ca_root, ccf_api_onboarding_url, ccf_discover_url, ccf_onboarding_url, ccf_publish_url, ccf_security_url\n", + "\n", + " logger.debug('Starting at ' + time.strftime('%Y%m%d-%H%M%S'))\n", + " logger.debug('\\t pwd= ' + os.getcwd())\n", + "\n", + " # Publish the MEC profile for CAPIF API\n", + " res = publish_api()\n", + " if len(res) == 0:\n", + " return\n", + " api_id, bundle, prov_user_uuid, prov_admin_token = res\n", + "\n", + " # Login for the new user for the invoker\n", + " refresh_token, admin_token = process_login()\n", + " if refresh_token is None:\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Create a new user for the invoker\n", + " res = create_user(admin_token)\n", + " if len(res) == 0:\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + " user_name, user_uuid = res\n", + "\n", + " # Get certificates\n", + " auth = get_auth(user_name, USER_PASSWORD)\n", + " if len(auth) == 0:\n", + " delete_user(user_name, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Sanity checks\n", + " if auth['ca_root'] != ca_root:\n", + " raise Exception('CA root mismatch')\n", + " if auth['ccf_api_onboarding_url'] != ccf_api_onboarding_url:\n", + " raise Exception('CCF API onboarding URL mismatch')\n", + " if auth['ccf_discover_url'] != ccf_discover_url:\n", + " raise Exception('CCF discover URL mismatch')\n", + " if auth['ccf_onboarding_url'] != ccf_onboarding_url:\n", + " raise Exception('CCF onboarding URL mismatch')\n", + " if auth['ccf_publish_url'] != ccf_publish_url:\n", + " raise Exception('CCF publish URL mismatch')\n", + " if auth['ccf_security_url'] != ccf_security_url:\n", + " raise Exception('CCF security URL mismatch')\n", + " access_token = auth['access_token']\n", + "\n", + " # Onboad the API invoker\n", + " res = onboard_invoker('API Invoker', access_token)\n", + " if len(res) == 0:\n", + " delete_user(user_uuid, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Do the discovery\n", + " invoker_id = res['apiInvokerId']\n", + " certs_bundle = (res['onboardingInformation']['apiInvokerCertificate'], res['csr'][1])\n", + " mec_api = discover(invoker_id, certs_bundle, access_token)\n", + " if len(mec_api) == 0:\n", + " files_bundle = offboard_invoker(invoker_id, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + " delete_user(user_uuid, admin_token)\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + " return\n", + "\n", + " # Extract the URL to access to the MEC Sandbox platform\n", + " addrs = extract_ipv4_and_uri(mec_api)\n", + " logger.debug('addrs: ' + str(addrs))\n", + "\n", + " # Discovering MEC services\n", + " url = ''\n", + " if 'ports' in addrs:\n", + " url = 'https://' + addrs['ipv4Addr'][0] + ':' + addrs['ports'][0]\n", + " else:\n", + " url = 'https://' + addrs['ipv4Addr'][0]\n", + " url += addrs['uri'][0]\n", + " mec_services = discovering_mec_services(url)\n", + " if len(mec_services) != 0:\n", + " logger.debug('===> The list of the MEC services exposed by ' + addrs['ipv4Addr'][0] + ' is: ' + str(mec_services))\n", + "\n", + " # Offboard the API invoker\n", + " files_bundle = offboard_invoker(invoker_id, certs_bundle)\n", + " if len(files_bundle) == 0:\n", + " for file in files_bundle:\n", + " os.remove(file)\n", + "\n", + " # Delete the invoker user\n", + " delete_user(user_uuid, admin_token)\n", + "\n", + " # Logout the invoker user\n", + " process_logout()\n", + "\n", + " # Remove the MEC profile for CAPIF API\n", + " remove_publish_api(prov_user_uuid, api_id, bundle, prov_admin_token)\n", + "\n", + "if __name__ == '__main__':\n", + " process_main()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cfy6D8wYt5GA" + }, + "source": [ + "### What to do next\n", + "\n", + "here is a list of several additional tasks yo can do to refine our CAPIF application:\n", + "1. Simply the code of the process_main() function above\n", + "2. Find the endpoint of the MEC Location API service (MEC 013) and send a request to get the list of zones available\n", + "3. Create your own CAPIF application" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "m_BbV7KdpX24" + }, + "source": [ + "## Conlusion: what do we learn?\n", + "\n", + "The main objective of this tutorial is to demonstrate how to use the MEC profile for CAPIF (as described in MEC 011 v3.x.x Clause 9) in a CAPIF application. Along this tutrial, we learned how to develop a basic CAPIF application, including both API provider and API invoker. We learned also how to use a published API to send REQUEST to a MEC Sandbox platform instance.\n", + "\n", + "\n", + "**That's all Folks**\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "colab": { + "private_outputs": true, + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} -- GitLab From afb8cba4c885880af11579c5392b418fc8225eb3 Mon Sep 17 00:00:00 2001 From: garciay <yann.garcia@fscom.fr> Date: Wed, 19 Mar 2025 07:53:27 +0100 Subject: [PATCH 9/9] Add schema in CAPIF tuto --- examples/demo6/python/README.md | 5 + .../CAPIF_And_ETSI_MEC_Tutorial.ipynb | 286 +++--------------- .../demo6/python/notebook/images/capif.png | Bin 0 -> 54534 bytes 3 files changed, 53 insertions(+), 238 deletions(-) create mode 100644 examples/demo6/python/README.md create mode 100644 examples/demo6/python/notebook/images/capif.png diff --git a/examples/demo6/python/README.md b/examples/demo6/python/README.md new file mode 100644 index 000000000..8eebdd712 --- /dev/null +++ b/examples/demo6/python/README.md @@ -0,0 +1,5 @@ +docker pull quay.io/jupyter/base-notebook:latest + +cd ~/etsi-mec-sandbox/examples/demo6/python/ && docker run --rm -it -d --expose 31111 -p 31111:31111 -p 9999:8888 -v"$PWD:/home/jovyan/work" quay.io/jupyter/base-notebook:latest + +curl --verbose --request GET http://mec-platform2.etsi.org:31111/sandbox/v1/statistic/v1/quantity --header "Accept: application/json" --data '{"time":20180124,"data1":"[1516752000,11590.6,11616.9,11590.4,11616.9,0.25202387,1516752060,11622.4,11651.7,11622.4,11644.6,1.03977764]"}' diff --git a/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb b/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb index 22c03d22f..2448f8d6b 100644 --- a/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb +++ b/examples/demo6/python/notebook/CAPIF_And_ETSI_MEC_Tutorial.ipynb @@ -12,8 +12,11 @@ "\n", "3GPP CAPIF (Common API Framework) is a standardized API management framework designed to enable a unified northbound API approach across 3GPP network functions (see 3GPP TS 23.222 version 18.6.0 Release 18/ETSI TS 123 222 V18.6.0 (2024-06) and 3GPP TS 29.222 version 18.6.0 Release 18/ETSI TS 129 222 V18.6.0 (2022-06)).\n", "\n", - "This tutorial introduces the step by step procedure to create a basic CAPIF application to exploit the ETSI MEC CAPIF profile as described in ETSI GS MEC 011 (V3.2.1) Clause 9.\n", - "It uses the ETSI MEC Sandbox simulator.\n", + "This tutorial introduces the step by step procedure to create a basic CAPIF application to exploit the ETSI MEC CAPIF profile as described in ETSI GS MEC 011 (V3.2.1) Clause 9. The cartoon below illustrate the \n", + "\n", + "\n", + "\n", + "**Note:** It uses the ETSI MEC Sandbox simulator.\n", "\n", "<div class=\"alert alert-block alert-danger\">\n", " <b>Note:</b> These source code examples are simplified and ignore return codes and error checks to a large extent. We do this to highlight how to use the MEC Sandbox API and the different MEC satndards and reduce unrelated code.\n", @@ -27,7 +30,7 @@ "id": "4DpxwmiomELg" }, "source": [ - "## The basics of developing a MEC application\n", + "## The basics of developing a CAPIF application\n", "\n", "\n", "<div class=\"alert alert-warning\" role=\"alert\">\n", @@ -41,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "id": "1gjo-NM6hD1k" }, @@ -83,31 +86,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "id": "xb4ReBZZEVLB" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: pyOpenSSL==25.0.0 in /opt/conda/lib/python3.11/site-packages (25.0.0)\n", - "Requirement already satisfied: cryptography<45,>=41.0.5 in /opt/conda/lib/python3.11/site-packages (from pyOpenSSL==25.0.0) (43.0.1)\n", - "Requirement already satisfied: typing-extensions>=4.9 in /opt/conda/lib/python3.11/site-packages (from pyOpenSSL==25.0.0) (4.12.2)\n", - "Requirement already satisfied: cffi>=1.12 in /opt/conda/lib/python3.11/site-packages (from cryptography<45,>=41.0.5->pyOpenSSL==25.0.0) (1.17.1)\n", - "Requirement already satisfied: pycparser in /opt/conda/lib/python3.11/site-packages (from cffi>=1.12->cryptography<45,>=41.0.5->pyOpenSSL==25.0.0) (2.22)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_3714172/2658395025.py:5: DeprecationWarning: CSR support in pyOpenSSL is deprecated. You should use the APIs in cryptography.\n", - " from OpenSSL.crypto import (dump_certificate_request, dump_privatekey, load_publickey, PKey, TYPE_RSA, X509Req, dump_publickey)\n" - ] - } - ], + "outputs": [], "source": [ "!pip3 install pyOpenSSL==25.0.0\n", "\n", @@ -129,7 +112,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": { "id": "rNibZWiBitPE" }, @@ -146,7 +129,7 @@ "USER_PASSWORD = 'password123'\n", "\n", "TRY_MEC_URL = 'try-mec.etsi.org' # MEC Sandbox URL\n", - "TRY_MEC_SESSION_ID = 'sbxx3i4jhr' # MEC Sandbox identifier\n", + "TRY_MEC_SESSION_ID = 'sbxgs9x587' # MEC Sandbox identifier\n", "TRY_MEC_PLTF = 'mep1' # MEC Platform identifier (depending of the network scenario loaded)\n" ] }, @@ -161,7 +144,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "id": "-cuxWhfantSw" }, @@ -187,7 +170,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "id": "7RC7UY-0oACq" }, @@ -213,7 +196,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "id": "OQjYWHgnYM4G" }, @@ -252,7 +235,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "id": "Ad8g1no-pH7i" }, @@ -302,7 +285,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "id": "XmyLOuFasuvU" }, @@ -333,19 +316,11 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "id": "XYC8PnDUpvui" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", "# Comment the line above to execute this cell\n", @@ -411,7 +386,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { "id": "Jq-9_sLI8WgW" }, @@ -471,7 +446,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "id": "WRIdwNMNFrdC" }, @@ -522,19 +497,11 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "id": "1M_x2I1B_Crp" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", "# Comment the line above to execute this cell\n", @@ -616,7 +583,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "id": "1glmqNSRK1cH" }, @@ -675,19 +642,11 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": { "id": "J002Vuz2OIKl" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", "# Comment the line above to execute this cell\n", @@ -772,7 +731,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": { "id": "gEIS3iAH2D4t" }, @@ -835,7 +794,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": { "id": "6cCn1vKLGe0k" }, @@ -925,7 +884,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": { "id": "rbpNr26tF2gr" }, @@ -1012,19 +971,11 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": { "id": "EDcPUuNEM26H" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", "# Comment the line above to execute this cell\n", @@ -1122,7 +1073,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": { "id": "S7InJDD1_g-v" }, @@ -1251,7 +1202,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": { "id": "nu-tEA6n2TpI" }, @@ -1322,7 +1273,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": { "id": "hEnFLfPI2hms" }, @@ -1371,7 +1322,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": { "id": "z_Cwazjl_xGJ" }, @@ -1434,19 +1385,11 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": { "id": "CRtfJ6cm3V6b" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", "# Comment the line above to execute this cell\n", @@ -1524,7 +1467,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": { "id": "ozCMG8jh0UMd" }, @@ -1577,7 +1520,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": { "id": "Ql9dC3P41_nO" }, @@ -1614,19 +1557,11 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": { "id": "bVYS13iV4-s8" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", "# Comment the line above to execute this cell\n", @@ -1686,7 +1621,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "id": "f11_uMS67I9J" }, @@ -1752,7 +1687,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": { "id": "KRC_xkGO9hEY" }, @@ -1805,7 +1740,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": { "id": "ofUuploUxuhn" }, @@ -1879,19 +1814,11 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": { "id": "QPZPYJZM9mNr" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "skipping\n" - ] - } - ], + "outputs": [], "source": [ "%%script echo skipping\n", "# Comment the line above to execute this cell\n", @@ -2021,7 +1948,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": { "id": "ZL8Gyo0Ao2_u" }, @@ -2079,129 +2006,12 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": { "id": "aTllbmoUpXKx", "scrolled": true }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2025-03-14 06:31:39,150 - __main__ - DEBUG - Starting at 20250314-063139\n", - "2025-03-14 06:31:39,151 - __main__ - DEBUG - \t pwd= /home/jovyan/work/notebook\n", - "2025-03-14 06:31:39,152 - __main__ - DEBUG - >>> publish_api\n", - "2025-03-14 06:31:39,153 - __main__ - DEBUG - >>> process_login\n", - "2025-03-14 06:31:39,154 - __main__ - DEBUG - process_login: url=https://lab-oai.etsi.org:31120/login\n", - "2025-03-14 06:31:39,154 - __main__ - DEBUG - process_login (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Basic YWRtaW46cGFzc3dvcmQxMjM='}\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:39,208 - __main__ - DEBUG - process_login (step2): result: {'access_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQxOTM0NDk5fQ.wNjsuLgh61j0G_6rtN6vxqfbJGMgtaetJd1xPUOoBb4', 'refresh_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQ0NTI1ODk5fQ.lgD4IZCAVJ3-smu1nFBjTtdhTW_trraua1OppvQd4rc'}\n", - "2025-03-14 06:31:39,209 - __main__ - DEBUG - >>> create_user\n", - "2025-03-14 06:31:39,210 - __main__ - DEBUG - create_user: url=https://lab-oai.etsi.org:31120/createUser\n", - "2025-03-14 06:31:39,210 - __main__ - DEBUG - create_user (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQxOTM0NDk5fQ.wNjsuLgh61j0G_6rtN6vxqfbJGMgtaetJd1xPUOoBb4'}\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:39,245 - __main__ - DEBUG - create_user (step2): response=<Response [201]>\n", - "2025-03-14 06:31:39,246 - __main__ - DEBUG - >>> get_auth\n", - "2025-03-14 06:31:39,247 - __main__ - DEBUG - get_auth: url=https://lab-oai.etsi.org:31120/getauth\n", - "2025-03-14 06:31:39,247 - __main__ - DEBUG - get_auth (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Basic ZmM1OWU4YWUtMDA5ZC0xMWYwLWI3MDYtMDI0MmFjMTEwMDAyOnBhc3N3b3JkMTIz'}\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:39,331 - __main__ - DEBUG - get_auth (step2): result: {'access_token': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiYjI0N2YyNzQtMDIxMC00ZDY4LTgwYzktMTAxNjliNTdiNzcyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjNTllOGFlLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA1ODg3NzE2YS0zNWVmLTVmOTktYmQ5NC0zOGQxZmEzZDAzMTciLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiJjNzNjNDJkYy0zOTJhLTQzNzQtOTQyNS1hODY0OTIzNjE1OTciLCJleHAiOjE3NDE5MzQ3OTl9.cQFJ7UfYr_8Fm7eG2hrazmBFqHn7z0WVtvEPmZxLqjUs27MiUmMhd-la89EZ9y_CL4ncZr68N6TCvLfhW6ZReEzdzUCivPJ6iOmw2UTsr1McAnaqf_bmExEIwz3746kYzO2BOPjja15JvaC4d8UiJSzHSW4PNTc3GrtEe2OuuOgsi1mmUH5zCYluyyBzykwZuM7N-mclarQbnmsacSCsMSlFeWVsa-fR560C8uXe0nEMdP9VBBq5O5zwol2ZNGOn3tTYTTibZTv9zZ4Qmay4KXCpQxbIKA_N5fhqSR6P6uQVJ9ihWB0lYXfmnEVlGu4H_IR4oZNs0TxNFNAZkjgROQ', 'ca_root': '-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n', 'ccf_api_onboarding_url': 'api-provider-management/v1/registrations', 'ccf_discover_url': 'service-apis/v1/allServiceAPIs?api-invoker-id=', 'ccf_onboarding_url': 'api-invoker-management/v1/onboardedInvokers', 'ccf_publish_url': 'published-apis/v1/<apfId>/service-apis', 'ccf_security_url': 'capif-security/v1/trustedInvokers/<apiInvokerId>', 'message': 'Token and CA root returned successfully'}\n", - "2025-03-14 06:31:39,332 - __main__ - DEBUG - >>> onboard_provider\n", - "2025-03-14 06:31:39,333 - __main__ - DEBUG - onboard_provider: url=https://lab-oai.etsi.org:443/api-provider-management/v1/registrations\n", - "2025-03-14 06:31:39,333 - __main__ - DEBUG - onboard_provider (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiYjI0N2YyNzQtMDIxMC00ZDY4LTgwYzktMTAxNjliNTdiNzcyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjNTllOGFlLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA1ODg3NzE2YS0zNWVmLTVmOTktYmQ5NC0zOGQxZmEzZDAzMTciLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiJjNzNjNDJkYy0zOTJhLTQzNzQtOTQyNS1hODY0OTIzNjE1OTciLCJleHAiOjE3NDE5MzQ3OTl9.cQFJ7UfYr_8Fm7eG2hrazmBFqHn7z0WVtvEPmZxLqjUs27MiUmMhd-la89EZ9y_CL4ncZr68N6TCvLfhW6ZReEzdzUCivPJ6iOmw2UTsr1McAnaqf_bmExEIwz3746kYzO2BOPjja15JvaC4d8UiJSzHSW4PNTc3GrtEe2OuuOgsi1mmUH5zCYluyyBzykwZuM7N-mclarQbnmsacSCsMSlFeWVsa-fR560C8uXe0nEMdP9VBBq5O5zwol2ZNGOn3tTYTTibZTv9zZ4Qmay4KXCpQxbIKA_N5fhqSR6P6uQVJ9ihWB0lYXfmnEVlGu4H_IR4oZNs0TxNFNAZkjgROQ'}\n", - "2025-03-14 06:31:39,334 - __main__ - DEBUG - >>> generate_csr\n", - "/tmp/ipykernel_3714172/1257919564.py:18: DeprecationWarning: CSR support in pyOpenSSL is deprecated. You should use the APIs in cryptography.\n", - " req = X509Req()\n", - "2025-03-14 06:31:39,373 - __main__ - DEBUG - generate_csr: PrivKey: b'-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCwGYlsHsmrt0wN\\nZ+0Tn7kVnf+ovdqnS07Yasvj3eAgoJWjl0pmMfX3Grqb5hAeF3DoaHZe5yMZq9eN\\nPGyMhtK7PozWWv7HQVaPQLRAW9yJ4vzzjYgyEOWXape3rGH1xhCGOvlXHyYIztMl\\npEWVmIPtOHx978WrD8i4oQrjVNO0ivYW4fCLFqv7L6uLT1rY/wns+7REtaTZopWI\\nNswZm2Upc/CBp8+mP8PCk0LWnUNeeG9J/tZNSQour5eRaadwCxPD58af2oq8fZ4L\\nHJ8W2UGYBVfLV0Gn9vzn0GGinu3qvdwKeKO+/MBlc4wcQFU2EBEBWpVFpjD09PvA\\n0dqQ+t6hAgMBAAECggEARIx8QPkvGcfNM/67e4MWQgLx7RaSUcLv2zCfU4Ef6LN5\\n7GdND8Ds9RaTg3In3f7T9bQMN98Te7+3tYPK4VzuFPNBUYO0X43givzt7YuUqpwQ\\nSSJT3OFU7f8RNe9FZq2F6ypzuuUmIhGYgbOTXqsy15nAZCl8sZ0ATlZp7Zosmr9C\\nxgHNJtBlieTsb40unETw097OKqvJV4JEWQQsvLgnsrIuKFC21d7efid09uy7O/Dm\\nYOAQI+zupQrtBWCZlxNdB0KDAFX1xjisVnunq4fPmp1uQQ+CUgDHZaTlRJ6awKjM\\ns20SP2eg9YYiygwV/6luWsB6OZOKyQe2x6JbGEnejQKBgQDs8ZyBtvSDomENiD1t\\niSq/DwExoaAB5+2a2kCwDPqIAITswgZqXTgQUK+LkFCZqj6brZ+TQaDRQCLhR+WU\\n23XET3OdywMex/fwH8URaOAdAXtsgQXqi92ZSDb9Y0h5mPE5QHFR8S4Z2rBpTjvc\\nDaHvRQW6LpTG9U/XpN8Nsa6Z8wKBgQC+Qzg3I43Wo9LCIgBaspZLwxlJdBdz4zsE\\nQpAROQYXds47bVsPQqaerCR9a259KZUhfmxwAxEH86lmsot6iPktMIWtg6O7G8jB\\nyWyVxA4f4J2t2g9RIkUG4HOkJ4lG/9QwRM7GtzMqlu8BXd5rp0wF5aWToZJmG/+o\\nWv8LqgNWGwKBgA8wObES8j+R5BjC9/USitvucUwmKSSWImP/w4/FdTXMmri2g0TE\\nLvjUwzv2B0SiZhsCmS0OUum268V3H19YZgcsdKPTxKJvfRQ2ZSKFj4AsfSXqa1+q\\nkYzm1SeO+rFYvXXHDLyM8kCUBSTq7+leMlgtG33gyIasaO7Q0b+F+URDAoGAJQeK\\nIlkeGt8pfrUFYqGNVNN00XHxupFIBC08Qs3ZXKm3u6Mt7MtpCEqyk2JcT6nPJ4Sm\\ncCp4nxaSKrWnJcMQ0/W0nq+XJyxiJy0foApXQWcC6Toebwj/bDY064byVcomrvpF\\nUDGJmMllXNu7FTKrPh6S2ifBCXR7jnWfW9LL8W8CgYAkItXeo5t/cbXjtp87WdIV\\nU+AXb4hbXdm++WQjV+m3PxkPAukHYaxZzHSxVv64urW5YdA9Xv9seJE++KRlpD+a\\nXvELsyJ/lScLp5YQoWcuNpisqkKn72dJWcGZHhfvd8GaFtIaxJ/Krtn2FO39lxjX\\nLdIp77vh3SB7KlsV7AKqwQ==\\n-----END PRIVATE KEY-----\\n'\n", - "2025-03-14 06:31:39,374 - __main__ - DEBUG - >>> generate_csr\n", - "2025-03-14 06:31:39,442 - __main__ - DEBUG - generate_csr: PrivKey: b'-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDN1nWrOlD9eyG+\\nx150iAbwsfVOGeRW0bgkZD935jI+ydF5mNpiJnRAfmdGflGDqDWDhDaCcZSjwH1t\\nX+ga0/FtUs/zHdUBGrNj1bwxFUZxL8LRMrqrkrJhH7JfcxmSSSNe7qJbLrTE3Ykg\\nldHjcZRMEkuK5zlQLycdTZQvPuBorUeMesuU+pG5+9F00QsCNVzZl/zqTQEAgkxe\\nLRBkyttqwSCENt9V9rycDCP9Q1m5uWwtE/zYyu9C36gvSGSGiM7xTv9HNTnts80j\\ngL/jsVzOyqJKPnT7ILs1TPA8rD8YrLWOL3p0mzTDBH3gFAzqCXOiOcv13WfEBom7\\nOFWMxH3vAgMBAAECggEAGs+y/w03DHVz9U4xyvsG8GY1vikYOYjStAbMz5jP9abD\\nHtQeqgc5QEdWu3Nrubr40XswwHf79QJ3M/54lLoDqgpN5Ox/WMf6Euh9vWT4ini+\\npxI1B3Exw/3ldZJgA9J8BXsO8dKZIEV8/Z4WO0qJlwkRBvRMXlnt/d6jjS3i/C9O\\nFLd/YhzUjDwwYdaSiMcQ/sgcOI5an/LouIPnbOAZFw6294bZu/MaEdr0OdeUCyjK\\nrPT2IBlP9RdEwTfZBnfCRcZ4nLHAyEKhcKB8/LLXNg2Cm6YnB3lN+2sUaaDChupy\\ndChtYsKz4mhKdjdWsO9S5CACT6T7w/GocxOGGYJbNQKBgQDv/JDffe9pTrKJm98b\\n20lLXUIenRIhOgmcQOIGnK8eTn0dugMU+TyDzKmyA97+ET/GGdAaDuV7O/f/tKkO\\nMImX/DTzoYqHpMoANo+TWMl8Jqaqu/LSp5G+y75tUlyDWR6iGIulq/Cfoo2mCzYS\\nc9exLafahogluWZTLMBdklcyFQKBgQDbkpC/UDYB1iEm6zmBpvTkT9+uoR8hBaCC\\nEZvUe6qAdAixMqi1kihTUlC0UNDBmSA2PUKvEi+0OJb2c1Ep+RFhdzN+kxPl6QbH\\nWtp75w0/FHf53Da2JS+MN5+UgzRdKHjJrwrPyPqDdcb+Bao8P8OdnZU2vAz8zKQh\\nPg3ki2Ek8wKBgQChHiCpWyRDwAkPZ+1nB1by2P6ODQfh9NQE6m6U39aV8z3+miZ9\\ni0L8fYgkMoMgcbYuKqBTDlM05DMAomqpUx7dQf7O3lJh9NmFQRwtYXuL9WsJzzsc\\nAFJSFPmY7aWcrVVqoC4JISFgG0McCgTYaJuToUJC7PU01n8DMlVHvPr7bQKBgERM\\n6R25s7MKirFyhibgalkCx4+oqug4ud773z9PYJAoh/Om0Hf6iPSyLEyKvwUZvr1g\\nfPe8bn5fBAaRhHPL+C4bSDddRNlFjUhB6KiWDLbMhS2B5LrwAkRZoRgvYACCcGA8\\nRN6xo8t0vXuTA4tOQmkq9ZlbEacpePymMugeuw8lAoGANKB5VXA1/pt2nSV1Bt0u\\nTDd8VdcXE+1oxnQ0Rt4+jHhSSVNZd7OBH4qxBkmaQSgkKC+IOqjfFYs6PTbgUI9Y\\npXyc6dMwAqvNZyP88unbooEZYQwm0ynK878pav2wiKOAZaypGkNmiRgzYKuYdZ1n\\nLSUBxB62CticCewzFLBkMnY=\\n-----END PRIVATE KEY-----\\n'\n", - "2025-03-14 06:31:39,443 - __main__ - DEBUG - >>> generate_csr\n", - "2025-03-14 06:31:39,498 - __main__ - DEBUG - generate_csr: PrivKey: b'-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC1u+4cipJsRh7M\\nT5psEHv/2mCDanU7/EyqMfGah/GX3rb66XQiqNuqktihif0x5qULo3aEm6nZQ1Ed\\n2W3iO+TMo+KYnHanQZig+wXDyHyh3aJfKwuu3/aNolc1PXlcoM6qxY5PAppZoTmb\\nlEcDTPvNG8ZatYkyTQZgsfDRnL3owuiM3XTBQ0hNoBNXFHHs+cEpPzqr5y+LL5LU\\nyKLGNNNmtYpggjhWns9koCgYbJrkowhSsQOTCiCiIP+nBZ472GeEyxr4I2AM85qK\\niYHVgQ3JkaJbJmg+Ompz65uW587EDaiK3ssePj59gF0KiSraTFlzg2H0mTsoJsB7\\nlsQqQsULAgMBAAECggEALikmkaRXDd5/uyirEjDbtkC7TBYR4iMzO/XEpxpJsOg2\\ntSPwRk96wGhdUybQI8kwefTSyVsauN9i7sCIKzNIafxktZvkfZZpVNZ9/91gXuMD\\nd8XgyVIE97PZD+Jl/bOw2uqkO4hvOT38+noe0YVP3ijkaKz8xMSQzqXUBAIasNDH\\nOXw4Zep5appuTtzllhxlt79c6lywtfjWg5NWAg7Gkutm3ZGYV1KjmCcphpqVwoAH\\nxgU4PODA41k2eio99iMmIZxYGPrddTD77slTGlVzCsZCpOqoL9Ho5YKLhidpug1Q\\nmUc54eaZncTnRmW7OQMm3/sppbEGG/Ac2XLcVW0SXQKBgQDqamhnxZQKeM+jCiqe\\n8LQ6nFTgUOEM5Wwt0OYxuhGA9O6+d0gVt2DXYUAb28xj1qoRM7LRlhdVg+zv4V3O\\nf/afJW5wJGBWPmn1yv6Rpca1KrW3xk0PQ3MwrF1pIjmla1E1kO7m4q1RqaBnBO7U\\nBBfxz1zeZM5yKFfNImIItsOwvwKBgQDGd7mKiDFydKlsrJx+XVGL7kS7A6v+FRaX\\n09Ita+9FaHcVtqPHrJlD4wHIKMkltsGHZwLV7Cuk4KQosevgD2bHzKxq5LG9A0mF\\nQaBcwckStrD1GvoCmSsw5kxWckRP+HUstcQyzM9FDI1qaQaGnzxdcXJUFxUyluBy\\nb7Of3X6ytQKBgC2Hl1m4dWWHS4T2P4r5Y3gSzyV2cA+qK9XGQj+cTTQH3qsdzeFx\\n6ZxLpkEC8vLdSdDngq7UgRm58vYwhqDKF+OXSJj9Z7y4iKoV0FYHpc2gSwUzvdne\\nFux4PfhijmHDs1U9Hjm4A0PeN2pq+dwyI1hzFy2W1MY1ccBFEldw8BdPAoGBAMGD\\naWEfDH+aKrxwzaIUoXd8Gn3yxZfXvhDKE4wASuv1QO/mBmmh7EsaI9mjkwV33dmF\\nYmltftyjwMyBNwlgWwoDkjYjyP4QH3aUF6V5ufHKOl6zMASqjkd+tf4wKlWDX9T5\\nYlVaB9s+swCHaTqINtax0BUX8K5EGJLcQVtmH6xlAoGBALu15n4ueD2x/QBwTYWX\\nYwawwPHckuuXxc1c2qJcJ1VAULrcJl0FCYWza4hIqmtdAZ6uzLsiRb/Qoh66tufP\\n2bCxvKO4aSjXQcGLbY4n4dmFjp0X/vl/isb/xGWk4GqVev3CCEsVzseVXCIiWax5\\nC5F9a0df0nPSTuCSn5cAlkjl\\n-----END PRIVATE KEY-----\\n'\n", - "2025-03-14 06:31:39,499 - __main__ - DEBUG - onboard_provider (step2): body: {'apiProvFuncs': [{'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQU1GMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAZiWweyau3\\nTA1n7ROfuRWd/6i92qdLTthqy+Pd4CCglaOXSmYx9fcaupvmEB4XcOhodl7nIxmr\\n1408bIyG0rs+jNZa/sdBVo9AtEBb3Ini/PONiDIQ5Zdql7esYfXGEIY6+VcfJgjO\\n0yWkRZWYg+04fH3vxasPyLihCuNU07SK9hbh8IsWq/svq4tPWtj/Cez7tES1pNmi\\nlYg2zBmbZSlz8IGnz6Y/w8KTQtadQ154b0n+1k1JCi6vl5Fpp3ALE8Pnxp/airx9\\nngscnxbZQZgFV8tXQaf2/OfQYaKe7eq93Ap4o778wGVzjBxAVTYQEQFalUWmMPT0\\n+8DR2pD63qECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQChwyyD3Rcxidm0SK9f\\nJwyrEcTwr5an1H5EuDWpvQnKFGiePEcQF1vVzRotMazWHHT6sgJNVZO+bJqkugvS\\nC9Pc/TnocB4lFYB3iZBT7rhrC8JnjufnZfkBeaJFx1AUJnu/DxEBc9lij5acpcJB\\nD+TMOfDNy/VVa7/2RJakwmHdlwAc8WKGaSUvJw9rFpMEYGQC+HljXtByvLyBne1H\\nOZTuayTZtDHGqM7tKOFsCP0ajLEW4BuVioMh8Ge+NozUl+zLbD8CA89M/PAotANV\\nKZl+EexnS/+c3N8p+7x8Lk/8A0AsW98Ktf+MQAA20Tab5FmWSPrg/X+XjSHvHdSw\\n1qgA\\n-----END CERTIFICATE REQUEST-----\\n'}, 'apiProvFuncRole': 'AMF'}, {'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQUVGMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM3Wdas6UP17\\nIb7HXnSIBvCx9U4Z5FbRuCRkP3fmMj7J0XmY2mImdEB+Z0Z+UYOoNYOENoJxlKPA\\nfW1f6BrT8W1Sz/Md1QEas2PVvDEVRnEvwtEyuquSsmEfsl9zGZJJI17uolsutMTd\\niSCV0eNxlEwSS4rnOVAvJx1NlC8+4GitR4x6y5T6kbn70XTRCwI1XNmX/OpNAQCC\\nTF4tEGTK22rBIIQ231X2vJwMI/1DWbm5bC0T/NjK70LfqC9IZIaIzvFO/0c1Oe2z\\nzSOAv+OxXM7Koko+dPsguzVM8DysPxistY4venSbNMMEfeAUDOoJc6I5y/XdZ8QG\\nibs4VYzEfe8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBDGmtyvcLOgeMKH5yK\\nKXIAhV3gkWT8PP/cM49jh7wsy5CJ1Sa++tki8xo1OHmwG8WZkp8AhlCmRCAhqa4d\\nWKNJMR/2baFbdj3CIPsKciOacoe+0fwA+rPqT4JKzCiZ0LtAtf4+YZn5tOMQcVhA\\nd/o16jugqLU5Cvdyf5iz42p67bCKInaR+JQNkJQv99cjJbLxHkOdke9HGL3YcZYY\\nqCJ4YY/UQbfUZGQr6gXxuYIqyIwHUOhVRdlakFQESVOz8lbgvkFhs5xKETgg/6A1\\nYQSyCgYt/r6W0YarDGW9TTTnOT1/bioGxaMs4jHtCF6quUpJ2lWiY25MXlNvKa9v\\nCvdM\\n-----END CERTIFICATE REQUEST-----\\n'}, 'apiProvFuncRole': 'AEF'}, {'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQVBGMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALW77hyKkmxG\\nHsxPmmwQe//aYINqdTv8TKox8ZqH8ZfetvrpdCKo26qS2KGJ/THmpQujdoSbqdlD\\nUR3ZbeI75Myj4picdqdBmKD7BcPIfKHdol8rC67f9o2iVzU9eVygzqrFjk8Cmlmh\\nOZuURwNM+80bxlq1iTJNBmCx8NGcvejC6IzddMFDSE2gE1cUcez5wSk/OqvnL4sv\\nktTIosY002a1imCCOFaez2SgKBhsmuSjCFKxA5MKIKIg/6cFnjvYZ4TLGvgjYAzz\\nmoqJgdWBDcmRolsmaD46anPrm5bnzsQNqIreyx4+Pn2AXQqJKtpMWXODYfSZOygm\\nwHuWxCpCxQsCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQApUoo3ZXsqvfPVeGFz\\nwPuG8oUTmcPzRT9jt27zsxPl02wVNU+ah5r6gENKUWF1YNHus1YOCEwkypFlTje2\\nlM1XnrGZ16kGmJOHs/K+CUCaRBOvQNJNCdhF6O56u0cT99p6E1MBtNARdITrC1Pf\\no2UE81ixqNmbYvUUTf6HfvLwMj7y9/ZflW777dY8/X4JqP8b7EcBQj26LcnfZSo8\\nbyVj9Dqm1aFAhqKg5z4/DNnTVWKo+hioK6/ML37aQqwCjHGtYhv5onB/tV5UVAHj\\n/XSjBDEkZuGntuQHJfGy82S2W+8sWQlY/DY+UbuLehPpDJQljj0AO4P6FIltbupo\\nYAyl\\n-----END CERTIFICATE REQUEST-----\\n'}, 'apiProvFuncRole': 'APF'}], 'apiProvDomInfo': 'MECSandbox_to_CAPIF_Provider', 'suppFeat': 'fff', 'failReason': 'string', 'regSec': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiYjI0N2YyNzQtMDIxMC00ZDY4LTgwYzktMTAxNjliNTdiNzcyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjNTllOGFlLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA1ODg3NzE2YS0zNWVmLTVmOTktYmQ5NC0zOGQxZmEzZDAzMTciLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiJjNzNjNDJkYy0zOTJhLTQzNzQtOTQyNS1hODY0OTIzNjE1OTciLCJleHAiOjE3NDE5MzQ3OTl9.cQFJ7UfYr_8Fm7eG2hrazmBFqHn7z0WVtvEPmZxLqjUs27MiUmMhd-la89EZ9y_CL4ncZr68N6TCvLfhW6ZReEzdzUCivPJ6iOmw2UTsr1McAnaqf_bmExEIwz3746kYzO2BOPjja15JvaC4d8UiJSzHSW4PNTc3GrtEe2OuuOgsi1mmUH5zCYluyyBzykwZuM7N-mclarQbnmsacSCsMSlFeWVsa-fR560C8uXe0nEMdP9VBBq5O5zwol2ZNGOn3tTYTTibZTv9zZ4Qmay4KXCpQxbIKA_N5fhqSR6P6uQVJ9ihWB0lYXfmnEVlGu4H_IR4oZNs0TxNFNAZkjgROQ'}\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:39,556 - __main__ - DEBUG - onboard_provider (step3): result: {'apiProvDomId': '7d1e7acfc6bb19a31614da0968a8f8', 'regSec': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiYjI0N2YyNzQtMDIxMC00ZDY4LTgwYzktMTAxNjliNTdiNzcyIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjNTllOGFlLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA1ODg3NzE2YS0zNWVmLTVmOTktYmQ5NC0zOGQxZmEzZDAzMTciLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiJjNzNjNDJkYy0zOTJhLTQzNzQtOTQyNS1hODY0OTIzNjE1OTciLCJleHAiOjE3NDE5MzQ3OTl9.cQFJ7UfYr_8Fm7eG2hrazmBFqHn7z0WVtvEPmZxLqjUs27MiUmMhd-la89EZ9y_CL4ncZr68N6TCvLfhW6ZReEzdzUCivPJ6iOmw2UTsr1McAnaqf_bmExEIwz3746kYzO2BOPjja15JvaC4d8UiJSzHSW4PNTc3GrtEe2OuuOgsi1mmUH5zCYluyyBzykwZuM7N-mclarQbnmsacSCsMSlFeWVsa-fR560C8uXe0nEMdP9VBBq5O5zwol2ZNGOn3tTYTTibZTv9zZ4Qmay4KXCpQxbIKA_N5fhqSR6P6uQVJ9ihWB0lYXfmnEVlGu4H_IR4oZNs0TxNFNAZkjgROQ', 'apiProvFuncs': [{'apiProvFuncId': 'AMF4b2ceb2ce84148aa56a88a07d9ab9c', 'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQU1GMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALAZiWweyau3\\nTA1n7ROfuRWd/6i92qdLTthqy+Pd4CCglaOXSmYx9fcaupvmEB4XcOhodl7nIxmr\\n1408bIyG0rs+jNZa/sdBVo9AtEBb3Ini/PONiDIQ5Zdql7esYfXGEIY6+VcfJgjO\\n0yWkRZWYg+04fH3vxasPyLihCuNU07SK9hbh8IsWq/svq4tPWtj/Cez7tES1pNmi\\nlYg2zBmbZSlz8IGnz6Y/w8KTQtadQ154b0n+1k1JCi6vl5Fpp3ALE8Pnxp/airx9\\nngscnxbZQZgFV8tXQaf2/OfQYaKe7eq93Ap4o778wGVzjBxAVTYQEQFalUWmMPT0\\n+8DR2pD63qECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQChwyyD3Rcxidm0SK9f\\nJwyrEcTwr5an1H5EuDWpvQnKFGiePEcQF1vVzRotMazWHHT6sgJNVZO+bJqkugvS\\nC9Pc/TnocB4lFYB3iZBT7rhrC8JnjufnZfkBeaJFx1AUJnu/DxEBc9lij5acpcJB\\nD+TMOfDNy/VVa7/2RJakwmHdlwAc8WKGaSUvJw9rFpMEYGQC+HljXtByvLyBne1H\\nOZTuayTZtDHGqM7tKOFsCP0ajLEW4BuVioMh8Ge+NozUl+zLbD8CA89M/PAotANV\\nKZl+EexnS/+c3N8p+7x8Lk/8A0AsW98Ktf+MQAA20Tab5FmWSPrg/X+XjSHvHdSw\\n1qgA\\n-----END CERTIFICATE REQUEST-----\\n', 'apiProvCert': '-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIUXWNSwZhcDK6gbO6bers6UlaK+kkwDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUFNRjRiMmNl\\nYjJjZTg0MTQ4YWE1NmE4OGEwN2Q5YWI5YzCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBALAZiWweyau3TA1n7ROfuRWd/6i92qdLTthqy+Pd4CCglaOXSmYx\\n9fcaupvmEB4XcOhodl7nIxmr1408bIyG0rs+jNZa/sdBVo9AtEBb3Ini/PONiDIQ\\n5Zdql7esYfXGEIY6+VcfJgjO0yWkRZWYg+04fH3vxasPyLihCuNU07SK9hbh8IsW\\nq/svq4tPWtj/Cez7tES1pNmilYg2zBmbZSlz8IGnz6Y/w8KTQtadQ154b0n+1k1J\\nCi6vl5Fpp3ALE8Pnxp/airx9ngscnxbZQZgFV8tXQaf2/OfQYaKe7eq93Ap4o778\\nwGVzjBxAVTYQEQFalUWmMPT0+8DR2pD63qECAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQf\\n6A3W+VuniqRJYpMK89aTSeVPVDAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFBTUY0YjJjZWIyY2U4NDE0OGFhNTZhODhhMDdkOWFi\\nOWMwDQYJKoZIhvcNAQELBQADggEBAKxQV3StdM8orcqILKtomgcot9ieq+4ijyRm\\nD86Y7pKLSPRAtVgR5IAfQHmGH1/OSOskXL2JW+4EL3qHAmLuka3pnCj8d8etgxKb\\nSKD4yUTRZ/yiIcd2gB5/ykUIOeTdghzzf2yI0x5SYHtpYupmi+FP6twhMxnP8IFh\\nZ3qaYMq1rx4g8riBQz7i9yVby5kc4Na6eUG7V/J/qBL/ONrsvVYW9YBRn6uaFNJJ\\nEAHsRZWAcBy+6NaMToqzp7lC4Pslwfv9YtaythQCguGGz5P7QTc2CrAFO/MDDoM/\\nDvkdUs0skrY+VBjInWpML6e+DJ2sRRC8PJTIjAk1Qgeoxlqnaqo=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----'}, 'apiProvFuncRole': 'AMF'}, {'apiProvFuncId': 'AEFb7a696201b577f7c0233923e4b5839', 'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQUVGMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM3Wdas6UP17\\nIb7HXnSIBvCx9U4Z5FbRuCRkP3fmMj7J0XmY2mImdEB+Z0Z+UYOoNYOENoJxlKPA\\nfW1f6BrT8W1Sz/Md1QEas2PVvDEVRnEvwtEyuquSsmEfsl9zGZJJI17uolsutMTd\\niSCV0eNxlEwSS4rnOVAvJx1NlC8+4GitR4x6y5T6kbn70XTRCwI1XNmX/OpNAQCC\\nTF4tEGTK22rBIIQ231X2vJwMI/1DWbm5bC0T/NjK70LfqC9IZIaIzvFO/0c1Oe2z\\nzSOAv+OxXM7Koko+dPsguzVM8DysPxistY4venSbNMMEfeAUDOoJc6I5y/XdZ8QG\\nibs4VYzEfe8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBDGmtyvcLOgeMKH5yK\\nKXIAhV3gkWT8PP/cM49jh7wsy5CJ1Sa++tki8xo1OHmwG8WZkp8AhlCmRCAhqa4d\\nWKNJMR/2baFbdj3CIPsKciOacoe+0fwA+rPqT4JKzCiZ0LtAtf4+YZn5tOMQcVhA\\nd/o16jugqLU5Cvdyf5iz42p67bCKInaR+JQNkJQv99cjJbLxHkOdke9HGL3YcZYY\\nqCJ4YY/UQbfUZGQr6gXxuYIqyIwHUOhVRdlakFQESVOz8lbgvkFhs5xKETgg/6A1\\nYQSyCgYt/r6W0YarDGW9TTTnOT1/bioGxaMs4jHtCF6quUpJ2lWiY25MXlNvKa9v\\nCvdM\\n-----END CERTIFICATE REQUEST-----\\n', 'apiProvCert': '-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIULl5w9Xdg+aoq0YvbrhTXrpB6pIIwDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUFFRmI3YTY5\\nNjIwMWI1NzdmN2MwMjMzOTIzZTRiNTgzOTCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBAM3Wdas6UP17Ib7HXnSIBvCx9U4Z5FbRuCRkP3fmMj7J0XmY2mIm\\ndEB+Z0Z+UYOoNYOENoJxlKPAfW1f6BrT8W1Sz/Md1QEas2PVvDEVRnEvwtEyuquS\\nsmEfsl9zGZJJI17uolsutMTdiSCV0eNxlEwSS4rnOVAvJx1NlC8+4GitR4x6y5T6\\nkbn70XTRCwI1XNmX/OpNAQCCTF4tEGTK22rBIIQ231X2vJwMI/1DWbm5bC0T/NjK\\n70LfqC9IZIaIzvFO/0c1Oe2zzSOAv+OxXM7Koko+dPsguzVM8DysPxistY4venSb\\nNMMEfeAUDOoJc6I5y/XdZ8QGibs4VYzEfe8CAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSj\\nU/uJ2mE3Vy2+cTPwR2uLYsQvkzAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFBRUZiN2E2OTYyMDFiNTc3ZjdjMDIzMzkyM2U0YjU4\\nMzkwDQYJKoZIhvcNAQELBQADggEBAG2+v/AB4L8ruUwo7jrb5l7ska4DlXWstx2O\\n8VOECWa7fXuqvptIW3I/ny4BVS0365SPbr4HdimH4b4G9Im2DBNHzgOgvptPOeUy\\nWBkn9tcdUkISiwdiqlLT4jDgbD+1/ptTVEfdg8Q5xr51NnNPz+XLZjnuAr85/7me\\nxZroCJ4flALdJIJIcBxusQCiQf2TOes3IKK94bTrrq4bAwgzUsQESONwgTinzGa/\\nsQvPD84LtD1RsCmXVbNZsbEHPU9aEiZMHyV7OtObQbrG6GVqEL9lfAexTG3U3um6\\n3lm5L5LxrPXM4CFcgIFUWWd2z7fhuUvOJ1zLY0mV77m3bagw0hA=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----'}, 'apiProvFuncRole': 'AEF'}, {'apiProvFuncId': 'APF5590794dcb278ebcf3d9325f547bee', 'regInfo': {'apiProvPubKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICbzCCAVcCAQAwKjEMMAoGA1UEAwwDQVBGMQ0wCwYDVQQKDARFVFNJMQswCQYD\\nVQQGEwJGcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALW77hyKkmxG\\nHsxPmmwQe//aYINqdTv8TKox8ZqH8ZfetvrpdCKo26qS2KGJ/THmpQujdoSbqdlD\\nUR3ZbeI75Myj4picdqdBmKD7BcPIfKHdol8rC67f9o2iVzU9eVygzqrFjk8Cmlmh\\nOZuURwNM+80bxlq1iTJNBmCx8NGcvejC6IzddMFDSE2gE1cUcez5wSk/OqvnL4sv\\nktTIosY002a1imCCOFaez2SgKBhsmuSjCFKxA5MKIKIg/6cFnjvYZ4TLGvgjYAzz\\nmoqJgdWBDcmRolsmaD46anPrm5bnzsQNqIreyx4+Pn2AXQqJKtpMWXODYfSZOygm\\nwHuWxCpCxQsCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQApUoo3ZXsqvfPVeGFz\\nwPuG8oUTmcPzRT9jt27zsxPl02wVNU+ah5r6gENKUWF1YNHus1YOCEwkypFlTje2\\nlM1XnrGZ16kGmJOHs/K+CUCaRBOvQNJNCdhF6O56u0cT99p6E1MBtNARdITrC1Pf\\no2UE81ixqNmbYvUUTf6HfvLwMj7y9/ZflW777dY8/X4JqP8b7EcBQj26LcnfZSo8\\nbyVj9Dqm1aFAhqKg5z4/DNnTVWKo+hioK6/ML37aQqwCjHGtYhv5onB/tV5UVAHj\\n/XSjBDEkZuGntuQHJfGy82S2W+8sWQlY/DY+UbuLehPpDJQljj0AO4P6FIltbupo\\nYAyl\\n-----END CERTIFICATE REQUEST-----\\n', 'apiProvCert': '-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIUVIf6wTIfDvMffnpLaSGeVqXrujAwDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUFQRjU1OTA3\\nOTRkY2IyNzhlYmNmM2Q5MzI1ZjU0N2JlZTCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBALW77hyKkmxGHsxPmmwQe//aYINqdTv8TKox8ZqH8ZfetvrpdCKo\\n26qS2KGJ/THmpQujdoSbqdlDUR3ZbeI75Myj4picdqdBmKD7BcPIfKHdol8rC67f\\n9o2iVzU9eVygzqrFjk8CmlmhOZuURwNM+80bxlq1iTJNBmCx8NGcvejC6IzddMFD\\nSE2gE1cUcez5wSk/OqvnL4svktTIosY002a1imCCOFaez2SgKBhsmuSjCFKxA5MK\\nIKIg/6cFnjvYZ4TLGvgjYAzzmoqJgdWBDcmRolsmaD46anPrm5bnzsQNqIreyx4+\\nPn2AXQqJKtpMWXODYfSZOygmwHuWxCpCxQsCAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRB\\nOpj37DyD3EoqrOLAqK5ti6avPjAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFBUEY1NTkwNzk0ZGNiMjc4ZWJjZjNkOTMyNWY1NDdi\\nZWUwDQYJKoZIhvcNAQELBQADggEBAGFhndkgIC2WYjbOpXI83HYtz+DaOCN1b2FQ\\nCfTYPU7+UlMqY7gokEZXaQPsABpV+T/iZDuAGyDbhJLQ8qtk4e6Zpa+d/J83AhyU\\n3b8qauPSWpTVh6TWH9+Io2WlAhLSFqREHCrBWdcbij0nTfT4MmkTHRj6uTf45RQb\\nKrnA5vYNtauJ9Pt/lLUHQd5YcVxGK2vCGTEzmoZn5L28SQLZB6dnA6mEZd/hdfYC\\nbEDGOxfq5Mhoxygzu1N+cF0F6ykI2fTpwM2xKkOyvmaEAoHRRXSgpZFXN7NZxCWM\\nxSFNtGN3UwveVmJfIQENQAxX7YVOKVYarrtnUt9Xn/Da4HiZVis=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----'}, 'apiProvFuncRole': 'APF'}], 'apiProvDomInfo': 'MECSandbox_to_CAPIF_Provider', 'suppFeat': 'fff', 'failReason': 'string'}\n", - "2025-03-14 06:31:39,558 - __main__ - DEBUG - >>> build_publish_api_from_mec_services: p_aefId=AEFb7a696201b577f7c0233923e4b5839\n", - "2025-03-14 06:31:39,559 - __main__ - DEBUG - <<< build_publish_api_from_mec_services: {'apiName': 'MEC Profile for CAPIF', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004Z', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string', 'apiStatus': {'aefIds': ['AEFb7a696201b577f7c0233923e4b5839']}}\n", - "2025-03-14 06:31:39,559 - __main__ - DEBUG - publish_api_req_body: {'apiName': 'MEC Profile for CAPIF', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004Z', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string', 'apiStatus': {'aefIds': ['AEFb7a696201b577f7c0233923e4b5839']}}\n", - "2025-03-14 06:31:39,560 - __main__ - DEBUG - >>> publish_capif_api\n", - "2025-03-14 06:31:39,560 - __main__ - DEBUG - publish_capif_api: url=https://lab-oai.etsi.org:443/published-apis/v1/APF5590794dcb278ebcf3d9325f547bee/service-apis\n", - "2025-03-14 06:31:39,561 - __main__ - DEBUG - publish_capif_api (step1): headers: {'Content-Type': 'application/json'}\n", - "2025-03-14 06:31:39,561 - __main__ - DEBUG - publish_capif_api (step2): body: {'apiName': 'MEC Profile for CAPIF', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004Z', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string', 'apiStatus': {'aefIds': ['AEFb7a696201b577f7c0233923e4b5839']}}\n", - "2025-03-14 06:31:39,561 - __main__ - DEBUG - >>> store_certificate_2_files\n", - "2025-03-14 06:31:39,562 - __main__ - DEBUG - publish_capif_api (step3): bundle: ['p_crt.crt', 'p_key.key', 'ca_root.pem']\n", - "2025-03-14 06:31:39,592 - __main__ - DEBUG - publish_capif_api (step4): result: {'apiName': 'MEC Profile for CAPIF', 'apiId': 'a55b2ecc0ee7a2ff0d5b7da3a63dd2', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004000+00:00', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string'}\n", - "2025-03-14 06:31:39,593 - __main__ - DEBUG - publish_capif_api (step5): res: {'apiName': 'MEC Profile for CAPIF', 'apiId': 'a55b2ecc0ee7a2ff0d5b7da3a63dd2', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004000+00:00', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string'}\n", - "2025-03-14 06:31:39,594 - __main__ - DEBUG - publish_api: ('7d1e7acfc6bb19a31614da0968a8f8', ('-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIUXWNSwZhcDK6gbO6bers6UlaK+kkwDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUFNRjRiMmNl\\nYjJjZTg0MTQ4YWE1NmE4OGEwN2Q5YWI5YzCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBALAZiWweyau3TA1n7ROfuRWd/6i92qdLTthqy+Pd4CCglaOXSmYx\\n9fcaupvmEB4XcOhodl7nIxmr1408bIyG0rs+jNZa/sdBVo9AtEBb3Ini/PONiDIQ\\n5Zdql7esYfXGEIY6+VcfJgjO0yWkRZWYg+04fH3vxasPyLihCuNU07SK9hbh8IsW\\nq/svq4tPWtj/Cez7tES1pNmilYg2zBmbZSlz8IGnz6Y/w8KTQtadQ154b0n+1k1J\\nCi6vl5Fpp3ALE8Pnxp/airx9ngscnxbZQZgFV8tXQaf2/OfQYaKe7eq93Ap4o778\\nwGVzjBxAVTYQEQFalUWmMPT0+8DR2pD63qECAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQf\\n6A3W+VuniqRJYpMK89aTSeVPVDAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFBTUY0YjJjZWIyY2U4NDE0OGFhNTZhODhhMDdkOWFi\\nOWMwDQYJKoZIhvcNAQELBQADggEBAKxQV3StdM8orcqILKtomgcot9ieq+4ijyRm\\nD86Y7pKLSPRAtVgR5IAfQHmGH1/OSOskXL2JW+4EL3qHAmLuka3pnCj8d8etgxKb\\nSKD4yUTRZ/yiIcd2gB5/ykUIOeTdghzzf2yI0x5SYHtpYupmi+FP6twhMxnP8IFh\\nZ3qaYMq1rx4g8riBQz7i9yVby5kc4Na6eUG7V/J/qBL/ONrsvVYW9YBRn6uaFNJJ\\nEAHsRZWAcBy+6NaMToqzp7lC4Pslwfv9YtaythQCguGGz5P7QTc2CrAFO/MDDoM/\\nDvkdUs0skrY+VBjInWpML6e+DJ2sRRC8PJTIjAk1Qgeoxlqnaqo=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----', b'-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCwGYlsHsmrt0wN\\nZ+0Tn7kVnf+ovdqnS07Yasvj3eAgoJWjl0pmMfX3Grqb5hAeF3DoaHZe5yMZq9eN\\nPGyMhtK7PozWWv7HQVaPQLRAW9yJ4vzzjYgyEOWXape3rGH1xhCGOvlXHyYIztMl\\npEWVmIPtOHx978WrD8i4oQrjVNO0ivYW4fCLFqv7L6uLT1rY/wns+7REtaTZopWI\\nNswZm2Upc/CBp8+mP8PCk0LWnUNeeG9J/tZNSQour5eRaadwCxPD58af2oq8fZ4L\\nHJ8W2UGYBVfLV0Gn9vzn0GGinu3qvdwKeKO+/MBlc4wcQFU2EBEBWpVFpjD09PvA\\n0dqQ+t6hAgMBAAECggEARIx8QPkvGcfNM/67e4MWQgLx7RaSUcLv2zCfU4Ef6LN5\\n7GdND8Ds9RaTg3In3f7T9bQMN98Te7+3tYPK4VzuFPNBUYO0X43givzt7YuUqpwQ\\nSSJT3OFU7f8RNe9FZq2F6ypzuuUmIhGYgbOTXqsy15nAZCl8sZ0ATlZp7Zosmr9C\\nxgHNJtBlieTsb40unETw097OKqvJV4JEWQQsvLgnsrIuKFC21d7efid09uy7O/Dm\\nYOAQI+zupQrtBWCZlxNdB0KDAFX1xjisVnunq4fPmp1uQQ+CUgDHZaTlRJ6awKjM\\ns20SP2eg9YYiygwV/6luWsB6OZOKyQe2x6JbGEnejQKBgQDs8ZyBtvSDomENiD1t\\niSq/DwExoaAB5+2a2kCwDPqIAITswgZqXTgQUK+LkFCZqj6brZ+TQaDRQCLhR+WU\\n23XET3OdywMex/fwH8URaOAdAXtsgQXqi92ZSDb9Y0h5mPE5QHFR8S4Z2rBpTjvc\\nDaHvRQW6LpTG9U/XpN8Nsa6Z8wKBgQC+Qzg3I43Wo9LCIgBaspZLwxlJdBdz4zsE\\nQpAROQYXds47bVsPQqaerCR9a259KZUhfmxwAxEH86lmsot6iPktMIWtg6O7G8jB\\nyWyVxA4f4J2t2g9RIkUG4HOkJ4lG/9QwRM7GtzMqlu8BXd5rp0wF5aWToZJmG/+o\\nWv8LqgNWGwKBgA8wObES8j+R5BjC9/USitvucUwmKSSWImP/w4/FdTXMmri2g0TE\\nLvjUwzv2B0SiZhsCmS0OUum268V3H19YZgcsdKPTxKJvfRQ2ZSKFj4AsfSXqa1+q\\nkYzm1SeO+rFYvXXHDLyM8kCUBSTq7+leMlgtG33gyIasaO7Q0b+F+URDAoGAJQeK\\nIlkeGt8pfrUFYqGNVNN00XHxupFIBC08Qs3ZXKm3u6Mt7MtpCEqyk2JcT6nPJ4Sm\\ncCp4nxaSKrWnJcMQ0/W0nq+XJyxiJy0foApXQWcC6Toebwj/bDY064byVcomrvpF\\nUDGJmMllXNu7FTKrPh6S2ifBCXR7jnWfW9LL8W8CgYAkItXeo5t/cbXjtp87WdIV\\nU+AXb4hbXdm++WQjV+m3PxkPAukHYaxZzHSxVv64urW5YdA9Xv9seJE++KRlpD+a\\nXvELsyJ/lScLp5YQoWcuNpisqkKn72dJWcGZHhfvd8GaFtIaxJ/Krtn2FO39lxjX\\nLdIp77vh3SB7KlsV7AKqwQ==\\n-----END PRIVATE KEY-----\\n'))\n", - "2025-03-14 06:31:39,594 - __main__ - DEBUG - >>> process_login\n", - "2025-03-14 06:31:39,595 - __main__ - DEBUG - process_login: url=https://lab-oai.etsi.org:31120/login\n", - "2025-03-14 06:31:39,595 - __main__ - DEBUG - process_login (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Basic YWRtaW46cGFzc3dvcmQxMjM='}\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:39,639 - __main__ - DEBUG - process_login (step2): result: {'access_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQxOTM0NDk5fQ.wNjsuLgh61j0G_6rtN6vxqfbJGMgtaetJd1xPUOoBb4', 'refresh_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQ0NTI1ODk5fQ.lgD4IZCAVJ3-smu1nFBjTtdhTW_trraua1OppvQd4rc'}\n", - "2025-03-14 06:31:39,640 - __main__ - DEBUG - >>> create_user\n", - "2025-03-14 06:31:39,640 - __main__ - DEBUG - create_user: url=https://lab-oai.etsi.org:31120/createUser\n", - "2025-03-14 06:31:39,641 - __main__ - DEBUG - create_user (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzQxOTM0NDk5fQ.wNjsuLgh61j0G_6rtN6vxqfbJGMgtaetJd1xPUOoBb4'}\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:39,673 - __main__ - DEBUG - create_user (step2): response=<Response [201]>\n", - "2025-03-14 06:31:39,674 - __main__ - DEBUG - >>> get_auth\n", - "2025-03-14 06:31:39,674 - __main__ - DEBUG - get_auth: url=https://lab-oai.etsi.org:31120/getauth\n", - "2025-03-14 06:31:39,675 - __main__ - DEBUG - get_auth (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Basic ZmM5YjlhZWMtMDA5ZC0xMWYwLWI3MDYtMDI0MmFjMTEwMDAyOnBhc3N3b3JkMTIz'}\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:39,760 - __main__ - DEBUG - get_auth (step2): result: {'access_token': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiNmEwYzAwMGMtODM1OS00Zjk2LTg2YmYtZWM0NmU0MWI4ZWVlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjOWI5YWVjLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA0MTFiOWM0MC1mZTUwLTUyN2EtOWU5OS1kMjU1ZWVkODg1OWIiLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiIxMzkyN2UyMC1mYzA3LTQwYTgtYTM1Ni1lNjBmOTRmN2NmYzgiLCJleHAiOjE3NDE5MzQ3OTl9.ajpnFHBQdSMKmy1s5NZVFaYP6ST3e54MpG_6-ExKIvuZ3vnbqae7Y4kRMOOh4wO6NEEoZcAkb7et4spws16q_r4uuonyh5lmXFJIp63wcF1x94Yw4P51y6Qu9lSpAhZuznF-AhgVoFhtK7BI5z37iqiiTywI9HgrEHfbpll2AD5HaB5MVbmjCiFsADdixK4gisf8FRpxdBUdaCpX1pDJsEAIPhRtWt-6ZYBZhrXffNVSoRyA_Pda8fglLiKMk8lWMPYgePYQ4LntE5Hr3K-_OdxstjLyMkHtnmFHlU02fOLSQxbqJEcCBBcTZfxPHLlGbltcXkt8iHeqFKMuK5-h7w', 'ca_root': '-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n', 'ccf_api_onboarding_url': 'api-provider-management/v1/registrations', 'ccf_discover_url': 'service-apis/v1/allServiceAPIs?api-invoker-id=', 'ccf_onboarding_url': 'api-invoker-management/v1/onboardedInvokers', 'ccf_publish_url': 'published-apis/v1/<apfId>/service-apis', 'ccf_security_url': 'capif-security/v1/trustedInvokers/<apiInvokerId>', 'message': 'Token and CA root returned successfully'}\n", - "2025-03-14 06:31:39,761 - __main__ - DEBUG - >>> onboard_invoker: API Invoker\n", - "2025-03-14 06:31:39,761 - __main__ - DEBUG - >>> onboard_invoker: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiNmEwYzAwMGMtODM1OS00Zjk2LTg2YmYtZWM0NmU0MWI4ZWVlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjOWI5YWVjLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA0MTFiOWM0MC1mZTUwLTUyN2EtOWU5OS1kMjU1ZWVkODg1OWIiLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiIxMzkyN2UyMC1mYzA3LTQwYTgtYTM1Ni1lNjBmOTRmN2NmYzgiLCJleHAiOjE3NDE5MzQ3OTl9.ajpnFHBQdSMKmy1s5NZVFaYP6ST3e54MpG_6-ExKIvuZ3vnbqae7Y4kRMOOh4wO6NEEoZcAkb7et4spws16q_r4uuonyh5lmXFJIp63wcF1x94Yw4P51y6Qu9lSpAhZuznF-AhgVoFhtK7BI5z37iqiiTywI9HgrEHfbpll2AD5HaB5MVbmjCiFsADdixK4gisf8FRpxdBUdaCpX1pDJsEAIPhRtWt-6ZYBZhrXffNVSoRyA_Pda8fglLiKMk8lWMPYgePYQ4LntE5Hr3K-_OdxstjLyMkHtnmFHlU02fOLSQxbqJEcCBBcTZfxPHLlGbltcXkt8iHeqFKMuK5-h7w\n", - "2025-03-14 06:31:39,762 - __main__ - DEBUG - onboard_invoker: url=https://lab-oai.etsi.org:443/api-invoker-management/v1/onboardedInvokers\n", - "2025-03-14 06:31:39,763 - __main__ - DEBUG - onboard_invoker (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiNmEwYzAwMGMtODM1OS00Zjk2LTg2YmYtZWM0NmU0MWI4ZWVlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjOWI5YWVjLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA0MTFiOWM0MC1mZTUwLTUyN2EtOWU5OS1kMjU1ZWVkODg1OWIiLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiIxMzkyN2UyMC1mYzA3LTQwYTgtYTM1Ni1lNjBmOTRmN2NmYzgiLCJleHAiOjE3NDE5MzQ3OTl9.ajpnFHBQdSMKmy1s5NZVFaYP6ST3e54MpG_6-ExKIvuZ3vnbqae7Y4kRMOOh4wO6NEEoZcAkb7et4spws16q_r4uuonyh5lmXFJIp63wcF1x94Yw4P51y6Qu9lSpAhZuznF-AhgVoFhtK7BI5z37iqiiTywI9HgrEHfbpll2AD5HaB5MVbmjCiFsADdixK4gisf8FRpxdBUdaCpX1pDJsEAIPhRtWt-6ZYBZhrXffNVSoRyA_Pda8fglLiKMk8lWMPYgePYQ4LntE5Hr3K-_OdxstjLyMkHtnmFHlU02fOLSQxbqJEcCBBcTZfxPHLlGbltcXkt8iHeqFKMuK5-h7w'}\n", - "2025-03-14 06:31:39,763 - __main__ - DEBUG - >>> generate_csr\n", - "2025-03-14 06:31:39,833 - __main__ - DEBUG - generate_csr: PrivKey: b'-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGj82/J4u6FSpP\\nSVh1FyO5gjjWJOlT3C9f+z1ZrC3QDSILgHiQlVOBVpaBnw4QWpIwFRnX+nHlL5N0\\njfDWvtqfxp1m3y4PGMv4DV9MRVXSITEnLA/V6hV7FzQkabYdaS4FDGWh7U5Gc2KJ\\n1Sddo5RlYxaH+NIiV9rCvu2frWJ8Ol+2EJwRJgXoBHNtEKkGm+P7IeT+UuPPUiF/\\nRRwgYDd530stLMqYCMTpxFwnxHuLpB1ylPgh7Qdvfg9qylTFndETKpUKkFbn4rvE\\nV939ekBor/Q0nAYXgkhOrbJqLk7LGspFNqC4fFBKgEueeVScIsWll57EDNb/UVAF\\nSYvsvS7NAgMBAAECggEAMEM83cUlc834MjxgKVm9/7W0zew74HvI8hn1sAmnOwYu\\nK8CGzZHWMb5Tp976wqPZG6HNXc9Mhsn5G9/yVagfKK3UjXAa2GWNElZhvv6tXP1f\\nXqeZk8Opg/lAXcdqEh0CKAz62RB+saYTIfBy+JItnDUpAVTL8rgK/sH8rQ6uPQZf\\ngUjXUKpLYrb79IrdYq1qrR6wfNDjru+OrFum/2HpQMDg4abo1whwWsp5nlK/tryj\\nQHN8oieZghg02SpiMwuYkAtAPuQFgN6ENSva08iddxUavO5pHf5BqyUhW2bjaVoo\\nYLT7sr5gx8vhn9h7/+s3H33/Lw+RCvKwdVYueGBOywKBgQDt8DBEIQhHZbSf81Mz\\nCvJpS5DioBQIiw3fmsIToE33bLD19LJNP1IqEilYvnUvC5ctXB9rM3WJ4Fx7uHo2\\ndQMq+/HyGKJNz38pK03/k6ABb+ZNbdI3Qq+4t5SDDwtEui3tGdTGzFFTvNrWuYGg\\n4yIkv0w9gr3STTn4+0MGELf3kwKBgQDVomsnTr1HP1StPh7JcCeMkGIYyW6J4wrZ\\nx3uUI5XR2/xyaaqjToUZu9BUZ0l261mGAsru8fNMIVT46Z3vekVeBuVzBxMg8CW3\\nANKmRqJfLr6MpyBdUyIRkpHUGvx9CnazGSbx+hWB7BNoBqyjRIiow05uNx2hjfnw\\noKONzCl8HwKBgQCJ8rNJDI2sNz8dbQlTkokwmusJOR3kRhppBWR31Hzfcli2gIPP\\nXWLZmWX3WZS/Dc08MyjUEiWXJkj4QeA5KmYHycJgRf/zdNWYnM6/2mrt6l5vjbhO\\n6Y7PXT/xLAuwcPCngk5mY5bTIa8OxsZs7MKi43XkQ6SiBLwTqjkVjyPZmQKBgHJ1\\nr+WCiWTn6I1dcA9LONVV8kkHe2MDMygVef+XxUiIDcybEqKmiieMegUOxcyiMffb\\n/TBij5EldqpaOJU7NHk5RqwHiVcnc32GQlZ2F77Zg5xGWs/Fn8Y8ekdjIg44kfpJ\\nKpWRAP74JjmhAdQD/xg0dAwXGZgaQmSLHLX3Qe7PAoGBANvuaZcN0umxqEYfq8mA\\nhmQ0WJuxaJw42Y2qLnx492kKA4aTODAVzUfkmuWFoIrw+0ivmXS6GUpz1iejSXce\\n3CNw/7Z8b40lnbZN9gVD3YiWLhFZMV7FUtYk5uNuOuXAeoKP9ydFtWANShrtIqFJ\\nyW94au/ArID/+5GvRJt7Taz/\\n-----END PRIVATE KEY-----\\n'\n", - "2025-03-14 06:31:39,834 - __main__ - DEBUG - onboard_invoker (step2): body: {'notificationDestination': 'http://host.docker.internal:8086/netapp_callback', 'supportedFeatures': 'fffffff', 'apiInvokerInformation': 'dummy', 'websockNotifConfig': {'requestWebsocketUri': True, 'websocketUri': 'websocketUri'}, 'onboardingInformation': {'apiInvokerPublicKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICdzCCAV8CAQAwMjEUMBIGA1UEAwwLQVBJIEludm9rZXIxDTALBgNVBAoMBEVU\\nU0kxCzAJBgNVBAYTAkZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\\nxo/NvyeLuhUqT0lYdRcjuYI41iTpU9wvX/s9Wawt0A0iC4B4kJVTgVaWgZ8OEFqS\\nMBUZ1/px5S+TdI3w1r7an8adZt8uDxjL+A1fTEVV0iExJywP1eoVexc0JGm2HWku\\nBQxloe1ORnNiidUnXaOUZWMWh/jSIlfawr7tn61ifDpfthCcESYF6ARzbRCpBpvj\\n+yHk/lLjz1Ihf0UcIGA3ed9LLSzKmAjE6cRcJ8R7i6QdcpT4Ie0Hb34PaspUxZ3R\\nEyqVCpBW5+K7xFfd/XpAaK/0NJwGF4JITq2yai5OyxrKRTaguHxQSoBLnnlUnCLF\\npZeexAzW/1FQBUmL7L0uzQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAAnTqKJS\\n5JCzYIHQ4J31iZzwq5Omg4z4SbowzQl41AnQmPDt5zRkZzhwM1HjAus4Qt0v8M/7\\nwe8BPlYQyRZbHF9HN0Gvqe61E7IAVtftfGdmEMwZigRUJ3wfwxF92a66JMg2IcV0\\nsHzsApr8XxEy7slzjINEDWHUmVEBrLsO9HTE7otGBPdbUQ+NheOYTQKxXUkcDKf5\\nmC1OG4xHWqm1vuxcnbXi1Bc7PHNbiSBckPyPgrGwMYrmSimVeSIJA6YzKGbSozdG\\n73b3ZPO6mCV3qXElgeq+igBC7pCo5EbsSOD0lyJX1Tu3EMo2Rvix5xwI5yx3MpQ2\\nC1iHUqsOd0yNbic=\\n-----END CERTIFICATE REQUEST-----\\n'}, 'requestTestNotification': True}\n", - "2025-03-14 06:31:39,867 - __main__ - DEBUG - onboard_invoker (step3): result: {'apiInvokerId': 'INV87e7a6dbc97d1fd9478862f6bd2ce5', 'onboardingInformation': {'apiInvokerPublicKey': '-----BEGIN CERTIFICATE REQUEST-----\\nMIICdzCCAV8CAQAwMjEUMBIGA1UEAwwLQVBJIEludm9rZXIxDTALBgNVBAoMBEVU\\nU0kxCzAJBgNVBAYTAkZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\\nxo/NvyeLuhUqT0lYdRcjuYI41iTpU9wvX/s9Wawt0A0iC4B4kJVTgVaWgZ8OEFqS\\nMBUZ1/px5S+TdI3w1r7an8adZt8uDxjL+A1fTEVV0iExJywP1eoVexc0JGm2HWku\\nBQxloe1ORnNiidUnXaOUZWMWh/jSIlfawr7tn61ifDpfthCcESYF6ARzbRCpBpvj\\n+yHk/lLjz1Ihf0UcIGA3ed9LLSzKmAjE6cRcJ8R7i6QdcpT4Ie0Hb34PaspUxZ3R\\nEyqVCpBW5+K7xFfd/XpAaK/0NJwGF4JITq2yai5OyxrKRTaguHxQSoBLnnlUnCLF\\npZeexAzW/1FQBUmL7L0uzQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAAnTqKJS\\n5JCzYIHQ4J31iZzwq5Omg4z4SbowzQl41AnQmPDt5zRkZzhwM1HjAus4Qt0v8M/7\\nwe8BPlYQyRZbHF9HN0Gvqe61E7IAVtftfGdmEMwZigRUJ3wfwxF92a66JMg2IcV0\\nsHzsApr8XxEy7slzjINEDWHUmVEBrLsO9HTE7otGBPdbUQ+NheOYTQKxXUkcDKf5\\nmC1OG4xHWqm1vuxcnbXi1Bc7PHNbiSBckPyPgrGwMYrmSimVeSIJA6YzKGbSozdG\\n73b3ZPO6mCV3qXElgeq+igBC7pCo5EbsSOD0lyJX1Tu3EMo2Rvix5xwI5yx3MpQ2\\nC1iHUqsOd0yNbic=\\n-----END CERTIFICATE REQUEST-----\\n', 'apiInvokerCertificate': '-----BEGIN CERTIFICATE-----\\nMIIDgjCCAmqgAwIBAgIUbRfvEE4B2gIGKB4FwwQWAKNT/rowDQYJKoZIhvcNAQEL\\nBQAwJzElMCMGA1UEAxMcY2FwaWYgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTAeFw0y\\nNTAzMTQwNjMxMDlaFw0yNTA5MDkxMDMxMzlaMCwxKjAoBgNVBAMTIUlOVjg3ZTdh\\nNmRiYzk3ZDFmZDk0Nzg4NjJmNmJkMmNlNTCCASIwDQYJKoZIhvcNAQEBBQADggEP\\nADCCAQoCggEBAMaPzb8ni7oVKk9JWHUXI7mCONYk6VPcL1/7PVmsLdANIguAeJCV\\nU4FWloGfDhBakjAVGdf6ceUvk3SN8Na+2p/GnWbfLg8Yy/gNX0xFVdIhMScsD9Xq\\nFXsXNCRpth1pLgUMZaHtTkZzYonVJ12jlGVjFof40iJX2sK+7Z+tYnw6X7YQnBEm\\nBegEc20QqQab4/sh5P5S489SIX9FHCBgN3nfSy0sypgIxOnEXCfEe4ukHXKU+CHt\\nB29+D2rKVMWd0RMqlQqQVufiu8RX3f16QGiv9DScBheCSE6tsmouTssaykU2oLh8\\nUEqAS555VJwixaWXnsQM1v9RUAVJi+y9Ls0CAwEAAaOBoDCBnTAOBgNVHQ8BAf8E\\nBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQ2\\nOXQyNZNJpml3Xyv+rWt333ETmTAfBgNVHSMEGDAWgBRyWzJdjOmGfJFOwo6wJiup\\nYnm00jAsBgNVHREEJTAjgiFJTlY4N2U3YTZkYmM5N2QxZmQ5NDc4ODYyZjZiZDJj\\nZTUwDQYJKoZIhvcNAQELBQADggEBAEAL/HW76QNK6eQsomekoYQA2mxlZQ+k216s\\nSxuE6wl+WEgi6SEZyAx3/ZhELLF2SUtURQvfm+RuDbgUMK1kWX4RBRm/8hXgwKyu\\n0H450FF+FwKhE08qMpfbDf4uv5yrGpTdtuLt+g90iXV6Dq2IzJeWiQ7dzXhqVxfX\\nfX/f81S0mbjbnaTSum/OZjJDf+p1lacTdcNLVKgP1YjJkt8qtLJz5UO4rNza4mka\\nSDwyVKyIEgz0OEnc5SuA6A99L4YMI0oZ4vpicvWEERzybyfOHn4M1TG1yHFM+dUJ\\nBv6pbo4mQhNsJ0AIUOgQmoVO2gnW/zNclmLmN3dh8Vknz4gWWRE=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDKDCCAhCgAwIBAgIUDUrJG3QedRyOQ5suvo1eB0WThr0wDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzAwMzA0MDcy\\nMzMxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB\\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxRlEt2FkvKfjxqiq5uFhgyx\\nqNQ0Lct/0CksmQQaR4CZjebZPF2MAnz56L4O8dtqGOFPOYjBY2I0XZ7+hlNBJMwA\\nPC4Ea/q7yPOBzTlJ0gRtIdW/2kiUzxt5TX4LBJsEtn7mZw9KRTSubDfXAt6X5LMA\\ndmG/IYiXERtd/sx3Qya8Hv6L4O9H+RGe2tOd3oVrpbX8QDOlNFHNWdALWFq1vB28\\nUwZ6bmW4ZU8xakc0s8TMFhIKNi3zNVSLyEQlkZ2o3tzSDsjevOJ6iiJWfArObLY+\\ncNaCJYk5g6MsfCp+kTP0TZ4DO5Z9Xwi+xNKM2BoYU3xBfzB9ULF+ZCCoZxnSnQID\\nAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\\nFgQUclsyXYzphnyRTsKOsCYrqWJ5tNIwHwYDVR0jBBgwFoAUkzuK1gy5rzhMq0ax\\nZ4aulK6HzMgwDQYJKoZIhvcNAQELBQADggEBAJx2XwG2+EB6kL0hHFnTEuuiXiqH\\nCOzTbNAMswFsgHb6K7pWJ9d3ZCp1Tm96+4+Q1tN8pj4ZVlps6bOxosznJxdWsMGm\\ncGT4Oxdw11tX5oOk8eTlYkmzwBsv1rXlBsyNxqnOO/z6OCjG0o14mOWEgL/cNC+0\\niZ0AAr1dSobOfe2bOvfcUjTwMfsHq5iqs6scIsMfghKxhUKreRFNsvMwIZGWHSxm\\nn8jpbgVKYl5jw4PbbEHnvWB2PupFGn3omxuFM14gT5SSHKizHqqjnc9xbVbo94ps\\nEFv/+YwAx+cy3q8khdgWfp5so8LR2s1fBA1YcFJMxig6ozbSelDcRadMvPM=\\n-----END CERTIFICATE-----\\n-----BEGIN CERTIFICATE-----\\nMIIDIzCCAgugAwIBAgIUY/66jK16pFsGagTpZLjVW2ow40QwDQYJKoZIhvcNAQEL\\nBQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzA1MDcyMzAxWhcNMzUwMzAzMDcy\\nMzMxWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAKHM8mtKDtJdwE0ZSr+CD+u0jY6GrWMJ1rhZsXltiLbPOGBIOqioGyZ2\\nR9YEfWnrChznY9vFST95PU53gUO1GtnHpNqbXUGlsmQ0KwTeE6MzKflXbq3b0RQG\\nHMO6hw9Juqllmv3FDwCjVl4ZNJyrF2ekoqqBoFlSUeNI8V7cZF5sMPDe3rljzeRb\\njkmv+63FEAaOX1VcUMx6PWHSpGPQsY7jskVoLO60yJurq6qWz598wyZMc9hUj3Al\\nF9H2YaxqU+tfuQyjt9x3qeI1xNT293jBldvI2Xvh0+eJ0niCzjuCLHGU2WJ4OOE3\\nUtCVE0sDpLViUB7KMH8PO4Te4cu5XHMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG\\nMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJM7itYMua84TKtGsWeGrpSuh8zI\\nMB8GA1UdIwQYMBaAFJM7itYMua84TKtGsWeGrpSuh8zIMBAGA1UdEQQJMAeCBWNh\\ncGlmMA0GCSqGSIb3DQEBCwUAA4IBAQCHCtL6PE+xrCCKL8B6W5RZP0whBftjrtfd\\nb/w7Lwq7/XmFeDrMdve12p0tjJ3Bo2EpJqgoTLHrVnd8THcOiFZS6zb2dDX3CKA/\\nMYMqc5+7qzH+0ts9Pi/+L3CsWMvCY2OUT0ojUGdpUmjQhSZqLKofTSBe5H2UzQmT\\n/3vilDHoICKNCE7ob8BJzHQJfvGk/XRtxmuySz+LuK/JOm4auL0IjsWtpqPR0SKJ\\nunNsmjPt7E012kYkmvcwbgDvN7JbK3Frwm32R4hYpETH6pnTEx6YNxbMZXGB50lS\\nnHiEj9eM3AvpecMNVlBJanetzoHfV+mYSoVGJEvuvn4QWXMzrVD4\\n-----END CERTIFICATE-----'}, 'notificationDestination': 'http://host.docker.internal:8086/netapp_callback', 'requestTestNotification': True, 'websockNotifConfig': {'websocketUri': 'websocketUri', 'requestWebsocketUri': True}, 'apiInvokerInformation': 'dummy', 'supportedFeatures': 'fffffff'}\n", - "2025-03-14 06:31:39,868 - __main__ - DEBUG - >>> Discover APIs published by capif core\n", - "2025-03-14 06:31:39,869 - __main__ - DEBUG - Discover: url=https://lab-oai.etsi.org:443/service-apis/v1/allServiceAPIs?api-invoker-id=INV87e7a6dbc97d1fd9478862f6bd2ce5\n", - "2025-03-14 06:31:39,869 - __main__ - DEBUG - Discover (step1): headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MTkzMzg5OSwianRpIjoiNmEwYzAwMGMtODM1OS00Zjk2LTg2YmYtZWM0NmU0MWI4ZWVlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImZjOWI5YWVjLTAwOWQtMTFmMC1iNzA2LTAyNDJhYzExMDAwMiA0MTFiOWM0MC1mZTUwLTUyN2EtOWU5OS1kMjU1ZWVkODg1OWIiLCJuYmYiOjE3NDE5MzM4OTksImNzcmYiOiIxMzkyN2UyMC1mYzA3LTQwYTgtYTM1Ni1lNjBmOTRmN2NmYzgiLCJleHAiOjE3NDE5MzQ3OTl9.ajpnFHBQdSMKmy1s5NZVFaYP6ST3e54MpG_6-ExKIvuZ3vnbqae7Y4kRMOOh4wO6NEEoZcAkb7et4spws16q_r4uuonyh5lmXFJIp63wcF1x94Yw4P51y6Qu9lSpAhZuznF-AhgVoFhtK7BI5z37iqiiTywI9HgrEHfbpll2AD5HaB5MVbmjCiFsADdixK4gisf8FRpxdBUdaCpX1pDJsEAIPhRtWt-6ZYBZhrXffNVSoRyA_Pda8fglLiKMk8lWMPYgePYQ4LntE5Hr3K-_OdxstjLyMkHtnmFHlU02fOLSQxbqJEcCBBcTZfxPHLlGbltcXkt8iHeqFKMuK5-h7w'}\n", - "2025-03-14 06:31:39,870 - __main__ - DEBUG - >>> store_certificate_2_files\n", - "2025-03-14 06:31:39,871 - __main__ - DEBUG - Discover (step2): bundle: ['p_crt.crt', 'p_key.key', 'ca_root.pem']\n", - "2025-03-14 06:31:39,900 - __main__ - DEBUG - Discover (step3): response=<Response [200]>\n", - "2025-03-14 06:31:39,901 - __main__ - DEBUG - Discover : result: {'serviceAPIDescriptions': [{'apiName': 'MEC Profile for CAPIF', 'apiId': 'a55b2ecc0ee7a2ff0d5b7da3a63dd2', 'aefProfiles': [{'aefId': 'AEFb7a696201b577f7c0233923e4b5839', 'versions': [{'apiVersion': 'v1', 'expiry': '2025-11-30T10:32:02.004000+00:00', 'resources': [{'resourceName': 'MEC Profile of CAPIF', 'commType': 'REQUEST_RESPONSE', 'uri': '/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs', 'custOpName': 'string', 'operations': ['GET'], 'description': 'Endpoint to access MEC services'}], 'custOperations': [{'commType': 'REQUEST_RESPONSE', 'custOpName': 'string', 'operations': ['GET'], 'description': 'string'}]}], 'protocol': 'HTTP_1_1', 'dataFormat': 'JSON', 'securityMethods': ['OAUTH'], 'interfaceDescriptions': [{'ipv4Addr': 'try-mec.etsi.org', 'securityMethods': ['OAUTH']}]}], 'description': 'MEC Profile of CAPIF', 'supportedFeatures': 'fffff', 'shareableInfo': {'isShareable': True, 'capifProvDoms': ['string']}, 'serviceAPICategory': 'string', 'apiSuppFeats': 'fffff', 'pubApiPath': {'ccfIds': ['string']}, 'ccfId': 'string'}]}\n", - "2025-03-14 06:31:39,902 - __main__ - DEBUG - addrs: {'ipv4Addr': ['try-mec.etsi.org'], 'uri': ['/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs']}\n", - "2025-03-14 06:31:39,902 - __main__ - DEBUG - >>> discovering_mec_services: https://try-mec.etsi.org/sbxx3i4jhr/mep1/service-apis/v1/allServiceAPIs\n", - "2025-03-14 06:31:39,903 - __main__ - DEBUG - discovering_mec_services (step1): headers: {'Content-Type': 'application/json', 'accept': 'application/json'}\n", - "2025-03-14 06:31:39,930 - __main__ - DEBUG - discovering_mec_services (step2): result: {'serviceAPIDescriptions': [{'apiName': 'mec016-1', 'apiId': 'fa5b0b09-c2e6-4bf6-b7f2-8853132a50da', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/dev_app/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'daiId', 'name': 'DAI', 'version': 'v1'}}}, {'apiName': 'mec015-1', 'apiId': '70ff5514-1479-4746-a867-406b904962fe', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/bwm/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'bwmId', 'name': 'BWM', 'version': 'v1'}}}, {'apiName': 'mec013-1', 'apiId': 'fb18ea66-b93c-4b4d-9d6f-9ed197355ad6', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['3.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/location/v3/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'locationId', 'name': 'Location', 'version': 'v2'}}}, {'apiName': 'mec028-1', 'apiId': '9c69f4b0-77e8-4cb3-ad8f-b666f38f2560', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/wai/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'waiId', 'name': 'WAI', 'version': 'v2'}}}, {'apiName': 'mec012-1', 'apiId': '89e1b639-0780-4fd1-9c12-fe771e0cbafb', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/rni/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'rniId', 'name': 'RNI', 'version': 'v2'}}}, {'apiName': 'mec015-1', 'apiId': '9ee651cc-2eb5-4db8-8c57-40036889835f', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/mts/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'mtsId', 'name': 'MTS', 'version': 'v1'}}}, {'apiName': 'mec030-1', 'apiId': 'f304467b-a269-4647-9aca-67df18b3b5b2', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['3.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/vis/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'visId', 'name': 'V2XI', 'version': 'v2'}}}]}\n", - "2025-03-14 06:31:39,932 - __main__ - DEBUG - ===> The list of the MEC services exposed by try-mec.etsi.org is: {'serviceAPIDescriptions': [{'apiName': 'mec016-1', 'apiId': 'fa5b0b09-c2e6-4bf6-b7f2-8853132a50da', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/dev_app/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'daiId', 'name': 'DAI', 'version': 'v1'}}}, {'apiName': 'mec015-1', 'apiId': '70ff5514-1479-4746-a867-406b904962fe', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/bwm/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'bwmId', 'name': 'BWM', 'version': 'v1'}}}, {'apiName': 'mec013-1', 'apiId': 'fb18ea66-b93c-4b4d-9d6f-9ed197355ad6', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['3.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/location/v3/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'locationId', 'name': 'Location', 'version': 'v2'}}}, {'apiName': 'mec028-1', 'apiId': '9c69f4b0-77e8-4cb3-ad8f-b666f38f2560', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/wai/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'waiId', 'name': 'WAI', 'version': 'v2'}}}, {'apiName': 'mec012-1', 'apiId': '89e1b639-0780-4fd1-9c12-fe771e0cbafb', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/rni/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'rniId', 'name': 'RNI', 'version': 'v2'}}}, {'apiName': 'mec015-1', 'apiId': '9ee651cc-2eb5-4db8-8c57-40036889835f', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['2.2.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/mts/v1/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'mtsId', 'name': 'MTS', 'version': 'v1'}}}, {'apiName': 'mec030-1', 'apiId': 'f304467b-a269-4647-9aca-67df18b3b5b2', 'aefProfiles': [{'aefId': 'sandboxTransport', 'versions': ['3.1.1'], 'interfaceDescriptions': {'uris': ['https://try-mec.etsi.org/sbxx3i4jhr/mep1/vis/v2/'], 'fqdn': None, 'addresses': None, 'alternative': None}, 'vendorSpecific-urn:etsi:mec:capifext:transport-info': {'name': 'REST', 'type': 'REST_HTTP', 'protocol': 'HTTP', 'version': '2.0'}}], 'vendorSpecific-urn:etsi:mec:capifext:service-info': {'serializer': 'JSON', 'state': 'ACTIVE', 'scopeOfLocality': 'MEC_SYSTEM', 'consumedLocalOnly': True, 'isLocal': True, 'category': {'href': 'catalogueHref', 'id': 'visId', 'name': 'V2XI', 'version': 'v2'}}}]}\n", - "2025-03-14 06:31:39,932 - __main__ - DEBUG - >>> offboard_invoker: INV87e7a6dbc97d1fd9478862f6bd2ce5\n", - "2025-03-14 06:31:39,933 - __main__ - DEBUG - offboard_invoker: url=https://lab-oai.etsi.org:443/api-invoker-management/v1/onboardedInvokers/INV87e7a6dbc97d1fd9478862f6bd2ce5\n", - "2025-03-14 06:31:39,933 - __main__ - DEBUG - offboard_invoker (step1): headers: {'Content-Type': 'application/json'}\n", - "2025-03-14 06:31:39,933 - __main__ - DEBUG - >>> store_certificate_2_files\n", - "2025-03-14 06:31:39,934 - __main__ - DEBUG - offboard_invoker (step2): bundle: ['p_crt.crt', 'p_key.key', 'ca_root.pem']\n", - "2025-03-14 06:31:39,967 - __main__ - DEBUG - offboard_invoker (step3): response=<Response [204]>\n", - "2025-03-14 06:31:39,968 - __main__ - DEBUG - >>> delete_user\n", - "2025-03-14 06:31:39,969 - __main__ - DEBUG - delete_user: url=https://lab-oai.etsi.org:31120/deleteUser/411b9c40-fe50-527a-9e99-d255eed8859b\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:40,020 - __main__ - DEBUG - delete_user: response=<Response [204]>\n", - "2025-03-14 06:31:40,021 - __main__ - DEBUG - >>> remove_publish_api \n", - "2025-03-14 06:31:40,021 - __main__ - DEBUG - >>> offboarding_provider: 5887716a-35ef-5f99-bd94-38d1fa3d0317\n", - "2025-03-14 06:31:40,022 - __main__ - DEBUG - >>> offboard_provider\n", - "2025-03-14 06:31:40,022 - __main__ - DEBUG - offboard_provider: url=https://lab-oai.etsi.org:443/api-provider-management/v1/registrations/7d1e7acfc6bb19a31614da0968a8f8\n", - "2025-03-14 06:31:40,022 - __main__ - DEBUG - offboard_provider (step1): headers: {'Content-Type': 'application/json'}\n", - "2025-03-14 06:31:40,023 - __main__ - DEBUG - >>> store_certificate_2_files\n", - "2025-03-14 06:31:40,024 - __main__ - DEBUG - offboard_provider (step2): bundle: ['p_crt.crt', 'p_key.key', 'ca_root.pem']\n", - "2025-03-14 06:31:40,050 - __main__ - DEBUG - offboard_provider (step3): response=<Response [204]>\n", - "2025-03-14 06:31:40,052 - __main__ - DEBUG - >>> delete_user\n", - "2025-03-14 06:31:40,053 - __main__ - DEBUG - delete_user: url=https://lab-oai.etsi.org:31120/deleteUser/5887716a-35ef-5f99-bd94-38d1fa3d0317\n", - "/opt/conda/lib/python3.11/site-packages/urllib3/connectionpool.py:1099: InsecureRequestWarning: Unverified HTTPS request is being made to host 'lab-oai.etsi.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", - " warnings.warn(\n", - "2025-03-14 06:31:40,089 - __main__ - DEBUG - delete_user: response=<Response [204]>\n" - ] - } - ], + "outputs": [], "source": [ "#%%script echo skipping\n", "# Comment the line above to execute this cell\n", diff --git a/examples/demo6/python/notebook/images/capif.png b/examples/demo6/python/notebook/images/capif.png new file mode 100644 index 0000000000000000000000000000000000000000..4bf3593483ff14719e71b01332633845b057710b GIT binary patch literal 54534 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 BkTw7S literal 0 HcmV?d00001 -- GitLab