Add support for user-friendly names in YANG data with aliases in KV store

Change-Id: I8b4fbb290dfb467b401e8cf20b825ddbb3bf897e
diff --git a/internal/core/adapter.go b/internal/core/adapter.go
index 5e19753..8f41c59 100644
--- a/internal/core/adapter.go
+++ b/internal/core/adapter.go
@@ -18,9 +18,13 @@
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
+	"strconv"
 
 	"github.com/golang/protobuf/ptypes/empty"
+	"github.com/opencord/voltha-lib-go/v7/pkg/db"
+	"github.com/opencord/voltha-lib-go/v7/pkg/db/kvstore"
 	"github.com/opencord/voltha-lib-go/v7/pkg/log"
 	"github.com/opencord/voltha-northbound-bbf-adapter/internal/clients"
 	"github.com/opencord/voltha-protos/v5/go/voltha"
@@ -28,15 +32,21 @@
 
 var AdapterInstance *VolthaYangAdapter
 
+const (
+	kvStoreServices = "services"
+)
+
 type VolthaYangAdapter struct {
 	volthaNbiClient *clients.VolthaNbiClient
 	onosClient      *clients.OnosClient
+	kvStore         *db.Backend
 }
 
-func NewVolthaYangAdapter(nbiClient *clients.VolthaNbiClient, onosClient *clients.OnosClient) *VolthaYangAdapter {
+func NewVolthaYangAdapter(nbiClient *clients.VolthaNbiClient, onosClient *clients.OnosClient, kvBackend *db.Backend) *VolthaYangAdapter {
 	return &VolthaYangAdapter{
 		volthaNbiClient: nbiClient,
 		onosClient:      onosClient,
+		kvStore:         kvBackend,
 	}
 }
 
@@ -76,6 +86,49 @@
 	return items, nil
 }
 
+func getLocationsToPortsMap(ports []clients.OnosPort) map[string]string {
+	//Create a map of port IDs to port names
+	//e.g. of:00000a0a0a0a0a0a/256 to BBSM000a0001-1
+	portNames := map[string]string{}
+
+	for _, port := range ports {
+		portId := fmt.Sprintf("%s/%s", port.Element, port.Port)
+		name, ok := port.Annotations["portName"]
+		if ok {
+			portNames[portId] = name
+		}
+	}
+
+	return portNames
+}
+
+func (t *VolthaYangAdapter) getServiceAliasOrFallback(ctx context.Context, uniTagServiceName string, key ServiceKey) (*ServiceAlias, error) {
+	alias, err := t.LoadServiceAlias(ctx, key)
+	if err != nil {
+		//Happens in case a service is provisioned using ONOS directly,
+		//bypassing the adapter
+		serviceName := fmt.Sprintf("%s-%s", key.Port, uniTagServiceName)
+		alias = &ServiceAlias{
+			Key:         key,
+			ServiceName: serviceName,
+			VlansName:   serviceName + "-vlans",
+		}
+
+		logger.Warnw(ctx, "cannot-load-service-alias", log.Fields{
+			"err":      err,
+			"fallback": alias,
+		})
+
+		//Store the fallback alias to avoid the fallback on future requests
+		err := t.StoreServiceAlias(ctx, *alias)
+		if err != nil {
+			return nil, fmt.Errorf("cannot-store-fallback-service-alias")
+		}
+	}
+
+	return alias, nil
+}
+
 func (t *VolthaYangAdapter) GetVlans(ctx context.Context) ([]YangItem, error) {
 	services, err := t.onosClient.GetProgrammedSubscribers()
 	if err != nil {
@@ -94,9 +147,32 @@
 	}
 	logger.Debugw(ctx, "get-onos-ports-success", log.Fields{"ports": ports})
 
-	items, err := translateVlans(services, ports)
-	if err != nil {
-		return nil, fmt.Errorf("cannot-translate-vlans: %v", err)
+	portNames := getLocationsToPortsMap(ports)
+
+	items := []YangItem{}
+
+	for _, service := range services {
+		portName, ok := portNames[service.Location]
+		if !ok {
+			return nil, fmt.Errorf("no-port-name-for-location: %s", service.Location)
+		}
+
+		alias, err := t.getServiceAliasOrFallback(ctx, service.TagInfo.ServiceName, ServiceKey{
+			Port: portName,
+			STag: strconv.Itoa(service.TagInfo.PonSTag),
+			CTag: strconv.Itoa(service.TagInfo.PonCTag),
+			TpId: strconv.Itoa(service.TagInfo.TechnologyProfileID),
+		})
+		if err != nil {
+			return nil, err
+		}
+
+		vlansItems, err := translateVlans(service.TagInfo, *alias)
+		if err != nil {
+			return nil, fmt.Errorf("cannot-translate-vlans: %v", err)
+		}
+
+		items = append(items, vlansItems...)
 	}
 
 	return items, nil
@@ -169,9 +245,32 @@
 	}
 	logger.Debugw(ctx, "get-onos-ports-success", log.Fields{"ports": ports})
 
-	items, err := translateServices(services, ports)
-	if err != nil {
-		return nil, fmt.Errorf("cannot-translate-services: %v", err)
+	portNames := getLocationsToPortsMap(ports)
+
+	items := []YangItem{}
+
+	for _, service := range services {
+		portName, ok := portNames[service.Location]
+		if !ok {
+			return nil, fmt.Errorf("no-port-name-for-location: %s", service.Location)
+		}
+
+		alias, err := t.getServiceAliasOrFallback(ctx, service.TagInfo.ServiceName, ServiceKey{
+			Port: portName,
+			STag: strconv.Itoa(service.TagInfo.PonSTag),
+			CTag: strconv.Itoa(service.TagInfo.PonCTag),
+			TpId: strconv.Itoa(service.TagInfo.TechnologyProfileID),
+		})
+		if err != nil {
+			return nil, err
+		}
+
+		serviceItems, err := translateService(service.TagInfo, *alias)
+		if err != nil {
+			return nil, fmt.Errorf("cannot-translate-service: %v", err)
+		}
+
+		items = append(items, serviceItems...)
 	}
 
 	return items, nil
@@ -186,3 +285,68 @@
 	_, err := t.onosClient.RemoveService(portName, sTag, cTag, technologyProfileId)
 	return err
 }
+
+//Used to uniquely identify the service and
+//construct a KV Store path to the service info
+type ServiceKey struct {
+	Port string `json:"port"`
+	STag string `json:"sTag"`
+	CTag string `json:"cTag"`
+	TpId string `json:"tpId"`
+}
+
+//Holds user provided names for the definition
+//of a service in the yang datastore
+type ServiceAlias struct {
+	Key         ServiceKey `json:"key"`
+	ServiceName string     `json:"serviceName"`
+	VlansName   string     `json:"vlansName"`
+}
+
+func getServiceAliasKVPath(key ServiceKey) string {
+	return fmt.Sprintf("%s/%s/%s/%s/%s", kvStoreServices, key.Port, key.STag, key.CTag, key.TpId)
+}
+
+func (t *VolthaYangAdapter) StoreServiceAlias(ctx context.Context, alias ServiceAlias) error {
+	json, err := json.Marshal(alias)
+	if err != nil {
+		return err
+	}
+
+	if err = t.kvStore.Put(ctx, getServiceAliasKVPath(alias.Key), json); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (t *VolthaYangAdapter) LoadServiceAlias(ctx context.Context, key ServiceKey) (*ServiceAlias, error) {
+	found, err := t.kvStore.Get(ctx, getServiceAliasKVPath(key))
+	if err != nil {
+		return nil, err
+	}
+
+	if found == nil {
+		return nil, fmt.Errorf("service-alias-not-found-in-kvstore: %s", key)
+	}
+
+	var foundAlias ServiceAlias
+	value, err := kvstore.ToByte(found.Value)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := json.Unmarshal(value, &foundAlias); err != nil {
+		return nil, err
+	}
+
+	return &foundAlias, nil
+}
+
+func (t *VolthaYangAdapter) DeleteServiceAlias(ctx context.Context, key ServiceKey) error {
+	err := t.kvStore.Delete(ctx, getServiceAliasKVPath(key))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}