Commit e3ca35c6 authored by Mudassar Khan's avatar Mudassar Khan
Browse files

Add CAPIF provider integration to meep-app-enablement

- Add capif-client package with full provider lifecycle: login,
  user creation, auth, onboarding, publish/unpublish, and cleanup
- Support DNS override for CAPIF hostname resolution in Kubernetes
  (bypass /etc/hosts limitation with custom DialContext)
- Auto-publish MEC services to CAPIF on registration
- Unpublish all services on scenario teardown before provider offboarding
- Add CAPIF configuration env vars to Helm values-template
- Update entrypoint.sh with CAPIF hostname /etc/hosts fallback
parent 4251290b
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -25,6 +25,15 @@ image:
    {{- if .IsMepService }}
    MEEP_MEP_NAME: {{.MepName}}
    {{- end }}
    CAPIF_ENABLED: "false"
    CAPIF_REGISTER_HOSTNAME: ""
    CAPIF_REGISTER_PORT: "443"
    CAPIF_REGISTER_USERNAME: ""
    CAPIF_REGISTER_PASSWORD: ""
    CAPIF_USER_PASSWORD: ""
    CAPIF_HOSTNAME: ""
    CAPIF_PORT: "443"
    CAPIF_PROVIDER_DOMAIN: "MECSandbox_to_CAPIF_Provider"
    {{- range .Env}}
    {{.}}
    {{- end}}
+20 −0
Original line number Diff line number Diff line
@@ -5,6 +5,26 @@ echo "MEEP_HOST_URL: ${MEEP_HOST_URL}"
echo "MEEP_SANDBOX_NAME: ${MEEP_SANDBOX_NAME}"
echo "MEEP_MEP_NAME: ${MEEP_MEP_NAME}"
echo "MEEP_CODECOV: ${MEEP_CODECOV}"
echo "CAPIF_ENABLED: ${CAPIF_ENABLED}"
echo "CAPIF_REGISTER_HOSTNAME: ${CAPIF_REGISTER_HOSTNAME}"
echo "CAPIF_REGISTER_PORT: ${CAPIF_REGISTER_PORT}"
echo "CAPIF_HOSTNAME: ${CAPIF_HOSTNAME}"
echo "CAPIF_PORT: ${CAPIF_PORT}"
echo "CAPIF_PROVIDER_DOMAIN: ${CAPIF_PROVIDER_DOMAIN}"

# Add /etc/hosts entry so CAPIF_HOSTNAME resolves to the CAPIF_REGISTER_HOSTNAME IP
# This is needed when the CAPIF CCF hostname (e.g. "capifcore") is not DNS-resolvable
# from inside the pod, but the Register server IP is reachable.
if [[ "${CAPIF_ENABLED}" == "true" && ! -z "${CAPIF_REGISTER_HOSTNAME}" && ! -z "${CAPIF_HOSTNAME}" && "${CAPIF_REGISTER_HOSTNAME}" != "${CAPIF_HOSTNAME}" ]]; then
    # Resolve CAPIF_REGISTER_HOSTNAME to an IP (handles both IPs and hostnames)
    CAPIF_IP=$(getent hosts "${CAPIF_REGISTER_HOSTNAME}" 2>/dev/null | awk '{print $1}' | head -1)
    if [[ -z "${CAPIF_IP}" ]]; then
        # If getent fails, assume CAPIF_REGISTER_HOSTNAME is already an IP
        CAPIF_IP="${CAPIF_REGISTER_HOSTNAME}"
    fi
    echo "Adding /etc/hosts entry: ${CAPIF_IP} ${CAPIF_HOSTNAME}"
    echo "${CAPIF_IP} ${CAPIF_HOSTNAME}" >> /etc/hosts
fi

if [[ ! -z "${MEEP_MEP_NAME}" ]]; then
    svcPath="${MEEP_SANDBOX_NAME}/${MEEP_MEP_NAME}"
+61 −2
Original line number Diff line number Diff line
@@ -20,10 +20,12 @@ import (
	"errors"
	"net/url"
	"os"
	"strconv"
	"strings"
	"sync"

	as "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/app-support"
	cc "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/capif-client"
	cm "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/capif-mgmt"
	sm "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/service-mgmt"
	httpLog "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-http-logger"
@@ -49,6 +51,7 @@ var mqLocal *mq.MsgQueue
var apiMgr *sam.SwaggerApiMgr
var activeModel *mod.Model
var currentStoreName = ""
var capifClient *cc.CapifClient

// Init - EPAE Service initialization
func Init() (err error) {
@@ -84,6 +87,47 @@ func Init() (err error) {
	}
	log.Info("MEEP_HOST_URL: ", hostUrl)

	// Read CAPIF configuration from environment variables
	capifEnabled, _ := strconv.ParseBool(strings.TrimSpace(os.Getenv("CAPIF_ENABLED")))
	capifRegisterHostname := strings.TrimSpace(os.Getenv("CAPIF_REGISTER_HOSTNAME"))
	capifRegisterPort := strings.TrimSpace(os.Getenv("CAPIF_REGISTER_PORT"))
	if capifRegisterPort == "" {
		capifRegisterPort = "443"
	}
	capifRegisterUsername := strings.TrimSpace(os.Getenv("CAPIF_REGISTER_USERNAME"))
	capifRegisterPassword := strings.TrimSpace(os.Getenv("CAPIF_REGISTER_PASSWORD"))
	capifUserPassword := strings.TrimSpace(os.Getenv("CAPIF_USER_PASSWORD"))
	capifHostname := strings.TrimSpace(os.Getenv("CAPIF_HOSTNAME"))
	capifPort := strings.TrimSpace(os.Getenv("CAPIF_PORT"))
	if capifPort == "" {
		capifPort = "443"
	}
	capifProviderDomain := strings.TrimSpace(os.Getenv("CAPIF_PROVIDER_DOMAIN"))
	if capifProviderDomain == "" {
		capifProviderDomain = "MECSandbox_to_CAPIF_Provider"
	}
	log.Info("CAPIF_ENABLED: ", capifEnabled)
	if capifEnabled {
		log.Info("CAPIF_REGISTER_HOSTNAME: ", capifRegisterHostname)
		log.Info("CAPIF_REGISTER_PORT: ", capifRegisterPort)
		log.Info("CAPIF_HOSTNAME: ", capifHostname)
		log.Info("CAPIF_PORT: ", capifPort)
		log.Info("CAPIF_PROVIDER_DOMAIN: ", capifProviderDomain)
	}

	capifCfg := cc.CapifConfig{
		Enabled:          capifEnabled,
		RegisterHostname: capifRegisterHostname,
		RegisterPort:     capifRegisterPort,
		RegisterUsername: capifRegisterUsername,
		RegisterPassword: capifRegisterPassword,
		UserPassword:     capifUserPassword,
		CapifHostname:    capifHostname,
		CapifPort:        capifPort,
		ProviderDomain:   capifProviderDomain,
	}
	capifClient = cc.NewCapifClient(capifCfg)

	// Create new active scenario model
	modelCfg := mod.ModelCfg{
		Name:      "activeScenario",
@@ -120,7 +164,7 @@ func Init() (err error) {
	log.Info("Swagger API Manager created")

	// Initialize Service Management
	err = sm.Init(sandboxName, mepName, hostUrl, mqLocal, redisAddr, &mutex)
	err = sm.Init(sandboxName, mepName, hostUrl, mqLocal, redisAddr, &mutex, capifClient)
	if err != nil {
		return err
	}
@@ -133,7 +177,7 @@ func Init() (err error) {
	}
	log.Info("Service Management created")
	// Initialize App Support
	err = as.Init(sandboxName, mepName, hostUrl, mqLocal, redisAddr, &mutex)
	err = as.Init(sandboxName, mepName, hostUrl, mqLocal, redisAddr, &mutex, capifClient)
	if err != nil {
		return err
	}
@@ -161,6 +205,13 @@ func Run() (err error) {
		return err
	}

	// Start CAPIF client initialization in background (non-blocking)
	go func() {
		if err := capifClient.Init(); err != nil {
			log.Warn("CAPIF client init failed: ", err.Error())
		}
	}()

	// Start Swagger API Manager (provider)
	err = apiMgr.Start(true, false)
	if err != nil {
@@ -196,6 +247,14 @@ func Run() (err error) {
func Stop() {
	log.Debug(">>> Stop")

	// Unpublish all CAPIF services before offboarding
	sm.UnpublishAllServices()

	// Cleanup CAPIF client (offboard provider, delete user)
	if capifClient != nil {
		capifClient.Cleanup()
	}

	if mqLocal != nil {
		mqLocal.UnregisterHandler(handlerId)
	}
+71 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import (
	"time"

	sm "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/service-mgmt"
	cc "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/capif-client"
	apps "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-applications"
	dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"
@@ -83,19 +84,21 @@ var subMgr *subs.SubscriptionMgr
var appStore *apps.ApplicationStore
var appInfoMap map[string]map[string]string
var gracefulTerminateMap = map[string]chan bool{}
var capifClient *cc.CapifClient

func notImplemented(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusNotImplemented)
}

func Init(sandbox string, mep string, host *url.URL, msgQueue *mq.MsgQueue, redisAddr_ string, globalMutex *sync.Mutex) (err error) {
func Init(sandbox string, mep string, host *url.URL, msgQueue *mq.MsgQueue, redisAddr_ string, globalMutex *sync.Mutex, capifCl *cc.CapifClient) (err error) {
	redisAddr = redisAddr_
	sandboxName = sandbox
	hostUrl = host
	mqLocal = msgQueue
	mutex = globalMutex
	mepName = mep
	capifClient = capifCl

	// Initialize app info cache
	appInfoMap = make(map[string]map[string]string)
@@ -1514,6 +1517,34 @@ func appRegistrationPOST(w http.ResponseWriter, r *http.Request) {
	log.Info("appRegistrationPOST: Generated resource URI:", resourceURI)
	w.Header().Set("Location", resourceURI)

	// CAPIF integration — publish registration asynchronously
	if capifClient != nil && capifClient.IsReady() {
		go func(appInfo AppInfo) {
			host := hostUrl.Hostname()
			port := 443
			if hostUrl.Port() != "" {
				if p, err := strconv.Atoi(hostUrl.Port()); err == nil {
					port = p
				}
			}
			uri := basePath + "registrations/" + appInfo.AppInstanceId
			apiId, err := capifClient.PublishServiceAPI(
				appInfo.AppName,
				"v2",
				uri,
				appInfo.AppName+" Registration",
				"MEC app registration: "+appInfo.AppName,
				host,
				port,
			)
			if err != nil {
				log.Error("CAPIF publish failed for registration ", appInfo.AppInstanceId, ": ", err.Error())
				return
			}
			storeCapifMapping("reg:"+appInfo.AppInstanceId, apiId)
		}(appInfo)
	}

	// Send JSON response with status 201 Created
	jsonResponse := convertAppInfoToJson(&appInfo)
	w.WriteHeader(http.StatusCreated)
@@ -1713,6 +1744,19 @@ func appRegistrationDELETE(w http.ResponseWriter, r *http.Request) {
		return
	}

	// CAPIF integration — unpublish registration asynchronously
	if capifClient != nil && capifClient.IsReady() {
		go func(appInstanceId string) {
			apiId := getCapifMapping("reg:" + appInstanceId)
			if apiId != "" {
				if err := capifClient.UnpublishServiceAPI(apiId); err != nil {
					log.Error("CAPIF unpublish failed for registration ", appInstanceId, ": ", err.Error())
				}
				deleteCapifMapping("reg:" + appInstanceId)
			}
		}(appInstanceId)
	}

	// Send response on successful deletion of registration
	w.WriteHeader(http.StatusNoContent)
}
@@ -2148,3 +2192,29 @@ func errHandlerProblemDetails(w http.ResponseWriter, error string, code int) {
	w.WriteHeader(code)
	fmt.Fprint(w, jsonResponse)
}

// CAPIF mapping helpers — store appInstanceId -> CAPIF apiId in Redis
func storeCapifMapping(localId string, apiId string) {
	key := baseKey + "capif:reg:" + localId
	err := rc.JSONSetEntry(key, ".", "\""+apiId+"\"")
	if err != nil {
		log.Error("Failed to store CAPIF mapping for ", localId, ": ", err.Error())
	}
}

func getCapifMapping(localId string) string {
	key := baseKey + "capif:reg:" + localId
	val, err := rc.JSONGetEntry(key, ".")
	if err != nil || val == "" {
		return ""
	}
	return strings.Trim(val, "\"")
}

func deleteCapifMapping(localId string) {
	key := baseKey + "capif:reg:" + localId
	err := rc.JSONDelEntry(key, ".")
	if err != nil {
		log.Error("Failed to delete CAPIF mapping for ", localId, ": ", err.Error())
	}
}
+650 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading