diff --git a/build/ci/.test.yml b/build/ci/.test.yml
index 225356ea6656130a0737e4b8b4a7b3ee39bcd5b2..dd4f07a0a70490c2ce539a91081ad3c196d73f3e 100644
--- a/build/ci/.test.yml
+++ b/build/ci/.test.yml
@@ -27,22 +27,23 @@ integration-test:
   allow_failure: true
   variables:
     GOSDN_LOG: "nolog"
+    GOSDN_CHANGE_TIMEOUT: "100ms"
   rules:
     - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH
     - if: $CI_NIGHTLY
     - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
     - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
       allow_failure: true
-  after_script:
-    - go tool cover -func=coverage.out
 
 unit-test:
   script:
     - go test -short -race $(go list ./... | grep -v /forks/ | grep -v /api/ | grep -v /mocks ) -v -coverprofile=coverage.out
+  after_script:
+    - go tool cover -func=coverage.out
   <<: *test
 
-http-api-test:
+controller-test:
   script:
     - cd ./nucleus
-    - go test -race -v -run Test_httpApi -coverprofile=coverage.out
+    - go test -race -v -run TestRun
   <<: *test
\ No newline at end of file
diff --git a/cli/http.go b/cli/http.go
index 746c4a45146a882d827ef4b58526a5e06e4ef9a3..b7c3254b655bda3c315dd79039deb5f730fc023f 100644
--- a/cli/http.go
+++ b/cli/http.go
@@ -3,11 +3,12 @@ package cli
 import (
 	"errors"
 	"fmt"
-	log "github.com/sirupsen/logrus"
-	"github.com/spf13/viper"
 	"io/ioutil"
 	"net/http"
 	"strings"
+
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
 )
 
 const apiRoot = "?"
@@ -26,6 +27,7 @@ func HTTPGet(apiEndpoint, f string, args ...string) error {
 	}
 	resp, err := http.Get(apiEndpoint + apiRoot + "q=" + f + builder.String())
 	if err != nil {
+		log.Info(fmt.Sprintf("Err: %s", err))
 		return err
 	}
 	builder.Reset()
@@ -38,8 +40,8 @@ func HTTPGet(apiEndpoint, f string, args ...string) error {
 		}
 		switch f {
 		case "init":
-			pnd := string(bytes[:36])
-			sbi := string(bytes[36:])
+			pnd := string(bytes[9:45])
+			sbi := string(bytes[55:91])
 			viper.Set("CLI_PND", pnd)
 			viper.Set("CLI_SBI", sbi)
 			err := viper.WriteConfig()
@@ -49,6 +51,7 @@ func HTTPGet(apiEndpoint, f string, args ...string) error {
 		default:
 			fmt.Println(string(bytes))
 		}
+
 	case http.StatusCreated:
 		defer resp.Body.Close()
 		bytes, err := ioutil.ReadAll(resp.Body)
@@ -58,6 +61,8 @@ func HTTPGet(apiEndpoint, f string, args ...string) error {
 		uuid := string(bytes[19:55])
 		viper.Set("LAST_DEVICE_UUID", uuid)
 		fmt.Println(string(bytes))
+
+	case http.StatusAccepted:
 	default:
 		log.WithFields(log.Fields{
 			"status code": resp.StatusCode,
diff --git a/cli/set.go b/cli/set.go
index 4d01d1477fd281446c3666faaf69e9a9899f7347..c5499d6f3a085db76d03fcef3227645662e829fc 100644
--- a/cli/set.go
+++ b/cli/set.go
@@ -3,10 +3,7 @@ package cli
 import (
 	"code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi"
 	"code.fbi.h-da.de/cocsn/gosdn/nucleus"
-	"code.fbi.h-da.de/cocsn/gosdn/nucleus/util/proto"
 	"context"
-	pb "google.golang.org/protobuf/proto"
-	"os"
 )
 
 // Set sends a gNMI Set request to the specified target. Only one
@@ -23,28 +20,5 @@ func Set(a, u, p, typ string, args ...string) error {
 	if err != nil {
 		return err
 	}
-
-	path := gnmi.SplitPath(args[0])
-	req := []interface{}{
-		&gnmi.Operation{
-			Type:   typ,
-			Origin: "",
-			Target: "",
-			Path:   path,
-			Val:    args[1],
-		},
-	}
-
-	resp, err := t.Set(context.Background(), req...)
-	if err != nil {
-		return err
-	}
-
-	_, tap := os.LookupEnv("GOSDN_TAP")
-	if tap {
-		if err := proto.Write(resp.(pb.Message), "resp-set-system-config-hostname"); err != nil {
-			return err
-		}
-	}
-	return nil
+	return t.Set(context.Background(), args)
 }
diff --git a/cmd/change.go b/cmd/change.go
new file mode 100644
index 0000000000000000000000000000000000000000..fc10bdb4fb746317c2450b4548cfae680115769f
--- /dev/null
+++ b/cmd/change.go
@@ -0,0 +1,49 @@
+/*
+Copyright © 2021 da/net research group <danet.fbi.h-da.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package cmd
+
+import (
+	"github.com/spf13/cobra"
+)
+
+// changeCmd represents the change command
+var changeCmd = &cobra.Command{
+	Use:   "change",
+	Short: "manage changes of the specified pnd",
+	Long: `use "change list" or "change list-pending" to list changes
+
+use "change commit" or "change confirm" respectively`,
+}
+
+func init() {
+	cliCmd.AddCommand(changeCmd)
+}
diff --git a/cmd/cliSet.go b/cmd/cliSet.go
index 9a79c630bed30d80c0d8438224edc42f084ff14d..e3f3a0743fdea2fa9c5c2ebd5686ef2e6675dc6b 100644
--- a/cmd/cliSet.go
+++ b/cmd/cliSet.go
@@ -41,12 +41,14 @@ var cliSetCmd = &cobra.Command{
 	Use:   "set",
 	Args:  cobra.ExactArgs(2),
 	Short: "set a value on a device",
-	Long: `Set a path value for a given device. Only one path and
-only one value supported for now`,
+	Long: `Update a path value for a given device. Only one path and
+only one value supported for now.
+
+Use "set replace" or "set delete" respectively`,
 	RunE: func(cmd *cobra.Command, args []string) error {
 		return cli.HTTPGet(
 			apiEndpoint,
-			"set",
+			"update",
 			"uuid="+uuid,
 			"cliSbi="+cliSbi,
 			"cliPnd="+cliPnd,
@@ -60,5 +62,5 @@ only one value supported for now`,
 func init() {
 	cliCmd.AddCommand(cliSetCmd)
 
-	cliSetCmd.Flags().StringVar(&uuid, "uuid", "", "uuid of the requested device")
+	cliSetCmd.PersistentFlags().StringVar(&uuid, "uuid", "", "uuid of the target device")
 }
diff --git a/cmd/commit.go b/cmd/commit.go
new file mode 100644
index 0000000000000000000000000000000000000000..a6ccb3b109b3c7f6afc4d7303c7c8ca88b196989
--- /dev/null
+++ b/cmd/commit.go
@@ -0,0 +1,57 @@
+/*
+Copyright © 2021 da/net research group <danet.fbi.h-da.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package cmd
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/cli"
+	"github.com/spf13/cobra"
+)
+
+// commitCmd represents the commit command
+var commitCmd = &cobra.Command{
+	Use:   "commit",
+	Args:  cobra.ExactArgs(1),
+	Short: "Commit the given change for the active PND",
+	Long:  ``,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return cli.HTTPGet(
+			apiEndpoint,
+			"change-commit",
+			"pnd="+cliPnd,
+			"cuid="+args[0],
+		)
+	},
+}
+
+func init() {
+	changeCmd.AddCommand(commitCmd)
+}
diff --git a/cmd/confirm.go b/cmd/confirm.go
new file mode 100644
index 0000000000000000000000000000000000000000..59cda76985aac03a9149da41a4e08f9545d0ac77
--- /dev/null
+++ b/cmd/confirm.go
@@ -0,0 +1,57 @@
+/*
+Copyright © 2021 da/net research group <danet.fbi.h-da.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package cmd
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/cli"
+	"github.com/spf13/cobra"
+)
+
+// confirmCmd represents the confirm command
+var confirmCmd = &cobra.Command{
+	Use:   "confirm",
+	Args:  cobra.ExactArgs(1),
+	Short: "Confirms the given change for the active PND",
+	Long:  ``,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return cli.HTTPGet(
+			apiEndpoint,
+			"change-confirm",
+			"pnd="+cliPnd,
+			"cuid="+args[0],
+		)
+	},
+}
+
+func init() {
+	changeCmd.AddCommand(confirmCmd)
+}
diff --git a/cmd/delete.go b/cmd/delete.go
new file mode 100644
index 0000000000000000000000000000000000000000..3c4e4e7603c32be00c4bba428bd22b09cd735eea
--- /dev/null
+++ b/cmd/delete.go
@@ -0,0 +1,71 @@
+/*
+Copyright © 2021 da/net research group <danet.fbi.h-da.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package cmd
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/cli"
+	"github.com/spf13/cobra"
+)
+
+// deleteCmd represents the delete command
+var deleteCmd = &cobra.Command{
+	Use:   "delete",
+	Args:  cobra.ExactArgs(1),
+	Short: "set a value on a device",
+	Long: `Set a path value for a given device. Only one path and
+only one value supported for now`,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return cli.HTTPGet(
+			apiEndpoint,
+			"delete",
+			"uuid="+uuid,
+			"cliSbi="+cliSbi,
+			"cliPnd="+cliPnd,
+			"path="+args[0],
+			"address="+address,
+		)
+	},
+}
+
+func init() {
+	cliSetCmd.AddCommand(deleteCmd)
+
+	// Here you will define your flags and configuration settings.
+
+	// Cobra supports Persistent Flags which will work for this command
+	// and all subcommands, e.g.:
+	// deleteCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+	// Cobra supports local flags which will only run when this command
+	// is called directly, e.g.:
+	// deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/cmd/list.go b/cmd/list.go
new file mode 100644
index 0000000000000000000000000000000000000000..198662268afafa640e5c73749c0f64899874188b
--- /dev/null
+++ b/cmd/list.go
@@ -0,0 +1,55 @@
+/*
+Copyright © 2021 da/net research group <danet.fbi.h-da.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package cmd
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/cli"
+	"github.com/spf13/cobra"
+)
+
+// listCmd represents the list command
+var listCmd = &cobra.Command{
+	Use:   "list",
+	Short: "Lists all committed changes",
+	Long:  ``,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return cli.HTTPGet(
+			apiEndpoint,
+			"change-list",
+			"pnd="+cliPnd,
+		)
+	},
+}
+
+func init() {
+	changeCmd.AddCommand(listCmd)
+}
diff --git a/cmd/listPending.go b/cmd/listPending.go
new file mode 100644
index 0000000000000000000000000000000000000000..c7893d97bfe5e891c0ca90c9df4b44856b39f045
--- /dev/null
+++ b/cmd/listPending.go
@@ -0,0 +1,55 @@
+/*
+Copyright © 2021 da/net research group <danet.fbi.h-da.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package cmd
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/cli"
+	"github.com/spf13/cobra"
+)
+
+// listPendingCmd represents the listPending command
+var listPendingCmd = &cobra.Command{
+	Use:   "listPending",
+	Short: "Lists all committed changes",
+	Long:  ``,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return cli.HTTPGet(
+			apiEndpoint,
+			"change-list-pending",
+			"pnd="+cliPnd,
+		)
+	},
+}
+
+func init() {
+	changeCmd.AddCommand(listPendingCmd)
+}
diff --git a/cmd/replace.go b/cmd/replace.go
new file mode 100644
index 0000000000000000000000000000000000000000..33529c6d0e647aa9494b0dec9eee4ea2b2253818
--- /dev/null
+++ b/cmd/replace.go
@@ -0,0 +1,62 @@
+/*
+Copyright © 2021 da/net research group <danet.fbi.h-da.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package cmd
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/cli"
+	"github.com/spf13/cobra"
+)
+
+// replaceCmd represents the replace command
+var replaceCmd = &cobra.Command{
+	Use:   "replace",
+	Args:  cobra.ExactArgs(2),
+	Short: "set a value on a device",
+	Long: `Set a path value for a given device. Only one path and
+only one value supported for now`,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return cli.HTTPGet(
+			apiEndpoint,
+			"replace",
+			"uuid="+uuid,
+			"cliSbi="+cliSbi,
+			"cliPnd="+cliPnd,
+			"path="+args[0],
+			"address="+address,
+			"value="+args[1],
+		)
+	},
+}
+
+func init() {
+	cliSetCmd.AddCommand(replaceCmd)
+}
diff --git a/cmd/update.go b/cmd/update.go
new file mode 100644
index 0000000000000000000000000000000000000000..93db8e374adf4553675bd14db98c29b09ee88052
--- /dev/null
+++ b/cmd/update.go
@@ -0,0 +1,62 @@
+/*
+Copyright © 2021 da/net research group <danet.fbi.h-da.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package cmd
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/cli"
+	"github.com/spf13/cobra"
+)
+
+// updateCmd represents the update command
+var updateCmd = &cobra.Command{
+	Use:   "update",
+	Args:  cobra.ExactArgs(2),
+	Short: "update a value on a device",
+	Long: `Update a path value for a given device. Only one path and
+only one value supported for now`,
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return cli.HTTPGet(
+			apiEndpoint,
+			"update",
+			"uuid="+uuid,
+			"cliSbi="+cliSbi,
+			"cliPnd="+cliPnd,
+			"path="+args[0],
+			"address="+address,
+			"value="+args[1],
+		)
+	},
+}
+
+func init() {
+	cliSetCmd.AddCommand(updateCmd)
+}
diff --git a/forks/goarista/gnmi/client.go b/forks/goarista/gnmi/client.go
index fc041336eb078e224f739bbad9f93dd2767c10fb..c4512f2c8f6a3ef34b405ccfe81a33fcfcd57f72 100644
--- a/forks/goarista/gnmi/client.go
+++ b/forks/goarista/gnmi/client.go
@@ -118,7 +118,7 @@ func DialContext(ctx context.Context, cfg *Config) (pb.GNMIClient, error) {
 
 	if cfg.TLS || cfg.CAFile != "" || cfg.CertFile != "" || cfg.Token != "" {
 		tlsConfig := &tls.Config{
-			MinVersion:                  tls.VersionTLS12,
+			MinVersion: tls.VersionTLS12,
 		}
 		if cfg.CAFile != "" {
 			b, err := ioutil.ReadFile(cfg.CAFile)
diff --git a/go.mod b/go.mod
index ac1e4cbb4135509efb5ecdec1fee89a8acb384bb..8e06b5944cab2bd7ffa365c59d78b8295043bc1f 100644
--- a/go.mod
+++ b/go.mod
@@ -9,17 +9,17 @@ require (
 	github.com/google/gnxi v0.0.0-20201221102247-c26672548161
 	github.com/google/uuid v1.1.2
 	github.com/neo4j/neo4j-go-driver v1.8.3
-	github.com/openconfig/gnmi v0.0.0-20200617225440-d2b4e6a45802
-	github.com/openconfig/goyang v0.2.3
-	github.com/openconfig/ygot v0.10.0
-	github.com/sirupsen/logrus v1.7.0
-	github.com/spf13/cobra v1.1.1
+	github.com/openconfig/gnmi v0.0.0-20210428141518-ae4d850000ab
+	github.com/openconfig/goyang v0.2.4
+	github.com/openconfig/ygot v0.10.5
+	github.com/sirupsen/logrus v1.8.1
+	github.com/spf13/cobra v1.1.3
 	github.com/spf13/viper v1.7.1
-	github.com/stretchr/testify v1.6.1
-	golang.org/x/net v0.0.0-20201216054612-986b41b23924
-	google.golang.org/grpc v1.34.0
+	github.com/stretchr/testify v1.7.0
+	golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
+	google.golang.org/grpc v1.37.0
 	google.golang.org/protobuf v1.26.0
-	k8s.io/api v0.20.5
-	k8s.io/apimachinery v0.20.5
-	k8s.io/client-go v0.20.5
+	k8s.io/api v0.21.0
+	k8s.io/apimachinery v0.21.0
+	k8s.io/client-go v0.21.0
 )
diff --git a/go.sum b/go.sum
index ff5b2f4c4e4729afb273a8e33337041676f2c38d..611ef282280814b75535b07660de63a3df5b77fa 100644
--- a/go.sum
+++ b/go.sum
@@ -25,11 +25,9 @@ code.fbi.h-da.de/cocsn/yang-models v0.0.7 h1:3TOo8J+EdAJKeq4o3aaNWZRhjSwguIS8wci
 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=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
-github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
+github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
 github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
 github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
 github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
@@ -72,6 +70,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -88,7 +87,6 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc
 github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
@@ -102,6 +100,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
 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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -112,7 +111,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
-github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -122,8 +120,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
-github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
-github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
+github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
 github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
 github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
@@ -134,8 +132,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 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/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -189,6 +187,7 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/protobuf v3.11.4+incompatible/go.mod h1:lUQ9D1ePzbH2PrIS7ob/bjm9HXyH5WHB0Akwh7URreM=
+github.com/google/protobuf v3.14.0+incompatible/go.mod h1:lUQ9D1ePzbH2PrIS7ob/bjm9HXyH5WHB0Akwh7URreM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
@@ -248,7 +247,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
@@ -293,6 +292,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/moby/moby v1.13.1/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
+github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -322,17 +322,20 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
 github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc=
 github.com/openconfig/gnmi v0.0.0-20200414194230-1597cc0f2600/go.mod h1:M/EcuapNQgvzxo1DDXHK4tx3QpYM/uG4l591v33jG2A=
 github.com/openconfig/gnmi v0.0.0-20200508230933-d19cebf5e7be/go.mod h1:M/EcuapNQgvzxo1DDXHK4tx3QpYM/uG4l591v33jG2A=
-github.com/openconfig/gnmi v0.0.0-20200617225440-d2b4e6a45802 h1:WXFwJlWOJINlwlyAZuNo4GdYZS6qPX36+rRUncLmN8Q=
 github.com/openconfig/gnmi v0.0.0-20200617225440-d2b4e6a45802/go.mod h1:M/EcuapNQgvzxo1DDXHK4tx3QpYM/uG4l591v33jG2A=
+github.com/openconfig/gnmi v0.0.0-20210428141518-ae4d850000ab h1:iLOhSO6bYXR+Ja9pa0HdX8ZSoAcY0EtRu1vfkLFY6+w=
+github.com/openconfig/gnmi v0.0.0-20210428141518-ae4d850000ab/go.mod h1:H/20NXlnWbCPFC593nxpiKJ+OU//7mW7s7Qk7uVdg3Q=
 github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU=
 github.com/openconfig/goyang v0.2.2/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
-github.com/openconfig/goyang v0.2.3 h1:pYxQ+VG6KNS3N5zkQeLmIBtc3gRs6JHZOKMD2/knlv4=
 github.com/openconfig/goyang v0.2.3/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
+github.com/openconfig/goyang v0.2.4 h1:xGmGr3zuhq9ASCu5jRdtMFdRnixhbg8TJEQ0nylyvxA=
+github.com/openconfig/goyang v0.2.4/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
 github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw=
 github.com/openconfig/ygot v0.6.0/go.mod h1:o30svNf7O0xK+R35tlx95odkDmZWS9JyWWQSmIhqwAs=
 github.com/openconfig/ygot v0.9.0/go.mod h1:oCQNdXnv7dWc8scTDgoFkauv1wwplJn5HspHcjlxSAQ=
-github.com/openconfig/ygot v0.10.0 h1:EmgwLXbFiCBmEUlSI4/1fPuRzgf4EsD0sThmAmRqbYM=
 github.com/openconfig/ygot v0.10.0/go.mod h1:oCQNdXnv7dWc8scTDgoFkauv1wwplJn5HspHcjlxSAQ=
+github.com/openconfig/ygot v0.10.5 h1:8RQxM/AY4bbfKAtYihWlyIEwKuLjNE1jzXI4gxPN27w=
+github.com/openconfig/ygot v0.10.5/go.mod h1:oCQNdXnv7dWc8scTDgoFkauv1wwplJn5HspHcjlxSAQ=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
@@ -379,8 +382,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
 github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
@@ -395,8 +398,9 @@ github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
 github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
+github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
+github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@@ -415,8 +419,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
@@ -432,6 +437,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
 github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -453,8 +460,8 @@ golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -484,6 +491,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -510,9 +518,12 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -525,6 +536,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -559,26 +571,30 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/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/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
+golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 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/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
-golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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=
@@ -612,6 +628,8 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
 golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -664,8 +682,10 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-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 v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+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=
@@ -684,6 +704,7 @@ gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPA
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -709,8 +730,9 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -720,24 +742,24 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.20.5 h1:zsMTffV0Le2EiI0aKvlTHEnXGxk1HiqGRhJcCPiI7JI=
-k8s.io/api v0.20.5/go.mod h1:FQjAceXnVaWDeov2YUWhOb6Yt+5UjErkp6UO3nczO1Y=
-k8s.io/apimachinery v0.20.5 h1:wO/FxMVRn223rAKxnBbwCyuN96bS9MFTIvP0e/V7cps=
-k8s.io/apimachinery v0.20.5/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
-k8s.io/client-go v0.20.5 h1:dJGtYUvFrFGjQ+GjXEIby0gZWdlAOc0xJBJqY3VyDxA=
-k8s.io/client-go v0.20.5/go.mod h1:Ee5OOMMYvlH8FCZhDsacjMlCBwetbGZETwo1OA+e6Zw=
+k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y=
+k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU=
+k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA=
+k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
+k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag=
+k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
-k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
+k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts=
+k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
+k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
 k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
 k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8=
 sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8=
+sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
 sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/mocks/PrincipalNetworkDomain.go b/mocks/PrincipalNetworkDomain.go
index 38233e76794d9913cf722aa848b2409fe4e1e499..6c795cb7388b80a8446f5c2c8314ed98fa3eba5f 100644
--- a/mocks/PrincipalNetworkDomain.go
+++ b/mocks/PrincipalNetworkDomain.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.6.0. DO NOT EDIT.
+// Code generated by mockery 2.7.4. DO NOT EDIT.
 
 package mocks
 
@@ -6,6 +6,8 @@ import (
 	mock "github.com/stretchr/testify/mock"
 
 	uuid "github.com/google/uuid"
+
+	ygot "github.com/openconfig/ygot/ygot"
 )
 
 // PrincipalNetworkDomain is an autogenerated mock type for the PrincipalNetworkDomain type
@@ -41,6 +43,71 @@ func (_m *PrincipalNetworkDomain) AddSbi(_a0 interface{}) error {
 	return r0
 }
 
+// ChangeOND provides a mock function with given fields: _a0, operation, path, value
+func (_m *PrincipalNetworkDomain) ChangeOND(_a0 uuid.UUID, operation interface{}, path string, value ...string) error {
+	_va := make([]interface{}, len(value))
+	for _i := range value {
+		_va[_i] = value[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, _a0, operation, path)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(uuid.UUID, interface{}, string, ...string) error); ok {
+		r0 = rf(_a0, operation, path, value...)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Commit provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) Commit(_a0 uuid.UUID) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(uuid.UUID) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Committed provides a mock function with given fields:
+func (_m *PrincipalNetworkDomain) Committed() []uuid.UUID {
+	ret := _m.Called()
+
+	var r0 []uuid.UUID
+	if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]uuid.UUID)
+		}
+	}
+
+	return r0
+}
+
+// Confirm provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) Confirm(_a0 uuid.UUID) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(uuid.UUID) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
 // ContainsDevice provides a mock function with given fields: _a0
 func (_m *PrincipalNetworkDomain) ContainsDevice(_a0 uuid.UUID) bool {
 	ret := _m.Called(_a0)
@@ -69,6 +136,22 @@ func (_m *PrincipalNetworkDomain) Destroy() error {
 	return r0
 }
 
+// Devices provides a mock function with given fields:
+func (_m *PrincipalNetworkDomain) Devices() []uuid.UUID {
+	ret := _m.Called()
+
+	var r0 []uuid.UUID
+	if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]uuid.UUID)
+		}
+	}
+
+	return r0
+}
+
 // GetDescription provides a mock function with given fields:
 func (_m *PrincipalNetworkDomain) GetDescription() string {
 	ret := _m.Called()
@@ -83,6 +166,29 @@ func (_m *PrincipalNetworkDomain) GetDescription() string {
 	return r0
 }
 
+// GetDevice provides a mock function with given fields: _a0
+func (_m *PrincipalNetworkDomain) GetDevice(_a0 uuid.UUID) (ygot.GoStruct, error) {
+	ret := _m.Called(_a0)
+
+	var r0 ygot.GoStruct
+	if rf, ok := ret.Get(0).(func(uuid.UUID) ygot.GoStruct); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(ygot.GoStruct)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // GetName provides a mock function with given fields:
 func (_m *PrincipalNetworkDomain) GetName() string {
 	ret := _m.Called()
@@ -113,7 +219,7 @@ func (_m *PrincipalNetworkDomain) GetSBIs() interface{} {
 	return r0
 }
 
-// Id provides a mock function with given fields:
+// ID provides a mock function with given fields:
 func (_m *PrincipalNetworkDomain) ID() uuid.UUID {
 	ret := _m.Called()
 
@@ -150,6 +256,22 @@ func (_m *PrincipalNetworkDomain) MarshalDevice(_a0 uuid.UUID) (string, error) {
 	return r0, r1
 }
 
+// Pending provides a mock function with given fields:
+func (_m *PrincipalNetworkDomain) Pending() []uuid.UUID {
+	ret := _m.Called()
+
+	var r0 []uuid.UUID
+	if rf, ok := ret.Get(0).(func() []uuid.UUID); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]uuid.UUID)
+		}
+	}
+
+	return r0
+}
+
 // RemoveDevice provides a mock function with given fields: _a0
 func (_m *PrincipalNetworkDomain) RemoveDevice(_a0 uuid.UUID) error {
 	ret := _m.Called(_a0)
diff --git a/mocks/SouthboundInterface.go b/mocks/SouthboundInterface.go
index 6217f5a7674db004f64254bfe5d5c8348ac733a7..38ffb81ff021989bb8153c33169ce40ad8863a33 100644
--- a/mocks/SouthboundInterface.go
+++ b/mocks/SouthboundInterface.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.6.0. DO NOT EDIT.
+// Code generated by mockery 2.7.4. DO NOT EDIT.
 
 package mocks
 
@@ -18,7 +18,7 @@ type SouthboundInterface struct {
 	mock.Mock
 }
 
-// Id provides a mock function with given fields:
+// ID provides a mock function with given fields:
 func (_m *SouthboundInterface) ID() uuid.UUID {
 	ret := _m.Called()
 
diff --git a/mocks/Storable.go b/mocks/Storable.go
index 630f70145d05ec4878461e243d238fe59958a483..e55a23eb41a51d2715f2ee94dfbb9e7a285a0145 100644
--- a/mocks/Storable.go
+++ b/mocks/Storable.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.6.0. DO NOT EDIT.
+// Code generated by mockery 2.7.4. DO NOT EDIT.
 
 package mocks
 
@@ -13,7 +13,7 @@ type Storable struct {
 	mock.Mock
 }
 
-// Id provides a mock function with given fields:
+// ID provides a mock function with given fields:
 func (_m *Storable) ID() uuid.UUID {
 	ret := _m.Called()
 
diff --git a/mocks/Transport.go b/mocks/Transport.go
index 6dc256acae1ee730623be6ab1fd5508f9f4ed9f5..7d8531e57334fd330a698165132428273d618f39 100644
--- a/mocks/Transport.go
+++ b/mocks/Transport.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.6.0. DO NOT EDIT.
+// Code generated by mockery 2.7.4. DO NOT EDIT.
 
 package mocks
 
@@ -76,29 +76,20 @@ func (_m *Transport) ProcessResponse(resp interface{}, root interface{}, models
 }
 
 // Set provides a mock function with given fields: ctx, params
-func (_m *Transport) Set(ctx context.Context, params ...interface{}) (interface{}, error) {
+func (_m *Transport) Set(ctx context.Context, params ...interface{}) error {
 	var _ca []interface{}
 	_ca = append(_ca, ctx)
 	_ca = append(_ca, params...)
 	ret := _m.Called(_ca...)
 
-	var r0 interface{}
-	if rf, ok := ret.Get(0).(func(context.Context, ...interface{}) interface{}); ok {
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, ...interface{}) error); ok {
 		r0 = rf(ctx, params...)
 	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(interface{})
-		}
-	}
-
-	var r1 error
-	if rf, ok := ret.Get(1).(func(context.Context, ...interface{}) error); ok {
-		r1 = rf(ctx, params...)
-	} else {
-		r1 = ret.Error(1)
+		r0 = ret.Error(0)
 	}
 
-	return r0, r1
+	return r0
 }
 
 // Subscribe provides a mock function with given fields: ctx, params
diff --git a/mocks/TransportOptions.go b/mocks/TransportOptions.go
index 4893655f8a5bc0fef06e4898e116fc3df7171421..b969fdb60e490658017a191dfb2bae818408b83a 100644
--- a/mocks/TransportOptions.go
+++ b/mocks/TransportOptions.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.6.0. DO NOT EDIT.
+// Code generated by mockery 2.7.4. DO NOT EDIT.
 
 package mocks
 
diff --git a/nucleus/controller_test.go b/nucleus/controller_test.go
index 47fa1026ccc54a35ca3296ce0779fc58af856e9b..1dc5b8bd001270ace098c39a81e027263df7869c 100644
--- a/nucleus/controller_test.go
+++ b/nucleus/controller_test.go
@@ -9,6 +9,9 @@ import (
 )
 
 func TestRun(t *testing.T) {
+	if testing.Short() {
+		t.Skip("this test is executed separately")
+	}
 	type args struct {
 		request string
 	}
diff --git a/nucleus/errors.go b/nucleus/errors.go
index 8bd01fe67150cba93b9c6f2cb5710ae63383b11c..7466b3ec10d37bf51b5f5605643882c359664f74 100644
--- a/nucleus/errors.go
+++ b/nucleus/errors.go
@@ -90,3 +90,13 @@ type ErrInvalidTransportOptions struct {
 func (e ErrInvalidTransportOptions) Error() string {
 	return fmt.Sprintf("invalid transport options: %v", reflect.TypeOf(e.t))
 }
+
+// ErrOperationNotSupported implements the Error interface and is called if the
+// wrong Operation has been provided.
+type ErrOperationNotSupported struct {
+	o interface{}
+}
+
+func (e ErrOperationNotSupported) Error() string {
+	return fmt.Sprintf("transport operation not supported: %v", reflect.TypeOf(e.o))
+}
diff --git a/nucleus/gnmi_transport.go b/nucleus/gnmi_transport.go
index 3bbcc03c6146804d862af1932f5cac2a4a0fca86..58ecadda376e4decae176cb8d6f079dc1a57727e 100644
--- a/nucleus/gnmi_transport.go
+++ b/nucleus/gnmi_transport.go
@@ -2,19 +2,21 @@ package nucleus
 
 import (
 	"code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi"
+	pathutils "code.fbi.h-da.de/cocsn/gosdn/nucleus/util/path"
 	"context"
 	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	"github.com/openconfig/gnmi/proto/gnmi_ext"
 	"github.com/openconfig/goyang/pkg/yang"
+	"github.com/openconfig/ygot/ygot"
 	"github.com/openconfig/ygot/ytypes"
 	log "github.com/sirupsen/logrus"
 	"reflect"
-	"strings"
 )
 
 // CtxKeyType is a custom type to be used as key in a context.WithValue() or
 // context.Value() call. For more information see:
 // https://www.calhoun.io/pitfalls-of-context-values-and-how-to-avoid-or-mitigate-them/
+// TODO: Unexport to comply with best practice
 type CtxKeyType string
 
 const (
@@ -22,8 +24,16 @@ const (
 	CtxKeyOpts CtxKeyType = "opts"
 	// CtxKeyConfig is a context key for gnmi.Config
 	CtxKeyConfig = "config"
+	// CtxKeyOperation is a context key for a gNMI operation (update, replace, delete)
+	CtxKeyOperation = "op"
 )
 
+var opmap = map[Operation]string{
+	TransportUpdate:  "update",
+	TransportReplace: "replace",
+	TransportDelete:  "delete",
+}
+
 // Gnmi implements the Transport interface and provides an SBI with the
 // possibility to access a gNMI endpoint.
 type Gnmi struct {
@@ -75,22 +85,55 @@ func (g *Gnmi) Get(ctx context.Context, params ...string) (interface{}, error) {
 
 // Set takes a slice of params. This slice must contain at least one operation.
 // It can contain an additional arbitrary amount of operations and extensions.
-func (g *Gnmi) Set(ctx context.Context, args ...interface{}) (interface{}, error) {
+func (g *Gnmi) Set(ctx context.Context, args ...interface{}) error {
 	if g.client == nil {
-		return nil, &ErrNilClient{}
+		return &ErrNilClient{}
 	}
 	if len(args) == 0 {
-		return nil, &ErrInvalidParameters{
+		return &ErrInvalidParameters{
 			f: "gnmi.Set()",
 			r: "no parameters provided",
 		}
 	}
 
+	if len(args) == 2 {
+		switch args[0].(type) {
+		case ygot.GoStruct:
+			return g.applyDiff(ctx, args[0])
+		default:
+
+		}
+	}
+
+	opts := make([]interface{}, 0)
+	for _, o := range args {
+		attrs, ok := o.([]string)
+		if !ok {
+			return &ErrInvalidTypeAssertion{
+				v: o,
+				t: reflect.TypeOf("placeholder"),
+			}
+		} else if attrs == nil || len(attrs) == 0 {
+			return &ErrInvalidParameters{
+				f: "gnmi.Set()",
+				r: "no parameters provided",
+			}
+		}
+		opts = append(opts, &gnmi.Operation{
+			// Hardcoded TransportUpdate until multiple operations are supported
+			Type:   opmap[TransportUpdate],
+			Origin: "",
+			Target: "",
+			Path:   gnmi.SplitPath(attrs[0]),
+			Val:    attrs[1],
+		})
+	}
+
 	// Loop over args and create ops and exts
 	// Invalid args cause unhealable error
 	ops := make([]*gnmi.Operation, 0)
 	exts := make([]*gnmi_ext.Extension, 0)
-	for _, p := range args {
+	for _, p := range opts {
 		switch p.(type) {
 		case *gnmi.Operation:
 			op := p.(*gnmi.Operation)
@@ -101,19 +144,66 @@ func (g *Gnmi) Set(ctx context.Context, args ...interface{}) (interface{}, error
 		case *gnmi_ext.Extension:
 			exts = append(exts, p.(*gnmi_ext.Extension))
 		default:
-			return nil, &ErrInvalidParameters{
+			return &ErrInvalidParameters{
 				f: "gnmi.Set()",
 				r: "args contain invalid type",
 			}
 		}
 	}
 	if len(ops) == 0 {
-		return nil, &ErrInvalidParameters{
+		return &ErrInvalidParameters{
 			f: "gnmi.Set()",
 			r: "no operations provided",
 		}
 	}
-	return g.set(ctx, ops, exts...)
+	resp, err := g.set(ctx, ops, exts...)
+	if err != nil {
+		return err
+	}
+	log.Info(resp)
+	return nil
+}
+
+func (g *Gnmi) applyDiff(ctx context.Context, payload ...interface{}) error {
+	if len(payload) != 2 {
+		return &ErrInvalidParameters{}
+	}
+	op := ctx.Value(CtxKeyOperation)
+	oldstate, ok := payload[0].(ygot.GoStruct)
+	if !ok {
+		return &ErrInvalidTypeAssertion{
+			v: payload[0],
+			t: reflect.TypeOf("ygot.GoStruct"),
+		}
+	}
+	newstate, ok := payload[1].(ygot.GoStruct)
+	if !ok {
+		return &ErrInvalidTypeAssertion{
+			v: payload[1],
+			t: reflect.TypeOf("ygot.GoStruct"),
+		}
+	}
+
+	diff, err := ygot.Diff(oldstate, newstate)
+	if err != nil {
+		return err
+	}
+	req := &gpb.SetRequest{}
+	if diff.Update != nil {
+		switch op {
+		case TransportUpdate:
+			req.Update = diff.Update
+		case TransportReplace:
+			req.Replace = diff.Update
+		default:
+			return &ErrOperationNotSupported{}
+		}
+	} else if diff.Delete != nil {
+		req.Delete = diff.Delete
+	}
+	resp, err := g.client.Set(ctx, req)
+	log.Info(resp)
+	return err
 }
 
 //Subscribe subscribes to a gNMI target
@@ -141,7 +231,7 @@ func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Sch
 			val, ok := update.Val.Value.(*gpb.TypedValue_JsonIetfVal)
 			if ok {
 				opts := []ytypes.UnmarshalOpt{&ytypes.IgnoreExtraFields{}}
-				if err := g.Unmarshal(val.JsonIetfVal, extraxtPathElements(fullPath), root, opts...); err != nil {
+				if err := g.Unmarshal(val.JsonIetfVal, pathutils.ToStrings(fullPath), root, opts...); err != nil {
 					return err
 				}
 				return nil
@@ -157,14 +247,6 @@ func (g *Gnmi) ProcessResponse(resp interface{}, root interface{}, s *ytypes.Sch
 	return nil
 }
 
-func extraxtPathElements(path *gpb.Path) []string {
-	elems := make([]string, len(path.Elem))
-	for i, e := range path.Elem {
-		elems[i] = strings.Title(e.Name)
-	}
-	return elems
-}
-
 // Capabilities calls GNMI capabilities
 func (g *Gnmi) Capabilities(ctx context.Context) (interface{}, error) {
 	log.WithFields(log.Fields{
diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go
index 38270c8da5fe3f31f030c5010de2b980a8d8d127..18627bd9349ab7d740a80bd5472c7eda045632bf 100644
--- a/nucleus/gnmi_transport_test.go
+++ b/nucleus/gnmi_transport_test.go
@@ -276,7 +276,6 @@ func TestGnmi_Set(t *testing.T) {
 		name    string
 		fields  fields
 		args    args
-		want    interface{}
 		wantErr bool
 	}{
 		{
@@ -285,20 +284,15 @@ func TestGnmi_Set(t *testing.T) {
 			args: args{
 				params: nil,
 			},
-			want:    nil,
 			wantErr: true,
 		},
 		// TODO: Positive test cases
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.fields.transport.Set(context.Background(), tt.args.params...)
+			err := tt.fields.transport.Set(context.Background(), tt.args.params...)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Set() got = %v, want %v", got, tt.want)
 			}
 		})
 	}
diff --git a/nucleus/http.go b/nucleus/http.go
index 56ad4cb0af707662e72656e1a7b6a4fa0a88f046..ee0590e4e5645ff665417c9be7ec3d6b1542d220 100644
--- a/nucleus/http.go
+++ b/nucleus/http.go
@@ -7,11 +7,18 @@ import (
 	"github.com/google/uuid"
 	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	log "github.com/sirupsen/logrus"
+	"io"
 	"net/http"
 	"net/url"
 	"time"
 )
 
+var apiOpmap = map[string]Operation{
+	"update":  TransportUpdate,
+	"replace": TransportReplace,
+	"delete":  TransportDelete,
+}
+
 func stopHttpServer() error {
 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()
@@ -61,7 +68,6 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) {
 		return
 	}
 
-
 	id, err := uuid.Parse(query.Get("uuid"))
 	if err != nil {
 		if err.Error() != "invalid UUID length: 0" {
@@ -88,23 +94,18 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) {
 	if query.Get("q") != "init" && query.Get("q") != "getIDs" {
 		pnd, err = c.pndc.get(pid)
 		if err != nil {
-			log.Error(err)
-			writer.WriteHeader(http.StatusInternalServerError)
+			handleServerError(writer, err)
 			return
 		}
 		sbic := pnd.GetSBIs()
 		sbi, err = sbic.(*sbiStore).get(sid)
 		if err != nil {
-			log.WithFields(log.Fields{
-				"requested uuid":  sid,
-				"available uuids": sbic.(*sbiStore).UUIDs(),
-			}).Error(err)
-			writer.WriteHeader(http.StatusInternalServerError)
+			handleServerError(writer, err)
 			return
 		}
 	}
 
-	switch query.Get("q") {
+	switch q := query.Get("q"); q {
 	case "addDevice":
 		d, err := NewDevice(sbi, &GnmiTransportOptions{
 			Config: gnmi.Config{
@@ -167,42 +168,75 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) {
 		writer.Header().Set("Content-Type", "application/json")
 		fmt.Fprintf(writer, "%v", device)
 	case "getIDs":
-		writeIDs := func(typ string, ids []uuid.UUID) {
-			fmt.Fprintf(writer, "%v:\n", typ)
-			for i, id := range ids {
-				fmt.Fprintf(writer, "%v: %v\n", i+1, id)
-			}
-		}
+
 		pnds := c.pndc.UUIDs()
-		writeIDs("PNDs", pnds)
-		writeIDs("SBIs", c.sbic.UUIDs())
-		for _,id := range pnds{
+		writeIDs(writer, "PNDs", pnds)
+		writeIDs(writer, "SBIs", c.sbic.UUIDs())
+		for _, id := range pnds {
 			p, err := c.pndc.get(id)
 			if err != nil {
-				writer.WriteHeader(http.StatusInternalServerError)
-				log.Error(err)
+				handleServerError(writer, err)
 				return
 			}
-			writeIDs("Devices", p.(*pndImplementation).devices.UUIDs())
+			writeIDs(writer, "Devices", p.Devices())
 		}
 	case "init":
-		writeIDs := func(typ string, ids []uuid.UUID) {
-			for _, id := range ids {
-				fmt.Fprintf(writer, "%v", id)
-			}
+		writeIDs(writer, "PNDs", c.pndc.UUIDs())
+		writeIDs(writer, "SBIs", c.sbic.UUIDs())
+	case "update", "replace":
+		if err := pnd.ChangeOND(id, apiOpmap[q], query.Get("path"), query.Get("value")); err != nil {
+			handleServerError(writer, err)
+			return
 		}
-		writeIDs("PNDs", c.pndc.UUIDs())
-		writeIDs("SBIs", c.sbic.UUIDs())
-	case "set":
-		resp, err := pnd.(*pndImplementation).Set(id, query.Get("path"), query.Get("value"))
-		if err != nil {
-			writer.WriteHeader(http.StatusInternalServerError)
-			log.Error(err)
+		writer.WriteHeader(http.StatusOK)
+	case "delete":
+		if err := pnd.ChangeOND(id, TransportDelete, query.Get("path")); err != nil {
+			handleServerError(writer, err)
 			return
 		}
 		writer.WriteHeader(http.StatusOK)
-		fmt.Fprintln(writer, resp)
+	case "change-list":
+		changes := pnd.Committed()
+		writeIDs(writer, "Tentative changes", changes)
+	case "change-list-pending":
+		changes := pnd.Pending()
+		writeIDs(writer, "Pending changes", changes)
+	case "change-commit":
+		cuid, err := uuid.Parse(query.Get("cuid"))
+		if err != nil {
+			handleServerError(writer, err)
+			return
+		}
+		if err := pnd.Commit(cuid); err != nil {
+			handleServerError(writer, err)
+			return
+		}
+		writer.WriteHeader(http.StatusAccepted)
+	case "change-confirm":
+		cuid, err := uuid.Parse(query.Get("cuid"))
+		if err != nil {
+			handleServerError(writer, err)
+			return
+		}
+		if err := pnd.Confirm(cuid); err != nil {
+			handleServerError(writer, err)
+			return
+		}
+		writer.WriteHeader(http.StatusAccepted)
 	default:
 		writer.WriteHeader(http.StatusBadRequest)
 	}
 }
+
+func writeIDs(w io.Writer, typ string, ids []uuid.UUID) {
+	fmt.Fprintf(w, "%v:\n", typ)
+	for i, id := range ids {
+		fmt.Fprintf(w, "%v: %v\n", i+1, id)
+	}
+}
+
+func handleServerError(w http.ResponseWriter, err error) {
+	w.WriteHeader(http.StatusInternalServerError)
+	fmt.Fprintf(w, "error: %v", err)
+	log.Error(err)
+}
diff --git a/nucleus/http_test.go b/nucleus/http_test.go
index 9efa72110a1fe89cf1507424d147e7faac6fd71b..aad389b3ede8f378369b7fb59badc0aee26ac34a 100644
--- a/nucleus/http_test.go
+++ b/nucleus/http_test.go
@@ -14,18 +14,18 @@ func testSetupHTTP() {
 	sbi = &OpenConfig{id: defaultSbiID}
 	sbi.Schema()
 	var err error
-	pnd, err = NewPND("test", "test pnd", defaultPndID, sbi)
+	httpTestPND, err = NewPND("test", "test pnd", defaultPndID, sbi)
 	if err != nil {
 		log.Fatal(err)
 	}
-	d = mockDevice()
-	tr := d.Transport.(*mocks.Transport)
+	httpTestDevice = mockDevice()
+	tr := httpTestDevice.Transport.(*mocks.Transport)
 	mockError := errors.New("mock error")
 	tr.On("Get", mockContext, "/system/config/hostname").Return(mock.Anything, nil)
 	tr.On("Get", mockContext, "error").Return(mock.Anything, mockError)
 	tr.On("Set", mockContext, mock.Anything).Return(mock.Anything, nil)
 	tr.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(nil)
-	if err := pnd.AddDevice(&d); err != nil {
+	if err := httpTestPND.AddDevice(&httpTestDevice); err != nil {
 		log.Fatal(err)
 	}
 	args = "&uuid=" + mdid.String() + "&pnd=" + defaultPndID.String() + "&sbi=" + defaultSbiID.String()
@@ -33,15 +33,12 @@ func testSetupHTTP() {
 	if err := c.sbic.add(sbi); err != nil {
 		log.Fatal(err)
 	}
-	if err := c.pndc.add(pnd); err != nil {
+	if err := c.pndc.add(httpTestPND); err != nil {
 		log.Fatal(err)
 	}
 }
 
 func Test_httpApi(t *testing.T) {
-	if testing.Short() {
-		t.Skip("this test is executed separately")
-	}
 	tests := []struct {
 		name    string
 		request string
@@ -123,10 +120,54 @@ func Test_httpApi(t *testing.T) {
 		},
 		{
 			name:    "set",
-			request: apiEndpoint + "/api?q=set" + args + "&path=/system/config/hostname&value=ceos3000",
+			request: apiEndpoint + "/api?q=update" + args + "&path=/system/config/hostname&value=ceos3000",
+			want:    &http.Response{StatusCode: http.StatusOK},
+			wantErr: false,
+		},
+		{
+			name:    "replace",
+			request: apiEndpoint + "/api?q=replace" + args + "&path=/system/config/hostname&value=ceos3000",
+			want:    &http.Response{StatusCode: http.StatusOK},
+			wantErr: false,
+		},
+		{
+			name:    "delete",
+			request: apiEndpoint + "/api?q=delete" + args + "&path=/system/config/hostname",
+			want:    &http.Response{StatusCode: http.StatusOK},
+			wantErr: false,
+		},
+		{
+			name:    "change list",
+			request: apiEndpoint + "/api?q=change-list" + args + "&path=/system/config/hostname",
+			want:    &http.Response{StatusCode: http.StatusOK},
+			wantErr: false,
+		},
+		{
+			name:    "change list pending",
+			request: apiEndpoint + "/api?q=change-list-pending" + args + "&path=/system/config/hostname",
 			want:    &http.Response{StatusCode: http.StatusOK},
 			wantErr: false,
 		},
+		{
+			name:    "change commit",
+			request: apiEndpoint + "/api?q=change-commit" + args + "&cuid=" + cuid.String(),
+			// TODO: Mock Change for testing
+			want:    &http.Response{StatusCode: http.StatusInternalServerError},
+			wantErr: false,
+		},
+		{
+			name:    "change confirm",
+			request: apiEndpoint + "/api?q=change-confirm" + args + "&cuid=" + cuid.String(),
+			// TODO: Mock Change for testing
+			want:    &http.Response{StatusCode: http.StatusInternalServerError},
+			wantErr: false,
+		},
+		{
+			name:    "bad request",
+			request: apiEndpoint + "/api?q=bad-request" + args,
+			want:    &http.Response{StatusCode: http.StatusBadRequest},
+			wantErr: false,
+		},
 		{
 			name:    "internal server errror: wrong pnd",
 			request: apiEndpoint + "/api?pnd=" + uuid.New().String(),
@@ -157,9 +198,9 @@ func Test_httpApi(t *testing.T) {
 				t.Errorf("httpApi() got: %v, want %v", got.StatusCode, tt.want.StatusCode)
 			}
 			if tt.name == "add-device" {
-				for k := range pnd.(*pndImplementation).devices.store {
+				for k := range httpTestPND.(*pndImplementation).devices.store {
 					if k != mdid {
-						if err := pnd.RemoveDevice(k); err != nil {
+						if err := httpTestPND.RemoveDevice(k); err != nil {
 							t.Error(err)
 							return
 						}
diff --git a/nucleus/initialise_test.go b/nucleus/initialise_test.go
index 6c90b054e44d009e03542821ced09f46cd65bb49..6d66325fe3ed7060b3ac780f272cbcb7015c4245 100644
--- a/nucleus/initialise_test.go
+++ b/nucleus/initialise_test.go
@@ -25,12 +25,13 @@ var defaultPndID uuid.UUID
 var ocUUID uuid.UUID
 var iid uuid.UUID
 var altIid uuid.UUID
+var cuid uuid.UUID
 
 var sbi SouthboundInterface
-var pnd PrincipalNetworkDomain
+var httpTestPND PrincipalNetworkDomain
 var gnmiMessages map[string]pb.Message
 var gnmiConfig *gnmi.Config
-var d Device
+var httpTestDevice Device
 
 var startGnmiTarget chan string
 var stopGnmiTarget chan bool
@@ -108,51 +109,38 @@ func newGnmiTransportOptions() *GnmiTransportOptions {
 func readTestUUIDs() {
 	var err error
 	did, err = uuid.Parse("4d8246f8-e884-41d6-87f5-c2c784df9e44")
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	mdid, err = uuid.Parse("688a264e-5f85-40f8-bd13-afc42fcd5c7a")
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	defaultSbiID, err = uuid.Parse("b70c8425-68c7-4d4b-bb5e-5586572bd64b")
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	defaultPndID, err = uuid.Parse("b4016412-eec5-45a1-aa29-f59915357bad")
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	ocUUID, err = uuid.Parse("5e252b70-38f2-4c99-a0bf-1b16af4d7e67")
-	if err != nil {
-		log.Fatal(err)
-	}
 	iid, err = uuid.Parse("8495a8ac-a1e8-418e-b787-10f5878b2690")
 	altIid, err = uuid.Parse("edc5de93-2d15-4586-b2a7-fb1bc770986b")
+	cuid, err = uuid.Parse("3e8219b0-e926-400d-8660-217f2a25a7c6")
 	if err != nil {
 		log.Fatal(err)
 	}
 }
 
 func mockDevice() Device {
+	sbi := &OpenConfig{}
 	return Device{
 		UUID:      mdid,
-		GoStruct:  nil,
-		SBI:       &OpenConfig{},
+		GoStruct:  sbi.Schema().Root,
+		SBI:       sbi,
 		Transport: &mocks.Transport{},
 	}
 }
 
 func newPnd() pndImplementation {
 	return pndImplementation{
-		name:        "default",
-		description: "default test pnd",
-		sbic:        sbiStore{store{}},
-		devices:     deviceStore{store{}},
-		id:          defaultPndID,
+		name:             "default",
+		description:      "default test pnd",
+		sbic:             sbiStore{store{}},
+		devices:          deviceStore{store{}},
+		pendingChanges:   changeStore{store{}},
+		committedChanges: changeStore{store{}},
+		confirmedChanges: changeStore{store{}},
+		id:               defaultPndID,
+		errChans:         make(map[uuid.UUID]chan error),
 	}
 }
diff --git a/nucleus/pnd/change.go b/nucleus/pnd/change.go
new file mode 100644
index 0000000000000000000000000000000000000000..55c7fc539f515d4383a6e9b5783986ed6feac270
--- /dev/null
+++ b/nucleus/pnd/change.go
@@ -0,0 +1,133 @@
+package pnd
+
+import (
+	"errors"
+	"github.com/google/uuid"
+	"github.com/openconfig/ygot/ygot"
+	log "github.com/sirupsen/logrus"
+	"golang.org/x/net/context"
+	"os"
+	"sync"
+	"time"
+)
+
+var changeTimeout time.Duration
+
+func init() {
+	var err error
+	e := os.Getenv("GOSDN_CHANGE_TIMEOUT")
+	if e != "" {
+		changeTimeout, err = time.ParseDuration(e)
+		if err != nil {
+			log.Fatal(err)
+		}
+	} else {
+		changeTimeout, err = time.ParseDuration("10m")
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+	log.Debugf("change timeout set to %v", changeTimeout)
+}
+
+// NewChange takes a Device UUID, a pair GoStructs (current and intended state)
+// a callback function and a channel for errors and returns a *Change
+// The callback function is used by the Commit() and Confirm() functions. It
+// must define how the change is carried out.
+func NewChange(device uuid.UUID, currentState ygot.GoStruct, change ygot.GoStruct, callback func(ygot.GoStruct, ygot.GoStruct) error, errChan chan error) *Change {
+	return &Change{
+		cuid:          uuid.New(),
+		duid:          device,
+		timestamp:     time.Now(),
+		previousState: currentState,
+		intendedState: change,
+		committed:     false,
+		confirmed:     false,
+		callback:      callback,
+		errChan:       errChan,
+		Done:          make(chan int),
+	}
+}
+
+// Change is an intended change to an OND. It is unique and immutable.
+// It has a cuid, a timestamp, and holds both the previous and the new
+// state. It keeps track if the state is committed and confirmed. A callback
+// exists to acess the proper transport for the changed OND
+type Change struct {
+	cuid          uuid.UUID
+	duid          uuid.UUID
+	timestamp     time.Time
+	previousState ygot.GoStruct
+	intendedState ygot.GoStruct
+	committed     bool
+	confirmed     bool
+	inconsistent  bool
+	callback      func(ygot.GoStruct, ygot.GoStruct) error
+	lock          sync.RWMutex
+	cancelFunc    context.CancelFunc
+	errChan       chan error
+	// TODO: Move nucleus.pndImplementation and Change to same package and unexport
+	Done chan int
+}
+
+// ID returns the Change's UUID
+func (c *Change) ID() uuid.UUID {
+	return c.cuid
+}
+
+// Commit pushes the cange to the OND using the callback() function
+// and starts the timeout-timer for the Change. If the timer expires
+// the change is rolled back.
+func (c *Change) Commit() error {
+	if err := c.callback(c.intendedState, c.previousState); err != nil {
+		return err
+	}
+	c.committed = true
+	log.WithFields(log.Fields{
+		"change uuid": c.cuid,
+		"device uuid": c.duid,
+	}).Debug("change commited")
+	ctx, cancel := context.WithCancel(context.Background())
+	c.cancelFunc = cancel
+	go c.rollbackHandler(ctx)
+	return nil
+}
+
+func (c *Change) rollbackHandler(ctx context.Context) {
+	select {
+	case <-ctx.Done():
+		return
+	case <-time.Tick(changeTimeout):
+		c.lock.RLock()
+		defer c.lock.RUnlock()
+		if !c.confirmed {
+			c.errChan <- c.callback(c.previousState, c.intendedState)
+			log.WithFields(log.Fields{
+				"change uuid": c.cuid,
+				"device uuid": c.duid,
+			}).Info("change timed out")
+		}
+	}
+}
+
+// Confirm confirms a committed Change and stops the rollback timer.
+func (c *Change) Confirm() error {
+	c.lock.RLock()
+	if !c.committed {
+		defer c.lock.RUnlock()
+		return errors.New("cannot confirm uncommitted change")
+	}
+	c.lock.RUnlock()
+	c.lock.Lock()
+	defer c.lock.Unlock()
+	c.confirmed = true
+	c.cancelFunc()
+	close(c.errChan)
+	c.Done <- 0
+	close(c.Done)
+	log.WithFields(log.Fields{
+		"change uuid": c.cuid,
+		"device uuid": c.duid,
+	}).Info("change confirmed")
+	return nil
+}
diff --git a/nucleus/pnd/change_test.go b/nucleus/pnd/change_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7871a0db04a1527246df36a23b0ad83d264d10ef
--- /dev/null
+++ b/nucleus/pnd/change_test.go
@@ -0,0 +1,244 @@
+package pnd
+
+import (
+	"context"
+	"errors"
+	"github.com/google/uuid"
+	"github.com/openconfig/ygot/exampleoc"
+	"github.com/openconfig/ygot/ygot"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+)
+
+var commit = "commit"
+var rollback = "rollback"
+
+var commitDevice = &exampleoc.Device{
+	System: &exampleoc.System{
+		Hostname: &commit,
+	},
+}
+
+var rollbackDevice = &exampleoc.Device{
+	System: &exampleoc.System{
+		Hostname: &rollback,
+	},
+}
+
+func TestChange_CommitRollback(t *testing.T) {
+	wantErr := false
+	want := rollback
+	callback := make(chan string)
+	c := &Change{
+		cuid:          changeUUID,
+		duid:          did,
+		timestamp:     time.Now(),
+		previousState: rollbackDevice,
+		intendedState: commitDevice,
+		callback: func(first ygot.GoStruct, second ygot.GoStruct) error {
+			hostname := *first.(*exampleoc.Device).System.Hostname
+			t.Logf("hostname: %v", hostname)
+			switch hostname {
+			case rollback:
+				callback <- rollback
+			}
+			return nil
+		},
+		lock: sync.RWMutex{},
+	}
+	go func() {
+		time.Sleep(time.Millisecond * 10)
+		if err := c.Commit(); (err != nil) != wantErr {
+			t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
+		}
+		time.Sleep(changeTimeout)
+	}()
+	got := <-callback
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Commit() = %v, want %v", got, want)
+	}
+	close(callback)
+}
+
+func TestChange_CommitRollbackError(t *testing.T) {
+	wantErr := false
+	want := errors.New("this is an expected error")
+	c := &Change{
+		cuid:          changeUUID,
+		duid:          did,
+		timestamp:     time.Now(),
+		previousState: rollbackDevice,
+		intendedState: commitDevice,
+		callback: func(first ygot.GoStruct, second ygot.GoStruct) error {
+			hostname := *first.(*exampleoc.Device).System.Hostname
+			t.Logf("hostname: %v", hostname)
+			switch hostname {
+			case rollback:
+				return errors.New("this is an expected error")
+			}
+			return nil
+		},
+		lock:    sync.RWMutex{},
+		errChan: make(chan error),
+	}
+	go func() {
+		time.Sleep(time.Millisecond * 10)
+		if err := c.Commit(); (err != nil) != wantErr {
+			t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
+		}
+		time.Sleep(changeTimeout)
+	}()
+	got := <-c.errChan
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Commit() = %v, want %v", got, want)
+	}
+	close(c.errChan)
+}
+
+func TestChange_CommitError(t *testing.T) {
+	wantErr := true
+	c := &Change{
+		cuid:          changeUUID,
+		duid:          did,
+		timestamp:     time.Now(),
+		previousState: rollbackDevice,
+		intendedState: commitDevice,
+		callback: func(first ygot.GoStruct, second ygot.GoStruct) error {
+			return errors.New("this is an expected error")
+		},
+		lock: sync.RWMutex{},
+	}
+	go func() {
+		time.Sleep(time.Millisecond * 10)
+		if err := c.Commit(); (err != nil) != wantErr {
+			t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
+		}
+	}()
+	got := c.committed
+	if !reflect.DeepEqual(got, false) {
+		t.Errorf("Commit() = %v, want %v", got, false)
+	}
+}
+
+func TestChange_Commit(t *testing.T) {
+	wantErr := false
+	want := commit
+	callback := make(chan string)
+
+	c := &Change{
+		cuid:          changeUUID,
+		duid:          did,
+		timestamp:     time.Now(),
+		previousState: rollbackDevice,
+		intendedState: commitDevice,
+		callback: func(first ygot.GoStruct, second ygot.GoStruct) error {
+			hostname := *first.(*exampleoc.Device).System.Hostname
+			t.Logf("hostname: %v", hostname)
+			callback <- hostname
+			return nil
+		},
+		lock:    sync.RWMutex{},
+		errChan: make(chan error),
+		Done:    make(chan int),
+	}
+	go func() {
+		time.Sleep(time.Millisecond * 10)
+		if err := c.Commit(); (err != nil) != wantErr {
+			t.Errorf("Commit() error = %v, wantErr %v", err, wantErr)
+		}
+		if err := c.Confirm(); err != nil {
+			t.Errorf("Commit() error = %v", err)
+		}
+	}()
+	got := <-callback
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Commit() = %v, want %v", got, want)
+	}
+	close(callback)
+}
+
+func TestChange_Confirm(t *testing.T) {
+	_, cancel := context.WithCancel(context.Background())
+	type fields struct {
+		cuid          uuid.UUID
+		duid          uuid.UUID
+		timestamp     time.Time
+		previousState ygot.GoStruct
+		intendedState ygot.GoStruct
+		callback      func(ygot.GoStruct, ygot.GoStruct) error
+		committed     bool
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		wantErr bool
+	}{
+		{
+			name: "committed",
+			fields: fields{
+				committed: true,
+			},
+			wantErr: false,
+		},
+		{
+			name:    "uncommitted",
+			fields:  fields{},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c := &Change{
+				committed: tt.fields.committed,
+				timestamp: tt.fields.timestamp,
+				previousState: &exampleoc.Device{
+					System: &exampleoc.System{
+						Hostname: &rollback,
+					},
+				},
+				intendedState: &exampleoc.Device{
+					System: &exampleoc.System{
+						Hostname: &commit,
+					},
+				},
+				cancelFunc: cancel,
+				lock:       sync.RWMutex{},
+				errChan:    make(chan error),
+				Done:       make(chan int, 1),
+			}
+			if err := c.Confirm(); (err != nil) != tt.wantErr {
+				t.Errorf("Confirm() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+	cancel()
+}
+
+func TestChange_ID(t *testing.T) {
+	type fields struct {
+		cuid uuid.UUID
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   uuid.UUID
+	}{
+		{
+			name:   "default",
+			fields: fields{cuid: changeUUID},
+			want:   changeUUID,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c := &Change{
+				cuid: tt.fields.cuid,
+			}
+			if got := c.ID(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ID() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/nucleus/pnd/initialise_test.go b/nucleus/pnd/initialise_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..674656c8825327bac8c34ef84a8db6548ea2baaf
--- /dev/null
+++ b/nucleus/pnd/initialise_test.go
@@ -0,0 +1,22 @@
+package pnd
+
+import (
+	"github.com/google/uuid"
+	log "github.com/sirupsen/logrus"
+	"os"
+	"testing"
+)
+
+// UUIDs for test cases
+var changeUUID uuid.UUID
+var did uuid.UUID
+
+func TestMain(m *testing.M) {
+	var err error
+	changeUUID, err = uuid.Parse("cfbb96cd-ecad-45d1-bebf-1851760f5087")
+	did, err = uuid.Parse("4d8246f8-e884-41d6-87f5-c2c784df9e44")
+	if err != nil {
+		log.Fatal(err)
+	}
+	os.Exit(m.Run())
+}
diff --git a/nucleus/principalNetworkDomain.go b/nucleus/principalNetworkDomain.go
index 88c08172e2f4c859d1a8212328a20680953e3054..0a1e69788165da250a97bba5c2a36451ee49248b 100644
--- a/nucleus/principalNetworkDomain.go
+++ b/nucleus/principalNetworkDomain.go
@@ -2,7 +2,10 @@ package nucleus
 
 import (
 	"code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi"
+	. "code.fbi.h-da.de/cocsn/gosdn/nucleus/pnd" //nolint
 	"context"
+	"github.com/openconfig/ygot/ygot"
+	"github.com/openconfig/ygot/ytypes"
 	log "github.com/sirupsen/logrus"
 
 	"encoding/json"
@@ -16,7 +19,10 @@ type PrincipalNetworkDomain interface {
 	AddSbi(interface{}) error
 	RemoveSbi(uuid.UUID) error
 	AddDevice(interface{}) error
+	GetDevice(uuid uuid.UUID) (ygot.GoStruct, error)
 	RemoveDevice(uuid.UUID) error
+	Devices() []uuid.UUID
+	ChangeOND(uuid uuid.UUID, operation interface{}, path string, value ...string) error
 	Request(uuid.UUID, string) error
 	RequestAll(string) error
 	GetName() string
@@ -25,24 +31,24 @@ type PrincipalNetworkDomain interface {
 	ContainsDevice(uuid.UUID) bool
 	GetSBIs() interface{}
 	ID() uuid.UUID
-}
-
-type pndImplementation struct {
-	name        string
-	description string
-	sbic        sbiStore
-	devices     deviceStore
-	id          uuid.UUID
+	Pending() []uuid.UUID
+	Committed() []uuid.UUID
+	Commit(uuid.UUID) error
+	Confirm(uuid.UUID) error
 }
 
 // NewPND creates a Principle Network Domain
 func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (PrincipalNetworkDomain, error) {
 	pnd := &pndImplementation{
-		name:        name,
-		description: description,
-		sbic:        sbiStore{store{}},
-		devices:     deviceStore{store{}},
-		id:          id,
+		name:             name,
+		description:      description,
+		sbic:             sbiStore{store{}},
+		devices:          deviceStore{store{}},
+		pendingChanges:   changeStore{store{}},
+		committedChanges: changeStore{store{}},
+		confirmedChanges: changeStore{store{}},
+		id:               id,
+		errChans:         make(map[uuid.UUID]chan error),
 	}
 	if err := pnd.sbic.add(sbi); err != nil {
 		return nil, err
@@ -50,10 +56,73 @@ func NewPND(name, description string, id uuid.UUID, sbi SouthboundInterface) (Pr
 	return pnd, nil
 }
 
+type pndImplementation struct {
+	name             string
+	description      string
+	sbic             sbiStore
+	devices          deviceStore
+	pendingChanges   changeStore
+	committedChanges changeStore
+	confirmedChanges changeStore
+	id               uuid.UUID
+	errChans         map[uuid.UUID]chan error
+}
+
+func (pnd *pndImplementation) Pending() []uuid.UUID {
+	return pnd.pendingChanges.UUIDs()
+}
+
+func (pnd *pndImplementation) Committed() []uuid.UUID {
+	return pnd.committedChanges.UUIDs()
+}
+
+func (pnd *pndImplementation) Commit(u uuid.UUID) error {
+	change, err := pnd.pendingChanges.get(u)
+	if err != nil {
+		return err
+	}
+	if err := change.Commit(); err != nil {
+		return err
+	}
+	go func() {
+		for {
+			select {
+			case err := <-pnd.errChans[u]:
+				if err != nil {
+					handleRollbackError(change.ID(), err)
+				}
+			case <-change.Done:
+			}
+		}
+	}()
+	if err := pnd.committedChanges.add(change); err != nil {
+		return err
+	}
+	return pnd.pendingChanges.delete(u)
+}
+
+func (pnd *pndImplementation) Confirm(u uuid.UUID) error {
+	change, err := pnd.committedChanges.get(u)
+	if err != nil {
+		return err
+	}
+	if err := change.Confirm(); err != nil {
+		return err
+	}
+	if err := pnd.confirmedChanges.add(change); err != nil {
+		return err
+	}
+	return pnd.committedChanges.delete(u)
+}
+
 func (pnd *pndImplementation) ID() uuid.UUID {
 	return pnd.id
 }
 
+func (pnd *pndImplementation) Devices() []uuid.UUID {
+	return pnd.devices.UUIDs()
+}
+
 // GetName returns the name of the PND
 func (pnd *pndImplementation) GetName() string {
 	return pnd.name
@@ -91,7 +160,7 @@ func (pnd *pndImplementation) AddSbi(sbi interface{}) error {
 	return pnd.addSbi(s)
 }
 
-// AddSbi removes a SBI from the PND
+// RemoveSbi removes a SBI from the PND
 // TODO: this should to recursivly through
 // devices and remove the devices using
 // this SBI
@@ -111,6 +180,14 @@ func (pnd *pndImplementation) AddDevice(device interface{}) error {
 	return pnd.addDevice(d)
 }
 
+func (pnd *pndImplementation) GetDevice(uuid uuid.UUID) (ygot.GoStruct, error) {
+	d, err := pnd.devices.get(uuid)
+	if err != nil {
+		return nil, err
+	}
+	return ygot.DeepCopy(d.GoStruct)
+}
+
 // RemoveDevice removes a device from the PND
 func (pnd *pndImplementation) RemoveDevice(uuid uuid.UUID) error {
 	return pnd.removeDevice(uuid)
@@ -190,24 +267,54 @@ func (pnd *pndImplementation) RequestAll(path string) error {
 	return nil
 }
 
-// Set sets the value to the given device path
-// TODO: Design commit/confirm mechanism
-func (pnd *pndImplementation) Set(uuid uuid.UUID, path string, value string) (interface{}, error) {
+// ChangeOND creates a change from the provided Operation, path and value. The Change is pending and
+func (pnd *pndImplementation) ChangeOND(uuid uuid.UUID, operation interface{}, path string, value ...string) error {
 	d, err := pnd.getDevice(uuid)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	ctx := context.Background()
+	cpy, err := ygot.DeepCopy(d.GoStruct)
+	ygot.BuildEmptyTree(cpy)
+
+	p, err := ygot.StringToStructuredPath(path)
+	if err != nil {
+		return err
+	}
+
+	if operation != TransportDelete && len(value) != 1 {
+		return &ErrInvalidParameters{
+			f: pnd.ChangeOND,
+			r: value,
+		}
+	}
+
+	switch operation {
+	case TransportUpdate, TransportReplace:
+		typedValue := gnmi.TypedValue(value[0])
+		if err := ytypes.SetNode(d.SBI.Schema().RootSchema(), cpy, p, typedValue); err != nil {
+			return err
+		}
+	case TransportDelete:
+		if err := ytypes.DeleteNode(d.SBI.Schema().RootSchema(), cpy, p); err != nil {
+			return err
+		}
+	default:
+		return &ErrOperationNotSupported{o: operation}
+	}
+
+	callback := func(state ygot.GoStruct, change ygot.GoStruct) error {
+		ctx := context.Background()
+		return d.Transport.Set(ctx, state, change)
+	}
+
+	errChan := make(chan error)
+	change := NewChange(uuid, d.GoStruct, cpy, callback, errChan)
+	pnd.errChans[change.ID()] = errChan
+
+	return pnd.pendingChanges.add(change)
+}
 
-	// TODO: Move to transport dependent func
-	opts := []interface{}{
-		&gnmi.Operation{
-			Type:   "update",
-			Origin: "",
-			Target: "",
-			Path:   gnmi.SplitPath(path),
-			Val:    value,
-		},
-	}
-	return d.Transport.Set(ctx, opts...)
+func handleRollbackError(id uuid.UUID, err error) {
+	log.Error(err)
+	// TODO: Notion of invalid state needed.
 }
diff --git a/nucleus/principalNetworkDomain_test.go b/nucleus/principalNetworkDomain_test.go
index f02f840bc948d4887983a4beb532808d9e8dc2c7..6ff0f9327387933ef5dbfe92a2fb944182b7aa87 100644
--- a/nucleus/principalNetworkDomain_test.go
+++ b/nucleus/principalNetworkDomain_test.go
@@ -5,6 +5,7 @@ import (
 	"code.fbi.h-da.de/cocsn/yang-models/generated/openconfig"
 	"errors"
 	"github.com/google/uuid"
+	"github.com/openconfig/ygot/ygot"
 	"github.com/stretchr/testify/mock"
 	"reflect"
 	"testing"
@@ -522,3 +523,223 @@ func Test_pndImplementation_RequestAll(t *testing.T) {
 		})
 	}
 }
+
+func Test_pndImplementation_ChangeOND(t *testing.T) {
+	type fields struct {
+	}
+	type args struct {
+		uuid      uuid.UUID
+		operation interface{}
+		path      string
+		value     []string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr bool
+	}{
+		{
+			name:   "update",
+			fields: fields{},
+			args: args{
+				uuid:      mdid,
+				operation: TransportUpdate,
+				path:      "/system/config/hostname",
+				value:     []string{"ceos3000"},
+			},
+			wantErr: false,
+		},
+		{
+			name:   "replace",
+			fields: fields{},
+			args: args{
+				uuid:      mdid,
+				operation: TransportReplace,
+				path:      "/system/config/hostname",
+				value:     []string{"ceos3000"},
+			},
+			wantErr: false,
+		},
+		{
+			name:   "delete",
+			fields: fields{},
+			args: args{
+				uuid:      mdid,
+				operation: TransportDelete,
+				path:      "/system/config/hostname",
+			},
+			wantErr: false,
+		},
+		{
+			name:   "delete w/args",
+			fields: fields{},
+			args: args{
+				uuid:      mdid,
+				operation: TransportDelete,
+				path:      "/system/config/hostname",
+				value:     []string{"ceos3000"},
+			},
+			wantErr: false,
+		},
+
+		// Negative test cases
+		{
+			name:   "invalid operation",
+			fields: fields{},
+			args: args{
+				uuid:      mdid,
+				operation: "INVALID",
+			},
+			wantErr: true,
+		},
+		{
+			name:   "invalid arg count",
+			fields: fields{},
+			args: args{
+				uuid:      mdid,
+				operation: TransportUpdate,
+				path:      "/system/config/hostname",
+				value:     []string{"ceos3000", "ceos3001"},
+			},
+			wantErr: true,
+		},
+		{
+			name:   "invalid arg count - update, no args",
+			fields: fields{},
+			args: args{
+				uuid:      mdid,
+				operation: TransportUpdate,
+				path:      "/system/config/hostname",
+			},
+			wantErr: true,
+		},
+		{
+			name:   "invalid arg count - replace, no args",
+			fields: fields{},
+			args: args{
+				uuid:      mdid,
+				operation: TransportUpdate,
+				path:      "/system/config/hostname",
+			},
+			wantErr: true,
+		},
+		{
+			name:   "device not found",
+			fields: fields{},
+			args: args{
+				uuid:      did,
+				operation: TransportUpdate,
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p := newPnd()
+			d := mockDevice()
+			if err := p.AddDevice(&d); err != nil {
+				t.Error(err)
+				return
+			}
+			if err := p.ChangeOND(tt.args.uuid, tt.args.operation, tt.args.path, tt.args.value...); (err != nil) != tt.wantErr {
+				t.Errorf("ChangeOND() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !tt.wantErr {
+				if len(p.pendingChanges.store) != 1 {
+					t.Errorf("ChangeOND() unexpected change count. got %v, want 1", len(p.pendingChanges.store))
+				}
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_GetDevice(t *testing.T) {
+	p := newPnd()
+	d, err := NewDevice(sbi, &GnmiTransportOptions{})
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if err = p.addDevice(d); err != nil {
+		t.Error(err)
+		return
+	}
+	type args struct {
+		uuid uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    ygot.GoStruct
+		wantErr bool
+	}{
+		{
+			name:    "default",
+			args:    args{uuid: d.ID()},
+			want:    sbi.Schema().Root,
+			wantErr: false,
+		},
+		{
+			name:    "device not found",
+			args:    args{uuid: mdid},
+			want:    nil,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := p.GetDevice(tt.args.uuid)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetDevice() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetDevice() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_Confirm(t *testing.T) {
+	tests := []struct {
+		name    string
+		wantErr bool
+	}{
+		{
+			name:    "default",
+			wantErr: false,
+		},
+		{
+			name:    "uncommitted",
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			pnd := newPnd()
+			d := mockDevice()
+			tr := d.Transport.(*mocks.Transport)
+			tr.On("Set", mockContext, mock.Anything, mock.Anything).Return(nil)
+			if err := pnd.addDevice(&d); err != nil {
+				t.Error(err)
+				return
+			}
+			if err := pnd.ChangeOND(d.ID(), TransportUpdate, "system/config/hostname", "ceos3000"); err != nil {
+				t.Error(err)
+				return
+			}
+			u := pnd.Pending()[0]
+			if tt.name != "uncommitted" {
+				if err := pnd.Commit(u); (err != nil) != tt.wantErr {
+					t.Errorf("Confirm() error = %v, wantErr %v", err, tt.wantErr)
+					return
+				}
+			}
+			if err := pnd.Confirm(u); (err != nil) != tt.wantErr {
+				t.Errorf("Confirm() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/nucleus/restconf_transport.go b/nucleus/restconf_transport.go
index 0dd477196745a83fda3b8460e1c5755b377748a1..44411bf26586abbbbe7f499f122aa624d9d6e17d 100644
--- a/nucleus/restconf_transport.go
+++ b/nucleus/restconf_transport.go
@@ -7,30 +7,34 @@ import (
 
 // Restconf implements the Transport interface and provides an SBI with the
 // possibility to access a Restconf endpoint.
-type Restconf struct {
-}
+type Restconf struct{}
 
-//Get not implemented yet
+// Get not yet implemented
 func (r Restconf) Get(ctx context.Context, params ...string) (interface{}, error) {
 	return nil, &ErrNotYetImplemented{}
 }
 
-//Set not implemented yet
-func (r Restconf) Set(ctx context.Context, params ...string) (interface{}, error) {
-	return nil, &ErrNotYetImplemented{}
+// Set not yet implemented
+func (r Restconf) Set(ctx context.Context, params ...interface{}) error {
+	return &ErrNotYetImplemented{}
 }
 
-// Subscribe not implemented yet
+// Subscribe not yet implemented
 func (r Restconf) Subscribe(ctx context.Context, params ...string) error {
 	return &ErrNotYetImplemented{}
 }
 
-// Type returns the RESTCONF transport type
+// Type not yet implemented
 func (r Restconf) Type() string {
 	return "restconf"
 }
 
-// ProcessResponse not implemented yet
+// GetOptions not yet implemented
+func (r Restconf) GetOptions() interface{} {
+	return &ErrNotYetImplemented{}
+}
+
+// ProcessResponse not yet implemented
 func (r Restconf) ProcessResponse(resp interface{}, root interface{}, models *ytypes.Schema) error {
 	return &ErrNotYetImplemented{}
 }
diff --git a/nucleus/restconf_transport_test.go b/nucleus/restconf_transport_test.go
index 5f1eb1e352f2cb8214686c5215eea419c465dcd4..b31df33d665563cb12c64aac28f98788454e7272 100644
--- a/nucleus/restconf_transport_test.go
+++ b/nucleus/restconf_transport_test.go
@@ -61,26 +61,21 @@ func TestRestconf_ProcessResponse(t *testing.T) {
 func TestRestconf_Set(t *testing.T) {
 	type args struct {
 		ctx    context.Context
-		params []string
+		params []interface{}
 	}
 	tests := []struct {
 		name    string
 		args    args
-		want    interface{}
 		wantErr bool
 	}{
-		{name: "not implemented", args: args{}, want: nil, wantErr: true},
+		{name: "not implemented", args: args{}, wantErr: true},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			r := Restconf{}
-			got, err := r.Set(tt.args.ctx, tt.args.params...)
+			err := r.Set(tt.args.ctx, tt.args.params...)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("Set() got = %v, want %v", got, tt.want)
 			}
 		})
 	}
diff --git a/nucleus/sbi-openconfig.go b/nucleus/sbi-openconfig.go
deleted file mode 100644
index 0fa4ffa903a1f894325aaf5211341e19a2df754a..0000000000000000000000000000000000000000
--- a/nucleus/sbi-openconfig.go
+++ /dev/null
@@ -1 +0,0 @@
-package nucleus
diff --git a/nucleus/southbound_test.go b/nucleus/southbound_test.go
index ab46901446e0cac93464c4aa111436953a51436a..5df2efeab8351538bea042965fb384d0c8affe53 100644
--- a/nucleus/southbound_test.go
+++ b/nucleus/southbound_test.go
@@ -1,6 +1,7 @@
 package nucleus
 
 import (
+	"code.fbi.h-da.de/cocsn/gosdn/nucleus/util/path"
 	"code.fbi.h-da.de/cocsn/gosdn/nucleus/util/proto"
 	"code.fbi.h-da.de/cocsn/yang-models/generated/openconfig"
 	"github.com/google/uuid"
@@ -161,7 +162,7 @@ func Test_unmarshal(t *testing.T) {
 			if err != nil {
 				t.Error(err)
 			}
-			fields := extraxtPathElements(resp.Notification[0].Update[0].Path)
+			fields := path.ToStrings(resp.Notification[0].Update[0].Path)
 			bytes := resp.Notification[0].Update[0].Val.GetJsonIetfVal()
 			if err := unmarshal(bytes, fields, tt.args.goStruct, tt.args.opt...); (err != nil) != tt.wantErr {
 				t.Errorf("unmarshal() error = %v, wantErr %v", err, tt.wantErr)
diff --git a/nucleus/store.go b/nucleus/store.go
index 0563a5a30533467809380ac25d949bfc4b578e68..7f6b98fed1ed15b2e23a4c3efb9ed2dce25d83e7 100644
--- a/nucleus/store.go
+++ b/nucleus/store.go
@@ -1,6 +1,7 @@
 package nucleus
 
 import (
+	p "code.fbi.h-da.de/cocsn/gosdn/nucleus/pnd"
 	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
 	"reflect"
@@ -131,7 +132,7 @@ func (s deviceStore) get(id uuid.UUID) (*Device, error) {
 	if !ok {
 		return nil, &ErrInvalidTypeAssertion{
 			v: device,
-			t: "Device",
+			t: reflect.TypeOf(&Device{}),
 		}
 	}
 	log.WithFields(log.Fields{
@@ -139,3 +140,25 @@ func (s deviceStore) get(id uuid.UUID) (*Device, error) {
 	}).Debug("device was accessed")
 	return device, nil
 }
+
+type changeStore struct {
+	store
+}
+
+func (s changeStore) get(id uuid.UUID) (*p.Change, error) {
+	item, err := s.store.get(id)
+	if err != nil {
+		return nil, err
+	}
+	change, ok := item.(*p.Change)
+	if !ok {
+		return nil, &ErrInvalidTypeAssertion{
+			v: change,
+			t: reflect.TypeOf(&p.Change{}),
+		}
+	}
+	log.WithFields(log.Fields{
+		"uuid": id,
+	}).Debug("change was accessed")
+	return change, nil
+}
diff --git a/nucleus/transport.go b/nucleus/transport.go
index 36dda96300e226ac74de3682d86885e20332191a..6df6e6b7de23206a7b8e1c692d897cfe5fbb9e0b 100644
--- a/nucleus/transport.go
+++ b/nucleus/transport.go
@@ -7,12 +7,25 @@ import (
 	"io"
 )
 
-// Transport provides an interface for
-// Transport implementations like RESTCONF
-// or gnmi
+// Operation codes numerous operations used to change the state of remote resources.
+// It is used as a unified code. Each Transport implementation needs to map these
+// accordingly to its specification.
+type Operation int
+
+const (
+	// TransportUpdate codes an update operation
+	TransportUpdate Operation = iota
+	// TransportReplace codes a replace operation
+	TransportReplace
+	// TransportDelete codes a delete operation
+	TransportDelete
+)
+
+// Transport provides an interface for Transport implementations
+// like RESTCONF or gnmi
 type Transport interface {
 	Get(ctx context.Context, params ...string) (interface{}, error)
-	Set(ctx context.Context, params ...interface{}) (interface{}, error)
+	Set(ctx context.Context, params ...interface{}) error
 	Subscribe(ctx context.Context, params ...string) error
 	Type() string
 	GetOptions() interface{}
diff --git a/nucleus/util/path/translate.go b/nucleus/util/path/translate.go
new file mode 100644
index 0000000000000000000000000000000000000000..e245344085d02130b5277334df9546aab98bc197
--- /dev/null
+++ b/nucleus/util/path/translate.go
@@ -0,0 +1,15 @@
+package path
+
+import (
+	gpb "github.com/openconfig/gnmi/proto/gnmi"
+	"strings"
+)
+
+// ToStrings translates a gNMI path to a slice of strings
+func ToStrings(path *gpb.Path) []string {
+	elems := make([]string, len(path.Elem))
+	for i, e := range path.Elem {
+		elems[i] = strings.Title(e.Name)
+	}
+	return elems
+}
diff --git a/nucleus/util/path/path_traversal.go b/nucleus/util/path/traverse.go
similarity index 100%
rename from nucleus/util/path/path_traversal.go
rename to nucleus/util/path/traverse.go
diff --git a/nucleus/util/path/path_traversal_test.go b/nucleus/util/path/traverse_test.go
similarity index 100%
rename from nucleus/util/path/path_traversal_test.go
rename to nucleus/util/path/traverse_test.go
diff --git a/test/integration/cmdIntegration_test.go b/test/integration/cmdIntegration_test.go
index 91871180d5939dda833fa58f6ee5c3fc269bab16..d5596ab70dad335efd43246023c13a10859f5b6a 100644
--- a/test/integration/cmdIntegration_test.go
+++ b/test/integration/cmdIntegration_test.go
@@ -28,7 +28,6 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
-
 func testSetupIntegration() {
 	if os.Getenv("GOSDN_LOG") == "nolog" {
 		log.SetLevel(log.PanicLevel)
@@ -86,7 +85,7 @@ func testSetupIntegration() {
 	}
 }
 
-func TestCliIntegration(t *testing.T) {
+func TestCmdIntegration(t *testing.T) {
 	if testing.Short() {
 		t.Skip("skipping integration test")
 	}
@@ -154,7 +153,7 @@ func TestCliIntegration(t *testing.T) {
 			hostname := guuid.New().String()
 			if err := cli.HTTPGet(
 				testAPIEndpoint,
-				"set",
+				"update",
 				"address="+testAddress,
 				"uuid="+did,
 				"sbi="+cliSbi,
diff --git a/test/integration/nucleusIntegration_test.go b/test/integration/nucleusIntegration_test.go
index 816888d6f77f857225f34d22b794017efe641629..ed6d78fd929ed1d8379ca0cc18bc11d2e65e5f83 100644
--- a/test/integration/nucleusIntegration_test.go
+++ b/test/integration/nucleusIntegration_test.go
@@ -23,13 +23,12 @@ func TestGnmi_SetIntegration(t *testing.T) {
 	}
 	type args struct {
 		ctx    context.Context
-		params []interface{}
+		params []string
 	}
 	tests := []struct {
 		name    string
 		fields  fields
 		args    args
-		want    interface{}
 		wantErr bool
 	}{
 		{
@@ -42,33 +41,28 @@ func TestGnmi_SetIntegration(t *testing.T) {
 			},
 			args: args{
 				ctx:    context.Background(),
-				params: []interface{}{&gnmi.Operation{}},
+				params: []string{"/system/config/hostname", "ceos3000"},
 			},
-			want:    nil,
 			wantErr: true,
 		},
 		{
 			name:   "valid update",
 			fields: fields{opt: opt},
 			args: args{
-				ctx: context.Background(),
-				params: []interface{}{
-					&gnmi.Operation{
-						Type:   "update",
-						Origin: "",
-						Target: "",
-						Path: []string{
-							"system",
-							"config",
-							"hostname",
-						},
-						Val: "ceos3000",
-					},
-				},
+				ctx:    context.Background(),
+				params: []string{"/system/config/hostname", "ceos3000"},
 			},
-			want:    gnmiMessages["../proto/resp-set-system-config-hostname"],
 			wantErr: false,
 		},
+		{
+			name:   "invalid update",
+			fields: fields{opt: opt},
+			args: args{
+				ctx:    context.Background(),
+				params: nil,
+			},
+			wantErr: true,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -77,21 +71,11 @@ func TestGnmi_SetIntegration(t *testing.T) {
 				t.Errorf("NewGnmiTransport() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			resp, err := g.Set(tt.args.ctx, tt.args.params...)
+			err = g.Set(tt.args.ctx, tt.args.params)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			got, ok := resp.(*gpb.SetResponse)
-			if !ok {
-				t.Errorf("want: %v, got %v, error: %v", reflect.TypeOf(&gpb.SetResponse{}), reflect.TypeOf(resp), &nucleus.ErrInvalidTypeAssertion{})
-			}
-			if err != nil && tt.wantErr {
-				return
-			} else if got.Prefix.Target != testAddress ||
-				got.Response[0].Op != gpb.UpdateResult_UPDATE {
-				t.Errorf("Set() got = %v, want %v", got, tt.want)
-			}
 		})
 	}
 }