Add initial support for provisioning and removing services, getting service data
Change-Id: Ie49206d788a202e70a8d64f083c3f85b92ced8fb
diff --git a/internal/clients/olt_app.go b/internal/clients/olt_app.go
deleted file mode 100644
index 6e171b9..0000000
--- a/internal/clients/olt_app.go
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
-* Copyright 2022-present Open Networking Foundation
-
-* 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 clients
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "time"
-
- "github.com/opencord/voltha-lib-go/v7/pkg/log"
-)
-
-const (
- oltAppHttpRequestTimeout = time.Second * 10
- oltAppBackoffInterval = time.Second * 10
-)
-
-type OltAppClient struct {
- httpClient *http.Client
- endpoint string
- username string
- password string
-}
-
-type RestResponse struct {
- Body string
- Code int
-}
-
-// Creates a new olt app client
-func NewOltAppClient(endpoint string, user string, pass string) *OltAppClient {
- return &OltAppClient{
- httpClient: &http.Client{
- Timeout: oltAppHttpRequestTimeout,
- },
- endpoint: endpoint,
- username: user,
- password: pass,
- }
-}
-
-func (c *OltAppClient) CheckConnection(ctx context.Context) error {
- logger.Debugw(ctx, "checking-connection-to-onos-olt-app-api", log.Fields{"endpoint": c.endpoint})
-
- for {
- if resp, err := c.GetStatus(); err == nil {
- logger.Debug(ctx, "onos-olt-app-api-reachable")
- break
- } else {
- logger.Warnw(ctx, "onos-olt-app-api-not-ready", log.Fields{
- "err": err,
- "response": resp,
- })
- }
-
- //Wait a bit before trying again
- select {
- case <-ctx.Done():
- return fmt.Errorf("onos-olt-app-connection-stopped-due-to-context-done")
- case <-time.After(oltAppBackoffInterval):
- continue
- }
- }
-
- return nil
-}
-
-func (c *OltAppClient) makeRequest(method string, url string) (RestResponse, error) {
- result := RestResponse{Code: 0}
-
- req, err := http.NewRequest(method, url, nil)
- if err != nil {
- return result, fmt.Errorf("cannot-create-request: %s", err)
- }
-
- req.SetBasicAuth(c.username, c.password)
-
- resp, err := c.httpClient.Do(req)
- if err != nil {
- return result, fmt.Errorf("cannot-get-response: %s", err)
- }
- defer resp.Body.Close()
-
- buffer, err := io.ReadAll(resp.Body)
- if err != nil {
- return result, fmt.Errorf("error-while-reading-response-body: %s", err)
- }
-
- result.Body = string(buffer)
- result.Code = resp.StatusCode
-
- if result.Code != http.StatusOK {
- return result, fmt.Errorf("status-code-not-ok: %s %s %d", method, url, result.Code)
- }
-
- return result, nil
-}
-
-func (c *OltAppClient) GetStatus() (RestResponse, error) {
- method := http.MethodGet
- url := fmt.Sprintf("http://%s/onos/olt/oltapp/status", c.endpoint)
-
- return c.makeRequest(method, url)
-}
-
-//NOTE: if methods are used to retrieve more complex information
-//it may be better to return an already deserialized structure
-//instead of the current RestResponse
-func (c *OltAppClient) ProvisionSubscriber(device string, port uint32) (RestResponse, error) {
- method := http.MethodPost
- url := fmt.Sprintf("http://%s/onos/olt/oltapp/%s/%d", c.endpoint, device, port)
-
- return c.makeRequest(method, url)
-}
-
-func (c *OltAppClient) RemoveSubscriber(device string, port uint32) (RestResponse, error) {
- method := http.MethodDelete
- url := fmt.Sprintf("http://%s/onos/olt/oltapp/%s/%d", c.endpoint, device, port)
-
- return c.makeRequest(method, url)
-}
diff --git a/internal/clients/onos.go b/internal/clients/onos.go
new file mode 100644
index 0000000..2de584c
--- /dev/null
+++ b/internal/clients/onos.go
@@ -0,0 +1,254 @@
+/*
+* Copyright 2022-present Open Networking Foundation
+
+* 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 clients
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+
+ "github.com/opencord/voltha-lib-go/v7/pkg/log"
+)
+
+const (
+ onosHttpRequestTimeout = time.Second * 10
+ onosBackoffInterval = time.Second * 10
+)
+
+type OnosClient struct {
+ httpClient *http.Client
+ endpoint string
+ username string
+ password string
+}
+
+type RestResponse struct {
+ Body string
+ Code int
+}
+
+// Creates a new olt app client
+func NewOnosClient(endpoint string, user string, pass string) *OnosClient {
+ return &OnosClient{
+ httpClient: &http.Client{
+ Timeout: onosHttpRequestTimeout,
+ },
+ endpoint: endpoint,
+ username: user,
+ password: pass,
+ }
+}
+
+func (c *OnosClient) CheckConnection(ctx context.Context) error {
+ logger.Debugw(ctx, "checking-connection-to-onos-olt-app-api", log.Fields{"endpoint": c.endpoint})
+
+ for {
+ if resp, err := c.GetStatus(); err == nil {
+ logger.Debug(ctx, "onos-olt-app-api-reachable")
+ break
+ } else {
+ logger.Warnw(ctx, "onos-olt-app-api-not-ready", log.Fields{
+ "err": err,
+ "response": resp,
+ })
+ }
+
+ //Wait a bit before trying again
+ select {
+ case <-ctx.Done():
+ return fmt.Errorf("onos-olt-app-connection-stopped-due-to-context-done")
+ case <-time.After(onosBackoffInterval):
+ continue
+ }
+ }
+
+ return nil
+}
+
+func (c *OnosClient) makeRequest(method string, url string) (RestResponse, error) {
+ result := RestResponse{Code: 0}
+
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ return result, fmt.Errorf("cannot-create-request: %s", err)
+ }
+
+ req.SetBasicAuth(c.username, c.password)
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return result, fmt.Errorf("cannot-get-response: %s", err)
+ }
+ defer resp.Body.Close()
+
+ buffer, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return result, fmt.Errorf("error-while-reading-response-body: %s", err)
+ }
+
+ result.Body = string(buffer)
+ result.Code = resp.StatusCode
+
+ if result.Code != http.StatusOK {
+ return result, fmt.Errorf("status-code-not-ok: %s %s %d", method, url, result.Code)
+ }
+
+ return result, nil
+}
+
+///////////////////////////////////////////////////////////////////////// ONOS OLT app APIs
+
+func (c *OnosClient) GetStatus() (RestResponse, error) {
+ method := http.MethodGet
+ url := fmt.Sprintf("http://%s/onos/olt/oltapp/status", c.endpoint)
+
+ return c.makeRequest(method, url)
+}
+
+func (c *OnosClient) ProvisionService(portName string, sTag string, cTag string, technologyProfileId string) (RestResponse, error) {
+ method := http.MethodPost
+ url := fmt.Sprintf("http://%s/onos/olt/oltapp/services/%s/%s/%s/%s", c.endpoint, portName, sTag, cTag, technologyProfileId)
+
+ return c.makeRequest(method, url)
+}
+
+func (c *OnosClient) RemoveService(portName string, sTag string, cTag string, trafficProfileId string) (RestResponse, error) {
+ method := http.MethodDelete
+ url := fmt.Sprintf("http://%s/onos/olt/oltapp/services/%s/%s/%s/%s", c.endpoint, portName, sTag, cTag, trafficProfileId)
+
+ return c.makeRequest(method, url)
+}
+
+type ProgrammedSubscriber struct {
+ Location string `json:"location"`
+ TagInfo SadisUniTag `json:"tagInfo"`
+}
+
+type SadisUniTag struct {
+ UniTagMatch int `json:"uniTagMatch,omitempty"`
+ PonCTag int `json:"ponCTag,omitempty"`
+ PonSTag int `json:"ponSTag,omitempty"`
+ TechnologyProfileID int `json:"technologyProfileId,omitempty"`
+ UpstreamBandwidthProfile string `json:"upstreamBandwidthProfile,omitempty"`
+ UpstreamOltBandwidthProfile string `json:"upstreamOltBandwidthProfile,omitempty"`
+ DownstreamBandwidthProfile string `json:"downstreamBandwidthProfile,omitempty"`
+ DownstreamOltBandwidthProfile string `json:"downstreamOltBandwidthProfile,omitempty"`
+ IsDhcpRequired bool `json:"isDhcpRequired,omitempty"`
+ IsIgmpRequired bool `json:"isIgmpRequired,omitempty"`
+ IsPPPoERequired bool `json:"isPppoeRequired,omitempty"`
+ ConfiguredMacAddress string `json:"configuredMacAddress,omitempty"`
+ EnableMacLearning bool `json:"enableMacLearning,omitempty"`
+ UsPonCTagPriority int `json:"usPonCTagPriority,omitempty"`
+ UsPonSTagPriority int `json:"usPonSTagPriority,omitempty"`
+ DsPonCTagPriority int `json:"dsPonCTagPriority,omitempty"`
+ DsPonSTagPriority int `json:"dsPonSTagPriority,omitempty"`
+ ServiceName string `json:"serviceName,omitempty"`
+}
+
+func (c *OnosClient) GetProgrammedSubscribers() ([]ProgrammedSubscriber, error) {
+ method := http.MethodGet
+ url := fmt.Sprintf("http://%s/onos/olt/oltapp/programmed-subscribers", c.endpoint)
+
+ response, err := c.makeRequest(method, url)
+ if err != nil {
+ return nil, err
+ }
+
+ var subscribers struct {
+ Entries []ProgrammedSubscriber `json:"entries"`
+ }
+ err = json.Unmarshal([]byte(response.Body), &subscribers)
+ if err != nil {
+ return nil, err
+ }
+
+ return subscribers.Entries, nil
+}
+
+///////////////////////////////////////////////////////////////////////// ONOS Core APIs
+
+type OnosPort struct {
+ Element string `json:"element"` //Device ID
+ Port string `json:"port"` //Port number
+ IsEnabled bool `json:"isEnabled"`
+ Type string `json:"type"`
+ PortSpeed uint `json:"portSpeed"`
+ Annotations map[string]string `json:"annotations"`
+}
+
+func (c *OnosClient) GetPorts() ([]OnosPort, error) {
+ method := http.MethodGet
+ url := fmt.Sprintf("http://%s/onos/v1/devices/ports", c.endpoint)
+
+ response, err := c.makeRequest(method, url)
+ if err != nil {
+ return nil, err
+ }
+
+ var ports struct {
+ Ports []OnosPort `json:"ports"`
+ }
+ err = json.Unmarshal([]byte(response.Body), &ports)
+ if err != nil {
+ return nil, err
+ }
+
+ return ports.Ports, nil
+}
+
+///////////////////////////////////////////////////////////////////////// ONOS SADIS APIs
+
+type BandwidthProfile struct {
+ Id string `json:"id"`
+ Cir int64 `json:"cir"`
+ Cbs string `json:"cbs"`
+ Air int64 `json:"air"`
+ Gir int64 `json:"gir"`
+ Eir int64 `json:"eir"`
+ Ebs string `json:"ebs"`
+ Pir int64 `json:"pir"`
+ Pbs string `json:"pbs"`
+}
+
+func (c *OnosClient) GetBandwidthProfile(id string) (*BandwidthProfile, error) {
+ method := http.MethodGet
+ url := fmt.Sprintf("http://%s/onos/sadis/bandwidthprofile/%s", c.endpoint, id)
+
+ response, err := c.makeRequest(method, url)
+ if err != nil {
+ return nil, err
+ }
+
+ var bwProfiles struct {
+ Entry []BandwidthProfile `json:"entry"`
+ }
+ err = json.Unmarshal([]byte(response.Body), &bwProfiles)
+ if err != nil {
+ return nil, err
+ }
+
+ //The response has a list, but always returns one item
+ //Verify this is correct and return it
+ if len(bwProfiles.Entry) != 1 {
+ return nil, fmt.Errorf("unexpected-number-of-bw-profile-entries: id=%s len=%d", id, len(bwProfiles.Entry))
+ }
+
+ return &bwProfiles.Entry[0], nil
+}
diff --git a/internal/clients/nbi.go b/internal/clients/voltha_nbi.go
similarity index 100%
rename from internal/clients/nbi.go
rename to internal/clients/voltha_nbi.go
diff --git a/internal/core/adapter.go b/internal/core/adapter.go
index 6a59650..5e19753 100644
--- a/internal/core/adapter.go
+++ b/internal/core/adapter.go
@@ -30,13 +30,13 @@
type VolthaYangAdapter struct {
volthaNbiClient *clients.VolthaNbiClient
- oltAppClient *clients.OltAppClient
+ onosClient *clients.OnosClient
}
-func NewVolthaYangAdapter(nbiClient *clients.VolthaNbiClient, oltClient *clients.OltAppClient) *VolthaYangAdapter {
+func NewVolthaYangAdapter(nbiClient *clients.VolthaNbiClient, onosClient *clients.OnosClient) *VolthaYangAdapter {
return &VolthaYangAdapter{
volthaNbiClient: nbiClient,
- oltAppClient: oltClient,
+ onosClient: onosClient,
}
}
@@ -58,7 +58,7 @@
if err != nil {
return nil, fmt.Errorf("get-onu-ports-failed: %v", err)
}
- logger.Debugw(ctx, "get-ports-success", log.Fields{"deviceId": device.Id, "ports": ports})
+ logger.Debugw(ctx, "get-onu-ports-success", log.Fields{"deviceId": device.Id, "ports": ports})
portsItems, err := translateOnuPorts(device.Id, ports)
if err != nil {
@@ -75,3 +75,114 @@
return items, nil
}
+
+func (t *VolthaYangAdapter) GetVlans(ctx context.Context) ([]YangItem, error) {
+ services, err := t.onosClient.GetProgrammedSubscribers()
+ if err != nil {
+ return nil, fmt.Errorf("get-programmed-subscribers-failed: %v", err)
+ }
+ logger.Debugw(ctx, "get-programmed-subscribers-success", log.Fields{"services": services})
+
+ //No need for other requests if there are no services
+ if len(services) == 0 {
+ return []YangItem{}, nil
+ }
+
+ ports, err := t.onosClient.GetPorts()
+ if err != nil {
+ return nil, fmt.Errorf("get-onos-ports-failed: %v", err)
+ }
+ 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)
+ }
+
+ return items, nil
+}
+
+func (t *VolthaYangAdapter) GetBandwidthProfiles(ctx context.Context) ([]YangItem, error) {
+ services, err := t.onosClient.GetProgrammedSubscribers()
+ if err != nil {
+ return nil, fmt.Errorf("get-programmed-subscribers-failed: %v", err)
+ }
+ logger.Debugw(ctx, "get-programmed-subscribers-success", log.Fields{"services": services})
+
+ //No need for other requests if there are no services
+ if len(services) == 0 {
+ return []YangItem{}, nil
+ }
+
+ bwProfilesMap := map[string]bool{}
+ bwProfiles := []clients.BandwidthProfile{}
+
+ for _, service := range services {
+ //Get information on downstream bw profile if new
+ if _, ok := bwProfilesMap[service.TagInfo.DownstreamBandwidthProfile]; !ok {
+ bw, err := t.onosClient.GetBandwidthProfile(service.TagInfo.DownstreamBandwidthProfile)
+ if err != nil {
+ return nil, fmt.Errorf("get-bw-profile-failed: %s %v", service.TagInfo.DownstreamBandwidthProfile, err)
+ }
+ logger.Debugw(ctx, "get-bw-profile-success", log.Fields{"bwProfile": bw})
+
+ bwProfiles = append(bwProfiles, *bw)
+ bwProfilesMap[service.TagInfo.DownstreamBandwidthProfile] = true
+ }
+
+ //Get information on upstream bw profile if new
+ if _, ok := bwProfilesMap[service.TagInfo.UpstreamBandwidthProfile]; !ok {
+ bw, err := t.onosClient.GetBandwidthProfile(service.TagInfo.UpstreamBandwidthProfile)
+ if err != nil {
+ return nil, fmt.Errorf("get-bw-profile-failed: %s %v", service.TagInfo.UpstreamBandwidthProfile, err)
+ }
+ logger.Debugw(ctx, "get-bw-profile-success", log.Fields{"bwProfile": bw})
+
+ bwProfiles = append(bwProfiles, *bw)
+ bwProfilesMap[service.TagInfo.UpstreamBandwidthProfile] = true
+ }
+ }
+
+ items, err := translateBandwidthProfiles(bwProfiles)
+ if err != nil {
+ return nil, fmt.Errorf("cannot-translate-bandwidth-profiles: %v", err)
+ }
+
+ return items, nil
+}
+
+func (t *VolthaYangAdapter) GetServices(ctx context.Context) ([]YangItem, error) {
+ services, err := t.onosClient.GetProgrammedSubscribers()
+ if err != nil {
+ return nil, fmt.Errorf("get-programmed-subscribers-failed: %v", err)
+ }
+ logger.Debugw(ctx, "get-programmed-subscribers-success", log.Fields{"services": services})
+
+ //No need for other requests if there are no services
+ if len(services) == 0 {
+ return []YangItem{}, nil
+ }
+
+ ports, err := t.onosClient.GetPorts()
+ if err != nil {
+ return nil, fmt.Errorf("get-onos-ports-failed: %v", err)
+ }
+ 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)
+ }
+
+ return items, nil
+}
+
+func (t *VolthaYangAdapter) ProvisionService(portName string, sTag string, cTag string, technologyProfileId string) error {
+ _, err := t.onosClient.ProvisionService(portName, sTag, cTag, technologyProfileId)
+ return err
+}
+
+func (t *VolthaYangAdapter) RemoveService(portName string, sTag string, cTag string, technologyProfileId string) error {
+ _, err := t.onosClient.RemoveService(portName, sTag, cTag, technologyProfileId)
+ return err
+}
diff --git a/internal/core/translation.go b/internal/core/translation.go
index ef6582d..623e261 100644
--- a/internal/core/translation.go
+++ b/internal/core/translation.go
@@ -18,8 +18,10 @@
import (
"fmt"
+ "strconv"
"time"
+ "github.com/opencord/voltha-northbound-bbf-adapter/internal/clients"
"github.com/opencord/voltha-protos/v5/go/common"
"github.com/opencord/voltha-protos/v5/go/voltha"
)
@@ -28,6 +30,15 @@
DeviceAggregationModule = "bbf-device-aggregation"
DevicesPath = "/" + DeviceAggregationModule + ":devices"
+ ServiceProfileModule = "bbf-nt-service-profile"
+ ServiceProfilesPath = "/" + ServiceProfileModule + ":service-profiles"
+
+ VlansModule = "bbf-l2-access-attributes"
+ VlansPath = "/" + VlansModule + ":vlan-translation-profiles"
+
+ BandwidthProfileModule = "bbf-nt-line-profile"
+ BandwidthProfilesPath = "/" + BandwidthProfileModule + ":line-bandwidth-profiles"
+
//Device types
DeviceTypeOlt = "bbf-device-types:olt"
DeviceTypeOnu = "bbf-device-types:onu"
@@ -49,6 +60,10 @@
eventContextKeyPonId = "pon-id"
eventContextKeyOnuSn = "serial-number"
eventContextKeyOltSn = "olt-serial-number"
+
+ //Values to allow any VLAN ID
+ YangVlanIdAny = "any"
+ VolthaVlanIdAny = 4096
)
type YangItem struct {
@@ -66,6 +81,16 @@
return fmt.Sprintf("%s/device[name='%s']/data/ietf-hardware:hardware/component[name='%s']", DevicesPath, id, id)
}
+//GetServicePortPath returns the yang path to a service's port node
+func GetServicePortPath(serviceName string, portName string) string {
+ return fmt.Sprintf("%s/service-profile[name='%s']/ports/port[name='%s']", ServiceProfilesPath, serviceName, portName)
+}
+
+//GetVlansPath returns the yang path to a vlan translation profile's root node
+func GetVlansPath(serviceName string) string {
+ return fmt.Sprintf("%s/vlan-translation-profile[name='%s']", VlansPath, serviceName)
+}
+
//ietfHardwareAdminState returns the string that represents the ietf-hardware admin state
//enum value corresponding to the one of VOLTHA
func ietfHardwareAdminState(volthaAdminState voltha.AdminState_Types) string {
@@ -146,10 +171,20 @@
})
} else {
//ONU
- result = append(result, YangItem{
- Path: devicePath + "/type",
- Value: DeviceTypeOnu,
- })
+ result = append(result, []YangItem{
+ {
+ Path: devicePath + "/type",
+ Value: DeviceTypeOnu,
+ },
+ {
+ Path: hardwarePath + "/parent",
+ Value: device.ParentId,
+ },
+ {
+ Path: hardwarePath + "/parent-rel-pos",
+ Value: strconv.FormatUint(uint64(device.ParentPortNo), 10),
+ },
+ }...)
}
//Vendor name
@@ -284,3 +319,193 @@
return notification, channelTermination, nil
}
+
+//translateServices returns a slice of yang items that represent the currently programmed services
+func translateServices(subscribers []clients.ProgrammedSubscriber, ports []clients.OnosPort) ([]YangItem, error) {
+ //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
+ }
+ }
+
+ result := []YangItem{}
+
+ for _, subscriber := range subscribers {
+ portName, ok := portNames[subscriber.Location]
+ if !ok {
+ return nil, fmt.Errorf("no-port-name-for-location: %s", subscriber.Location)
+ }
+
+ serviceName := fmt.Sprintf("%s-%s", portName, subscriber.TagInfo.ServiceName)
+
+ portPath := GetServicePortPath(serviceName, portName)
+
+ if subscriber.TagInfo.ConfiguredMacAddress != "" {
+ result = append(result, YangItem{
+ Path: portPath + "/bbf-nt-service-profile-voltha:configured-mac-address",
+ Value: subscriber.TagInfo.ConfiguredMacAddress,
+ })
+ }
+
+ result = append(result, []YangItem{
+ {
+ Path: fmt.Sprintf("%s/port-vlans/port-vlan[name='%s']", portPath, serviceName),
+ Value: "",
+ },
+ {
+ Path: portPath + "/bbf-nt-service-profile-voltha:technology-profile-id",
+ Value: strconv.Itoa(subscriber.TagInfo.TechnologyProfileID),
+ },
+ {
+ Path: portPath + "/bbf-nt-service-profile-voltha:downstream-subscriber-bp-name",
+ Value: subscriber.TagInfo.DownstreamBandwidthProfile,
+ },
+ {
+ Path: portPath + "/bbf-nt-service-profile-voltha:upstream-subscriber-bp-name",
+ Value: subscriber.TagInfo.UpstreamBandwidthProfile,
+ },
+ {
+ Path: portPath + "/bbf-nt-service-profile-voltha:mac-learning-enabled",
+ Value: strconv.FormatBool(subscriber.TagInfo.EnableMacLearning),
+ },
+ {
+ Path: portPath + "/bbf-nt-service-profile-voltha:dhcp-required",
+ Value: strconv.FormatBool(subscriber.TagInfo.IsDhcpRequired),
+ },
+ {
+ Path: portPath + "/bbf-nt-service-profile-voltha:igmp-required",
+ Value: strconv.FormatBool(subscriber.TagInfo.IsIgmpRequired),
+ },
+ {
+ Path: portPath + "/bbf-nt-service-profile-voltha:pppoe-required",
+ Value: strconv.FormatBool(subscriber.TagInfo.IsPPPoERequired),
+ },
+ }...)
+
+ if subscriber.TagInfo.UpstreamOltBandwidthProfile != "" {
+ result = append(result, YangItem{
+ Path: portPath + "/bbf-nt-service-profile-voltha:upstream-olt-bp-name",
+ Value: subscriber.TagInfo.UpstreamOltBandwidthProfile,
+ })
+ }
+
+ if subscriber.TagInfo.DownstreamOltBandwidthProfile != "" {
+ result = append(result, YangItem{
+ Path: portPath + "/bbf-nt-service-profile-voltha:downstream-olt-bp-name",
+ Value: subscriber.TagInfo.UpstreamOltBandwidthProfile,
+ })
+ }
+ }
+
+ return result, nil
+}
+
+//translateVlans returns a slice of yang items that represent the vlans used by programmed services
+func translateVlans(subscribers []clients.ProgrammedSubscriber, ports []clients.OnosPort) ([]YangItem, error) {
+ //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
+ }
+ }
+
+ result := []YangItem{}
+
+ for _, subscriber := range subscribers {
+ portName, ok := portNames[subscriber.Location]
+ if !ok {
+ return nil, fmt.Errorf("no-port-name-for-location: %s", subscriber.Location)
+ }
+
+ serviceName := fmt.Sprintf("%s-%s", portName, subscriber.TagInfo.ServiceName)
+
+ vlansPath := GetVlansPath(serviceName)
+
+ uniTagMatch := YangVlanIdAny
+ sTag := YangVlanIdAny
+ cTag := YangVlanIdAny
+
+ if subscriber.TagInfo.UniTagMatch != VolthaVlanIdAny {
+ uniTagMatch = strconv.Itoa(subscriber.TagInfo.UniTagMatch)
+ }
+ if subscriber.TagInfo.PonSTag != VolthaVlanIdAny {
+ sTag = strconv.Itoa(subscriber.TagInfo.PonSTag)
+ }
+ if subscriber.TagInfo.PonCTag != VolthaVlanIdAny {
+ cTag = strconv.Itoa(subscriber.TagInfo.PonCTag)
+ }
+
+ if subscriber.TagInfo.UniTagMatch > 0 {
+ result = append(result, []YangItem{
+ {
+ Path: vlansPath + "/match-criteria/outer-tag/vlan-id",
+ Value: uniTagMatch,
+ },
+ {
+ Path: vlansPath + "/match-criteria/second-tag/vlan-id",
+ Value: "any",
+ },
+ }...)
+ }
+
+ if subscriber.TagInfo.UsPonSTagPriority >= 0 {
+ result = append(result, YangItem{
+ Path: vlansPath + "/ingress-rewrite/push-outer-tag/pbit",
+ Value: strconv.Itoa(subscriber.TagInfo.UsPonSTagPriority),
+ })
+ }
+ if subscriber.TagInfo.DsPonSTagPriority >= 0 {
+ result = append(result, YangItem{
+ Path: vlansPath + "/ingress-rewrite/push-outer-tag/bbf-voltha-vlan-translation:dpbit",
+ Value: strconv.Itoa(subscriber.TagInfo.DsPonSTagPriority),
+ })
+ }
+ if subscriber.TagInfo.UsPonCTagPriority >= 0 {
+ result = append(result, YangItem{
+ Path: vlansPath + "/ingress-rewrite/push-second-tag/pbit",
+ Value: strconv.Itoa(subscriber.TagInfo.UsPonCTagPriority),
+ })
+ }
+ if subscriber.TagInfo.DsPonCTagPriority >= 0 {
+ result = append(result, YangItem{
+ Path: vlansPath + "/ingress-rewrite/push-second-tag/bbf-voltha-vlan-translation:dpbit",
+ Value: strconv.Itoa(subscriber.TagInfo.DsPonCTagPriority),
+ })
+ }
+
+ result = append(result, []YangItem{
+ {
+ Path: vlansPath + "/ingress-rewrite/push-outer-tag/vlan-id",
+ Value: sTag,
+ },
+ {
+ Path: vlansPath + "/ingress-rewrite/push-second-tag/vlan-id",
+ Value: cTag,
+ },
+ }...)
+ }
+
+ return result, nil
+}
+
+//translateBandwidthProfiles returns a slice of yang items that represent the bandwidth profiles used by programmed services
+func translateBandwidthProfiles(bwProfiles []clients.BandwidthProfile) ([]YangItem, error) {
+ result := []YangItem{}
+
+ //TODO: The best way to translate this information is still under discussion, but the code
+ // to retrieve it is ready. Since this is not fundamental at the moment, an empty slice is
+ // returned, and the correct translation can be added here at a later time.
+
+ return result, nil
+}
diff --git a/internal/core/translation_test.go b/internal/core/translation_test.go
index 41dc1dc..837acd9 100644
--- a/internal/core/translation_test.go
+++ b/internal/core/translation_test.go
@@ -21,6 +21,7 @@
"testing"
"time"
+ "github.com/opencord/voltha-northbound-bbf-adapter/internal/clients"
"github.com/opencord/voltha-protos/v5/go/openflow_13"
"github.com/opencord/voltha-protos/v5/go/voltha"
"github.com/stretchr/testify/assert"
@@ -43,7 +44,22 @@
func TestDevicePath(t *testing.T) {
path := getDevicePath(testDeviceId)
- assert.Equal(t, fmt.Sprintf("/bbf-device-aggregation:devices/device[name='%s']", testDeviceId), path)
+ assert.Equal(t, "/bbf-device-aggregation:devices/device[name='123145abcdef']", path)
+}
+
+func TestDeviceHardwarePath(t *testing.T) {
+ path := getDeviceHardwarePath(testDeviceId)
+ assert.Equal(t, "/bbf-device-aggregation:devices/device[name='123145abcdef']/data/ietf-hardware:hardware/component[name='123145abcdef']", path)
+}
+
+func TestServicePortPath(t *testing.T) {
+ path := GetServicePortPath("testService", "testPort")
+ assert.Equal(t, "/bbf-nt-service-profile:service-profiles/service-profile[name='testService']/ports/port[name='testPort']", path)
+}
+
+func TestVlansPath(t *testing.T) {
+ path := GetVlansPath("testProfile")
+ assert.Equal(t, "/bbf-l2-access-attributes:vlan-translation-profiles/vlan-translation-profile[name='testProfile']", path)
}
func TestTranslateDevice(t *testing.T) {
@@ -115,6 +131,8 @@
FirmwareVersion: "v0.0.3",
AdminState: voltha.AdminState_ENABLED,
OperStatus: voltha.OperStatus_ACTIVE,
+ ParentId: "abcdef1234",
+ ParentPortNo: 1,
}
items = translateDevice(onu)
@@ -154,6 +172,14 @@
Path: onuHwPath + "/state/oper-state",
Value: ietfOperStateEnabled,
},
+ {
+ Path: onuHwPath + "/parent",
+ Value: "abcdef1234",
+ },
+ {
+ Path: onuHwPath + "/parent-rel-pos",
+ Value: "1",
+ },
}
assert.NotEmpty(t, items, "No ONU items")
@@ -297,3 +323,192 @@
assert.Equal(t, e.Value, val, "Wrong value for "+e.Path)
}
}
+
+func TestTranslateServices(t *testing.T) {
+ subscribers := []clients.ProgrammedSubscriber{
+ {
+ Location: "of:00001/256",
+ TagInfo: clients.SadisUniTag{
+ UniTagMatch: 100,
+ PonCTag: 4096,
+ PonSTag: 102,
+ TechnologyProfileID: 64,
+ UpstreamBandwidthProfile: "BW1",
+ DownstreamBandwidthProfile: "BW2",
+ UpstreamOltBandwidthProfile: "OLTBW",
+ IsDhcpRequired: true,
+ IsIgmpRequired: false,
+ IsPPPoERequired: false,
+ ConfiguredMacAddress: "00:11:22:33:44:55",
+ EnableMacLearning: true,
+ UsPonCTagPriority: 1,
+ UsPonSTagPriority: 2,
+ DsPonCTagPriority: 3,
+ DsPonSTagPriority: -1,
+ ServiceName: "testService",
+ },
+ },
+ }
+
+ ports := []clients.OnosPort{
+ {
+ Element: "of:00001",
+ Port: "256",
+ Annotations: map[string]string{
+ "portName": "TESTPORT-1",
+ },
+ },
+ {
+ Element: "of:00001",
+ Port: "257",
+ Annotations: map[string]string{
+ "portName": "TESTPORT-2",
+ },
+ },
+ }
+
+ servicesItesm, err := translateServices(subscribers, ports)
+ assert.Nil(t, err, "Translation error")
+
+ assert.NotEmpty(t, servicesItesm, "No services items")
+
+ servicePortPath := ServiceProfilesPath + "/service-profile[name='TESTPORT-1-testService']/ports/port[name='TESTPORT-1']"
+
+ expected := []YangItem{
+ {
+ Path: servicePortPath + "/bbf-nt-service-profile-voltha:configured-mac-address",
+ Value: "00:11:22:33:44:55",
+ },
+ {
+ Path: servicePortPath + "/bbf-nt-service-profile-voltha:upstream-subscriber-bp-name",
+ Value: "BW1",
+ },
+ {
+ Path: servicePortPath + "/bbf-nt-service-profile-voltha:downstream-subscriber-bp-name",
+ Value: "BW2",
+ },
+ {
+ Path: servicePortPath + "/bbf-nt-service-profile-voltha:upstream-olt-bp-name",
+ Value: "OLTBW",
+ },
+ {
+ Path: servicePortPath + "/bbf-nt-service-profile-voltha:mac-learning-enabled",
+ Value: "true",
+ },
+ {
+ Path: servicePortPath + "/bbf-nt-service-profile-voltha:dhcp-required",
+ Value: "true",
+ },
+ {
+ Path: servicePortPath + "/bbf-nt-service-profile-voltha:igmp-required",
+ Value: "false",
+ },
+ {
+ Path: servicePortPath + "/bbf-nt-service-profile-voltha:pppoe-required",
+ Value: "false",
+ },
+ }
+
+ _, ok := getItemWithPath(servicesItesm, servicePortPath+"/port-vlans/port-vlan[name='TESTPORT-1-testService']")
+ assert.True(t, ok, "No vlans leafref in services")
+
+ _, ok = getItemWithPath(servicesItesm, servicePortPath+"/bbf-nt-service-profile-voltha:downstream-olt-bp-name")
+ assert.False(t, ok, "Downstream OLT bandwidth profile should not be present")
+
+ for _, e := range expected {
+ val, ok := getItemWithPath(servicesItesm, e.Path)
+ assert.True(t, ok, e.Path+" missing for services")
+ assert.Equal(t, e.Value, val, "Wrong value for "+e.Path)
+ }
+}
+
+func TestTranslateVlans(t *testing.T) {
+ subscribers := []clients.ProgrammedSubscriber{
+ {
+ Location: "of:00001/256",
+ TagInfo: clients.SadisUniTag{
+ UniTagMatch: 100,
+ PonCTag: 4096,
+ PonSTag: 102,
+ TechnologyProfileID: 64,
+ UpstreamBandwidthProfile: "BW1",
+ DownstreamBandwidthProfile: "BW2",
+ UpstreamOltBandwidthProfile: "OLTBW",
+ IsDhcpRequired: true,
+ IsIgmpRequired: false,
+ IsPPPoERequired: false,
+ ConfiguredMacAddress: "00:11:22:33:44:55",
+ EnableMacLearning: true,
+ UsPonCTagPriority: 1,
+ UsPonSTagPriority: 2,
+ DsPonCTagPriority: 3,
+ DsPonSTagPriority: -1,
+ ServiceName: "testService",
+ },
+ },
+ }
+
+ ports := []clients.OnosPort{
+ {
+ Element: "of:00001",
+ Port: "256",
+ Annotations: map[string]string{
+ "portName": "TESTPORT-1",
+ },
+ },
+ {
+ Element: "of:00001",
+ Port: "257",
+ Annotations: map[string]string{
+ "portName": "TESTPORT-2",
+ },
+ },
+ }
+
+ vlanItems, err := translateVlans(subscribers, ports)
+ assert.Nil(t, err, "Translation error")
+
+ assert.NotEmpty(t, vlanItems, "No vlans items")
+
+ vlanPath := VlansPath + "/vlan-translation-profile[name='TESTPORT-1-testService']"
+
+ expected := []YangItem{
+ {
+ Path: vlanPath + "/match-criteria/outer-tag/vlan-id",
+ Value: "100",
+ },
+ {
+ Path: vlanPath + "/ingress-rewrite/push-second-tag/vlan-id",
+ Value: "any",
+ },
+ {
+ Path: vlanPath + "/ingress-rewrite/push-outer-tag/vlan-id",
+ Value: "102",
+ },
+ {
+ Path: vlanPath + "/match-criteria/second-tag/vlan-id",
+ Value: "any",
+ },
+ {
+ Path: vlanPath + "/ingress-rewrite/push-second-tag/pbit",
+ Value: "1",
+ },
+ {
+ Path: vlanPath + "/ingress-rewrite/push-outer-tag/pbit",
+ Value: "2",
+ },
+ {
+ Path: vlanPath + "/ingress-rewrite/push-second-tag/bbf-voltha-vlan-translation:dpbit",
+ Value: "3",
+ },
+ }
+
+ _, ok := getItemWithPath(vlanItems, vlanPath+"/ingress-rewrite/push-outer-tag/bbf-voltha-vlan-translation:dpbit")
+ assert.False(t, ok, "Pbit value should not be present")
+
+ for _, e := range expected {
+ val, ok := getItemWithPath(vlanItems, e.Path)
+ assert.True(t, ok, e.Path+" missing for vlans")
+ assert.Equal(t, e.Value, val, "Wrong value for "+e.Path)
+ }
+}
diff --git a/internal/sysrepo/callbacks.go b/internal/sysrepo/callbacks.go
index 1256568..27c8362 100644
--- a/internal/sysrepo/callbacks.go
+++ b/internal/sysrepo/callbacks.go
@@ -21,6 +21,8 @@
import "C"
import (
"context"
+ "fmt"
+ "strconv"
"github.com/opencord/voltha-lib-go/v7/pkg/log"
"github.com/opencord/voltha-northbound-bbf-adapter/internal/core"
@@ -65,3 +67,331 @@
return C.SR_ERR_OK
}
+
+//export get_services_cb
+func get_services_cb(session *C.sr_session_ctx_t, parent **C.lyd_node) C.sr_error_t {
+ //This function is a callback for the retrieval of devices from sysrepo
+ //The "export" comment instructs CGO to create a C function for it
+
+ ctx := context.Background()
+ logger.Debug(ctx, "processing-get-services-request")
+
+ if session == nil {
+ logger.Error(ctx, "sysrepo-get-services-null-session")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ if parent == nil {
+ logger.Error(ctx, "sysrepo-get-services-null-parent-node")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ if core.AdapterInstance == nil {
+ logger.Error(ctx, "sysrepo-get-services-nil-translator")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ services, err := core.AdapterInstance.GetServices(ctx)
+ if err != nil {
+ logger.Errorw(ctx, "sysrepo-get-services-translation-error", log.Fields{"err": err})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ err = updateYangTree(ctx, session, parent, services)
+ if err != nil {
+ logger.Errorw(ctx, "sysrepo-get-services-update-error", log.Fields{"err": err})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ logger.Info(ctx, "services-information-request-served")
+
+ return C.SR_ERR_OK
+}
+
+//export get_vlans_cb
+func get_vlans_cb(session *C.sr_session_ctx_t, parent **C.lyd_node) C.sr_error_t {
+ //This function is a callback for the retrieval of vlans from sysrepo
+ //The "export" comment instructs CGO to create a C function for it
+
+ ctx := context.Background()
+ logger.Debug(ctx, "processing-get-vlans-request")
+
+ if session == nil {
+ logger.Error(ctx, "sysrepo-get-vlans-null-session")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ if parent == nil {
+ logger.Error(ctx, "sysrepo-get-vlans-null-parent-node")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ if core.AdapterInstance == nil {
+ logger.Error(ctx, "sysrepo-get-vlans-nil-translator")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ vlans, err := core.AdapterInstance.GetVlans(ctx)
+ if err != nil {
+ logger.Errorw(ctx, "sysrepo-get-vlans-translation-error", log.Fields{"err": err})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ err = updateYangTree(ctx, session, parent, vlans)
+ if err != nil {
+ logger.Errorw(ctx, "sysrepo-get-vlans-update-error", log.Fields{"err": err})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ logger.Info(ctx, "vlans-information-request-served")
+
+ return C.SR_ERR_OK
+}
+
+//export get_bandwidth_profiles_cb
+func get_bandwidth_profiles_cb(session *C.sr_session_ctx_t, parent **C.lyd_node) C.sr_error_t {
+ //This function is a callback for the retrieval of bandwidth profiles from sysrepo
+ //The "export" comment instructs CGO to create a C function for it
+
+ ctx := context.Background()
+ logger.Debug(ctx, "processing-get-bandwidth-profiles-request")
+
+ if session == nil {
+ logger.Error(ctx, "sysrepo-get-bandwidth-profiles-null-session")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ if parent == nil {
+ logger.Error(ctx, "sysrepo-get-bandwidth-profiles-null-parent-node")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ if core.AdapterInstance == nil {
+ logger.Error(ctx, "sysrepo-get-bandwidth-profiles-nil-translator")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ bwProfiles, err := core.AdapterInstance.GetBandwidthProfiles(ctx)
+ if err != nil {
+ logger.Errorw(ctx, "sysrepo-get-bandwidth-profiles-translation-error", log.Fields{"err": err})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ err = updateYangTree(ctx, session, parent, bwProfiles)
+ if err != nil {
+ logger.Errorw(ctx, "sysrepo-get-bandwidth-profiles-update-error", log.Fields{"err": err})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ logger.Info(ctx, "bandwidth-profiles-information-request-served")
+
+ return C.SR_ERR_OK
+}
+
+//export edit_service_profiles_cb
+func edit_service_profiles_cb(editSession *C.sr_session_ctx_t, runningSession *C.sr_session_ctx_t, event C.sr_event_t) C.sr_error_t {
+ //This function is a callback for changes on service profiles
+ //The "export" comment instructs CGO to create a C function for it
+
+ if event != C.SR_EV_CHANGE {
+ return C.SR_ERR_OK
+ }
+
+ ctx := context.Background()
+ logger.Debug(ctx, "processing-service-profile-changes")
+
+ serviceNamesChanges, err := getChangesList(ctx, editSession, core.ServiceProfilesPath+"/service-profile/name")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-names-changes", log.Fields{"err": err})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ for _, n := range serviceNamesChanges {
+ switch n.Operation {
+ case C.SR_OP_CREATED:
+ if errCode := edit_service_create(ctx, editSession, runningSession, n.Value); errCode != C.SR_ERR_OK {
+ return errCode
+ }
+ case C.SR_OP_DELETED:
+ if errCode := edit_service_delete(ctx, editSession, runningSession, n.Value); errCode != C.SR_ERR_OK {
+ return errCode
+ }
+ default:
+ return C.SR_ERR_UNSUPPORTED
+ }
+ }
+
+ return C.SR_ERR_OK
+}
+
+func edit_service_create(ctx context.Context, editSession *C.sr_session_ctx_t, runningSession *C.sr_session_ctx_t, serviceName string) C.sr_error_t {
+ portName, err := getSingleChangeValue(ctx, editSession, fmt.Sprintf("%s/service-profile[name='%s']/ports/port/name", core.ServiceProfilesPath, serviceName))
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-port-changes", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ servicePortPath := core.GetServicePortPath(serviceName, portName)
+
+ tpId, err := getSingleChangeValue(ctx, editSession, servicePortPath+"/bbf-nt-service-profile-voltha:technology-profile-id")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-tp-id-change", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ vlanName, err := getSingleChangeValue(ctx, editSession, servicePortPath+"/port-vlans/port-vlan/name")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-vlan-change", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ vlansPath := core.GetVlansPath(vlanName)
+
+ sTag, err := getSingleChangeValue(ctx, editSession, vlansPath+"/ingress-rewrite/push-outer-tag/vlan-id")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-stag-changes", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+ if sTag == core.YangVlanIdAny {
+ sTag = strconv.Itoa(core.VolthaVlanIdAny)
+ }
+
+ cTag, err := getSingleChangeValue(ctx, editSession, vlansPath+"/ingress-rewrite/push-second-tag/vlan-id")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-stag-changes", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+ if cTag == core.YangVlanIdAny {
+ cTag = strconv.Itoa(core.VolthaVlanIdAny)
+ }
+
+ logger.Infow(ctx, "new-service-profile-information", log.Fields{
+ "service": serviceName,
+ "port": portName,
+ "vlanName": vlanName,
+ "tpId": tpId,
+ "sTag": sTag,
+ "cTag": cTag,
+ })
+
+ if core.AdapterInstance == nil {
+ logger.Error(ctx, "sysrepo-service-changes-nil-translator")
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ if err := core.AdapterInstance.ProvisionService(portName, sTag, cTag, tpId); err != nil {
+ logger.Errorw(ctx, "service-provisioning-error", log.Fields{
+ "service": serviceName,
+ "err": err,
+ })
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ logger.Infow(ctx, "service-profile-creation-request-served", log.Fields{
+ "service": serviceName,
+ })
+
+ return C.SR_ERR_OK
+}
+
+func edit_service_delete(ctx context.Context, editSession *C.sr_session_ctx_t, runningSession *C.sr_session_ctx_t, serviceName string) C.sr_error_t {
+ portName, err := getDatastoreLeafValue(ctx, runningSession, fmt.Sprintf("%s/service-profile[name='%s']/ports/port/name", core.ServiceProfilesPath, serviceName))
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-port-leaf", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ servicePortPath := core.GetServicePortPath(serviceName, portName)
+
+ tpId, err := getDatastoreLeafValue(ctx, runningSession, servicePortPath+"/bbf-nt-service-profile-voltha:technology-profile-id")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-tp-id-leaf", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ vlanName, err := getDatastoreLeafValue(ctx, runningSession, servicePortPath+"/port-vlans/port-vlan/name")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-vlan-leaf", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ vlansPath := core.GetVlansPath(vlanName)
+
+ sTag, err := getDatastoreLeafValue(ctx, runningSession, vlansPath+"/ingress-rewrite/push-outer-tag/vlan-id")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-stag-leaf", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+ if sTag == core.YangVlanIdAny {
+ sTag = strconv.Itoa(core.VolthaVlanIdAny)
+ }
+
+ cTag, err := getDatastoreLeafValue(ctx, runningSession, vlansPath+"/ingress-rewrite/push-second-tag/vlan-id")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-service-profile-stag-leaf", log.Fields{"err": err, "service": serviceName})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+ if cTag == core.YangVlanIdAny {
+ cTag = strconv.Itoa(core.VolthaVlanIdAny)
+ }
+
+ logger.Infow(ctx, "service-profile-deletion-information", log.Fields{
+ "service": serviceName,
+ "port": portName,
+ "vlanName": vlanName,
+ "tpId": tpId,
+ "sTag": sTag,
+ "cTag": cTag,
+ })
+
+ if err := core.AdapterInstance.RemoveService(portName, sTag, cTag, tpId); err != nil {
+ logger.Errorw(ctx, "service-removal-error", log.Fields{
+ "service": serviceName,
+ "err": err,
+ })
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ logger.Infow(ctx, "service-profile-removal-request-served", log.Fields{
+ "service": serviceName,
+ })
+
+ return C.SR_ERR_OK
+}
+
+//export edit_vlans_cb
+func edit_vlans_cb(editSession *C.sr_session_ctx_t, event C.sr_event_t) C.sr_error_t {
+ //This function is a callback for changes on VLANs
+ //The "export" comment instructs CGO to create a C function for it
+
+ if event != C.SR_EV_CHANGE {
+ return C.SR_ERR_OK
+ }
+
+ ctx := context.Background()
+ logger.Debug(ctx, "processing-vlans-changes")
+
+ vlanChanges, err := getChangesList(ctx, editSession, core.VlansPath+"//.")
+ if err != nil {
+ logger.Errorw(ctx, "cannot-get-vlans-changes", log.Fields{"err": err})
+ return C.SR_ERR_OPERATION_FAILED
+ }
+
+ for _, n := range vlanChanges {
+ //VLANs must be defined through creation (for service provisioning)
+ //or deletion (for service removal). Changes to the VLAN values
+ //are not supported, because VOLTHA does not support dynamic changes
+ //to the service.
+ switch n.Operation {
+ case C.SR_OP_CREATED:
+ case C.SR_OP_DELETED:
+ //Everything will be handled in the services callback
+ //Just approve the change here
+ return C.SR_ERR_OK
+ default:
+ return C.SR_ERR_UNSUPPORTED
+ }
+ }
+
+ return C.SR_ERR_OK
+}
diff --git a/internal/sysrepo/plugin.c b/internal/sysrepo/plugin.c
index f9f53c6..82e9e3e 100644
--- a/internal/sysrepo/plugin.c
+++ b/internal/sysrepo/plugin.c
@@ -42,6 +42,11 @@
// Exported by callbacks.go
sr_error_t get_devices_cb(sr_session_ctx_t *session, lyd_node **parent);
+sr_error_t get_services_cb(sr_session_ctx_t *session, lyd_node **parent);
+sr_error_t get_vlans_cb(sr_session_ctx_t *session, lyd_node **parent);
+sr_error_t get_bandwidth_profiles_cb(sr_session_ctx_t *session, lyd_node **parent);
+sr_error_t edit_service_profiles_cb(sr_session_ctx_t *session, sr_session_ctx_t *runningSession, sr_event_t event);
+sr_error_t edit_vlans_cb(sr_session_ctx_t *session, sr_event_t event);
//The wrapper functions are needed because CGO cannot express some keywords
//such as "const", and thus it can't match sysrepo's callback signature
@@ -57,4 +62,68 @@
void *private_data)
{
return get_devices_cb(session, parent);
+}
+
+int get_services_cb_wrapper(
+ sr_session_ctx_t *session,
+ uint32_t subscription_id,
+ const char *module_name,
+ const char *path,
+ const char *request_xpath,
+ uint32_t request_id,
+ struct lyd_node **parent,
+ void *private_data)
+{
+ return get_services_cb(session, parent);
+}
+
+int get_vlans_cb_wrapper(
+ sr_session_ctx_t *session,
+ uint32_t subscription_id,
+ const char *module_name,
+ const char *path,
+ const char *request_xpath,
+ uint32_t request_id,
+ struct lyd_node **parent,
+ void *private_data)
+{
+ return get_vlans_cb(session, parent);
+}
+
+int get_bandwidth_profiles_cb_wrapper(
+ sr_session_ctx_t *session,
+ uint32_t subscription_id,
+ const char *module_name,
+ const char *path,
+ const char *request_xpath,
+ uint32_t request_id,
+ struct lyd_node **parent,
+ void *private_data)
+{
+ return get_bandwidth_profiles_cb(session, parent);
+}
+
+int edit_service_profiles_cb_wrapper(
+ sr_session_ctx_t *session,
+ uint32_t subscription_id,
+ const char *module_name,
+ const char *path,
+ sr_event_t event,
+ uint32_t request_id,
+ void *private_data)
+{
+ sr_session_ctx_t* runningSession = (sr_session_ctx_t*)private_data;
+ return edit_service_profiles_cb(session, runningSession, event);
+}
+
+int edit_vlans_cb_wrapper(
+ sr_session_ctx_t *session,
+ uint32_t subscription_id,
+ const char *module_name,
+ const char *path,
+ sr_event_t event,
+ uint32_t request_id,
+ void *private_data)
+{
+ return edit_vlans_cb(session, event);
}
\ No newline at end of file
diff --git a/internal/sysrepo/sysrepo.go b/internal/sysrepo/sysrepo.go
index 973dbb9..a2b19af 100644
--- a/internal/sysrepo/sysrepo.go
+++ b/internal/sysrepo/sysrepo.go
@@ -133,6 +133,22 @@
defer freeCString(devicesModule)
defer freeCString(devicesPath)
+ servicesModule := C.CString(core.ServiceProfileModule)
+ servicesPath := C.CString(core.ServiceProfilesPath + "/*")
+ defer freeCString(servicesModule)
+ defer freeCString(servicesPath)
+
+ vlansModule := C.CString(core.VlansModule)
+ vlansPath := C.CString(core.VlansPath + "/*")
+ defer freeCString(vlansModule)
+ defer freeCString(vlansPath)
+
+ bwProfilesModule := C.CString(core.BandwidthProfileModule)
+ bwProfilesPath := C.CString(core.BandwidthProfilesPath + "/*")
+ defer freeCString(bwProfilesModule)
+ defer freeCString(bwProfilesPath)
+
+ //Get devices
errCode := C.sr_oper_get_subscribe(
plugin.operationalSession,
devicesModule,
@@ -143,7 +159,90 @@
&plugin.subscription,
)
if errCode != C.SR_ERR_OK {
- err := fmt.Errorf("sysrepo-failed-subscription-to-get-events")
+ err := fmt.Errorf("sysrepo-failed-subscription-to-get-devices")
+ logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
+ return nil, err
+ }
+
+ //Get services
+ errCode = C.sr_oper_get_subscribe(
+ plugin.operationalSession,
+ servicesModule,
+ servicesPath,
+ C.function(C.get_services_cb_wrapper),
+ C.NULL,
+ C.SR_SUBSCR_DEFAULT,
+ &plugin.subscription,
+ )
+ if errCode != C.SR_ERR_OK {
+ err := fmt.Errorf("sysrepo-failed-subscription-to-get-services")
+ logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
+ return nil, err
+ }
+
+ //Get vlans
+ errCode = C.sr_oper_get_subscribe(
+ plugin.operationalSession,
+ vlansModule,
+ vlansPath,
+ C.function(C.get_vlans_cb_wrapper),
+ C.NULL,
+ C.SR_SUBSCR_DEFAULT,
+ &plugin.subscription,
+ )
+ if errCode != C.SR_ERR_OK {
+ err := fmt.Errorf("sysrepo-failed-subscription-to-get-services")
+ logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
+ return nil, err
+ }
+
+ //Get bandwidth profiles
+ errCode = C.sr_oper_get_subscribe(
+ plugin.operationalSession,
+ bwProfilesModule,
+ bwProfilesPath,
+ C.function(C.get_bandwidth_profiles_cb_wrapper),
+ C.NULL,
+ C.SR_SUBSCR_DEFAULT,
+ &plugin.subscription,
+ )
+ if errCode != C.SR_ERR_OK {
+ err := fmt.Errorf("sysrepo-failed-subscription-to-get-services")
+ logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
+ return nil, err
+ }
+
+ //Subscribe with a callback to changes of configuration in the services modules
+ //Changes to services
+ errCode = C.sr_module_change_subscribe(
+ plugin.runningSession,
+ servicesModule,
+ servicesPath,
+ C.function(C.edit_service_profiles_cb_wrapper),
+ unsafe.Pointer(plugin.runningSession), //Pass session for running datastore to get current data
+ 0,
+ C.SR_SUBSCR_DEFAULT,
+ &plugin.subscription,
+ )
+ if errCode != C.SR_ERR_OK {
+ err := fmt.Errorf("sysrepo-failed-subscription-to-change-services")
+ logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
+ return nil, err
+ }
+
+ //Changes to VLANs
+ errCode = C.sr_module_change_subscribe(
+ plugin.runningSession,
+ vlansModule,
+ vlansPath,
+ C.function(C.edit_vlans_cb_wrapper),
+ C.NULL,
+ 0,
+ C.SR_SUBSCR_DEFAULT,
+ &plugin.subscription,
+ )
+ if errCode != C.SR_ERR_OK {
+ err := fmt.Errorf("sysrepo-failed-subscription-to-change-vlans")
logger.Errorw(ctx, err.Error(), log.Fields{"errCode": errCode, "errMsg": srErrorMsg(errCode)})
return nil, err
}
diff --git a/internal/sysrepo/utils.go b/internal/sysrepo/utils.go
index d564dc7..4db9a53 100644
--- a/internal/sysrepo/utils.go
+++ b/internal/sysrepo/utils.go
@@ -135,7 +135,7 @@
path := C.CString(item.Path)
value := C.CString(item.Value)
- lyErr := C.lyd_new_path(*parent, ly_ctx, path, value, 0, nil)
+ lyErr := C.lyd_new_path(*parent, ly_ctx, path, value, C.LYD_NEW_PATH_UPDATE, nil)
if lyErr != C.LY_SUCCESS {
freeCString(path)
freeCString(value)
@@ -171,3 +171,110 @@
return nil
}
+
+type YangChange struct {
+ Path string
+ Value string
+ Operation C.sr_change_oper_t
+ /* Operation values:
+ SR_OP_CREATED
+ SR_OP_MODIFIED
+ SR_OP_DELETED
+ SR_OP_MOVED
+ */
+}
+
+//Provides a list of the changes occured under a specific path
+//Should only be used on the session from an sr_module_change_subscribe callback
+func getChangesList(ctx context.Context, editSession *C.sr_session_ctx_t, path string) ([]YangChange, error) {
+ result := []YangChange{}
+
+ changesPath := C.CString(path)
+ defer freeCString(changesPath)
+
+ var changesIterator *C.sr_change_iter_t
+ errCode := C.sr_get_changes_iter(editSession, changesPath, &changesIterator)
+ if errCode != C.SR_ERR_OK {
+ return nil, fmt.Errorf("cannot-get-iterator: %d %s", errCode, srErrorMsg(errCode))
+ }
+ defer C.sr_free_change_iter(changesIterator)
+
+ //Iterate over the changes
+ var operation C.sr_change_oper_t
+ var prevValue, prevList *C.char
+ var prevDefault C.int
+
+ var node *C.lyd_node
+ defer C.lyd_free_all(node)
+
+ errCode = C.sr_get_change_tree_next(editSession, changesIterator, &operation, &node, &prevValue, &prevList, &prevDefault)
+ for errCode != C.SR_ERR_NOT_FOUND {
+ if errCode != C.SR_ERR_OK {
+ return nil, fmt.Errorf("next-change-error: %d %s", errCode, srErrorMsg(errCode))
+ }
+
+ currentChange := YangChange{}
+ currentChange.Operation = operation
+
+ nodePath := C.lyd_path(node, C.LYD_PATH_STD, nil, 0)
+ if nodePath == nil {
+ return nil, fmt.Errorf("cannot-get-change-path")
+ }
+ currentChange.Path = C.GoString(nodePath)
+ freeCString(nodePath)
+
+ nodeValue := C.lyd_get_value(node)
+ if nodeValue != nil {
+ currentChange.Value = C.GoString(nodeValue)
+ result = append(result, currentChange)
+ }
+
+ errCode = C.sr_get_change_tree_next(editSession, changesIterator, &operation, &node, &prevValue, &prevList, &prevDefault)
+ }
+
+ return result, nil
+}
+
+//Verify that only one change occured under the specified path, and return its value
+//Should only be used on the session from an sr_module_change_subscribe callback
+func getSingleChangeValue(ctx context.Context, session *C.sr_session_ctx_t, path string) (string, error) {
+ changesList, err := getChangesList(ctx, session, path)
+ if err != nil {
+ return "", err
+ }
+
+ if len(changesList) != 1 {
+ logger.Errorw(ctx, "unexpected-number-of-yang-changes", log.Fields{
+ "changes": changesList,
+ })
+ return "", fmt.Errorf("unexpected-number-of-yang-changes")
+ }
+
+ return changesList[0].Value, nil
+}
+
+//Get the value of a leaf from the datastore
+//The target datastore is the one on which the session has been created
+func getDatastoreLeafValue(ctx context.Context, session *C.sr_session_ctx_t, path string) (string, error) {
+ cPath := C.CString(path)
+ defer freeCString(cPath)
+
+ var data *C.sr_data_t
+ defer C.sr_release_data(data)
+
+ errCode := C.sr_get_subtree(session, cPath, 0, &data)
+ if errCode != C.SR_ERR_OK {
+ return "", fmt.Errorf("cannot-get-data-from-datastore: %d %s", errCode, srErrorMsg(errCode))
+ }
+
+ if data == nil {
+ return "", fmt.Errorf("no-data-found-for-path: %s", path)
+ }
+
+ nodeValue := C.lyd_get_value(data.tree)
+ if nodeValue == nil {
+ return "", fmt.Errorf("cannot-get-value-from-data: %s", path)
+ }
+
+ return C.GoString(nodeValue), nil
+}