Commit cb262155 authored by Muhammad Umair Khan's avatar Muhammad Umair Khan
Browse files

meep-app-enablement: replace registry facade with per-API EDGE stores

- Remove server/registry; restore app-support, service-mgmt and capif-mgmt
  to upstream
- Add EAS registration store owned by EDGE-3 (read API for EDGE-1)
- Add EEC registration and service provisioning store owned by EDGE-1
parent e801b4d0
Loading
Loading
Loading
Loading
+83 −37
Changes for go-apps/meep-app-enablement/server/app-support/app-support.go: 83 added lines, 37 removed lines.
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import (
	"sync"
	"time"

	"github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/registry"
	sm "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/service-mgmt"
	apps "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-applications"
	dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
@@ -45,8 +44,8 @@ const moduleName = "meep-app-enablement"
const appSupportBasePath = "mec_app_support/v2/"
const appEnablementKey = "app-enablement"
const globalLocationName = "global"
const APP_STATE_INITIALIZED = registry.AppStateInitialized
const APP_STATE_READY = registry.AppStateReady
const APP_STATE_INITIALIZED = "INITIALIZED"
const APP_STATE_READY = "READY"
const APP_TERMINATION_NOTIF_SUB_TYPE = "AppTerminationNotificationSubscription"
const APP_TERMINATION_NOTIF_TYPE = "AppTerminationNotification"
const DEFAULT_GRACEFUL_TIMEOUT = 10
@@ -55,9 +54,12 @@ const serviceName = "App Enablement Service"

// App Info fields
const (
	fieldAppId   string = registry.FieldAppId
	fieldPersist string = registry.FieldPersist
	fieldState   string = registry.FieldState
	fieldAppId   string = "id"
	fieldName    string = "name"
	fieldNode    string = "node"
	fieldType    string = "type"
	fieldPersist string = "persist"
	fieldState   string = "state"
)

// MQ payload fields
@@ -79,7 +81,6 @@ var basePath string
var baseKey string
var subMgr *subs.SubscriptionMgr
var appStore *apps.ApplicationStore
var reg *registry.Registry
var appInfoMap map[string]map[string]string
var gracefulTerminateMap = map[string]chan bool{}

@@ -116,14 +117,6 @@ func Init(sandbox string, mep string, host *url.URL, msgQueue *mq.MsgQueue, redi
	}
	log.Info("Connected to Redis DB")

	// Create Registry facade
	reg, err = registry.New(sandboxName, mepName, redisAddr, mqLocal)
	if err != nil {
		log.Error("Failed to create Registry. Error: ", err)
		return err
	}
	log.Info("Created Registry")

	// Create Application Store
	cfg := &apps.ApplicationStoreCfg{
		Name:      moduleName,
@@ -1367,8 +1360,9 @@ func getEASDiscInfo(w http.ResponseWriter, r *http.Request) {
	}

	// Construct the datastore key for retrieving app information.
	log.Info("getEASDiscInfo: retrieving app information", "appInstanceId", appInstanceId)
	jsonAppInfo, err := reg.GetAppRegistration(appInstanceId)
	keyName := baseKey + "appInfo:" + appInstanceId
	log.Info("getEASDiscInfo: retrieving app information", "keyName", keyName)
	jsonAppInfo, err := rc.JSONGetEntry(keyName, ".")
	if err != nil {
		errMsg := "appInfo not found for the provided appInstanceId"
		log.Error("getEASDiscInfo:", errMsg, "appInstanceId", appInstanceId, "error", err)
@@ -1507,8 +1501,9 @@ func appRegistrationPOST(w http.ResponseWriter, r *http.Request) {
	}

	// Store AppInfo in Redis
	log.Info("appRegistrationPOST: Storing registration of appInstanceId:", appInfo.AppInstanceId)
	if err := reg.SetAppRegistration(appInfo.AppInstanceId, convertAppInfoToJson(&appInfo)); err != nil {
	keyName := baseKey + "appInfo:" + appInfo.AppInstanceId
	log.Info("appRegistrationPOST: Storing data with key:", keyName)
	if err := rc.JSONSetEntry(keyName, ".", convertAppInfoToJson(&appInfo)); err != nil {
		log.Error("Failed to store registration in Redis:", err)
		errHandlerProblemDetails(w, "Server error. Could not store registration.", http.StatusInternalServerError)
		return
@@ -1561,8 +1556,9 @@ func appRegistrationGET(w http.ResponseWriter, r *http.Request) {
	mutex.Lock()
	defer mutex.Unlock()

	log.Info("appRegistrationGET: appInstanceId: ", appInstanceId)
	jsonAppInfo, err := reg.GetAppRegistration(appInstanceId)
	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())
@@ -1613,8 +1609,9 @@ func appRegistrationPUT(w http.ResponseWriter, r *http.Request) {
	log.Info("appRegistrationPUT: Parsed appInfoPut:", appInfoPut)

	// Check if the appInfo exists in Redis for the given appInstanceId
	log.Info("appRegistrationPUT: Checking existence of registration:", appInstanceId)
	jsonAppInfo, _ := reg.GetAppRegistration(appInstanceId)
	keyName := baseKey + "appInfo:" + appInstanceId
	log.Info("appRegistrationPUT: Checking existence of key:", keyName)
	jsonAppInfo, _ := rc.JSONGetEntry(keyName, ".")
	if jsonAppInfo == "" {
		log.Error("No appInfo found for provided appInstanceId")
		errHandlerProblemDetails(w, "appInfo not found for the provided appInstanceId", http.StatusNotFound)
@@ -1673,7 +1670,7 @@ func appRegistrationPUT(w http.ResponseWriter, r *http.Request) {
	// Set appInstanceId in appInfoPut and store in Redis
	appInfoPut.AppInstanceId = appInstanceId
	log.Info("appRegistrationPUT: Storing updated appInfo for appInstanceId:", appInstanceId)
	if err := reg.SetAppRegistration(appInstanceId, convertAppInfoToJson(&appInfoPut)); err != nil {
	if err := rc.JSONSetEntry(keyName, ".", convertAppInfoToJson(&appInfoPut)); err != nil {
		log.Error("Failed to store updated AppInfo in Redis:", err)
		errHandlerProblemDetails(w, "Server error. Could not store updated appInfo.", http.StatusInternalServerError)
		return
@@ -1697,8 +1694,10 @@ func appRegistrationDELETE(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	appInstanceId := vars["appInstanceId"]

	keyName := baseKey + "appInfo:" + appInstanceId

	// Find appInfo entry in redis DB
	_, err := reg.GetAppRegistration(appInstanceId)
	_, err := rc.JSONGetEntry(keyName, ".")
	if err != nil {
		err = errors.New("appInfo not found against the provided appInstanceId")
		log.Error(err.Error())
@@ -1707,7 +1706,7 @@ func appRegistrationDELETE(w http.ResponseWriter, r *http.Request) {
	}

	// Delete appInfo entry from redis DB
	err = reg.DeleteAppRegistration(appInstanceId)
	err = rc.JSONDelEntry(keyName, ".")
	if err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusNotFound)
@@ -1745,8 +1744,11 @@ func deleteAppInstance(appId string) {
func getAppList() ([]map[string]string, error) {
	log.Debug(">>> getAppList")

	var appInfoList []map[string]string

	// Get all applications from DB
	appInfoList, err := reg.ListAppInstances()
	keyMatchStr := baseKey + "app:*:info"
	err := rc.ForEachEntry(keyMatchStr, populateAppInfo, &appInfoList)
	if err != nil {
		log.Error("Failed to get app info list with error: ", err.Error())
		return nil, err
@@ -1754,31 +1756,71 @@ func getAppList() ([]map[string]string, error) {
	return appInfoList, nil
}

func populateAppInfo(key string, entry map[string]string, userData interface{}) error {
	appInfoList := userData.(*[]map[string]string)

	// Copy entry
	appInfo := make(map[string]string, len(entry))
	for k, v := range entry {
		appInfo[k] = v
	}

	// Add app info to list
	*appInfoList = append(*appInfoList, appInfo)
	return nil
}

func validateAppInfo(appInfo map[string]string) (int, string, error) {
	// Make sure App is in ready state
	if err := registry.CheckAppInstanceReady(appInfo); err != nil {
	if appInfo[fieldState] != APP_STATE_READY {
		var problemDetails ProblemDetails
		problemDetails.Status = http.StatusForbidden
		problemDetails.Detail = registry.AppNotReadyDetail
		return http.StatusForbidden, convertProblemDetailsToJson(&problemDetails), err
		problemDetails.Detail = "App Instance not ready. Waiting for AppReadyConfirmation."
		return http.StatusForbidden, convertProblemDetailsToJson(&problemDetails), errors.New("App Instance not ready")
	}
	return http.StatusOK, "", nil
}

func newAppInfo(app *apps.Application) (map[string]string, error) {
	log.Debug(">>> newAppInfo: ", app)
	return registry.NewAppInstanceInfo(app)

	// Validate app
	if app == nil {
		return nil, errors.New("nil application")
	}

	// Create App Info
	appInfo := make(map[string]string)
	appInfo[fieldAppId] = app.Id
	appInfo[fieldName] = app.Name
	appInfo[fieldNode] = app.Node
	appInfo[fieldType] = app.Type
	appInfo[fieldPersist] = strconv.FormatBool(app.Persist)
	appInfo[fieldState] = APP_STATE_INITIALIZED
	return appInfo, nil
}

func setAppInfo(appInfo map[string]string) error {
	log.Debug(">>> setAppInfo: ", appInfo)

	appId, found := appInfo[fieldAppId]
	if !found || appId == "" {
		return errors.New("missing app instance id")
	}

	// Convert value type to interface{} before storing app info
	entry := make(map[string]interface{}, len(appInfo))
	for k, v := range appInfo {
		entry[k] = v
	}

	// Store entry
	err := reg.SetAppInstance(appInfo)
	keyName := baseKey + "app:" + appId + ":info"
	log.Info("setAppInfo: keyName: ", keyName)
	err := rc.SetEntry(keyName, entry)
	if err != nil {
		return err
	}
	appId := appInfo[fieldAppId]

	// Cache entry
	appInfoMap[appId] = appInfo
@@ -2079,13 +2121,17 @@ func sendAppRemoveCnf(id string) {
func getAppInfo(appId string) (map[string]string, error) {
	log.Debug(">>> getAppInfo: ", appId)

	var appInfo map[string]string

	// Get app instance from local MEP only
	appInfo, err := reg.GetAppInstance(appId)
	if err == registry.ErrNotFound {
		return nil, errors.New("app instance not found")
	} else if err != nil {
	keyName := baseKey + "app:" + appId + ":info"
	log.Info("getAppInfo: keyName: ", keyName)
	appInfo, err := rc.GetEntry(keyName)
	if err != nil {
		log.Error(err.Error())
		return nil, err
	} else if len(appInfo) == 0 {
		return nil, errors.New("app instance not found")
	}
	log.Info("getAppInfo: appInfo: ", appInfo)

+29 −20
Changes for go-apps/meep-app-enablement/server/capif-mgmt/service-mgmt.go: 29 added lines, 20 removed lines.
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import (
	"sync"
	"time"

	"github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/registry"
	dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"
	mq "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-mq"
@@ -45,13 +44,14 @@ const appEnablementKey = "app-enablement"
const globalLocationName = "global"
const SER_AVAILABILITY_NOTIF_SUB_TYPE = "SerAvailabilityNotificationSubscription"
const SER_AVAILABILITY_NOTIF_TYPE = "SerAvailabilityNotification"
const APP_STATE_READY = registry.AppStateReady
const APP_STATE_READY = "READY"
const capifEventsBasePath = "capif-events/v1"

// const logModuleAppEnablement = "meep-app-enablement"
const serviceName = "App Enablement Service"

// App Info fields
const fieldState = "state"

// MQ payload fields
const fieldSvcInfo = "svc-info"
@@ -63,7 +63,6 @@ var mutex *sync.Mutex
var redisAddr string // = "meep-redis-master.default.svc.cluster.local:6379"
var APP_ENABLEMENT_DB = 0
var rc *redis.Connector
var reg *registry.Registry
var mqLocal *mq.MsgQueue
var hostUrl *url.URL
var sandboxName string
@@ -123,14 +122,6 @@ func Init(sandbox string, mep string, host *url.URL, msgQueue *mq.MsgQueue, redi
	}
	log.Info("Connected to Redis DB")

	// Create Registry facade
	reg, err = registry.New(sandboxName, mepName, redisAddr, mqLocal)
	if err != nil {
		log.Error("Failed to create Registry. Error: ", err)
		return err
	}
	log.Info("Created Registry")

	// Create Subscription Manager
	subMgrCfg := &subs.SubscriptionMgrCfg{
		Module:         moduleName,
@@ -2225,32 +2216,50 @@ func getLocationNameFromKey(key string) string {
}

func getAppInfo(appId string) (map[string]string, error) {
	var appInfo map[string]string

	// Get app instance from local MEP only
	appInfo, err := reg.GetAppInstance(appId)
	if err != nil {
	key := baseKey + "app:" + appId + ":info"
	appInfo, err := rc.GetEntry(key)
	if err != nil || len(appInfo) == 0 {
		return nil, errors.New("App Instance not found")
	}
	return appInfo, nil
}

func getAppInfoAnyMep(appId string) (map[string]string, error) {
	log.Debug(">>> getAppInfoAnyMep: appId=", appId)
	var appInfoList []map[string]string

	// Get app instance from any MEP
	appInfo, err := reg.GetAppInstanceAnyMep(appId)
	if err != nil {
	keyMatchStr := baseKeyAnyMep + "app:" + appId + ":info"
	err := rc.ForEachEntry(keyMatchStr, populateAppInfo, &appInfoList)
	if err != nil || len(appInfoList) != 1 {
		return nil, errors.New("App Instance not found")
	}
	return appInfo, nil
	return appInfoList[0], nil
}

func populateAppInfo(key string, entry map[string]string, userData interface{}) error {
	appInfoList := userData.(*[]map[string]string)

	// Copy entry
	appInfo := make(map[string]string, len(entry))
	for k, v := range entry {
		appInfo[k] = v
	}

	// Add app info to list
	*appInfoList = append(*appInfoList, appInfo)
	return nil
}

func validateAppInfo(appInfo map[string]string) (int, string, error) {
	// Make sure App is in ready state
	if err := registry.CheckAppInstanceReady(appInfo); err != nil {
	if appInfo[fieldState] != APP_STATE_READY {
		var problemDetails ProblemDetails
		problemDetails.Status = http.StatusForbidden
		problemDetails.Detail = registry.AppNotReadyDetail
		return http.StatusForbidden, convertProblemDetailsToJson(&problemDetails), err
		problemDetails.Detail = "App Instance not ready. Waiting for AppReadyConfirmation."
		return http.StatusForbidden, convertProblemDetailsToJson(&problemDetails), errors.New("App Instance not ready")
	}
	return http.StatusOK, "", nil
}
+9 −2
Changes for go-apps/meep-app-enablement/server/edge-1/edge1.go: 9 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -35,10 +35,17 @@ var mepName string

// Init - EDGE-1 service initialization
func Init(sandbox string, mep string, host *url.URL, msgQueue *mq.MsgQueue, redisAddr_ string, globalMutex *sync.Mutex) (err error) {
	// Same signature as the other app-enablement services; the platform context (host URL, message
	// queue, Redis, global mutex) is used from the registry and connector phases onwards.
	// Same signature as the other app-enablement services; the host URL, message queue and global
	// mutex are used by the connector from the next phases onwards.
	sandboxName = sandbox
	mepName = mep

	// Connect the EEC registration store owned by this service
	err = initStore(sandbox, mep, redisAddr_)
	if err != nil {
		log.Error("Failed to connect to Redis DB. Error: ", err)
		return err
	}
	log.Info(serviceName, " initialized: sandbox ", sandboxName, ", MEP ", mepName)
	return nil
}
+159 −0
Changes for go-apps/meep-app-enablement/server/edge-1/eec_store.go: 159 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*
 * Copyright (c) 2026  The AdvantEDGE Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// EEC registration and service provisioning store of the MEP, owned by EDGE-1 / EDGE-4.
//
// Records are kept as JSON under the MEP base key, in the EDGE namespace, which the MEC 011 app
// clean-up (key prefix app:<appInstanceId>) never touches:
//
//	<sandbox key root>app-enablement:mep:<mep>:edge:eec:<registrationId>     EEC registration
//	<sandbox key root>app-enablement:mep:<mep>:edge:eec-by-id:<eecId>        registrationId of an EEC
//	<sandbox key root>app-enablement:mep:<mep>:edge:ecs-prov:<eecId>         last service provisioning request
//
// (app-enablement:mep-global: instead of app-enablement:mep:<mep>: for the global instance)
// EAS registrations are owned by EDGE-3 and read through its exported read functions.
package server

import (
	"errors"

	dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
	redis "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis"
)

const appEnablementKey = "app-enablement"
const globalLocationName = "global"
const redisTable = 0

const (
	eecRegistrationKind = "eec"
	eecIdIndexKind      = "eec-by-id"
	ecsProvisioningKind = "ecs-prov"
)

var errNotFound = errors.New("not found")
var errStoreNotInitialized = errors.New("EEC registration store not initialized")

var rc *redis.Connector
var edgeBaseKey string

func initStore(sandbox string, mep string, redisAddr string) (err error) {
	if mep == globalLocationName {
		edgeBaseKey = dkm.GetKeyRoot(sandbox) + appEnablementKey + ":mep-global:edge:"
	} else {
		edgeBaseKey = dkm.GetKeyRoot(sandbox) + appEnablementKey + ":mep:" + mep + ":edge:"
	}
	rc, err = redis.NewConnector(redisAddr, redisTable)
	return err
}

func storeKey(kind string, id string) string {
	return edgeBaseKey + kind + ":" + id
}

// EEC registrations

// setEecRegistration - create or replace an EEC registration and its eecId index
func setEecRegistration(registrationId string, eecId string, registrationJson string) error {
	if registrationId == "" || eecId == "" {
		return errors.New("missing registrationId or eecId")
	}
	if err := storeSet(eecRegistrationKind, registrationId, registrationJson); err != nil {
		return err
	}
	return storeSet(eecIdIndexKind, eecId, registrationId)
}

// getEecRegistration - EEC registration (JSON), errNotFound if absent
func getEecRegistration(registrationId string) (string, error) {
	return storeGet(eecRegistrationKind, registrationId)
}

// getEecRegistrationIdByEecId - registrationId of a registered EEC, errNotFound if absent
func getEecRegistrationIdByEecId(eecId string) (string, error) {
	return storeGet(eecIdIndexKind, eecId)
}

// deleteEecRegistration - remove an EEC registration and its eecId index, errNotFound if absent
func deleteEecRegistration(registrationId string, eecId string) error {
	if err := storeDelete(eecRegistrationKind, registrationId); err != nil {
		return err
	}
	if err := storeDelete(eecIdIndexKind, eecId); err != nil && err != errNotFound {
		return err
	}
	return nil
}

// EDGE-4 service provisioning (informational only: not a precondition of EEC registration, gap G2)

// setEcsProvisioning - last service provisioning request (JSON) of an EEC
func setEcsProvisioning(eecId string, requestJson string) error {
	if eecId == "" {
		return errors.New("missing eecId")
	}
	return storeSet(ecsProvisioningKind, eecId, requestJson)
}

// getEcsProvisioning - last service provisioning request (JSON) of an EEC, errNotFound if absent
func getEcsProvisioning(eecId string) (string, error) {
	return storeGet(ecsProvisioningKind, eecId)
}

// flushEdge1Store - remove all EDGE-1 / EDGE-4 records of this MEP
func flushEdge1Store() error {
	if rc == nil {
		return errStoreNotInitialized
	}
	for _, kind := range []string{eecRegistrationKind, eecIdIndexKind, ecsProvisioningKind} {
		if err := rc.DBFlush(storeKey(kind, "")); err != nil {
			return err
		}
	}
	return nil
}

func storeSet(kind string, id string, value string) error {
	if rc == nil {
		return errStoreNotInitialized
	}
	return rc.JSONSetEntry(storeKey(kind, id), ".", value)
}

func storeGet(kind string, id string) (string, error) {
	if rc == nil {
		return "", errStoreNotInitialized
	}
	if id == "" {
		return "", errNotFound
	}
	key := storeKey(kind, id)
	if !rc.EntryExists(key) {
		return "", errNotFound
	}
	return rc.JSONGetEntry(key, ".")
}

func storeDelete(kind string, id string) error {
	if rc == nil {
		return errStoreNotInitialized
	}
	key := storeKey(kind, id)
	if id == "" || !rc.EntryExists(key) {
		return errNotFound
	}
	return rc.JSONDelEntry(key, ".")
}
+164 −0
Changes for go-apps/meep-app-enablement/server/edge-1/eec_store_test.go: 164 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*
 * Copyright (c) 2026  The AdvantEDGE Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package server

import (
	"testing"

	edge3 "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-app-enablement/server/edge-3"
	dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
	redis "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis"
)

// Same test Redis as the other meep-app-enablement unit tests
const redisTestAddr = "localhost:30380"
const storeTestSandbox = "edge1-store-test"

// EAS discovery (EDGE-1) reads the EAS registrations owned by EDGE-3 through its read API.
// EDGE-1 may import EDGE-3 (EDGE-3 never imports EDGE-1, so there is no import cycle).
var _ = []interface{}{edge3.GetEasRegistration, edge3.GetEasRegistrationIdByEasId, edge3.ListEasRegistrations}

func flushTestSandbox(t *testing.T) {
	t.Helper()
	c, err := redis.NewConnector(redisTestAddr, redisTable)
	if err != nil {
		t.Fatalf("Redis not available at %s: %v", redisTestAddr, err)
	}
	_ = c.DBFlush(dkm.GetKeyRoot(storeTestSandbox))
}

func TestEdge1StoreNotInitialized(t *testing.T) {
	saved := rc
	rc = nil
	defer func() { rc = saved }()
	if _, err := getEecRegistration("r1"); err != errStoreNotInitialized {
		t.Errorf("get: %v", err)
	}
	if err := setEecRegistration("r1", "eec1", "{}"); err != errStoreNotInitialized {
		t.Errorf("set: %v", err)
	}
	if err := deleteEecRegistration("r1", "eec1"); err != errStoreNotInitialized {
		t.Errorf("delete: %v", err)
	}
	if err := flushEdge1Store(); err != errStoreNotInitialized {
		t.Errorf("flush: %v", err)
	}
}

func TestEdge1StoreBaseKeys(t *testing.T) {
	root := dkm.GetKeyRoot(storeTestSandbox) + "app-enablement:"
	if err := initStore(storeTestSandbox, "global", redisTestAddr); err != nil {
		t.Fatal(err)
	}
	if edgeBaseKey != root+"mep-global:edge:" {
		t.Errorf("global base key: %s", edgeBaseKey)
	}
	if err := initStore(storeTestSandbox, "mep1", redisTestAddr); err != nil {
		t.Fatal(err)
	}
	if storeKey(eecRegistrationKind, "r1") != root+"mep:mep1:edge:eec:r1" {
		t.Errorf("registration key: %s", storeKey(eecRegistrationKind, "r1"))
	}
}

func TestEecRegistrationStore(t *testing.T) {
	flushTestSandbox(t)
	defer flushTestSandbox(t)
	if err := initStore(storeTestSandbox, "mep1", redisTestAddr); err != nil {
		t.Fatal(err)
	}

	if _, err := getEecRegistration("r1"); err != errNotFound {
		t.Errorf("unknown registration: %v", err)
	}
	if _, err := getEecRegistration(""); err != errNotFound {
		t.Errorf("empty id: %v", err)
	}
	if err := setEecRegistration("r1", "", "{}"); err == nil {
		t.Errorf("missing eecId: expected error")
	}
	if err := deleteEecRegistration("r1", "eec1"); err != errNotFound {
		t.Errorf("delete unknown: %v", err)
	}

	reg := `{"eecId":"eec1","ueId":"msisdn-1"}`
	if err := setEecRegistration("r1", "eec1", reg); err != nil {
		t.Fatal(err)
	}
	if v, err := getEecRegistration("r1"); err != nil || v != reg {
		t.Errorf("get: %q %v", v, err)
	}
	if v, err := getEecRegistrationIdByEecId("eec1"); err != nil || v != "r1" {
		t.Errorf("index: %q %v", v, err)
	}

	// Another MEP does not see the registration
	_ = initStore(storeTestSandbox, "mep2", redisTestAddr)
	if _, err := getEecRegistration("r1"); err != errNotFound {
		t.Errorf("mep2 sees mep1 registration: %v", err)
	}
	_ = initStore(storeTestSandbox, "mep1", redisTestAddr)

	if err := deleteEecRegistration("r1", "eec1"); err != nil {
		t.Errorf("delete: %v", err)
	}
	if _, err := getEecRegistrationIdByEecId("eec1"); err != errNotFound {
		t.Errorf("index not deleted: %v", err)
	}
}

func TestEcsProvisioningStore(t *testing.T) {
	flushTestSandbox(t)
	defer flushTestSandbox(t)
	_ = initStore(storeTestSandbox, "mep1", redisTestAddr)

	if err := setEcsProvisioning("", "{}"); err == nil {
		t.Errorf("missing eecId: expected error")
	}
	req := `{"eecId":"eec1","acProfs":[{"acId":"ac-1"}]}`
	if err := setEcsProvisioning("eec1", req); err != nil {
		t.Fatal(err)
	}
	if v, err := getEcsProvisioning("eec1"); err != nil || v != req {
		t.Errorf("get: %q %v", v, err)
	}

	// Flush removes all EDGE-1 / EDGE-4 records
	_ = setEecRegistration("r1", "eec1", `{"eecId":"eec1"}`)
	if err := flushEdge1Store(); err != nil {
		t.Errorf("flush: %v", err)
	}
	if _, err := getEcsProvisioning("eec1"); err != errNotFound {
		t.Errorf("flush left provisioning: %v", err)
	}
	if _, err := getEecRegistration("r1"); err != errNotFound {
		t.Errorf("flush left registration: %v", err)
	}
}

// The MEC 011 app instance clean-up flushes by key prefix app:<appInstanceId>; EEC records must
// survive it (the legacy EDGE code stored EEC data under app:<eecId>, gap G1).
func TestEdge1StoreSurvivesAppFlush(t *testing.T) {
	flushTestSandbox(t)
	defer flushTestSandbox(t)
	_ = initStore(storeTestSandbox, "mep1", redisTestAddr)
	_ = setEecRegistration("eec1", "eec1", `{"eecId":"eec1"}`)
	appPrefix := dkm.GetKeyRoot(storeTestSandbox) + "app-enablement:mep:mep1:app:eec1"
	_ = rc.DBFlush(appPrefix)
	if _, err := getEecRegistration("eec1"); err != nil {
		t.Errorf("EEC registration removed by the MEC 011 app clean-up: %v", err)
	}
}
Loading