diff --git a/api/apiIntegration_test.go b/api/apiIntegration_test.go
index 9839da725ee9a573f3afc768654ea40e126ddd7f..5f1759243e209c2b64caad0b4bd921b7867fd86d 100644
--- a/api/apiIntegration_test.go
+++ b/api/apiIntegration_test.go
@@ -1,86 +1,15 @@
 package api
 
 import (
-	"os"
 	"testing"
 
+	"code.fbi.h-da.de/cocsn/api/go/gosdn/pnd"
 	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"
-	"code.fbi.h-da.de/cocsn/gosdn/nucleus/util/proto"
+	"github.com/google/uuid"
 	guuid "github.com/google/uuid"
-	gpb "github.com/openconfig/gnmi/proto/gnmi"
-	log "github.com/sirupsen/logrus"
 	"github.com/spf13/viper"
-	pb "google.golang.org/protobuf/proto"
 )
 
-const unreachable = "203.0.113.10:6030"
-const testPath = "/system/config/hostname"
-
-var testAddress = "141.100.70.170:6030"
-var testAPIEndpoint = "gosdn-latest.apps.ocp.fbi.h-da.de"
-var testUsername = "admin"
-var testPassword = "arista"
-var opt *tpb.TransportOption
-var gnmiMessages map[string]pb.Message
-
-func TestMain(m *testing.M) {
-	testSetupIntegration()
-	os.Exit(m.Run())
-}
-
-func testSetupIntegration() {
-	if os.Getenv("GOSDN_LOG") == "nolog" {
-		log.SetLevel(log.PanicLevel)
-	}
-
-	addr := os.Getenv("GOSDN_TEST_ENDPOINT")
-	if addr != "" {
-		testAddress = addr
-		log.Infof("GOSDN_TEST_ENDPOINT set to %v", testAddress)
-	}
-	api := os.Getenv("GOSDN_TEST_API_ENDPOINT")
-	if api != "" {
-		testAPIEndpoint = api
-		log.Infof("GOSDN_TEST_API_ENDPOINT set to %v", testAPIEndpoint)
-	}
-	u := os.Getenv("GOSDN_TEST_USER")
-	if u != "" {
-		testUsername = u
-		log.Infof("GOSDN_TEST_USER set to %v", testUsername)
-	}
-	p := os.Getenv("GOSDN_TEST_PASSWORD")
-	if p != "" {
-		testPassword = p
-		log.Infof("GOSDN_TEST_PASSWORD set to %v", testPassword)
-	}
-
-	gnmiMessages = map[string]pb.Message{
-		"../test/proto/cap-resp-arista-ceos":                  &gpb.CapabilityResponse{},
-		"../test/proto/req-full-node":                         &gpb.GetRequest{},
-		"../test/proto/req-full-node-arista-ceos":             &gpb.GetRequest{},
-		"../test/proto/req-interfaces-arista-ceos":            &gpb.GetRequest{},
-		"../test/proto/req-interfaces-interface-arista-ceos":  &gpb.GetRequest{},
-		"../test/proto/req-interfaces-wildcard":               &gpb.GetRequest{},
-		"../test/proto/resp-full-node":                        &gpb.GetResponse{},
-		"../test/proto/resp-full-node-arista-ceos":            &gpb.GetResponse{},
-		"../test/proto/resp-interfaces-arista-ceos":           &gpb.GetResponse{},
-		"../test/proto/resp-interfaces-interface-arista-ceos": &gpb.GetResponse{},
-		"../test/proto/resp-interfaces-wildcard":              &gpb.GetResponse{},
-		"../test/proto/resp-set-system-config-hostname":       &gpb.SetResponse{},
-	}
-	for k, v := range gnmiMessages {
-		if err := proto.Read(k, v); err != nil {
-			log.Fatalf("error parsing %v: %v", k, err)
-		}
-	}
-
-	opt = &tpb.TransportOption{
-		Address:  testAddress,
-		Username: testUsername,
-		Password: testPassword,
-	}
-}
-
 func TestApiIntegration(t *testing.T) {
 	// TDOO: Remove once openshift grpc support is available
 	t.Skip("skipped due to openshift limitations")
@@ -110,21 +39,31 @@ func TestApiIntegration(t *testing.T) {
 			cliPnd := viper.GetString("CLI_PND")
 			cliSbi := viper.GetString("CLI_SBI")
 
-			if _, err := AddDevice(
+			cliPndUUID, err := uuid.Parse(cliPnd)
+			clisbiUUID, err := uuid.Parse(cliSbi)
+
+			opt := &tpb.TransportOption{
+				Address:  testAddress,
+				Username: testUsername,
+				Password: testPassword,
+				TransportOption: &tpb.TransportOption_GnmiTransportOption{
+					GnmiTransportOption: &tpb.GnmiTransportOption{},
+				},
+			}
+
+			if _, err := addDevice(
 				testAPIEndpoint,
-				testUsername,
-				testPassword,
-				cliSbi,
-				cliPnd,
-				testAddress,
 				"test-device",
+				opt,
+				clisbiUUID,
+				cliPndUUID,
 			); (err != nil) != tt.wantErr {
 				t.Errorf("gosdn cli add-device error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 			did := viper.GetString("LAST_DEVICE_UUID")
 
-			_, err := GetDevice(
+			_, err = getDevice(
 				testAPIEndpoint,
 				cliPnd,
 				testPath,
@@ -135,7 +74,7 @@ func TestApiIntegration(t *testing.T) {
 				return
 			}
 
-			_, err = GetDevice(
+			_, err = getDevice(
 				testAPIEndpoint,
 				cliPnd,
 				"",
@@ -147,19 +86,20 @@ func TestApiIntegration(t *testing.T) {
 			}
 
 			hostname := guuid.New().String()
-			_, err = Update(
+			_, err = changeRequest(
 				testAPIEndpoint,
 				did,
 				cliPnd,
 				testPath,
 				hostname,
+				pnd.ApiOperation_UPDATE,
 			)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("gosdn cli set error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 
-			resp, err := GetDevice(testAddress, testUsername, testPassword, testPath)
+			resp, err := getDevice(testAddress, testUsername, testPassword, testPath)
 			if err != nil {
 				if !tt.wantErr {
 					t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
diff --git a/api/go.mod b/api/go.mod
index e3c75264d71e73e5a04413b5717b77667865118c..551e38da5afd80810c840d6a10e7f45a48936844 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -3,8 +3,9 @@ module code.fbi.h-da.de/cocsn/gosdn/api
 go 1.16
 
 require (
-	code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a
+	code.fbi.h-da.de/cocsn/api/go v0.0.0-20210617162200-4b2e8d3035c7
 	code.fbi.h-da.de/cocsn/gosdn v0.0.3-0.20210609130706-9cca50b3d195
+	code.fbi.h-da.de/cocsn/gosdn/interfaces v0.0.0-20210616220833-9f9d4a562783
 	code.fbi.h-da.de/cocsn/yang-models v0.0.7
 	github.com/google/uuid v1.2.0
 	github.com/openconfig/gnmi v0.0.0-20210527163611-d3a3e30199da
diff --git a/api/go.sum b/api/go.sum
index 697c8302ec0d6fb98a2900b54d8c6ea3c9d4bdce..834132dd2ce8af8a12def42d14cdfa0454864e5e 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -22,10 +22,13 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609120033-1ef56612bd26/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
-code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a h1:C2tdh2A4RckZJXUymtF2ec8hE8mTkhn7EQJShv+i/Jk=
-code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210616091959-ec840cb65202/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210617162200-4b2e8d3035c7 h1:pNZ+M0wzJocRP3HXx9NMw042/mvTRrobRspqG3gNTfw=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210617162200-4b2e8d3035c7/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
 code.fbi.h-da.de/cocsn/gosdn v0.0.3-0.20210609130706-9cca50b3d195 h1:PKIXDaEtaqmGviMAx4RWK0/bgPgkCNcxKO+XC85lnIg=
 code.fbi.h-da.de/cocsn/gosdn v0.0.3-0.20210609130706-9cca50b3d195/go.mod h1:EK0GUnwNB+qMMjSJxGzLpfDw+KsUHS2LZkdzshldm4Q=
+code.fbi.h-da.de/cocsn/gosdn/interfaces v0.0.0-20210616220833-9f9d4a562783 h1:7ewcRJE+p8p7nJEB5qiGwREcV60UlWB42aXEtaywJuE=
+code.fbi.h-da.de/cocsn/gosdn/interfaces v0.0.0-20210616220833-9f9d4a562783/go.mod h1:tFBnAuqTB/1GeNvVhKt+lzZ9KLQE+ri2oEEd4YjVHJw=
 code.fbi.h-da.de/cocsn/yang-models v0.0.7 h1:3TOo8J+EdAJKeq4o3aaNWZRhjSwguIS8wciW1U9PkSk=
 code.fbi.h-da.de/cocsn/yang-models v0.0.7/go.mod h1:M+2HinfhTT8nA8qvn2cpWNlOtuiizTNDWA3yfy72K/g=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
diff --git a/api/grpc.go b/api/grpc.go
index 6a475df699387bd5e6b78471c8775cecb7b83075..f820f4ccfdae3188f30cd06f7446c11c4dadcb9e 100644
--- a/api/grpc.go
+++ b/api/grpc.go
@@ -5,14 +5,15 @@ import (
 	"errors"
 	"time"
 
+	pb "code.fbi.h-da.de/cocsn/api/go/gosdn/core"
 	ppb "code.fbi.h-da.de/cocsn/api/go/gosdn/pnd"
 	spb "code.fbi.h-da.de/cocsn/api/go/gosdn/southbound"
 	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"
 	nbi "code.fbi.h-da.de/cocsn/gosdn/northbound/client"
+	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/viper"
 
-	pb "code.fbi.h-da.de/cocsn/api/go/gosdn/core"
 	"google.golang.org/grpc"
 )
 
@@ -107,8 +108,8 @@ func GetPnd(addr string, args ...string) (*pb.GetResponse, error) {
 	return coreClient.Get(ctx, req)
 }
 
-// GetChanges requests all pending and unconfirmed changes from the controller
-func GetChanges(addr, pnd string) (*ppb.GetResponse, error) {
+// getChanges requests all pending and unconfirmed changes from the controller
+func getChanges(addr, pnd string) (*ppb.GetResponse, error) {
 	ctx := context.Background()
 	client, err := nbi.PndClient(addr, dialOptions...)
 	if err != nil {
@@ -126,9 +127,9 @@ func GetChanges(addr, pnd string) (*ppb.GetResponse, error) {
 	return client.Get(ctx, req)
 }
 
-// Commit sends a commit request for one or multiple changes to the
+// commit sends a commit request for one or multiple changes to the
 // controller.
-func Commit(addr, pnd string, cuids ...string) (*ppb.SetResponse, error) {
+func commit(addr, pnd string, cuids ...string) (*ppb.SetResponse, error) {
 	changes := make([]*ppb.SetChange, len(cuids))
 	for i, arg := range cuids {
 		changes[i] = &ppb.SetChange{
@@ -139,9 +140,9 @@ func Commit(addr, pnd string, cuids ...string) (*ppb.SetResponse, error) {
 	return commitConfirm(addr, pnd, changes)
 }
 
-// Confirm sends a confirm request for one or multiple changes to the
+// confirm sends a confirm request for one or multiple changes to the
 // controller
-func Confirm(addr, pnd string, cuids ...string) (*ppb.SetResponse, error) {
+func confirm(addr, pnd string, cuids ...string) (*ppb.SetResponse, error) {
 	changes := make([]*ppb.SetChange, len(cuids))
 	for i, arg := range cuids {
 		changes[i] = &ppb.SetChange{
@@ -166,9 +167,9 @@ func commitConfirm(addr, pnd string, changes []*ppb.SetChange) (*ppb.SetResponse
 	return client.Set(ctx, req)
 }
 
-// AddDevice adds a new device to the controller. The device name is optional.
+// addDevice adds a new device to the controller. The device name is optional.
 // If no name is provided a name will be generated upon device creation.
-func AddDevice(addr, username, password, sbi, pnd, deviceAddress, deviceName string) (*ppb.SetResponse, error) {
+func addDevice(addr, deviceName string, opt *tpb.TransportOption, sid, pid uuid.UUID) (*ppb.SetResponse, error) {
 	pndClient, err := nbi.PndClient(addr, dialOptions...)
 	if err != nil {
 		return nil, err
@@ -178,31 +179,29 @@ func AddDevice(addr, username, password, sbi, pnd, deviceAddress, deviceName str
 		Timestamp: time.Now().UnixNano(),
 		Ond: []*ppb.SetOnd{
 			{
-				Address: deviceAddress,
+				Address: opt.GetAddress(),
 				Sbi: &spb.SouthboundInterface{
-					Id: sbi,
-				},
-				DeviceName: deviceName,
-				TransportOption: &tpb.TransportOption{
-					Address:  addr,
-					Username: username,
-					Password: password,
-					TransportOption: &tpb.TransportOption_GnmiTransportOption{
-						GnmiTransportOption: &tpb.GnmiTransportOption{},
-					},
+					Id: sid.String(),
 				},
+				DeviceName:      deviceName,
+				TransportOption: opt,
 			},
 		},
-		Pid: pnd,
+		Pid: pid.String(),
+	}
+	if opt.Csbi {
+		req.Ond[0].Sbi.Id = uuid.Nil.String()
+		req.Ond[0].Sbi.Type = spb.Type_CONTAINERISED
+		req.Ond[0].TransportOption.Csbi = true
 	}
 	ctx := context.Background()
 	return pndClient.Set(ctx, req)
 }
 
-// GetDevice requests one or multiple devices belonging to a given
+// getDevice requests one or multiple devices belonging to a given
 // PrincipalNetworkDomain from the controller. If no device identifier
 // is provided, all devices are requested.
-func GetDevice(addr, pid, path string, did ...string) (*ppb.GetResponse, error) {
+func getDevice(addr, pid string, did ...string) (*ppb.GetResponse, error) {
 	pndClient, err := nbi.PndClient(addr, dialOptions...)
 	if err != nil {
 		return nil, err
@@ -227,37 +226,52 @@ func GetDevice(addr, pid, path string, did ...string) (*ppb.GetResponse, error)
 	return pndClient.Get(ctx, req)
 }
 
-// Update creates a ChangeRequest to update the given path with the given value
-// at the given OND on the controller.
-func Update(addr, did, pid, path, value string) (*ppb.SetResponse, error) {
-	req := &ppb.ChangeRequest{
-		Id:    did,
-		Path:  path,
-		Value: value,
-		ApiOp: ppb.ApiOperation_UPDATE,
+func getPath(addr, pid, did, path string) (*ppb.GetResponse, error) {
+	pndClient, err := nbi.PndClient(addr, dialOptions...)
+	if err != nil {
+		return nil, err
 	}
-	return sendChangeRequest(addr, pid, req)
+
+	req := &ppb.GetRequest{
+		Timestamp: time.Now().UnixNano(),
+		Request: &ppb.GetRequest_Path{
+			Path: &ppb.GetPath{
+				Did:  did,
+				Path: path,
+			},
+		},
+		Pid: pid,
+	}
+	ctx := context.Background()
+	return pndClient.Get(ctx, req)
 }
 
-// Replace creates a ChangeRequest to replace the given path with the given value
-// at the given OND on the controller.
-func Replace(addr, did, pid, path, value string) (*ppb.SetResponse, error) {
-	req := &ppb.ChangeRequest{
-		Id:    did,
-		Path:  path,
-		Value: value,
-		ApiOp: ppb.ApiOperation_REPLACE,
+func deleteDevice(addr, pid, did string) (*ppb.DeleteResponse, error) {
+	pndClient, err := nbi.PndClient(addr, dialOptions...)
+	if err != nil {
+		return nil, err
 	}
-	return sendChangeRequest(addr, pid, req)
+
+	req := &ppb.DeleteRequest{
+		Timestamp: time.Now().UnixNano(),
+		Type:      ppb.DeleteRequest_OND,
+		Uuid:      did,
+		Pid:       pid,
+	}
+	ctx := context.Background()
+	return pndClient.Delete(ctx, req)
 }
 
-// Delete creates a ChangeRequest to delete the given path node
-// at the given OND on the controller.
-func Delete(addr, did, pid, path string) (*ppb.SetResponse, error) {
+// change creates a ChangeRequest for the specified OND. ApiOperations are
+// used to specify the type of the change (update, replace, delete as specified
+// in https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#34-modifying-state)
+// For delete operations the value field needs to contain an empty string.
+func changeRequest(addr, did, pid, path, value string, op ppb.ApiOperation) (*ppb.SetResponse, error) {
 	req := &ppb.ChangeRequest{
 		Id:    did,
 		Path:  path,
-		ApiOp: ppb.ApiOperation_DELETE,
+		Value: value,
+		ApiOp: op,
 	}
 	return sendChangeRequest(addr, pid, req)
 }
diff --git a/api/grpc_test.go b/api/grpc_test.go
index 549652a2ee0afd4939159ce37fa5cb39d2ecf72f..76bd952b3b631b1d5bcb895c5f00f47d5037599d 100644
--- a/api/grpc_test.go
+++ b/api/grpc_test.go
@@ -1,96 +1,18 @@
 package api
 
 import (
-	"context"
-	"net"
 	"testing"
 
-	pb "code.fbi.h-da.de/cocsn/api/go/gosdn/core"
 	ppb "code.fbi.h-da.de/cocsn/api/go/gosdn/pnd"
 	spb "code.fbi.h-da.de/cocsn/api/go/gosdn/southbound"
-	"code.fbi.h-da.de/cocsn/gosdn/mocks"
-	nbi "code.fbi.h-da.de/cocsn/gosdn/northbound/server"
+	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"
 	"code.fbi.h-da.de/cocsn/gosdn/nucleus"
-	"code.fbi.h-da.de/cocsn/yang-models/generated/openconfig"
-	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
-	"github.com/stretchr/testify/mock"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/test/bufconn"
+	"github.com/spf13/viper"
 )
 
-/*
-Based on this StackOverflow answer: https://stackoverflow.com/a/52080545/4378176
-*/
-
-const bufSize = 1024 * 1024
-const bufnet = "bufnet"
-const pndID = "2043519e-46d1-4963-9a8e-d99007e104b8"
-const changeID = "0992d600-f7d4-4906-9559-409b04d59a5f"
-const sbiID = "f6fd4b35-f039-4111-9156-5e4501bb8a5a"
-const ondID = "7e0ed8cc-ebf5-46fa-9794-741494914883"
-
-var pndStore *nucleus.PndStore
-var lis *bufconn.Listener
-
-func init() {
-	dialOptions = []grpc.DialOption{
-		grpc.WithContextDialer(bufDialer),
-		grpc.WithInsecure(),
-	}
-	lis = bufconn.Listen(bufSize)
-	s := grpc.NewServer()
-	pndStore = nucleus.NewPndStore()
-
-	pndUUID, err := uuid.Parse(pndID)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	changeUUID, err := uuid.Parse(changeID)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	deviceUUID, err := uuid.Parse(ondID)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	mockPnd := mocks.PrincipalNetworkDomain{}
-	mockPnd.On("ID").Return(pndUUID)
-	mockPnd.On("GetName").Return("test")
-	mockPnd.On("GetDescription").Return("test")
-	mockPnd.On("PendingChanges").Return([]uuid.UUID{changeUUID})
-	mockPnd.On("CommittedChanges").Return([]uuid.UUID{changeUUID})
-	mockPnd.On("GetChange", mock.Anything).Return(&nucleus.Change{}, nil)
-	mockPnd.On("AddDevice", mock.Anything, mock.Anything, mock.Anything).Return(nil)
-	mockPnd.On("GetDevice", mock.Anything).Return(&nucleus.CommonDevice{
-		UUID:     deviceUUID,
-		GoStruct: &openconfig.Device{},
-	}, nil)
-	mockPnd.On("Commit", mock.Anything).Return(nil)
-	mockPnd.On("Confirm", mock.Anything).Return(nil)
-	mockPnd.On("ChangeOND", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
-
-	if err := pndStore.Add(&mockPnd); err != nil {
-		log.Fatal(err)
-	}
-	northbound := nbi.NewNBI(pndStore)
-	pb.RegisterCoreServer(s, northbound.Core)
-	ppb.RegisterPndServer(s, northbound.Pnd)
-	go func() {
-		if err := s.Serve(lis); err != nil {
-			log.Fatalf("Server exited with error: %v", err)
-		}
-	}()
-}
-
-func bufDialer(context.Context, string) (net.Conn, error) {
-	return lis.Dial()
-}
-
 func Test_Init(t *testing.T) {
+	viper.SetConfigFile("./api_test.toml")
 	if err := Init(bufnet); err != nil {
 		t.Error(err)
 	}
@@ -128,7 +50,7 @@ func Test_GetPnd(t *testing.T) {
 }
 
 func Test_GetChanges(t *testing.T) {
-	resp, err := GetChanges(bufnet, pndID)
+	resp, err := getChanges(bufnet, pndID)
 	if err != nil {
 		t.Error(err)
 		return
@@ -137,14 +59,14 @@ func Test_GetChanges(t *testing.T) {
 }
 
 func Test_CommitConfirm(t *testing.T) {
-	resp, err := Commit(bufnet, pndID, changeID)
+	resp, err := commit(bufnet, pndID, changeID)
 	if err != nil {
 		t.Error(err)
 		return
 	}
 	log.Info(resp)
 
-	resp, err = Confirm(bufnet, pndID, changeID)
+	resp, err = confirm(bufnet, pndID, changeID)
 	if err != nil {
 		t.Error(err)
 		return
@@ -154,7 +76,15 @@ func Test_CommitConfirm(t *testing.T) {
 }
 
 func Test_AddDevice(t *testing.T) {
-	resp, err := AddDevice(bufnet, "test", "test", sbiID, pndID, "test", "test")
+	opt := &tpb.TransportOption{
+		Address:  "test",
+		Username: "test",
+		Password: "test",
+		TransportOption: &tpb.TransportOption_GnmiTransportOption{
+			GnmiTransportOption: &tpb.GnmiTransportOption{},
+		},
+	}
+	resp, err := addDevice(bufnet, "test", opt, sbiUUID, pndUUID)
 	if err != nil {
 		t.Error(err)
 		return
@@ -163,7 +93,7 @@ func Test_AddDevice(t *testing.T) {
 }
 
 func Test_GetDevice(t *testing.T) {
-	resp, err := GetDevice(bufnet, pndID, "", ondID)
+	resp, err := getDevice(bufnet, pndID, ondID)
 	if err != nil {
 		t.Error(err)
 		return
@@ -175,7 +105,7 @@ func Test_GetDevice(t *testing.T) {
 }
 
 func Test_Update(t *testing.T) {
-	resp, err := Update(bufnet, ondID, pndID, "", "")
+	resp, err := changeRequest(bufnet, ondID, pndID, "", "", ppb.ApiOperation_UPDATE)
 	if err != nil {
 		t.Error(err)
 		return
@@ -184,7 +114,7 @@ func Test_Update(t *testing.T) {
 }
 
 func Test_Replace(t *testing.T) {
-	resp, err := Replace(bufnet, ondID, pndID, "", "")
+	resp, err := changeRequest(bufnet, ondID, pndID, "", "", ppb.ApiOperation_REPLACE)
 	if err != nil {
 		t.Error(err)
 		return
@@ -193,7 +123,7 @@ func Test_Replace(t *testing.T) {
 }
 
 func Test_Delete(t *testing.T) {
-	resp, err := Delete(bufnet, ondID, pndID, "")
+	resp, err := changeRequest(bufnet, ondID, pndID, "", "", ppb.ApiOperation_DELETE)
 	if err != nil {
 		t.Error(err)
 		return
diff --git a/api/initialise_test.go b/api/initialise_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8271cbf8b9e9b92a12a901e8138a4b84fd026d0d
--- /dev/null
+++ b/api/initialise_test.go
@@ -0,0 +1,172 @@
+package api
+
+import (
+	"context"
+	"net"
+	"os"
+	"testing"
+
+	cpb "code.fbi.h-da.de/cocsn/api/go/gosdn/core"
+	ppb "code.fbi.h-da.de/cocsn/api/go/gosdn/pnd"
+	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"
+	"code.fbi.h-da.de/cocsn/gosdn/mocks"
+	nbi "code.fbi.h-da.de/cocsn/gosdn/northbound/server"
+	"code.fbi.h-da.de/cocsn/gosdn/nucleus"
+	"code.fbi.h-da.de/cocsn/gosdn/nucleus/util/proto"
+	"code.fbi.h-da.de/cocsn/yang-models/generated/openconfig"
+	"github.com/google/uuid"
+	log "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/mock"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/test/bufconn"
+
+	gpb "github.com/openconfig/gnmi/proto/gnmi"
+	pb "google.golang.org/protobuf/proto"
+)
+
+/*
+Based on this StackOverflow answer: https://stackoverflow.com/a/52080545/4378176
+*/
+
+const bufSize = 1024 * 1024
+const bufnet = "bufnet"
+const pndID = "2043519e-46d1-4963-9a8e-d99007e104b8"
+const changeID = "0992d600-f7d4-4906-9559-409b04d59a5f"
+const sbiID = "f6fd4b35-f039-4111-9156-5e4501bb8a5a"
+const ondID = "7e0ed8cc-ebf5-46fa-9794-741494914883"
+
+var pndStore *nucleus.PndStore
+var lis *bufconn.Listener
+var pndUUID uuid.UUID
+var sbiUUID uuid.UUID
+
+func bootstrapUnitTest() {
+	dialOptions = []grpc.DialOption{
+		grpc.WithContextDialer(bufDialer),
+		grpc.WithInsecure(),
+	}
+	lis = bufconn.Listen(bufSize)
+	s := grpc.NewServer()
+	pndStore = nucleus.NewPndStore()
+
+	changeUUID, err := uuid.Parse(changeID)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	pndUUID, err = uuid.Parse(pndID)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	sbiUUID, err = uuid.Parse(sbiID)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	deviceUUID, err := uuid.Parse(ondID)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	mockPnd := mocks.PrincipalNetworkDomain{}
+	mockPnd.On("ID").Return(pndUUID)
+	mockPnd.On("GetName").Return("test")
+	mockPnd.On("GetDescription").Return("test")
+	mockPnd.On("PendingChanges").Return([]uuid.UUID{changeUUID})
+	mockPnd.On("CommittedChanges").Return([]uuid.UUID{changeUUID})
+	mockPnd.On("GetChange", mock.Anything).Return(&nucleus.Change{}, nil)
+	mockPnd.On("AddDevice", mock.Anything, mock.Anything, mock.Anything).Return(nil)
+	mockPnd.On("GetDevice", mock.Anything).Return(&nucleus.CommonDevice{
+		UUID:     deviceUUID,
+		GoStruct: &openconfig.Device{},
+	}, nil)
+	mockPnd.On("Commit", mock.Anything).Return(nil)
+	mockPnd.On("Confirm", mock.Anything).Return(nil)
+	mockPnd.On("ChangeOND", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
+
+	if err := pndStore.Add(&mockPnd); err != nil {
+		log.Fatal(err)
+	}
+	northbound := nbi.NewNBI(pndStore)
+	cpb.RegisterCoreServer(s, northbound.Core)
+	ppb.RegisterPndServer(s, northbound.Pnd)
+	go func() {
+		if err := s.Serve(lis); err != nil {
+			log.Fatalf("Server exited with error: %v", err)
+		}
+	}()
+}
+
+func bufDialer(context.Context, string) (net.Conn, error) {
+	return lis.Dial()
+}
+
+const unreachable = "203.0.113.10:6030"
+const testPath = "/system/config/hostname"
+
+var testAddress = "141.100.70.170:6030"
+var testAPIEndpoint = "gosdn-latest.apps.ocp.fbi.h-da.de"
+var testUsername = "admin"
+var testPassword = "arista"
+var opt *tpb.TransportOption
+var gnmiMessages map[string]pb.Message
+
+func TestMain(m *testing.M) {
+	bootstrapUnitTest()
+	bootstrapIntegrationTest()
+	os.Exit(m.Run())
+}
+
+func bootstrapIntegrationTest() {
+	if os.Getenv("GOSDN_LOG") == "nolog" {
+		log.SetLevel(log.PanicLevel)
+	}
+
+	addr := os.Getenv("GOSDN_TEST_ENDPOINT")
+	if addr != "" {
+		testAddress = addr
+		log.Infof("GOSDN_TEST_ENDPOINT set to %v", testAddress)
+	}
+	api := os.Getenv("GOSDN_TEST_API_ENDPOINT")
+	if api != "" {
+		testAPIEndpoint = api
+		log.Infof("GOSDN_TEST_API_ENDPOINT set to %v", testAPIEndpoint)
+	}
+	u := os.Getenv("GOSDN_TEST_USER")
+	if u != "" {
+		testUsername = u
+		log.Infof("GOSDN_TEST_USER set to %v", testUsername)
+	}
+	p := os.Getenv("GOSDN_TEST_PASSWORD")
+	if p != "" {
+		testPassword = p
+		log.Infof("GOSDN_TEST_PASSWORD set to %v", testPassword)
+	}
+
+	gnmiMessages = map[string]pb.Message{
+		"../test/proto/cap-resp-arista-ceos":                  &gpb.CapabilityResponse{},
+		"../test/proto/req-full-node":                         &gpb.GetRequest{},
+		"../test/proto/req-full-node-arista-ceos":             &gpb.GetRequest{},
+		"../test/proto/req-interfaces-arista-ceos":            &gpb.GetRequest{},
+		"../test/proto/req-interfaces-interface-arista-ceos":  &gpb.GetRequest{},
+		"../test/proto/req-interfaces-wildcard":               &gpb.GetRequest{},
+		"../test/proto/resp-full-node":                        &gpb.GetResponse{},
+		"../test/proto/resp-full-node-arista-ceos":            &gpb.GetResponse{},
+		"../test/proto/resp-interfaces-arista-ceos":           &gpb.GetResponse{},
+		"../test/proto/resp-interfaces-interface-arista-ceos": &gpb.GetResponse{},
+		"../test/proto/resp-interfaces-wildcard":              &gpb.GetResponse{},
+		"../test/proto/resp-set-system-config-hostname":       &gpb.SetResponse{},
+	}
+	for k, v := range gnmiMessages {
+		if err := proto.Read(k, v); err != nil {
+			log.Fatalf("error parsing %v: %v", k, err)
+		}
+	}
+
+	opt = &tpb.TransportOption{
+		Address:  testAddress,
+		Username: testUsername,
+		Password: testPassword,
+	}
+}
diff --git a/api/pnd.go b/api/pnd.go
new file mode 100644
index 0000000000000000000000000000000000000000..86d2b09713152d43920912fcf9b3b2ea346f7019
--- /dev/null
+++ b/api/pnd.go
@@ -0,0 +1,180 @@
+package api
+
+import (
+	ppb "code.fbi.h-da.de/cocsn/api/go/gosdn/pnd"
+	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/change"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/device"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/networkdomain"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/southbound"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/store"
+	"code.fbi.h-da.de/cocsn/gosdn/nucleus/errors"
+	"github.com/google/uuid"
+	log "github.com/sirupsen/logrus"
+)
+
+type PrincipalNetworkDomainAdapter struct {
+	id       uuid.UUID
+	endpoint string
+}
+
+func NewAdapter(id, endpoint string) (networkdomain.NetworkDomain, error) {
+	pid, err := uuid.Parse(id)
+	if err != nil {
+		return nil, err
+	}
+	return &PrincipalNetworkDomainAdapter{
+		id:       pid,
+		endpoint: endpoint,
+	}, nil
+}
+
+func (p *PrincipalNetworkDomainAdapter) Destroy() error {
+	return &errors.ErrNotYetImplemented{}
+}
+
+func (p *PrincipalNetworkDomainAdapter) AddSbi(s southbound.SouthboundInterface) error {
+	return &errors.ErrNotYetImplemented{}
+}
+
+func (p *PrincipalNetworkDomainAdapter) RemoveSbi(uuid.UUID) error {
+	return &errors.ErrNotYetImplemented{}
+}
+
+// AddDevice adds a new device to the controller. The device name is optional.
+// If no name is provided a name will be generated upon device creation.
+func (p *PrincipalNetworkDomainAdapter) AddDevice(name string, opts *tpb.TransportOption, sid uuid.UUID) error {
+	resp, err := addDevice(p.endpoint, name, opts, sid, p.ID())
+	if err != nil {
+		return err
+	}
+	log.Info(resp)
+	return nil
+}
+
+// GetDevice requests one or multiple devices belonging to a given
+// PrincipalNetworkDomain from the controller. If no device identifier
+// is provided, all devices are requested.
+func (p *PrincipalNetworkDomainAdapter) GetDevice(identifier string) (device.Device, error) {
+	resp, err := getDevice(p.endpoint, p.id.String(), identifier)
+	if err != nil {
+		return nil, err
+	}
+	// TODO: Parse into device.Device
+	log.Info(resp)
+	return nil, nil
+}
+
+func (p *PrincipalNetworkDomainAdapter) RemoveDevice(did uuid.UUID) error {
+	resp, err := deleteDevice(p.endpoint, p.id.String(), did.String())
+	if err != nil {
+		return err
+	}
+	log.Info(resp)
+	return nil
+}
+
+func (p *PrincipalNetworkDomainAdapter) Devices() []uuid.UUID {
+	return nil
+}
+
+func (p *PrincipalNetworkDomainAdapter) ChangeOND(uuid uuid.UUID, operation ppb.ApiOperation, path string, value ...string) error {
+	var v string
+	if len(value) != 0 {
+		v = value[0]
+	}
+	resp, err := changeRequest(p.endpoint, uuid.String(), p.id.String(), path, v, operation)
+	if err != nil {
+		return err
+	}
+	log.Info(resp)
+	return err
+}
+
+func (p *PrincipalNetworkDomainAdapter) Request(did uuid.UUID, path string) error {
+	resp, err := getPath(p.endpoint, p.id.String(), did.String(), path)
+	if err != nil {
+		return err
+	}
+	log.Info(resp)
+	return nil
+}
+
+func (p *PrincipalNetworkDomainAdapter) RequestAll(string) error {
+	return &errors.ErrNotYetImplemented{}
+}
+
+func (p *PrincipalNetworkDomainAdapter) GetName() string {
+	return ""
+}
+
+func (p *PrincipalNetworkDomainAdapter) GetDescription() string {
+	return ""
+}
+
+func (p *PrincipalNetworkDomainAdapter) MarshalDevice(string) (string, error) {
+	return "", &errors.ErrNotYetImplemented{}
+}
+
+func (p *PrincipalNetworkDomainAdapter) ContainsDevice(uuid.UUID) bool {
+	return false
+}
+
+func (p *PrincipalNetworkDomainAdapter) GetSBIs() store.Store {
+	return nil
+}
+
+func (p *PrincipalNetworkDomainAdapter) ID() uuid.UUID {
+	return p.id
+}
+
+func (p *PrincipalNetworkDomainAdapter) PendingChanges() []uuid.UUID {
+	resp, err := getChanges(p.endpoint, p.id.String())
+	if err != nil {
+		log.Error(err)
+		return nil
+	}
+	return filterChanges(ppb.Change_PENDING, resp)
+}
+
+func (p *PrincipalNetworkDomainAdapter) CommittedChanges() []uuid.UUID {
+	resp, err := getChanges(p.endpoint, p.id.String())
+	if err != nil {
+		log.Error(err)
+		return nil
+	}
+	return filterChanges(ppb.Change_COMMITTED, resp)
+}
+
+func (p *PrincipalNetworkDomainAdapter) GetChange(uuid.UUID, ...int) (change.Change, error) {
+	return nil, &errors.ErrNotYetImplemented{}
+}
+
+func (p *PrincipalNetworkDomainAdapter) Commit(cuid uuid.UUID) error {
+	resp, err := commit(p.endpoint, p.id.String(), cuid.String())
+	if err != nil {
+		return err
+	}
+	log.Info(resp)
+	return nil
+}
+
+func (p *PrincipalNetworkDomainAdapter) Confirm(cuid uuid.UUID) error {
+	resp, err := confirm(p.endpoint, p.id.String(), cuid.String())
+	if err != nil {
+		return err
+	}
+	log.Info(resp)
+	return nil
+}
+
+func filterChanges(state ppb.Change_State, resp *ppb.GetResponse) []uuid.UUID {
+	changes := make([]uuid.UUID, 0)
+	for _, ch := range resp.Change {
+		if ch.State == ppb.Change_PENDING {
+			id, _ := uuid.Parse(ch.Id)
+			changes = append(changes, id)
+		}
+	}
+	return changes
+}
diff --git a/api/pnd_test.go b/api/pnd_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5f6238157715197eadb7e3b71aa696c2cc06cac3
--- /dev/null
+++ b/api/pnd_test.go
@@ -0,0 +1,665 @@
+package api
+
+import (
+	"reflect"
+	"testing"
+
+	ppb "code.fbi.h-da.de/cocsn/api/go/gosdn/pnd"
+	tpb "code.fbi.h-da.de/cocsn/api/go/gosdn/transport"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/change"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/device"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/networkdomain"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/southbound"
+	"code.fbi.h-da.de/cocsn/gosdn/interfaces/store"
+	"github.com/google/uuid"
+)
+
+func TestNewAdapter(t *testing.T) {
+	type args struct {
+		id       string
+		endpoint string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    networkdomain.NetworkDomain
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := NewAdapter(tt.args.id, tt.args.endpoint)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("NewAdapter() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewAdapter() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_Destroy(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.Destroy(); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.Destroy() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_AddSbi(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		s southbound.SouthboundInterface
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.AddSbi(tt.args.s); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.AddSbi() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_RemoveSbi(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		in0 uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.RemoveSbi(tt.args.in0); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.RemoveSbi() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_AddDevice(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		name string
+		opts *tpb.TransportOption
+		sid  uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.AddDevice(tt.args.name, tt.args.opts, tt.args.sid); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.AddDevice() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_GetDevice(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		identifier string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    device.Device
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			got, err := p.GetDevice(tt.args.identifier)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.GetDevice() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PrincipalNetworkDomainAdapter.GetDevice() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_RemoveDevice(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		did uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.RemoveDevice(tt.args.did); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.RemoveDevice() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_Devices(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   []uuid.UUID
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if got := p.Devices(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PrincipalNetworkDomainAdapter.Devices() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_ChangeOND(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		uuid      uuid.UUID
+		operation ppb.ApiOperation
+		path      string
+		value     []string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.ChangeOND(tt.args.uuid, tt.args.operation, tt.args.path, tt.args.value...); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.ChangeOND() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_Request(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		did  uuid.UUID
+		path string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.Request(tt.args.did, tt.args.path); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.Request() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_RequestAll(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		in0 string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.RequestAll(tt.args.in0); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.RequestAll() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_GetName(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   string
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if got := p.GetName(); got != tt.want {
+				t.Errorf("PrincipalNetworkDomainAdapter.GetName() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_GetDescription(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   string
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if got := p.GetDescription(); got != tt.want {
+				t.Errorf("PrincipalNetworkDomainAdapter.GetDescription() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_MarshalDevice(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		in0 string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    string
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			got, err := p.MarshalDevice(tt.args.in0)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.MarshalDevice() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("PrincipalNetworkDomainAdapter.MarshalDevice() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_ContainsDevice(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		in0 uuid.UUID
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		args   args
+		want   bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if got := p.ContainsDevice(tt.args.in0); got != tt.want {
+				t.Errorf("PrincipalNetworkDomainAdapter.ContainsDevice() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_GetSBIs(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   store.Store
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if got := p.GetSBIs(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PrincipalNetworkDomainAdapter.GetSBIs() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_ID(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   uuid.UUID
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if got := p.ID(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PrincipalNetworkDomainAdapter.ID() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_PendingChanges(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   []uuid.UUID
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if got := p.PendingChanges(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PrincipalNetworkDomainAdapter.PendingChanges() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_CommittedChanges(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   []uuid.UUID
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if got := p.CommittedChanges(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PrincipalNetworkDomainAdapter.CommittedChanges() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_GetChange(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		in0 uuid.UUID
+		in1 []int
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		want    change.Change
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			got, err := p.GetChange(tt.args.in0, tt.args.in1...)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.GetChange() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PrincipalNetworkDomainAdapter.GetChange() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_Commit(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		cuid uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.Commit(tt.args.cuid); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.Commit() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestPrincipalNetworkDomainAdapter_Confirm(t *testing.T) {
+	type fields struct {
+		id       uuid.UUID
+		endpoint string
+	}
+	type args struct {
+		cuid uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := &PrincipalNetworkDomainAdapter{
+				id:       tt.fields.id,
+				endpoint: tt.fields.endpoint,
+			}
+			if err := p.Confirm(tt.args.cuid); (err != nil) != tt.wantErr {
+				t.Errorf("PrincipalNetworkDomainAdapter.Confirm() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_filterChanges(t *testing.T) {
+	type args struct {
+		state ppb.Change_State
+		resp  *ppb.GetResponse
+	}
+	tests := []struct {
+		name string
+		args args
+		want []uuid.UUID
+	}{
+		// TODO: Add test cases.
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := filterChanges(tt.args.state, tt.args.resp); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("filterChanges() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/go.mod b/go.mod
index 60e1819514a94ca9fc97f316e0e7dcff6ad31380..c76110a4e4f115879d5ce2314e609c07945a3fdf 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,8 @@ module code.fbi.h-da.de/cocsn/gosdn
 go 1.16
 
 require (
-	code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a
+	code.fbi.h-da.de/cocsn/api/go v0.0.0-20210616091959-ec840cb65202
+	code.fbi.h-da.de/cocsn/gosdn/interfaces v0.0.0-20210616221000-dfaf18a8b4b0
 	code.fbi.h-da.de/cocsn/yang-models v0.0.7
 	github.com/aristanetworks/goarista v0.0.0-20201120222254-94a892eb0c6a
 	github.com/docker/docker v20.10.6+incompatible
diff --git a/go.sum b/go.sum
index 22b65647e51ec81fcc37ff1f8df5a26f412b6d70..a98d01739b3af6d3413ce9f50f908096de12a61f 100644
--- a/go.sum
+++ b/go.sum
@@ -21,8 +21,10 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a h1:C2tdh2A4RckZJXUymtF2ec8hE8mTkhn7EQJShv+i/Jk=
-code.fbi.h-da.de/cocsn/api/go v0.0.0-20210609143151-4dabee5ab99a/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210616091959-ec840cb65202 h1:SiRiAm0cXUFkwNBJZdv7twdj6V7Bt6jRFMiMO170CkQ=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210616091959-ec840cb65202/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
+code.fbi.h-da.de/cocsn/gosdn/interfaces v0.0.0-20210616221000-dfaf18a8b4b0 h1:HW0TKYIO5qnxxaZ3KAAufoT/wv89t+eqZwKGHBo824g=
+code.fbi.h-da.de/cocsn/gosdn/interfaces v0.0.0-20210616221000-dfaf18a8b4b0/go.mod h1:tFBnAuqTB/1GeNvVhKt+lzZ9KLQE+ri2oEEd4YjVHJw=
 code.fbi.h-da.de/cocsn/yang-models v0.0.7 h1:3TOo8J+EdAJKeq4o3aaNWZRhjSwguIS8wciW1U9PkSk=
 code.fbi.h-da.de/cocsn/yang-models v0.0.7/go.mod h1:M+2HinfhTT8nA8qvn2cpWNlOtuiizTNDWA3yfy72K/g=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
diff --git a/interfaces/go.mod b/interfaces/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..aa90ec51d9cce806be53424a4974cba0fefd4f3e
--- /dev/null
+++ b/interfaces/go.mod
@@ -0,0 +1,12 @@
+module code.fbi.h-da.de/cocsn/gosdn/interfaces
+
+go 1.16
+
+require (
+	code.fbi.h-da.de/cocsn/api/go v0.0.0-20210616091959-ec840cb65202
+	github.com/google/uuid v1.1.2
+	github.com/openconfig/gnmi v0.0.0-20210430192044-ab96b57c5113
+	github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea
+	github.com/openconfig/ygot v0.6.0
+	google.golang.org/protobuf v1.26.0
+)
diff --git a/interfaces/go.sum b/interfaces/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..1b331011787ba6075feaf77f083a7d88b43e8ef5
--- /dev/null
+++ b/interfaces/go.sum
@@ -0,0 +1,117 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210616091959-ec840cb65202 h1:SiRiAm0cXUFkwNBJZdv7twdj6V7Bt6jRFMiMO170CkQ=
+code.fbi.h-da.de/cocsn/api/go v0.0.0-20210616091959-ec840cb65202/go.mod h1:2+rnE92IyXLbiy3/92EM7JrtsY5tXPAKX90QmsT2+m0=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/protobuf v3.14.0+incompatible/go.mod h1:lUQ9D1ePzbH2PrIS7ob/bjm9HXyH5WHB0Akwh7URreM=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/openconfig/gnmi v0.0.0-20210430192044-ab96b57c5113 h1:gHZS9U5wmDYeS8vPu4eEXbWHAVwayPwJ/Ze5RGYGXLw=
+github.com/openconfig/gnmi v0.0.0-20210430192044-ab96b57c5113/go.mod h1:H/20NXlnWbCPFC593nxpiKJ+OU//7mW7s7Qk7uVdg3Q=
+github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea h1:5MyIz4bN4vpH6aHDN339bkWXAjTkhg1ZKMhR4aIi5Rk=
+github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU=
+github.com/openconfig/ygot v0.6.0 h1:kJJFPBrczC6TDnz/HMlFTJEdW2CuyUftV13XveIukg0=
+github.com/openconfig/ygot v0.6.0/go.mod h1:o30svNf7O0xK+R35tlx95odkDmZWS9JyWWQSmIhqwAs=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
+golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d h1:HV9Z9qMhQEsdlvxNFELgQ11RkMzO3CMkjEySjCtuLes=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=