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
+}