diff --git a/controller/configs/ci-testing-gosdn.toml b/controller/configs/ci-testing-gosdn.toml
index 7c78182ab21fbc4cc3f6797b1b3ccb84765d138c..d9f5427487fab4c3005bcc1b598ca919d09a9f73 100644
--- a/controller/configs/ci-testing-gosdn.toml
+++ b/controller/configs/ci-testing-gosdn.toml
@@ -1,3 +1,3 @@
-basepnduuid = "d4dd9fcb-7fe3-4657-aa33-f9071b2ae257"
+basepnduuid = "e3a04432-a5de-4c6a-9d06-cacc0a349b77"
 basesouthboundtype = 1
-basesouthbounduuid = "a325693a-aa16-45fc-965d-08c986c476c1"
+basesouthbounduuid = "94f48ae8-6028-4da0-b495-4c554f886366"
diff --git a/controller/nucleus/gnmi_transport.go b/controller/nucleus/gnmi_transport.go
index a0bf2fad0c5b6d911480cd41c8e4e505e8c328cd..5b85a685c21f1c6a3d70fa56799899548a9f57fe 100644
--- a/controller/nucleus/gnmi_transport.go
+++ b/controller/nucleus/gnmi_transport.go
@@ -10,9 +10,9 @@ import (
 
 	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
 
-	"code.fbi.h-da.de/danet/forks/goarista/gnmi"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/types"
+	"code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
 	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	"github.com/openconfig/goyang/pkg/yang"
 	"github.com/openconfig/ygot/ygot"
diff --git a/controller/nucleus/gnmi_transport_test.go b/controller/nucleus/gnmi_transport_test.go
index ce3ed2bc669cc089aa86dc0b40a6fbca0aedd76d..e7715e7fd6c2dd0184ec80de4b9ee71bf493f1e5 100644
--- a/controller/nucleus/gnmi_transport_test.go
+++ b/controller/nucleus/gnmi_transport_test.go
@@ -13,8 +13,8 @@ import (
 	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
 	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 
-	"code.fbi.h-da.de/danet/forks/goarista/gnmi"
 	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
+	"code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
 	"code.fbi.h-da.de/danet/gosdn/models/generated/openconfig"
 	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	"github.com/openconfig/goyang/pkg/yang"
diff --git a/controller/nucleus/initialise_test.go b/controller/nucleus/initialise_test.go
index 9feef90276b7d7a6dddc15102ecf33b0d5f57cb3..34bca52db2724f4fca579457d68d3394bd4ba396 100644
--- a/controller/nucleus/initialise_test.go
+++ b/controller/nucleus/initialise_test.go
@@ -10,10 +10,10 @@ import (
 
 	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 
-	"code.fbi.h-da.de/danet/forks/goarista/gnmi"
 	"code.fbi.h-da.de/danet/gosdn/controller/mocks"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/util/proto"
 	"code.fbi.h-da.de/danet/gosdn/controller/test"
+	"code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
 	"github.com/google/uuid"
 	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	log "github.com/sirupsen/logrus"
diff --git a/controller/nucleus/principalNetworkDomain.go b/controller/nucleus/principalNetworkDomain.go
index 3d23862bcc27251e1d8ddb82893b2bb9675b3ca6..69795871e51da7d81d867d1a817dc6aca8413963 100644
--- a/controller/nucleus/principalNetworkDomain.go
+++ b/controller/nucleus/principalNetworkDomain.go
@@ -22,12 +22,12 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/protobuf/proto"
 
-	"code.fbi.h-da.de/danet/forks/goarista/gnmi"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/change"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/southbound"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
+	"code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
 
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 
diff --git a/controller/test/integration/nucleusIntegration_test.go b/controller/test/integration/nucleusIntegration_test.go
index c7d44567d667848102abb11e83d896f3d4c4724c..04899ccee07a31faf95e180cee1ffaf9df8ca386 100644
--- a/controller/test/integration/nucleusIntegration_test.go
+++ b/controller/test/integration/nucleusIntegration_test.go
@@ -15,11 +15,11 @@ import (
 	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
 	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/change"
 
-	"code.fbi.h-da.de/danet/forks/goarista/gnmi"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/errors"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/types"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/util/proto"
+	"code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
 	gpb "github.com/openconfig/gnmi/proto/gnmi"
 	log "github.com/sirupsen/logrus"
 	pb "google.golang.org/protobuf/proto"
diff --git a/controller/test/targets.go b/controller/test/targets.go
index 08e5ca74bd79467bc739f2bc987e422eef068d1f..c9a119918de894dde0d8e745f3dc1451a59a6de6 100644
--- a/controller/test/targets.go
+++ b/controller/test/targets.go
@@ -4,7 +4,7 @@ import (
 	"net"
 	"reflect"
 
-	"code.fbi.h-da.de/danet/forks/google/gnmi"
+	"code.fbi.h-da.de/danet/gosdn/forks/google/gnmi"
 	oc "code.fbi.h-da.de/danet/gosdn/models/generated/openconfig"
 	pb "github.com/openconfig/gnmi/proto/gnmi"
 	"github.com/openconfig/goyang/pkg/yang"
diff --git a/csbi/resources/csbi.go b/csbi/resources/csbi.go
index 3463723306d0edde8540259e87110c35da09dce2..2631616599ebda2cb71dbf6a4984bf896b6e067e 100644
--- a/csbi/resources/csbi.go
+++ b/csbi/resources/csbi.go
@@ -6,10 +6,10 @@ import (
 	"reflect"
 	"time"
 
-	goarista "code.fbi.h-da.de/danet/forks/goarista/gnmi"
 	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
 	d "code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
 	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
+	goarista "code.fbi.h-da.de/danet/gosdn/forks/goarista/gnmi"
 	"github.com/google/gnxi/gnmi"
 	"github.com/google/uuid"
 	pb "github.com/openconfig/gnmi/proto/gnmi"
diff --git a/csbi/resources/go.mod b/csbi/resources/go.mod
index 0954673cb5542786882a27e975d859a09ed9cd12..0929ba6affa68a4d1d2693dafc6d17da8caa3ebb 100644
--- a/csbi/resources/go.mod
+++ b/csbi/resources/go.mod
@@ -3,8 +3,8 @@ module code.fbi.h-da.de/danet/gosdn/csbi-autogen
 go 1.18
 
 require (
-	code.fbi.h-da.de/danet/forks/goarista v0.0.0-20210709163519-47ee8958ef40
-	code.fbi.h-da.de/danet/gosdn v0.0.3-0.20220322112239-21849886e0c6
+    code.fbi.h-da.de/danet/gosdn/forks/goarista v0.0.0-20210709163519-47ee8958ef40
+    code.fbi.h-da.de/danet/gosdn v0.0.3-0.20220322112239-21849886e0c6
 	github.com/google/gnxi v0.0.0-20210423111716-4b504ef806a7
 	github.com/google/uuid v1.2.0
 	github.com/openconfig/gnmi v0.0.0-20210914185457-51254b657b7d
diff --git a/csbi/resources/go.sum b/csbi/resources/go.sum
index e0ad5b9ba5dd9f051b02b50ea6c28a544126df9e..11ab920fd9dfd4bb203bc8acbabc7322cf9b0a37 100644
--- a/csbi/resources/go.sum
+++ b/csbi/resources/go.sum
@@ -45,8 +45,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
 code.fbi.h-da.de/danet/forks/goarista v0.0.0-20210709163519-47ee8958ef40 h1:x7rVYGqfJSMWuYBp+JE6JVMcFP03Gx0mnR2ftsgqjVI=
 code.fbi.h-da.de/danet/forks/goarista v0.0.0-20210709163519-47ee8958ef40/go.mod h1:uVe3gCeF2DcIho8K9CIO46uAkHW/lUF+fAaUX1vHrF0=
 code.fbi.h-da.de/danet/forks/google v0.0.0-20210709163519-47ee8958ef40 h1:B45k5tGEdjjdsKK4f+0dQoyReFmsWdwYEzHofA7DPM8=
-code.fbi.h-da.de/danet/gosdn v0.0.3-0.20220322112239-21849886e0c6 h1:FaJ37jmO7k1sJz/z1YvYbuBb+chclModm8M4ajATcIY=
-code.fbi.h-da.de/danet/gosdn v0.0.3-0.20220322112239-21849886e0c6/go.mod h1:pZKEO+vuBIlims7fSzcLEDYvm0mUaRp0lCxpPP4wFKI=
 code.fbi.h-da.de/danet/yang-models v0.1.1-0.20220317144831-bcba189e26ec h1:30UHRiCJp4AjLy8WAbLUvFPOcH5cBpluys7eCML+QjY=
 code.fbi.h-da.de/danet/yang-models v0.1.1-0.20220317144831-bcba189e26ec/go.mod h1:0TNkzPA1OW9lF9ey18GQWcMd4ORvOfhhFOA/t0SjenM=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
diff --git a/forks/LICENSE b/forks/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..dad54ab9257329e28f8fb3ca23be7a58161c792d
--- /dev/null
+++ b/forks/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2021 danet
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/forks/README.md b/forks/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..13af386d5aed7935d5747a741b94345c3eef88d3
--- /dev/null
+++ b/forks/README.md
@@ -0,0 +1,3 @@
+# forks
+
+Forks of other projects modified for the use in da/net projects
\ No newline at end of file
diff --git a/forks/goarista/gnmi/arbitration.go b/forks/goarista/gnmi/arbitration.go
new file mode 100644
index 0000000000000000000000000000000000000000..78225d70240584b7e4e8b048bd833753b39ebc5e
--- /dev/null
+++ b/forks/goarista/gnmi/arbitration.go
@@ -0,0 +1,58 @@
+// Copyright (c) 2019 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/openconfig/gnmi/proto/gnmi_ext"
+)
+
+// ArbitrationExt takes a string representation of a master arbitration value
+// (e.g. "23", "role:42") and return a *gnmi_ext.Extension.
+func ArbitrationExt(s string) (*gnmi_ext.Extension, error) {
+	if s == "" {
+		return nil, nil
+	}
+	roleID, electionID, err := parseArbitrationString(s)
+	if err != nil {
+		return nil, err
+	}
+	arb := &gnmi_ext.MasterArbitration{
+		Role:       &gnmi_ext.Role{Id: roleID},
+		ElectionId: &gnmi_ext.Uint128{High: 0, Low: electionID},
+	}
+	ext := gnmi_ext.Extension_MasterArbitration{MasterArbitration: arb}
+	return &gnmi_ext.Extension{Ext: &ext}, nil
+}
+
+// parseArbitrationString parses the supplied string and returns the role and election id
+// values. Input is of the form [<role>:]<election_id>, where election_id is a uint64.
+//
+// Examples:
+//  "1"
+//  "admin:42"
+func parseArbitrationString(s string) (string, uint64, error) {
+	tokens := strings.Split(s, ":")
+	switch len(tokens) {
+	case 1: // just election id
+		id, err := parseElectionID(tokens[0])
+		return "", id, err
+	case 2: // role and election id
+		id, err := parseElectionID(tokens[1])
+		return tokens[0], id, err
+	}
+	return "", 0, fmt.Errorf("badly formed arbitration id (%s)", s)
+}
+
+func parseElectionID(s string) (uint64, error) {
+	id, err := strconv.ParseUint(s, 0, 64)
+	if err != nil {
+		return 0, fmt.Errorf("badly formed arbitration id (%s)", s)
+	}
+	return id, nil
+}
diff --git a/forks/goarista/gnmi/arbitration_test.go b/forks/goarista/gnmi/arbitration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..cdcc37c35b8c332f3ae4279b66ddf7b46e8b8798
--- /dev/null
+++ b/forks/goarista/gnmi/arbitration_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2019 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/aristanetworks/goarista/test"
+
+	"github.com/openconfig/gnmi/proto/gnmi_ext"
+)
+
+func arbitration(role string, id *gnmi_ext.Uint128) *gnmi_ext.Extension {
+	arb := &gnmi_ext.MasterArbitration{
+		Role:       &gnmi_ext.Role{Id: role},
+		ElectionId: id,
+	}
+	ext := gnmi_ext.Extension_MasterArbitration{MasterArbitration: arb}
+	return &gnmi_ext.Extension{Ext: &ext}
+}
+
+func electionID(high, low uint64) *gnmi_ext.Uint128 {
+	return &gnmi_ext.Uint128{High: high, Low: low}
+}
+
+func TestArbitrationExt(t *testing.T) {
+	testCases := map[string]struct {
+		s   string
+		ext *gnmi_ext.Extension
+		err error
+	}{
+		"empty": {},
+		"no_role": {
+			s:   "1",
+			ext: arbitration("", electionID(0, 1)),
+		},
+		"with_role": {
+			s:   "admin:1",
+			ext: arbitration("admin", electionID(0, 1)),
+		},
+		"large_no_role": {
+			s:   "9223372036854775807",
+			ext: arbitration("", electionID(0, 9223372036854775807)),
+		},
+		"large_with_role": {
+			s:   "admin:18446744073709551615",
+			ext: arbitration("admin", electionID(0, 18446744073709551615)),
+		},
+		"invalid": {
+			s:   "cat",
+			err: fmt.Errorf("badly formed arbitration id (%s)", "cat"),
+		},
+		"invalid_too_many_colons": {
+			s:   "dog:1:2",
+			err: fmt.Errorf("badly formed arbitration id (%s)", "dog:1:2"),
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			ext, err := ArbitrationExt(tc.s)
+			if !test.DeepEqual(tc.ext, ext) {
+				t.Errorf("Expected %#v, got %#v", tc.ext, ext)
+			}
+			if !test.DeepEqual(tc.err, err) {
+				t.Errorf("Expected %v, got %v", tc.err, err)
+			}
+		})
+	}
+}
diff --git a/forks/goarista/gnmi/client.go b/forks/goarista/gnmi/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..c4512f2c8f6a3ef34b405ccfe81a33fcfcd57f72
--- /dev/null
+++ b/forks/goarista/gnmi/client.go
@@ -0,0 +1,286 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"errors"
+	"fmt"
+	"math"
+	"net"
+	"os"
+
+	"io/ioutil"
+	"strings"
+
+	"github.com/golang/protobuf/proto"
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/encoding/gzip"
+	"google.golang.org/grpc/metadata"
+)
+
+const (
+	defaultPort = "6030"
+	// HostnameArg is the value to be replaced by the actual hostname
+	HostnameArg = "HOSTNAME"
+)
+
+// PublishFunc is the method to publish responses
+type PublishFunc func(addr string, message proto.Message)
+
+// ParseHostnames parses a comma-separated list of names and replaces HOSTNAME with the current
+// hostname in it
+func ParseHostnames(list string) ([]string, error) {
+	items := strings.Split(list, ",")
+	hostname, err := os.Hostname()
+	if err != nil {
+		return nil, err
+	}
+	names := make([]string, len(items))
+	for i, name := range items {
+		if name == HostnameArg {
+			name = hostname
+		}
+		names[i] = name
+	}
+	return names, nil
+}
+
+// Config is the gnmi.Client config
+type Config struct {
+	Addr        string
+	CAFile      string
+	CertFile    string
+	KeyFile     string
+	Password    string
+	Username    string
+	TLS         bool
+	Compression string
+	DialOptions []grpc.DialOption
+	Token       string
+	Encoding    pb.Encoding
+}
+
+// SubscribeOptions is the gNMI subscription request options
+type SubscribeOptions struct {
+	UpdatesOnly       bool
+	Prefix            string
+	Mode              string
+	StreamMode        string
+	SampleInterval    uint64
+	SuppressRedundant bool
+	HeartbeatInterval uint64
+	Paths             [][]string
+	Origin            string
+	Target            string
+}
+
+// accessTokenCred implements credentials.PerRPCCredentials, the gRPC
+// interface for credentials that need to attach security information
+// to every RPC.
+type accessTokenCred struct {
+	bearerToken string
+}
+
+// newAccessTokenCredential constructs a new per-RPC credential from a token.
+func newAccessTokenCredential(token string) credentials.PerRPCCredentials {
+	bearerFmt := "Bearer %s"
+	return &accessTokenCred{bearerToken: fmt.Sprintf(bearerFmt, token)}
+}
+
+func (a *accessTokenCred) GetRequestMetadata(ctx context.Context,
+	uri ...string) (map[string]string, error) {
+	authHeader := "Authorization"
+	return map[string]string{
+		authHeader: a.bearerToken,
+	}, nil
+}
+
+func (a *accessTokenCred) RequireTransportSecurity() bool { return true }
+
+// DialContext connects to a gnmi service and returns a client
+func DialContext(ctx context.Context, cfg *Config) (pb.GNMIClient, error) {
+	opts := append([]grpc.DialOption(nil), cfg.DialOptions...)
+
+	switch cfg.Compression {
+	case "":
+	case "gzip":
+		opts = append(opts, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name)))
+	default:
+		return nil, fmt.Errorf("unsupported compression option: %q", cfg.Compression)
+	}
+
+	if cfg.TLS || cfg.CAFile != "" || cfg.CertFile != "" || cfg.Token != "" {
+		tlsConfig := &tls.Config{
+			MinVersion: tls.VersionTLS12,
+		}
+		if cfg.CAFile != "" {
+			b, err := ioutil.ReadFile(cfg.CAFile)
+			if err != nil {
+				return nil, err
+			}
+			cp := x509.NewCertPool()
+			if !cp.AppendCertsFromPEM(b) {
+				return nil, fmt.Errorf("credentials: failed to append certificates")
+			}
+			tlsConfig.RootCAs = cp
+		} else {
+			tlsConfig.InsecureSkipVerify = true
+		}
+		if cfg.CertFile != "" {
+			if cfg.KeyFile == "" {
+				return nil, fmt.Errorf("please provide both -certfile and -keyfile")
+			}
+			cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile)
+			if err != nil {
+				return nil, err
+			}
+			tlsConfig.Certificates = []tls.Certificate{cert}
+		}
+		if cfg.Token != "" {
+			opts = append(opts,
+				grpc.WithPerRPCCredentials(newAccessTokenCredential(cfg.Token)))
+		}
+		opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
+	} else {
+		opts = append(opts, grpc.WithInsecure())
+	}
+
+	dial := func(ctx context.Context, addrIn string) (conn net.Conn, err error) {
+		var network, addr string
+
+		split := strings.Split(addrIn, "://")
+		if l := len(split); l == 2 {
+			network = split[0]
+			addr = split[1]
+		} else {
+			network = "tcp"
+			addr = split[0]
+		}
+
+		conn, err = (&net.Dialer{}).DialContext(ctx, network, addr)
+		return
+	}
+
+	opts = append(opts,
+		grpc.WithContextDialer(dial),
+
+		// Allows received protobuf messages to be larger than 4MB
+		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(math.MaxInt32)),
+	)
+
+	grpcconn, err := grpc.DialContext(ctx, cfg.Addr, opts...)
+	if err != nil {
+		return nil, fmt.Errorf("failed to dial: %s", err)
+	}
+
+	return pb.NewGNMIClient(grpcconn), nil
+}
+
+// Dial connects to a gnmi service and returns a client
+func Dial(cfg *Config) (pb.GNMIClient, error) {
+	return DialContext(context.Background(), cfg)
+}
+
+// NewContext returns a new context with username and password
+// metadata if they are set in cfg.
+func NewContext(ctx context.Context, cfg *Config) context.Context {
+	if cfg.Username != "" {
+		ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
+			"username", cfg.Username,
+			"password", cfg.Password))
+	}
+	return ctx
+}
+
+// NewGetRequest returns a GetRequest for the given paths
+func NewGetRequest(ctx context.Context, paths [][]string, origin string) (*pb.GetRequest, error) {
+	val := ctx.Value("config")
+	cfg, ok := val.(*Config)
+	if !ok {
+		return nil, errors.New("invalid type assertion")
+	}
+	req := &pb.GetRequest{
+		Path:     make([]*pb.Path, len(paths)),
+		Encoding: cfg.Encoding,
+	}
+	for i, p := range paths {
+		gnmiPath, err := ParseGNMIElements(p)
+		if err != nil {
+			return nil, err
+		}
+		req.Path[i] = gnmiPath
+		req.Path[i].Origin = origin
+	}
+	return req, nil
+}
+
+// NewSubscribeRequest returns a SubscribeRequest for the given paths
+func NewSubscribeRequest(subscribeOptions *SubscribeOptions) (*pb.SubscribeRequest, error) {
+	var mode pb.SubscriptionList_Mode
+	switch subscribeOptions.Mode {
+	case "once":
+		mode = pb.SubscriptionList_ONCE
+	case "poll":
+		mode = pb.SubscriptionList_POLL
+	case "":
+		fallthrough
+	case "stream":
+		mode = pb.SubscriptionList_STREAM
+	default:
+		return nil, fmt.Errorf("subscribe mode (%s) invalid", subscribeOptions.Mode)
+	}
+
+	var streamMode pb.SubscriptionMode
+	switch subscribeOptions.StreamMode {
+	case "on_change":
+		streamMode = pb.SubscriptionMode_ON_CHANGE
+	case "sample":
+		streamMode = pb.SubscriptionMode_SAMPLE
+	case "":
+		fallthrough
+	case "target_defined":
+		streamMode = pb.SubscriptionMode_TARGET_DEFINED
+	default:
+		return nil, fmt.Errorf("subscribe stream mode (%s) invalid", subscribeOptions.StreamMode)
+	}
+
+	prefixPath, err := ParseGNMIElements(SplitPath(subscribeOptions.Prefix))
+	if err != nil {
+		return nil, err
+	}
+	subList := &pb.SubscriptionList{
+		Subscription: make([]*pb.Subscription, len(subscribeOptions.Paths)),
+		Mode:         mode,
+		UpdatesOnly:  subscribeOptions.UpdatesOnly,
+		Prefix:       prefixPath,
+	}
+	if subscribeOptions.Target != "" {
+		if subList.Prefix == nil {
+			subList.Prefix = &pb.Path{}
+		}
+		subList.Prefix.Target = subscribeOptions.Target
+	}
+	for i, p := range subscribeOptions.Paths {
+		gnmiPath, err := ParseGNMIElements(p)
+		if err != nil {
+			return nil, err
+		}
+		gnmiPath.Origin = subscribeOptions.Origin
+		subList.Subscription[i] = &pb.Subscription{
+			Path:              gnmiPath,
+			Mode:              streamMode,
+			SampleInterval:    subscribeOptions.SampleInterval,
+			SuppressRedundant: subscribeOptions.SuppressRedundant,
+			HeartbeatInterval: subscribeOptions.HeartbeatInterval,
+		}
+	}
+	return &pb.SubscribeRequest{Request: &pb.SubscribeRequest_Subscribe{
+		Subscribe: subList}}, nil
+}
diff --git a/forks/goarista/gnmi/json.go b/forks/goarista/gnmi/json.go
new file mode 100644
index 0000000000000000000000000000000000000000..30aacd3df8239f39dd1af90a4fca9582cff6de1c
--- /dev/null
+++ b/forks/goarista/gnmi/json.go
@@ -0,0 +1,35 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"github.com/openconfig/gnmi/proto/gnmi"
+)
+
+// NotificationToMap converts a Notification into a map[string]interface{}
+func NotificationToMap(notif *gnmi.Notification) (map[string]interface{}, error) {
+	m := make(map[string]interface{}, 1)
+	m["timestamp"] = notif.Timestamp
+	m["path"] = StrPath(notif.Prefix)
+	if len(notif.Update) != 0 {
+		updates := make(map[string]interface{}, len(notif.Update))
+		var err error
+		for _, update := range notif.Update {
+			updates[StrPath(update.Path)] = StrUpdateVal(update)
+			if err != nil {
+				return nil, err
+			}
+		}
+		m["updates"] = updates
+	}
+	if len(notif.Delete) != 0 {
+		deletes := make([]string, len(notif.Delete))
+		for i, del := range notif.Delete {
+			deletes[i] = StrPath(del)
+		}
+		m["deletes"] = deletes
+	}
+	return m, nil
+}
diff --git a/forks/goarista/gnmi/operation.go b/forks/goarista/gnmi/operation.go
new file mode 100644
index 0000000000000000000000000000000000000000..01005d6f0877102e4e73124238b4e7cde93fcda9
--- /dev/null
+++ b/forks/goarista/gnmi/operation.go
@@ -0,0 +1,517 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"bufio"
+	"bytes"
+	"context"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+	"github.com/openconfig/gnmi/proto/gnmi_ext"
+)
+
+// GetWithRequest takes a fully formed GetRequest, performs the Get,
+// and displays any response.
+func GetWithRequest(ctx context.Context, client pb.GNMIClient,
+	req *pb.GetRequest) error {
+	resp, err := client.Get(ctx, req)
+	if err != nil {
+		return err
+	}
+	for _, notif := range resp.Notification {
+		prefix := StrPath(notif.Prefix)
+		for _, update := range notif.Update {
+			fmt.Printf("%s:\n", path.Join(prefix, StrPath(update.Path)))
+			fmt.Println(StrUpdateVal(update))
+		}
+	}
+	return nil
+}
+
+// Get sends a GetRequest to the given server.
+func Get(ctx context.Context, client pb.GNMIClient, paths [][]string,
+	origin string) error {
+	req, err := NewGetRequest(ctx, paths, origin)
+	if err != nil {
+		return err
+	}
+	return GetWithRequest(ctx, client, req)
+}
+
+// val may be a path to a file or it may be json. First see if it is a
+// file, if so return its contents, otherwise return val
+func extractJSON(val string) []byte {
+	if jsonBytes, err := ioutil.ReadFile(val); err == nil {
+		return jsonBytes
+	}
+	// Best effort check if the value might a string literal, in which
+	// case wrap it in quotes. This is to allow a user to do:
+	//   gnmi update ../hostname host1234
+	//   gnmi update ../description 'This is a description'
+	// instead of forcing them to quote the string:
+	//   gnmi update ../hostname '"host1234"'
+	//   gnmi update ../description '"This is a description"'
+	maybeUnquotedStringLiteral := func(s string) bool {
+		if s == "true" || s == "false" || s == "null" || // JSON reserved words
+			strings.ContainsAny(s, `"'{}[]`) { // Already quoted or is a JSON object or array
+			return false
+		} else if _, err := strconv.ParseInt(s, 0, 32); err == nil {
+			// Integer. Using byte size of 32 because larger integer
+			// types are supposed to be sent as strings in JSON.
+			return false
+		} else if _, err := strconv.ParseFloat(s, 64); err == nil {
+			// Float
+			return false
+		}
+
+		return true
+	}
+	if maybeUnquotedStringLiteral(val) {
+		out := make([]byte, len(val)+2)
+		out[0] = '"'
+		copy(out[1:], val)
+		out[len(out)-1] = '"'
+		return out
+	}
+	return []byte(val)
+}
+
+// StrUpdateVal will return a string representing the value within the supplied update
+func StrUpdateVal(u *pb.Update) string {
+	if u.Value != nil {
+		// Backwards compatibility with pre-v0.4 gnmi
+		switch u.Value.Type {
+		case pb.Encoding_JSON, pb.Encoding_JSON_IETF:
+			return strJSON(u.Value.Value)
+		case pb.Encoding_BYTES, pb.Encoding_PROTO:
+			return base64.StdEncoding.EncodeToString(u.Value.Value)
+		case pb.Encoding_ASCII:
+			return string(u.Value.Value)
+		default:
+			return string(u.Value.Value)
+		}
+	}
+	return StrVal(u.Val)
+}
+
+// StrVal will return a string representing the supplied value
+func StrVal(val *pb.TypedValue) string {
+	switch v := val.GetValue().(type) {
+	case *pb.TypedValue_StringVal:
+		return v.StringVal
+	case *pb.TypedValue_JsonIetfVal:
+		return strJSON(v.JsonIetfVal)
+	case *pb.TypedValue_JsonVal:
+		return strJSON(v.JsonVal)
+	case *pb.TypedValue_IntVal:
+		return strconv.FormatInt(v.IntVal, 10)
+	case *pb.TypedValue_UintVal:
+		return strconv.FormatUint(v.UintVal, 10)
+	case *pb.TypedValue_BoolVal:
+		return strconv.FormatBool(v.BoolVal)
+	case *pb.TypedValue_BytesVal:
+		return base64.StdEncoding.EncodeToString(v.BytesVal)
+	case *pb.TypedValue_DecimalVal:
+		return strDecimal64(v.DecimalVal)
+	case *pb.TypedValue_FloatVal:
+		return strconv.FormatFloat(float64(v.FloatVal), 'g', -1, 32)
+	case *pb.TypedValue_LeaflistVal:
+		return strLeaflist(v.LeaflistVal)
+	case *pb.TypedValue_AsciiVal:
+		return v.AsciiVal
+	case *pb.TypedValue_AnyVal:
+		return v.AnyVal.String()
+	case *pb.TypedValue_ProtoBytes:
+		return base64.StdEncoding.EncodeToString(v.ProtoBytes)
+	default:
+		panic(v)
+	}
+}
+
+func strJSON(inJSON []byte) string {
+	var (
+		out bytes.Buffer
+		err error
+	)
+	// Check for ',' as simple heuristic on whether to expand JSON
+	// onto multiple lines, or compact it to a single line.
+	if bytes.Contains(inJSON, []byte{','}) {
+		err = json.Indent(&out, inJSON, "", "  ")
+	} else {
+		err = json.Compact(&out, inJSON)
+	}
+	if err != nil {
+		return fmt.Sprintf("(error unmarshalling json: %s)\n", err) + string(inJSON)
+	}
+	return out.String()
+}
+
+func strDecimal64(d *pb.Decimal64) string {
+	var i, frac int64
+	if d.Precision > 0 {
+		div := int64(10)
+		it := d.Precision - 1
+		for it > 0 {
+			div *= 10
+			it--
+		}
+		i = d.Digits / div
+		frac = d.Digits % div
+	} else {
+		i = d.Digits
+	}
+	if frac < 0 {
+		frac = -frac
+	}
+	return fmt.Sprintf("%d.%d", i, frac)
+}
+
+// strLeafList builds a human-readable form of a leaf-list. e.g. [1, 2, 3] or [a, b, c]
+func strLeaflist(v *pb.ScalarArray) string {
+	var b strings.Builder
+	b.WriteByte('[')
+
+	for i, elm := range v.Element {
+		b.WriteString(StrVal(elm))
+		if i < len(v.Element)-1 {
+			b.WriteString(", ")
+		}
+	}
+
+	b.WriteByte(']')
+	return b.String()
+}
+
+// TypedValue marshals an interface into a gNMI TypedValue value
+func TypedValue(val interface{}) *pb.TypedValue {
+	// TODO: handle more types:
+	// float64
+	// maps
+	// key.Key
+	// key.Map
+	// ... etc
+	switch v := val.(type) {
+	case string:
+		return &pb.TypedValue{Value: &pb.TypedValue_StringVal{StringVal: v}}
+	case int:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: int64(v)}}
+	case int8:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: int64(v)}}
+	case int16:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: int64(v)}}
+	case int32:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: int64(v)}}
+	case int64:
+		return &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: v}}
+	case uint:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: uint64(v)}}
+	case uint8:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: uint64(v)}}
+	case uint16:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: uint64(v)}}
+	case uint32:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: uint64(v)}}
+	case uint64:
+		return &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: v}}
+	case bool:
+		return &pb.TypedValue{Value: &pb.TypedValue_BoolVal{BoolVal: v}}
+	case float32:
+		return &pb.TypedValue{Value: &pb.TypedValue_FloatVal{FloatVal: v}}
+	case []interface{}:
+		gnmiElems := make([]*pb.TypedValue, len(v))
+		for i, elem := range v {
+			gnmiElems[i] = TypedValue(elem)
+		}
+		return &pb.TypedValue{
+			Value: &pb.TypedValue_LeaflistVal{
+				LeaflistVal: &pb.ScalarArray{
+					Element: gnmiElems,
+				}}}
+	default:
+		panic(fmt.Sprintf("unexpected type %T for value %v", val, val))
+	}
+}
+
+// ExtractValue pulls a value out of a gNMI Update, parsing JSON if present.
+// Possible return types:
+//  string
+//  int64
+//  uint64
+//  bool
+//  []byte
+//  float32
+//  *gnmi.Decimal64
+//  json.Number
+//  *any.Any
+//  []interface{}
+//  map[string]interface{}
+func ExtractValue(update *pb.Update) (interface{}, error) {
+	var i interface{}
+	var err error
+	if update == nil {
+		return nil, fmt.Errorf("empty update")
+	}
+	if update.Val != nil {
+		i, err = extractValueV04(update.Val)
+	} else if update.Value != nil {
+		i, err = extractValueV03(update.Value)
+	}
+	return i, err
+}
+
+func extractValueV04(val *pb.TypedValue) (interface{}, error) {
+	switch v := val.Value.(type) {
+	case *pb.TypedValue_StringVal:
+		return v.StringVal, nil
+	case *pb.TypedValue_IntVal:
+		return v.IntVal, nil
+	case *pb.TypedValue_UintVal:
+		return v.UintVal, nil
+	case *pb.TypedValue_BoolVal:
+		return v.BoolVal, nil
+	case *pb.TypedValue_BytesVal:
+		return v.BytesVal, nil
+	case *pb.TypedValue_FloatVal:
+		return v.FloatVal, nil
+	case *pb.TypedValue_DecimalVal:
+		return v.DecimalVal, nil
+	case *pb.TypedValue_LeaflistVal:
+		elementList := v.LeaflistVal.Element
+		l := make([]interface{}, len(elementList))
+		for i, element := range elementList {
+			el, err := extractValueV04(element)
+			if err != nil {
+				return nil, err
+			}
+			l[i] = el
+		}
+		return l, nil
+	case *pb.TypedValue_AnyVal:
+		return v.AnyVal, nil
+	case *pb.TypedValue_JsonVal:
+		return decode(v.JsonVal)
+	case *pb.TypedValue_JsonIetfVal:
+		return decode(v.JsonIetfVal)
+	case *pb.TypedValue_AsciiVal:
+		return v.AsciiVal, nil
+	case *pb.TypedValue_ProtoBytes:
+		return v.ProtoBytes, nil
+	}
+	return nil, fmt.Errorf("unhandled type of value %v", val.GetValue())
+}
+
+func extractValueV03(val *pb.Value) (interface{}, error) {
+	switch val.Type {
+	case pb.Encoding_JSON, pb.Encoding_JSON_IETF:
+		return decode(val.Value)
+	case pb.Encoding_BYTES, pb.Encoding_PROTO:
+		return val.Value, nil
+	case pb.Encoding_ASCII:
+		return string(val.Value), nil
+	}
+	return nil, fmt.Errorf("unhandled type of value %v", val.GetValue())
+}
+
+func decode(byteArr []byte) (interface{}, error) {
+	decoder := json.NewDecoder(bytes.NewReader(byteArr))
+	decoder.UseNumber()
+	var value interface{}
+	err := decoder.Decode(&value)
+	return value, err
+}
+
+// DecimalToFloat converts a gNMI Decimal64 to a float64
+func DecimalToFloat(dec *pb.Decimal64) float64 {
+	return float64(dec.Digits) / math.Pow10(int(dec.Precision))
+}
+
+func update(p *pb.Path, val string) (*pb.Update, error) {
+	var v *pb.TypedValue
+	switch p.Origin {
+	case "":
+		v = &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: extractJSON(val)}}
+	case "eos_native":
+		v = &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: extractJSON(val)}}
+	case "cli", "test-regen-cli":
+		v = &pb.TypedValue{
+			Value: &pb.TypedValue_AsciiVal{AsciiVal: val}}
+	case "p4_config":
+		b, err := ioutil.ReadFile(val)
+		if err != nil {
+			return nil, err
+		}
+		v = &pb.TypedValue{
+			Value: &pb.TypedValue_ProtoBytes{ProtoBytes: b}}
+	default:
+		return nil, fmt.Errorf("unexpected origin: %q", p.Origin)
+	}
+
+	return &pb.Update{Path: p, Val: v}, nil
+}
+
+// Operation describes an gNMI operation.
+type Operation struct {
+	Type   string
+	Origin string
+	Target string
+	Path   []string
+	Val    string
+}
+
+func newSetRequest(setOps []*Operation, exts ...*gnmi_ext.Extension) (*pb.SetRequest, error) {
+	req := &pb.SetRequest{}
+	for _, op := range setOps {
+		p, err := ParseGNMIElements(op.Path)
+		if err != nil {
+			return nil, err
+		}
+		p.Origin = op.Origin
+
+		// Target must apply to the entire SetRequest.
+		if op.Target != "" {
+			req.Prefix = &pb.Path{
+				Target: op.Target,
+			}
+		}
+
+		switch op.Type {
+		case "delete":
+			req.Delete = append(req.Delete, p)
+		case "update":
+			u, err := update(p, op.Val)
+			if err != nil {
+				return nil, err
+			}
+			req.Update = append(req.Update, u)
+		case "replace":
+			u, err := update(p, op.Val)
+			if err != nil {
+				return nil, err
+			}
+			req.Replace = append(req.Replace, u)
+		}
+	}
+	for _, ext := range exts {
+		req.Extension = append(req.Extension, ext)
+	}
+	return req, nil
+}
+
+// Set sends a SetRequest to the given client.
+func Set(ctx context.Context, client pb.GNMIClient, setOps []*Operation,
+	exts ...*gnmi_ext.Extension) (*pb.SetResponse, error) {
+	req, err := newSetRequest(setOps, exts...)
+	if err != nil {
+		return nil, err
+	}
+	return client.Set(ctx, req)
+}
+
+// Subscribe sends a SubscribeRequest to the given client.
+// Deprecated: Use SubscribeErr instead.
+func Subscribe(ctx context.Context, client pb.GNMIClient, subscribeOptions *SubscribeOptions,
+	respChan chan<- *pb.SubscribeResponse, errChan chan<- error) {
+	defer close(errChan)
+	if err := SubscribeErr(ctx, client, subscribeOptions, respChan); err != nil {
+		errChan <- err
+	}
+}
+
+// SubscribeErr makes a gNMI.Subscribe call and writes the responses
+// to the respChan. Before returning respChan will be closed.
+func SubscribeErr(ctx context.Context, client pb.GNMIClient, subscribeOptions *SubscribeOptions,
+	respChan chan<- *pb.SubscribeResponse) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+	defer close(respChan)
+
+	stream, err := client.Subscribe(ctx)
+	if err != nil {
+		return err
+	}
+	req, err := NewSubscribeRequest(subscribeOptions)
+	if err != nil {
+		return err
+	}
+	if err := stream.Send(req); err != nil {
+		return err
+	}
+
+	for {
+		resp, err := stream.Recv()
+		if err != nil {
+			if err == io.EOF {
+				return nil
+			}
+			return err
+		}
+		respChan <- resp
+
+		// For POLL subscriptions, initiate a poll request by pressing ENTER
+		if subscribeOptions.Mode == "poll" {
+			switch resp.Response.(type) {
+			case *pb.SubscribeResponse_SyncResponse:
+				fmt.Print("Press ENTER to send a poll request: ")
+				reader := bufio.NewReader(os.Stdin)
+				reader.ReadString('\n')
+
+				pollReq := &pb.SubscribeRequest{
+					Request: &pb.SubscribeRequest_Poll{
+						Poll: &pb.Poll{},
+					},
+				}
+				if err := stream.Send(pollReq); err != nil {
+					return err
+				}
+			}
+		}
+	}
+}
+
+// LogSubscribeResponse logs update responses to stderr.
+func LogSubscribeResponse(response *pb.SubscribeResponse) error {
+	switch resp := response.Response.(type) {
+	case *pb.SubscribeResponse_Error:
+		return errors.New(resp.Error.Message)
+	case *pb.SubscribeResponse_SyncResponse:
+		if !resp.SyncResponse {
+			return errors.New("initial sync failed")
+		}
+	case *pb.SubscribeResponse_Update:
+		t := time.Unix(0, resp.Update.Timestamp).UTC()
+		prefix := StrPath(resp.Update.Prefix)
+		var target string
+		if t := resp.Update.Prefix.GetTarget(); t != "" {
+			target = "(" + t + ") "
+		}
+		for _, update := range resp.Update.Update {
+			fmt.Printf("[%s] %s%s = %s\n", t.Format(time.RFC3339Nano),
+				target,
+				path.Join(prefix, StrPath(update.Path)),
+				StrUpdateVal(update))
+		}
+		for _, del := range resp.Update.Delete {
+			fmt.Printf("[%s] %sDeleted %s\n", t.Format(time.RFC3339Nano),
+				target,
+				path.Join(prefix, StrPath(del)))
+		}
+	}
+	return nil
+}
diff --git a/forks/goarista/gnmi/operation_test.go b/forks/goarista/gnmi/operation_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd575d10aa5ea2766b9da07a4c4865ee101d923c
--- /dev/null
+++ b/forks/goarista/gnmi/operation_test.go
@@ -0,0 +1,423 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/aristanetworks/goarista/test"
+	"github.com/golang/protobuf/proto"
+	"github.com/golang/protobuf/ptypes/any"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+func TestNewSetRequest(t *testing.T) {
+	pathFoo := &pb.Path{
+		Element: []string{"foo"},
+		Elem:    []*pb.PathElem{{Name: "foo"}},
+	}
+	pathCli := &pb.Path{
+		Origin: "cli",
+	}
+	pathP4 := &pb.Path{
+		Origin: "p4_config",
+	}
+
+	p4FileContent := "p4_config test"
+	p4TestFile, err := ioutil.TempFile("", "p4TestFile")
+	if err != nil {
+		t.Errorf("cannot create test file for p4_config")
+	}
+	p4Filename := p4TestFile.Name()
+
+	defer os.Remove(p4Filename)
+
+	if _, err := p4TestFile.WriteString(p4FileContent); err != nil {
+		t.Errorf("cannot write test file for p4_config")
+	}
+	p4TestFile.Close()
+
+	testCases := map[string]struct {
+		setOps []*Operation
+		exp    pb.SetRequest
+	}{
+		"delete": {
+			setOps: []*Operation{{Type: "delete", Path: []string{"foo"}}},
+			exp:    pb.SetRequest{Delete: []*pb.Path{pathFoo}},
+		},
+		"update": {
+			setOps: []*Operation{{Type: "update", Path: []string{"foo"}, Val: "true"}},
+			exp: pb.SetRequest{
+				Update: []*pb.Update{{
+					Path: pathFoo,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("true")}},
+				}},
+			},
+		},
+		"replace": {
+			setOps: []*Operation{{Type: "replace", Path: []string{"foo"}, Val: "true"}},
+			exp: pb.SetRequest{
+				Replace: []*pb.Update{{
+					Path: pathFoo,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("true")}},
+				}},
+			},
+		},
+		"cli-replace": {
+			setOps: []*Operation{{Type: "replace", Origin: "cli",
+				Val: "hostname foo\nip routing"}},
+			exp: pb.SetRequest{
+				Replace: []*pb.Update{{
+					Path: pathCli,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_AsciiVal{AsciiVal: "hostname foo\nip routing"}},
+				}},
+			},
+		},
+		"p4_config": {
+			setOps: []*Operation{{Type: "replace", Origin: "p4_config",
+				Val: p4Filename}},
+			exp: pb.SetRequest{
+				Replace: []*pb.Update{{
+					Path: pathP4,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_ProtoBytes{ProtoBytes: []byte(p4FileContent)}},
+				}},
+			},
+		},
+		"target": {
+			setOps: []*Operation{{Type: "replace", Target: "JPE1234567",
+				Path: []string{"foo"}, Val: "true"}},
+			exp: pb.SetRequest{
+				Prefix: &pb.Path{Target: "JPE1234567"},
+				Replace: []*pb.Update{{
+					Path: pathFoo,
+					Val: &pb.TypedValue{
+						Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("true")}},
+				}},
+			},
+		},
+	}
+
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			got, err := newSetRequest(tc.setOps)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if diff := test.Diff(tc.exp, *got); diff != "" {
+				t.Errorf("unexpected diff: %s", diff)
+			}
+		})
+	}
+}
+
+func TestStrUpdateVal(t *testing.T) {
+	anyBytes, err := proto.Marshal(&pb.ModelData{Name: "foobar"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	anyMessage := &any.Any{TypeUrl: "gnmi/ModelData", Value: anyBytes}
+	anyString := proto.CompactTextString(anyMessage)
+
+	for name, tc := range map[string]struct {
+		update *pb.Update
+		exp    string
+	}{
+		"JSON Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte(`{"foo":"bar"}`),
+					Type:  pb.Encoding_JSON}},
+			exp: `{"foo":"bar"}`,
+		},
+		"JSON_IETF Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte(`{"foo":"bar"}`),
+					Type:  pb.Encoding_JSON_IETF}},
+			exp: `{"foo":"bar"}`,
+		},
+		"BYTES Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte{0xde, 0xad},
+					Type:  pb.Encoding_BYTES}},
+			exp: "3q0=",
+		},
+		"PROTO Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte{0xde, 0xad},
+					Type:  pb.Encoding_PROTO}},
+			exp: "3q0=",
+		},
+		"ASCII Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte("foobar"),
+					Type:  pb.Encoding_ASCII}},
+			exp: "foobar",
+		},
+		"INVALID Value": {
+			update: &pb.Update{
+				Value: &pb.Value{
+					Value: []byte("foobar"),
+					Type:  pb.Encoding(42)}},
+			exp: "foobar",
+		},
+		"StringVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_StringVal{StringVal: "foobar"}}},
+			exp: "foobar",
+		},
+		"IntVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_IntVal{IntVal: -42}}},
+			exp: "-42",
+		},
+		"UintVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_UintVal{UintVal: 42}}},
+			exp: "42",
+		},
+		"BoolVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_BoolVal{BoolVal: true}}},
+			exp: "true",
+		},
+		"BytesVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_BytesVal{BytesVal: []byte{0xde, 0xad}}}},
+			exp: "3q0=",
+		},
+		"FloatVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_FloatVal{FloatVal: 3.14}}},
+			exp: "3.14",
+		},
+		"DecimalVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_DecimalVal{
+					DecimalVal: &pb.Decimal64{Digits: 314, Precision: 2},
+				}}},
+			exp: "3.14",
+		},
+		"LeafListVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_LeaflistVal{
+					LeaflistVal: &pb.ScalarArray{Element: []*pb.TypedValue{
+						{Value: &pb.TypedValue_BoolVal{BoolVal: true}},
+						{Value: &pb.TypedValue_AsciiVal{AsciiVal: "foobar"}},
+					}},
+				}}},
+			exp: "[true, foobar]",
+		},
+		"AnyVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_AnyVal{AnyVal: anyMessage}}},
+			exp: anyString,
+		},
+		"JsonVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":"bar"}`)}}},
+			exp: `{"foo":"bar"}`,
+		},
+		"JsonVal_complex": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":"bar","baz":"qux"}`)}}},
+			exp: `{
+  "foo": "bar",
+  "baz": "qux"
+}`,
+		},
+		"JsonIetfVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte(`{"foo":"bar"}`)}}},
+			exp: `{"foo":"bar"}`,
+		},
+		"AsciiVal": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_AsciiVal{AsciiVal: "foobar"}}},
+			exp: "foobar",
+		},
+		"ProtoBytes": {
+			update: &pb.Update{Val: &pb.TypedValue{
+				Value: &pb.TypedValue_ProtoBytes{ProtoBytes: anyBytes}}},
+			exp: "CgZmb29iYXI=",
+		},
+	} {
+		t.Run(name, func(t *testing.T) {
+			got := StrUpdateVal(tc.update)
+			if got != tc.exp {
+				t.Errorf("Expected: %q Got: %q", tc.exp, got)
+			}
+		})
+	}
+}
+
+func TestTypedValue(t *testing.T) {
+	for tname, tcase := range map[string]struct {
+		in  interface{}
+		exp *pb.TypedValue
+	}{
+		"string": {
+			in:  "foo",
+			exp: &pb.TypedValue{Value: &pb.TypedValue_StringVal{StringVal: "foo"}},
+		},
+		"int": {
+			in:  42,
+			exp: &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: 42}},
+		},
+		"int64": {
+			in:  int64(42),
+			exp: &pb.TypedValue{Value: &pb.TypedValue_IntVal{IntVal: 42}},
+		},
+		"uint": {
+			in:  uint(42),
+			exp: &pb.TypedValue{Value: &pb.TypedValue_UintVal{UintVal: 42}},
+		},
+		"bool": {
+			in:  true,
+			exp: &pb.TypedValue{Value: &pb.TypedValue_BoolVal{BoolVal: true}},
+		},
+		"slice": {
+			in: []interface{}{"foo", 1, uint(2), true},
+			exp: &pb.TypedValue{Value: &pb.TypedValue_LeaflistVal{LeaflistVal: &pb.ScalarArray{
+				Element: []*pb.TypedValue{
+					{Value: &pb.TypedValue_StringVal{StringVal: "foo"}},
+					{Value: &pb.TypedValue_IntVal{IntVal: 1}},
+					{Value: &pb.TypedValue_UintVal{UintVal: 2}},
+					{Value: &pb.TypedValue_BoolVal{BoolVal: true}},
+				}}}},
+		},
+	} {
+		t.Run(tname, func(t *testing.T) {
+			if got := TypedValue(tcase.in); !test.DeepEqual(got, tcase.exp) {
+				t.Errorf("Expected: %q Got: %q", tcase.exp, got)
+			}
+		})
+	}
+}
+
+func TestExtractJSON(t *testing.T) {
+	jsonFile, err := ioutil.TempFile("", "extractJSON")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(jsonFile.Name())
+	if _, err := jsonFile.Write([]byte(`"jsonFile"`)); err != nil {
+		jsonFile.Close()
+		t.Fatal(err)
+	}
+	if err := jsonFile.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	for val, exp := range map[string][]byte{
+		jsonFile.Name(): []byte(`"jsonFile"`),
+		"foobar":        []byte(`"foobar"`),
+		`"foobar"`:      []byte(`"foobar"`),
+		"Val: true":     []byte(`"Val: true"`),
+		"host42":        []byte(`"host42"`),
+		"42":            []byte("42"),
+		"-123.43":       []byte("-123.43"),
+		"0xFFFF":        []byte("0xFFFF"),
+		// Int larger than can fit in 32 bits should be quoted
+		"0x8000000000":  []byte(`"0x8000000000"`),
+		"-0x8000000000": []byte(`"-0x8000000000"`),
+		"true":          []byte("true"),
+		"false":         []byte("false"),
+		"null":          []byte("null"),
+		"{true: 42}":    []byte("{true: 42}"),
+		"[]":            []byte("[]"),
+	} {
+		t.Run(val, func(t *testing.T) {
+			got := extractJSON(val)
+			if !bytes.Equal(exp, got) {
+				t.Errorf("Unexpected diff. Expected: %q Got: %q", exp, got)
+			}
+		})
+	}
+}
+
+func TestExtractValue(t *testing.T) {
+	cases := []struct {
+		in  *pb.Update
+		exp interface{}
+	}{{
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_StringVal{StringVal: "foo"}}},
+		exp: "foo",
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_IntVal{IntVal: 123}}},
+		exp: int64(123),
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_UintVal{UintVal: 123}}},
+		exp: uint64(123),
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_BoolVal{BoolVal: true}}},
+		exp: true,
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_BytesVal{BytesVal: []byte{0xde, 0xad}}}},
+		exp: []byte{0xde, 0xad},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_FloatVal{FloatVal: -12.34}}},
+		exp: float32(-12.34),
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_DecimalVal{DecimalVal: &pb.Decimal64{
+				Digits: -1234, Precision: 2}}}},
+		exp: &pb.Decimal64{Digits: -1234, Precision: 2},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_LeaflistVal{LeaflistVal: &pb.ScalarArray{
+				Element: []*pb.TypedValue{
+					{Value: &pb.TypedValue_StringVal{StringVal: "foo"}},
+					{Value: &pb.TypedValue_IntVal{IntVal: 123}}}}}}},
+		exp: []interface{}{"foo", int64(123)},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`12.34`)}}},
+		exp: json.Number("12.34"),
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`[12.34, 123, "foo"]`)}}},
+		exp: []interface{}{json.Number("12.34"), json.Number("123"), "foo"},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":"bar"}`)}}},
+		exp: map[string]interface{}{"foo": "bar"},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":45.67}`)}}},
+		exp: map[string]interface{}{"foo": json.Number("45.67")},
+	}, {
+		in: &pb.Update{Val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte(`{"foo":"bar"}`)}}},
+		exp: map[string]interface{}{"foo": "bar"},
+	}}
+	for _, tc := range cases {
+		out, err := ExtractValue(tc.in)
+		if err != nil {
+			t.Errorf(err.Error())
+		}
+		if !test.DeepEqual(tc.exp, out) {
+			t.Errorf("Extracted value is incorrect. Expected %+v, got %+v", tc.exp, out)
+		}
+	}
+}
diff --git a/forks/goarista/gnmi/path.go b/forks/goarista/gnmi/path.go
new file mode 100644
index 0000000000000000000000000000000000000000..00280a8fc5924785e036e8daf7e9e187ec8a0406
--- /dev/null
+++ b/forks/goarista/gnmi/path.go
@@ -0,0 +1,251 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+// nextTokenIndex returns the end index of the first token.
+func nextTokenIndex(path string) int {
+	var inBrackets bool
+	var escape bool
+	for i, c := range path {
+		switch c {
+		case '[':
+			inBrackets = true
+			escape = false
+		case ']':
+			if !escape {
+				inBrackets = false
+			}
+			escape = false
+		case '\\':
+			escape = !escape
+		case '/':
+			if !inBrackets && !escape {
+				return i
+			}
+			escape = false
+		default:
+			escape = false
+		}
+	}
+	return len(path)
+}
+
+// SplitPath splits a gnmi path according to the spec. See
+// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-path-conventions.md
+// No validation is done. Behavior is undefined if path is an invalid
+// gnmi path. TODO: Do validation?
+func SplitPath(path string) []string {
+	var result []string
+	if len(path) > 0 && path[0] == '/' {
+		path = path[1:]
+	}
+	for len(path) > 0 {
+		i := nextTokenIndex(path)
+		result = append(result, path[:i])
+		path = path[i:]
+		if len(path) > 0 && path[0] == '/' {
+			path = path[1:]
+		}
+	}
+	return result
+}
+
+// SplitPaths splits multiple gnmi paths
+func SplitPaths(paths []string) [][]string {
+	out := make([][]string, len(paths))
+	for i, path := range paths {
+		out[i] = SplitPath(path)
+	}
+	return out
+}
+
+// StrPath builds a human-readable form of a gnmi path.
+// e.g. /a/b/c[e=f]
+func StrPath(path *pb.Path) string {
+	if path == nil {
+		return "/"
+	} else if len(path.Elem) != 0 {
+		return strPathV04(path)
+	} else if len(path.Element) != 0 {
+		return strPathV03(path)
+	}
+	return "/"
+}
+
+// strPathV04 handles the v0.4 gnmi and later path.Elem member.
+func strPathV04(path *pb.Path) string {
+	b := &strings.Builder{}
+	for _, elm := range path.Elem {
+		b.WriteRune('/')
+		writeSafeString(b, elm.Name, '/')
+		if len(elm.Key) > 0 {
+			// Sort the keys so that they print in a conistent
+			// order. We don't have the YANG AST information, so the
+			// best we can do is sort them alphabetically.
+			keys := make([]string, 0, len(elm.Key))
+			for k := range elm.Key {
+				keys = append(keys, k)
+			}
+			sort.Strings(keys)
+			for _, k := range keys {
+				b.WriteRune('[')
+				b.WriteString(k)
+				b.WriteRune('=')
+				writeSafeString(b, elm.Key[k], ']')
+				b.WriteRune(']')
+			}
+		}
+	}
+	return b.String()
+}
+
+// strPathV03 handles the v0.3 gnmi and earlier path.Element member.
+func strPathV03(path *pb.Path) string {
+	return "/" + strings.Join(path.Element, "/")
+}
+
+// upgradePath modernizes a Path by translating the contents of the Element field to Elem
+func upgradePath(path *pb.Path) *pb.Path {
+	if len(path.Elem) == 0 {
+		var elems []*pb.PathElem
+		for _, element := range path.Element {
+			n, keys, _ := parseElement(element)
+			elems = append(elems, &pb.PathElem{Name: n, Key: keys})
+		}
+		path.Elem = elems
+		path.Element = nil
+	}
+	return path
+}
+
+// JoinPaths joins multiple gnmi paths and returns a string representation
+func JoinPaths(paths ...*pb.Path) *pb.Path {
+	var elems []*pb.PathElem
+	for _, path := range paths {
+		path = upgradePath(path)
+		elems = append(elems, path.Elem...)
+	}
+	return &pb.Path{Elem: elems}
+}
+
+func writeSafeString(b *strings.Builder, s string, esc rune) {
+	for _, c := range s {
+		if c == esc || c == '\\' {
+			b.WriteRune('\\')
+		}
+		b.WriteRune(c)
+	}
+}
+
+// ParseGNMIElements builds up a gnmi path, from user-supplied text
+func ParseGNMIElements(elms []string) (*pb.Path, error) {
+	var parsed []*pb.PathElem
+	for _, e := range elms {
+		n, keys, err := parseElement(e)
+		if err != nil {
+			return nil, err
+		}
+		parsed = append(parsed, &pb.PathElem{Name: n, Key: keys})
+	}
+	return &pb.Path{
+		Element: elms, // Backwards compatibility with pre-v0.4 gnmi
+		Elem:    parsed,
+	}, nil
+}
+
+// parseElement parses a path element, according to the gNMI specification. See
+// https://github.com/openconfig/reference/blame/master/rpc/gnmi/gnmi-path-conventions.md
+//
+// It returns the first string (the current element name), and an optional map of key name
+// value pairs.
+func parseElement(pathElement string) (string, map[string]string, error) {
+	// First check if there are any keys, i.e. do we have at least one '[' in the element
+	name, keyStart := findUnescaped(pathElement, '[')
+	if keyStart < 0 {
+		return name, nil, nil
+	}
+
+	// Error if there is no element name or if the "[" is at the beginning of the path element
+	if len(name) == 0 {
+		return "", nil, fmt.Errorf("failed to find element name in %q", pathElement)
+	}
+
+	// Look at the keys now.
+	keys := make(map[string]string)
+	keyPart := pathElement[keyStart:]
+	for keyPart != "" {
+		k, v, nextKey, err := parseKey(keyPart)
+		if err != nil {
+			return "", nil, err
+		}
+		keys[k] = v
+		keyPart = nextKey
+	}
+	return name, keys, nil
+}
+
+// parseKey returns the key name, key value and the remaining string to be parsed,
+func parseKey(s string) (string, string, string, error) {
+	if s[0] != '[' {
+		return "", "", "", fmt.Errorf("failed to find opening '[' in %q", s)
+	}
+	k, iEq := findUnescaped(s[1:], '=')
+	if iEq < 0 {
+		return "", "", "", fmt.Errorf("failed to find '=' in %q", s)
+	}
+	if k == "" {
+		return "", "", "", fmt.Errorf("failed to find key name in %q", s)
+	}
+
+	rhs := s[1+iEq+1:]
+	v, iClosBr := findUnescaped(rhs, ']')
+	if iClosBr < 0 {
+		return "", "", "", fmt.Errorf("failed to find ']' in %q", s)
+	}
+	if v == "" {
+		return "", "", "", fmt.Errorf("failed to find key value in %q", s)
+	}
+
+	next := rhs[iClosBr+1:]
+	return k, v, next, nil
+}
+
+// findUnescaped will return the index of the first unescaped match of 'find', and the unescaped
+// string leading up to it.
+func findUnescaped(s string, find byte) (string, int) {
+	// Take a fast track if there are no escape sequences
+	if strings.IndexByte(s, '\\') == -1 {
+		i := strings.IndexByte(s, find)
+		if i < 0 {
+			return s, -1
+		}
+		return s[:i], i
+	}
+
+	// Find the first match, taking care of escaped chars.
+	var b strings.Builder
+	var i int
+	len := len(s)
+	for i = 0; i < len; {
+		ch := s[i]
+		if ch == find {
+			return b.String(), i
+		} else if ch == '\\' && i < len-1 {
+			i++
+			ch = s[i]
+		}
+		b.WriteByte(ch)
+		i++
+	}
+	return b.String(), -1
+}
diff --git a/forks/goarista/gnmi/path_test.go b/forks/goarista/gnmi/path_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..27318b65c10a64949326995b727347d3f5de211a
--- /dev/null
+++ b/forks/goarista/gnmi/path_test.go
@@ -0,0 +1,308 @@
+// Copyright (c) 2017 Arista Networks, Inc.
+// Use of this source code is governed by the Apache License 2.0
+// that can be found in the COPYING file.
+
+package gnmi
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/aristanetworks/goarista/test"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+func p(s ...string) []string {
+	return s
+}
+
+func TestSplitPath(t *testing.T) {
+	for i, tc := range []struct {
+		in  string
+		exp []string
+	}{{
+		in:  "/foo/bar",
+		exp: p("foo", "bar"),
+	}, {
+		in:  "/foo/bar/",
+		exp: p("foo", "bar"),
+	}, {
+		in:  "//foo//bar//",
+		exp: p("", "foo", "", "bar", ""),
+	}, {
+		in:  "/foo[name=///]/bar",
+		exp: p("foo[name=///]", "bar"),
+	}, {
+		in:  `/foo[name=[\\\]/]/bar`,
+		exp: p(`foo[name=[\\\]/]`, "bar"),
+	}, {
+		in:  `/foo[name=[\\]/bar`,
+		exp: p(`foo[name=[\\]`, "bar"),
+	}, {
+		in:  "/foo[a=1][b=2]/bar",
+		exp: p("foo[a=1][b=2]", "bar"),
+	}, {
+		in:  "/foo[a=1\\]2][b=2]/bar",
+		exp: p("foo[a=1\\]2][b=2]", "bar"),
+	}, {
+		in:  "/foo[a=1][b=2]/bar\\baz",
+		exp: p("foo[a=1][b=2]", "bar\\baz"),
+	}} {
+		got := SplitPath(tc.in)
+		if !test.DeepEqual(tc.exp, got) {
+			t.Errorf("[%d] unexpect split for %q. Expected: %v, Got: %v",
+				i, tc.in, tc.exp, got)
+		}
+	}
+}
+
+func TestStrPath(t *testing.T) {
+	for i, tc := range []struct {
+		path string
+	}{{
+		path: "/",
+	}, {
+		path: "/foo/bar",
+	}, {
+		path: "/foo[name=a]/bar",
+	}, {
+		path: "/foo[a=1][b=2]/bar",
+	}, {
+		path: "/foo[a=1\\]2][b=2]/bar",
+	}, {
+		path: "/foo[a=1][b=2]/bar\\/baz",
+	}} {
+		sElms := SplitPath(tc.path)
+		pbPath, err := ParseGNMIElements(sElms)
+		if err != nil {
+			t.Errorf("failed to parse %s: %s", sElms, err)
+		}
+		s := StrPath(pbPath)
+		if !test.DeepEqual(tc.path, s) {
+			t.Errorf("[%d] want %s, got %s", i, tc.path, s)
+		}
+	}
+}
+
+func TestStrPathBackwardsCompat(t *testing.T) {
+	for i, tc := range []struct {
+		path *pb.Path
+		str  string
+	}{{
+		path: &pb.Path{
+			Element: p("foo[a=1][b=2]", "bar"),
+		},
+		str: "/foo[a=1][b=2]/bar",
+	}} {
+		got := StrPath(tc.path)
+		if got != tc.str {
+			t.Errorf("[%d] want %q, got %q", i, tc.str, got)
+		}
+	}
+}
+
+func TestParseElement(t *testing.T) {
+	// test cases
+	cases := []struct {
+		// name is the name of the test useful if you want to run a single test
+		// from the command line -run TestParseElement/<name>
+		name string
+		// in is the path element to be parsed
+		in string
+		// fieldName is field name (YANG node name) expected to be parsed from the path element.
+		// Normally this is simply the path element, or if the path element contains keys this is
+		// the text before the first [
+		fieldName string
+		// keys is a map of the expected key value pairs from within the []s in the
+		// `path element.
+		//
+		// For example prefix[ip-prefix=10.0.0.0/24][masklength-range=26..28]
+		// fieldName would be "prefix"
+		// keys would be {"ip-prefix": "10.0.0.0/24", "masklength-range": "26..28"}
+		keys map[string]string
+		// expectedError is the exact error we expect.
+		expectedError error
+	}{{
+		name:      "no_elms",
+		in:        "hello",
+		fieldName: "hello",
+	}, {
+		name:          "single_open",
+		in:            "[",
+		expectedError: fmt.Errorf("failed to find element name in %q", "["),
+	}, {
+		name:          "no_equal_no_close",
+		in:            "hello[there",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[there"),
+	}, {
+		name:          "no_equals",
+		in:            "hello[there]",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[there]"),
+	}, {
+		name:          "no_left_side",
+		in:            "hello[=there]",
+		expectedError: fmt.Errorf("failed to find key name in %q", "[=there]"),
+	}, {
+		name:          "no_right_side",
+		in:            "hello[there=]",
+		expectedError: fmt.Errorf("failed to find key value in %q", "[there=]"),
+	}, {
+		name:          "hanging_escape",
+		in:            "hello[there\\",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[there\\"),
+	}, {
+		name:      "single_name_value",
+		in:        "hello[there=where]",
+		fieldName: "hello",
+		keys:      map[string]string{"there": "where"},
+	}, {
+		name:      "single_value_with=",
+		in:        "hello[there=whe=r=e]",
+		fieldName: "hello",
+		keys:      map[string]string{"there": "whe=r=e"},
+	}, {
+		name:      "single_value_with=_and_escaped_]",
+		in:        `hello[there=whe=\]r=e]`,
+		fieldName: "hello",
+		keys:      map[string]string{"there": `whe=]r=e`},
+	}, {
+		name:      "single_value_with[",
+		in:        "hello[there=w[[here]",
+		fieldName: "hello",
+		keys:      map[string]string{"there": "w[[here"},
+	}, {
+		name:          "value_single_open",
+		in:            "hello[first=value][",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "["),
+	}, {
+		name:          "value_no_close",
+		in:            "hello[there=where][somename",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[somename"),
+	}, {
+		name:          "value_no_equals",
+		in:            "hello[there=where][somename]",
+		expectedError: fmt.Errorf("failed to find '=' in %q", "[somename]"),
+	}, {
+		name:          "no_left_side",
+		in:            "hello[there=where][=somevalue]",
+		expectedError: fmt.Errorf("failed to find key name in %q", "[=somevalue]"),
+	}, {
+		name:          "no_right_side",
+		in:            "hello[there=where][somename=]",
+		expectedError: fmt.Errorf("failed to find key value in %q", "[somename=]"),
+	}, {
+		name:      "two_name_values",
+		in:        "hello[there=where][somename=somevalue]",
+		fieldName: "hello",
+		keys:      map[string]string{"there": "where", "somename": "somevalue"},
+	}, {
+		name:      "three_name_values",
+		in:        "hello[there=where][somename=somevalue][anothername=value]",
+		fieldName: "hello",
+		keys: map[string]string{"there": "where", "somename": "somevalue",
+			"anothername": "value"},
+	}, {
+		name:      "aserisk_value",
+		in:        "hello[there=*][somename=somevalue][anothername=value]",
+		fieldName: "hello",
+		keys: map[string]string{"there": "*", "somename": "somevalue",
+			"anothername": "value"},
+	}}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			fieldName, keys, err := parseElement(tc.in)
+			if !test.DeepEqual(tc.expectedError, err) {
+				t.Fatalf("[%s] expected err %#v, got %#v", tc.name, tc.expectedError, err)
+			}
+			if !test.DeepEqual(tc.keys, keys) {
+				t.Fatalf("[%s] expected output %#v, got %#v", tc.name, tc.keys, keys)
+			}
+			if tc.fieldName != fieldName {
+				t.Fatalf("[%s] expected field name %s, got %s", tc.name, tc.fieldName, fieldName)
+			}
+		})
+	}
+}
+
+func strToPath(pathStr string) *pb.Path {
+	splitPath := SplitPath(pathStr)
+	path, _ := ParseGNMIElements(splitPath)
+	path.Element = nil
+	return path
+}
+
+func strsToPaths(pathStrs []string) []*pb.Path {
+	var paths []*pb.Path
+	for _, splitPath := range SplitPaths(pathStrs) {
+		path, _ := ParseGNMIElements(splitPath)
+		path.Element = nil
+		paths = append(paths, path)
+	}
+	return paths
+}
+
+func TestJoinPath(t *testing.T) {
+	cases := []struct {
+		paths []*pb.Path
+		exp   string
+	}{{
+		paths: strsToPaths([]string{"/foo/bar", "/baz/qux"}),
+		exp:   "/foo/bar/baz/qux",
+	},
+		{
+			paths: strsToPaths([]string{
+				"/foo/bar[somekey=someval][otherkey=otherval]", "/baz/qux"}),
+			exp: "/foo/bar[otherkey=otherval][somekey=someval]/baz/qux",
+		},
+		{
+			paths: strsToPaths([]string{
+				"/foo/bar[somekey=someval][otherkey=otherval]",
+				"/baz/qux[somekey=someval][otherkey=otherval]"}),
+			exp: "/foo/bar[otherkey=otherval][somekey=someval]/" +
+				"baz/qux[otherkey=otherval][somekey=someval]",
+		},
+		{
+			paths: []*pb.Path{
+				{Element: []string{"foo", "bar[somekey=someval][otherkey=otherval]"}},
+				{Element: []string{"baz", "qux[somekey=someval][otherkey=otherval]"}}},
+			exp: "/foo/bar[somekey=someval][otherkey=otherval]/" +
+				"baz/qux[somekey=someval][otherkey=otherval]",
+		},
+	}
+
+	for _, tc := range cases {
+		got := JoinPaths(tc.paths...)
+		exp := strToPath(tc.exp)
+		exp.Element = nil
+		if !test.DeepEqual(got, exp) {
+			t.Fatalf("ERROR!\n Got: %s,\n Want %s\n", got, exp)
+		}
+	}
+}
+
+func BenchmarkPathElementToSigleElementName(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _, _ = parseElement("hello")
+	}
+}
+
+func BenchmarkPathElementTwoKeys(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _, _ = parseElement("hello[hello=world][bye=moon]")
+	}
+}
+
+func BenchmarkPathElementBadKeys(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _, _ = parseElement("hello[hello=world][byemoon]")
+	}
+}
+
+func BenchmarkPathElementMaxKeys(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _, _ = parseElement("hello[name=firstName][name=secondName][name=thirdName]" +
+			"[name=fourthName][name=fifthName][name=sixthName]")
+	}
+}
diff --git a/forks/google/gnmi/model.go b/forks/google/gnmi/model.go
new file mode 100644
index 0000000000000000000000000000000000000000..cf704a545dda6c5f0ca8bc367cfcce93220da010
--- /dev/null
+++ b/forks/google/gnmi/model.go
@@ -0,0 +1,79 @@
+/* Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package gnmi
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"sort"
+
+	"github.com/openconfig/goyang/pkg/yang"
+	"github.com/openconfig/ygot/ygot"
+	"github.com/openconfig/ygot/ytypes"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+// JSONUnmarshaler is the signature of the Unmarshal() function in the GoStruct code generated by openconfig ygot library.
+type JSONUnmarshaler func([]byte, ygot.GoStruct, ...ytypes.UnmarshalOpt) error
+
+// GoStructEnumData is the data type to maintain GoStruct enum type.
+type GoStructEnumData map[string]map[int64]ygot.EnumDefinition
+
+// Model contains the model data and GoStruct information for the device to config.
+type Model struct {
+	modelData       []*pb.ModelData
+	structRootType  reflect.Type
+	schemaTreeRoot  *yang.Entry
+	jsonUnmarshaler JSONUnmarshaler
+	enumData        GoStructEnumData
+}
+
+// NewModel returns an instance of Model struct.
+func NewModel(m []*pb.ModelData, t reflect.Type, r *yang.Entry, f JSONUnmarshaler, e GoStructEnumData) *Model {
+	return &Model{
+		modelData:       m,
+		structRootType:  t,
+		schemaTreeRoot:  r,
+		jsonUnmarshaler: f,
+		enumData:        e,
+	}
+}
+
+func (m *Model) newRootValue() interface{} {
+	return reflect.New(m.structRootType.Elem()).Interface()
+}
+
+// NewConfigStruct creates a ValidatedGoStruct of this model from jsonConfig. If jsonConfig is nil, creates an empty GoStruct.
+func (m *Model) NewConfigStruct(jsonConfig []byte) (ygot.ValidatedGoStruct, error) {
+	rootStruct, ok := m.newRootValue().(ygot.ValidatedGoStruct)
+	if !ok {
+		return nil, errors.New("root node is not a ygot.ValidatedGoStruct")
+	}
+
+	return rootStruct, nil
+}
+
+// SupportedModels returns a list of supported models.
+func (m *Model) SupportedModels() []string {
+	mDesc := make([]string, len(m.modelData))
+	for i, m := range m.modelData {
+		mDesc[i] = fmt.Sprintf("%s %s", m.Name, m.Version)
+	}
+	sort.Strings(mDesc)
+	return mDesc
+}
diff --git a/forks/google/gnmi/server.go b/forks/google/gnmi/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..21e54686916bfab859bd4acf18e6412516c7603f
--- /dev/null
+++ b/forks/google/gnmi/server.go
@@ -0,0 +1,602 @@
+/* Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package gnmi implements a gnmi server to mock a device with YANG models.
+package gnmi
+
+import (
+	"bytes"
+	"compress/gzip"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"reflect"
+	"strconv"
+	"sync"
+	"time"
+
+	"golang.org/x/net/context"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/openconfig/gnmi/value"
+	"github.com/openconfig/ygot/util"
+	"github.com/openconfig/ygot/ygot"
+	"github.com/openconfig/ygot/ytypes"
+	log "github.com/sirupsen/logrus"
+
+	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+// ConfigCallback is the signature of the function to apply a validated config to the physical device.
+type ConfigCallback func(ygot.ValidatedGoStruct) error
+
+var (
+	pbRootPath         = &pb.Path{}
+	supportedEncodings = []pb.Encoding{pb.Encoding_PROTO, pb.Encoding_JSON_IETF, pb.Encoding_JSON}
+)
+
+// Server struct maintains the data structure for device config and implements the interface of gnmi server.
+// It supports Capabilities, Get, and Set APIs.
+// Typical usage:
+//	g := grpc.NewServer()
+//	s, err := Server.NewServer(model, config, callback)
+//	pb.NewServer(g, s)
+//	reflection.Register(g)
+//	listen, err := net.Listen("tcp", ":8080")
+//	g.Serve(listen)
+//
+// For a real device, apply the config changes to the hardware in the callback function.
+// Arguments:
+//		newConfig: new root config to be applied on the device.
+
+type Server struct {
+	model    *Model
+	callback ConfigCallback
+
+	config ygot.ValidatedGoStruct
+	mu     sync.RWMutex // mu is the RW lock to protect the access to config
+}
+
+// NewServer creates an instance of Server with given json config.
+func NewServer(model *Model, config []byte, callback ConfigCallback) (*Server, error) {
+	rootStruct, err := model.NewConfigStruct(config)
+	if err != nil {
+		return nil, err
+	}
+	s := &Server{
+		model:    model,
+		config:   rootStruct,
+		callback: callback,
+	}
+	if config != nil && s.callback != nil {
+		if err := s.callback(rootStruct); err != nil {
+			return nil, err
+		}
+	}
+	return s, nil
+}
+
+// checkEncodingAndModel checks whether encoding and models are supported by the server. Return error if anything is unsupported.
+func (s *Server) checkEncodingAndModel(encoding pb.Encoding, models []*pb.ModelData) error {
+	hasSupportedEncoding := false
+	for _, supportedEncoding := range supportedEncodings {
+		if encoding == supportedEncoding {
+			hasSupportedEncoding = true
+			break
+		}
+	}
+	if !hasSupportedEncoding {
+		return fmt.Errorf("unsupported encoding: %s", pb.Encoding_name[int32(encoding)])
+	}
+	for _, m := range models {
+		isSupported := false
+		for _, supportedModel := range s.model.modelData {
+			if reflect.DeepEqual(m, supportedModel) {
+				isSupported = true
+				break
+			}
+		}
+		if !isSupported {
+			return fmt.Errorf("unsupported model: %v", m)
+		}
+	}
+	return nil
+}
+
+// doDelete deletes the path from the json tree if the path exists. If success,
+// it calls the callback function to apply the change to the device hardware.
+func (s *Server) doDelete(jsonTree map[string]interface{}, prefix, path *pb.Path) (*pb.UpdateResult, error) {
+	// Update json tree of the device config
+	var curNode interface{} = jsonTree
+	pathDeleted := false
+	fullPath := gnmiFullPath(prefix, path)
+	schema := s.model.schemaTreeRoot
+	for i, elem := range fullPath.Elem { // Delete sub-tree or leaf node.
+		node, ok := curNode.(map[string]interface{})
+		if !ok {
+			break
+		}
+
+		// Delete node
+		if i == len(fullPath.Elem)-1 {
+			if elem.GetKey() == nil {
+				delete(node, elem.Name)
+				pathDeleted = true
+				break
+			}
+			pathDeleted = deleteKeyedListEntry(node, elem)
+			break
+		}
+
+		if curNode, schema = getChildNode(node, schema, elem, false); curNode == nil {
+			break
+		}
+	}
+	if reflect.DeepEqual(fullPath, pbRootPath) { // Delete root
+		for k := range jsonTree {
+			delete(jsonTree, k)
+		}
+	}
+
+	// Apply the validated operation to the config tree and device.
+	if pathDeleted {
+		newConfig, err := s.toGoStruct(jsonTree)
+		if err != nil {
+			return nil, status.Error(codes.Internal, err.Error())
+		}
+		if s.callback != nil {
+			if applyErr := s.callback(newConfig); applyErr != nil {
+				if rollbackErr := s.callback(s.config); rollbackErr != nil {
+					return nil, status.Errorf(codes.Internal, "error in rollback the failed operation (%v): %v", applyErr, rollbackErr)
+				}
+				return nil, status.Errorf(codes.Aborted, "error in applying operation to device: %v", applyErr)
+			}
+		}
+	}
+	return &pb.UpdateResult{
+		Path: path,
+		Op:   pb.UpdateResult_DELETE,
+	}, nil
+}
+
+// doReplaceOrUpdate validates the replace or update operation to be applied to
+// the device, modifies the json tree of the config struct, then calls the
+// callback function to apply the operation to the device hardware.
+func (s *Server) doReplaceOrUpdate(jsonTree map[string]interface{}, op pb.UpdateResult_Operation, prefix, path *pb.Path, val *pb.TypedValue) (*pb.UpdateResult, error) {
+	// Validate the operation.
+	fullPath := gnmiFullPath(prefix, path)
+	emptyNode, _, err := ytypes.GetOrCreateNode(s.model.schemaTreeRoot, s.model.newRootValue(), fullPath)
+	if err != nil {
+		return nil, status.Errorf(codes.NotFound, "path %v is not found in the config structure: %v", fullPath, err)
+	}
+	var nodeVal interface{}
+	nodeStruct, ok := emptyNode.(ygot.ValidatedGoStruct)
+	if ok {
+		if err := s.model.jsonUnmarshaler(val.GetJsonIetfVal(), nodeStruct); err != nil {
+			return nil, status.Errorf(codes.InvalidArgument, "unmarshaling json data to config struct fails: %v", err)
+		}
+		if err := nodeStruct.ΛValidate(); err != nil {
+			return nil, status.Errorf(codes.InvalidArgument, "config data validation fails: %v", err)
+		}
+		var err error
+		if nodeVal, err = ygot.ConstructIETFJSON(nodeStruct, &ygot.RFC7951JSONConfig{}); err != nil {
+			msg := fmt.Sprintf("error in constructing IETF JSON tree from config struct: %v", err)
+			log.Error(msg)
+			return nil, status.Error(codes.Internal, msg)
+		}
+	} else {
+		var err error
+		if nodeVal, err = value.ToScalar(val); err != nil {
+			return nil, status.Errorf(codes.Internal, "cannot convert leaf node to scalar type: %v", err)
+		}
+	}
+
+	// Update json tree of the device config.
+	var curNode interface{} = jsonTree
+	schema := s.model.schemaTreeRoot
+	for i, elem := range fullPath.Elem {
+		switch node := curNode.(type) {
+		case map[string]interface{}:
+			// Set node value.
+			if i == len(fullPath.Elem)-1 {
+				if elem.GetKey() == nil {
+					if grpcStatusError := setPathWithoutAttribute(op, node, elem, nodeVal); grpcStatusError != nil {
+						return nil, grpcStatusError
+					}
+					break
+				}
+				if grpcStatusError := setPathWithAttribute(op, node, elem, nodeVal); grpcStatusError != nil {
+					return nil, grpcStatusError
+				}
+				break
+			}
+
+			if curNode, schema = getChildNode(node, schema, elem, true); curNode == nil {
+				return nil, status.Errorf(codes.NotFound, "path elem not found: %v", elem)
+			}
+		case []interface{}:
+			return nil, status.Errorf(codes.NotFound, "incompatible path elem: %v", elem)
+		default:
+			return nil, status.Errorf(codes.Internal, "wrong node type: %T", curNode)
+		}
+	}
+	if reflect.DeepEqual(fullPath, pbRootPath) { // Replace/Update root.
+		if op == pb.UpdateResult_UPDATE {
+			return nil, status.Error(codes.Unimplemented, "update the root of config tree is unsupported")
+		}
+		nodeValAsTree, ok := nodeVal.(map[string]interface{})
+		if !ok {
+			return nil, status.Errorf(codes.InvalidArgument, "expect a tree to replace the root, got a scalar value: %T", nodeVal)
+		}
+		for k := range jsonTree {
+			delete(jsonTree, k)
+		}
+		for k, v := range nodeValAsTree {
+			jsonTree[k] = v
+		}
+	}
+	newConfig, err := s.toGoStruct(jsonTree)
+	if err != nil {
+		return nil, status.Error(codes.Internal, err.Error())
+	}
+
+	// Apply the validated operation to the device.
+	if s.callback != nil {
+		if applyErr := s.callback(newConfig); applyErr != nil {
+			if rollbackErr := s.callback(s.config); rollbackErr != nil {
+				return nil, status.Errorf(codes.Internal, "error in rollback the failed operation (%v): %v", applyErr, rollbackErr)
+			}
+			return nil, status.Errorf(codes.Aborted, "error in applying operation to device: %v", applyErr)
+		}
+	}
+	return &pb.UpdateResult{
+		Path: path,
+		Op:   op,
+	}, nil
+}
+
+func (s *Server) toGoStruct(jsonTree map[string]interface{}) (ygot.ValidatedGoStruct, error) {
+	jsonDump, err := json.Marshal(jsonTree)
+	if err != nil {
+		return nil, fmt.Errorf("error in marshaling IETF JSON tree to bytes: %v", err)
+	}
+	goStruct, err := s.model.NewConfigStruct(jsonDump)
+	if err != nil {
+		return nil, fmt.Errorf("error in creating config struct from IETF JSON data: %v", err)
+	}
+	return goStruct, nil
+}
+
+// getGNMIServiceVersion returns a pointer to the gNMI service version string.
+// The method is non-trivial because of the way it is defined in the proto file.
+func getGNMIServiceVersion() (*string, error) {
+	gzB := (&pb.Update{}).ProtoReflect().Descriptor()
+	r, err := gzip.NewReader(bytes.NewReader([]byte(gzB.Name())))
+	if err != nil {
+		return nil, fmt.Errorf("error in initializing gzip reader: %v", err)
+	}
+	defer r.Close()
+	b, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, fmt.Errorf("error in reading gzip data: %v", err)
+	}
+	desc := &dpb.FileDescriptorProto{}
+	if err := proto.Unmarshal(b, desc); err != nil {
+		return nil, fmt.Errorf("error in unmarshaling proto: %v", err)
+	}
+	ver, err := proto.GetExtension(desc.Options, pb.E_GnmiService)
+	if err != nil {
+		return nil, fmt.Errorf("error in getting version from proto extension: %v", err)
+	}
+	return ver.(*string), nil
+}
+
+// deleteKeyedListEntry deletes the keyed list entry from node that matches the
+// path elem. If the entry is the only one in keyed list, deletes the entire
+// list. If the entry is found and deleted, the function returns true. If it is
+// not found, the function returns false.
+func deleteKeyedListEntry(node map[string]interface{}, elem *pb.PathElem) bool {
+	curNode, ok := node[elem.Name]
+	if !ok {
+		return false
+	}
+
+	keyedList, ok := curNode.([]interface{})
+	if !ok {
+		return false
+	}
+	for i, n := range keyedList {
+		m, ok := n.(map[string]interface{})
+		if !ok {
+			log.Errorf("expect map[string]interface{} for a keyed list entry, got %T", n)
+			return false
+		}
+		keyMatching := true
+		for k, v := range elem.Key {
+			attrVal, ok := m[k]
+			if !ok {
+				return false
+			}
+			if v != fmt.Sprintf("%v", attrVal) {
+				keyMatching = false
+				break
+			}
+		}
+		if keyMatching {
+			listLen := len(keyedList)
+			if listLen == 1 {
+				delete(node, elem.Name)
+				return true
+			}
+			keyedList[i] = keyedList[listLen-1]
+			node[elem.Name] = keyedList[0 : listLen-1]
+			return true
+		}
+	}
+	return false
+}
+
+// gnmiFullPath builds the full path from the prefix and path.
+func gnmiFullPath(prefix, path *pb.Path) *pb.Path {
+	fullPath := &pb.Path{Origin: path.Origin}
+	if path.GetElem() != nil {
+		fullPath.Elem = append(prefix.GetElem(), path.GetElem()...)
+	}
+	return fullPath
+}
+
+// isNIl checks if an interface is nil or its value is nil.
+func isNil(i interface{}) bool {
+	if i == nil {
+		return true
+	}
+	switch kind := reflect.ValueOf(i).Kind(); kind {
+	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+		return reflect.ValueOf(i).IsNil()
+	default:
+		return false
+	}
+}
+
+// setPathWithAttribute replaces or updates a child node of curNode in the IETF
+// JSON config tree, where the child node is indexed by pathElem with attribute.
+// The function returns grpc status error if unsuccessful.
+func setPathWithAttribute(op pb.UpdateResult_Operation, curNode map[string]interface{}, pathElem *pb.PathElem, nodeVal interface{}) error {
+	nodeValAsTree, ok := nodeVal.(map[string]interface{})
+	if !ok {
+		return status.Errorf(codes.InvalidArgument, "expect nodeVal is a json node of map[string]interface{}, received %T", nodeVal)
+	}
+	m := getKeyedListEntry(curNode, pathElem, true)
+	if m == nil {
+		return status.Errorf(codes.NotFound, "path elem not found: %v", pathElem)
+	}
+	if op == pb.UpdateResult_REPLACE {
+		for k := range m {
+			delete(m, k)
+		}
+	}
+	for attrKey, attrVal := range pathElem.GetKey() {
+		m[attrKey] = attrVal
+		if asNum, err := strconv.ParseFloat(attrVal, 64); err == nil {
+			m[attrKey] = asNum
+		}
+		for k, v := range nodeValAsTree {
+			if k == attrKey && fmt.Sprintf("%v", v) != attrVal {
+				return status.Errorf(codes.InvalidArgument, "invalid config data: %v is a path attribute", k)
+			}
+		}
+	}
+	for k, v := range nodeValAsTree {
+		m[k] = v
+	}
+	return nil
+}
+
+// setPathWithoutAttribute replaces or updates a child node of curNode in the
+// IETF config tree, where the child node is indexed by pathElem without
+// attribute. The function returns grpc status error if unsuccessful.
+func setPathWithoutAttribute(op pb.UpdateResult_Operation, curNode map[string]interface{}, pathElem *pb.PathElem, nodeVal interface{}) error {
+	target, hasElem := curNode[pathElem.Name]
+	nodeValAsTree, nodeValIsTree := nodeVal.(map[string]interface{})
+	if op == pb.UpdateResult_REPLACE || !hasElem || !nodeValIsTree {
+		curNode[pathElem.Name] = nodeVal
+		return nil
+	}
+	targetAsTree, ok := target.(map[string]interface{})
+	if !ok {
+		return status.Errorf(codes.Internal, "error in setting path: expect map[string]interface{} to update, got %T", target)
+	}
+	for k, v := range nodeValAsTree {
+		targetAsTree[k] = v
+	}
+	return nil
+}
+
+// Capabilities returns supported encodings and supported models.
+func (s *Server) Capabilities(ctx context.Context, req *pb.CapabilityRequest) (*pb.CapabilityResponse, error) {
+	ver, err := getGNMIServiceVersion()
+	if err != nil {
+		return nil, status.Errorf(codes.Internal, "error in getting gnmi service version: %v", err)
+	}
+	return &pb.CapabilityResponse{
+		SupportedModels:    s.model.modelData,
+		SupportedEncodings: supportedEncodings,
+		GNMIVersion:        *ver,
+	}, nil
+}
+
+// Get implements the Get RPC in gNMI spec.
+func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
+	if req.GetType() != pb.GetRequest_ALL {
+		return nil, status.Errorf(codes.Unimplemented, "unsupported request type: %s", pb.GetRequest_DataType_name[int32(req.GetType())])
+	}
+	if err := s.checkEncodingAndModel(req.GetEncoding(), req.GetUseModels()); err != nil {
+		return nil, status.Error(codes.Unimplemented, err.Error())
+	}
+
+	prefix := req.GetPrefix()
+	paths := req.GetPath()
+	notifications := make([]*pb.Notification, 0)
+
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+
+	for _, path := range paths {
+		// Get schema node for path from config struct.
+		fullPath := path
+		if prefix != nil {
+			fullPath = gnmiFullPath(prefix, path)
+		}
+		if fullPath.GetElem() == nil && fullPath.GetElement() != nil {
+			return nil, status.Error(codes.Unimplemented, "deprecated path element type is unsupported")
+		}
+		opts := []ytypes.GetNodeOpt{&ytypes.GetHandleWildcards{}, &ytypes.GetPartialKeyMatch{}}
+		nodes, err := ytypes.GetNode(s.model.schemaTreeRoot, s.config, fullPath, opts...)
+		if len(nodes) == 0 || err != nil || util.IsValueNil(nodes[0].Data) {
+			return nil, status.Errorf(codes.NotFound, "path %v not found: %v", fullPath, err)
+		}
+		for _, n := range nodes {
+			node := n.Data
+			ts := time.Now().UnixNano()
+
+			nodeStruct, ok := node.(ygot.GoStruct)
+			// Return leaf node.
+			if !ok {
+				var val *pb.TypedValue
+				switch kind := reflect.ValueOf(node).Kind(); kind {
+				case reflect.Ptr, reflect.Interface:
+					var err error
+					val, err = value.FromScalar(reflect.ValueOf(node).Elem().Interface())
+					if err != nil {
+						msg := fmt.Sprintf("leaf node %v does not contain a scalar type value: %v", path, err)
+						log.Error(msg)
+						return nil, status.Error(codes.Internal, msg)
+					}
+				case reflect.Int64:
+					enumMap, ok := s.model.enumData[reflect.TypeOf(node).Name()]
+					if !ok {
+						return nil, status.Error(codes.Internal, "not a GoStruct enumeration type")
+					}
+					val = &pb.TypedValue{
+						Value: &pb.TypedValue_StringVal{
+							StringVal: enumMap[reflect.ValueOf(node).Int()].Name,
+						},
+					}
+				default:
+					return nil, status.Errorf(codes.Internal, "unexpected kind of leaf node type: %v %v", node, kind)
+				}
+
+				update := &pb.Update{Path: path, Val: val}
+				notification := &pb.Notification{
+					Timestamp: ts,
+					Prefix:    prefix,
+					Update:    []*pb.Update{update},
+				}
+				notifications = append(notifications, notification)
+				continue
+			}
+
+			if req.GetUseModels() != nil {
+				return nil, status.Errorf(codes.Unimplemented, "filtering Get using use_models is unsupported, got: %v", req.GetUseModels())
+			}
+
+			nots, err := ygot.TogNMINotifications(nodeStruct, ts, ygot.GNMINotificationsConfig{
+				UsePathElem:       false,
+				StringSlicePrefix: []string{"interfaces", "interface"},
+			})
+
+			if err != nil {
+				return nil, err
+			}
+
+			notifications = append(notifications, nots...)
+
+		}
+	}
+
+	return &pb.GetResponse{Notification: notifications}, nil
+}
+
+// Set implements the Set RPC in gNMI spec.
+func (s *Server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, error) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	jsonTree, err := ygot.ConstructIETFJSON(s.config, &ygot.RFC7951JSONConfig{})
+	if err != nil {
+		msg := fmt.Sprintf("error in constructing IETF JSON tree from config struct: %v", err)
+		log.Error(msg)
+		return nil, status.Error(codes.Internal, msg)
+	}
+
+	prefix := req.GetPrefix()
+	var results []*pb.UpdateResult
+
+	for _, path := range req.GetDelete() {
+		res, grpcStatusError := s.doDelete(jsonTree, prefix, path)
+		if grpcStatusError != nil {
+			return nil, grpcStatusError
+		}
+		results = append(results, res)
+	}
+	for _, upd := range req.GetReplace() {
+		res, grpcStatusError := s.doReplaceOrUpdate(jsonTree, pb.UpdateResult_REPLACE, prefix, upd.GetPath(), upd.GetVal())
+		if grpcStatusError != nil {
+			return nil, grpcStatusError
+		}
+		results = append(results, res)
+	}
+	for _, upd := range req.GetUpdate() {
+		res, grpcStatusError := s.doReplaceOrUpdate(jsonTree, pb.UpdateResult_UPDATE, prefix, upd.GetPath(), upd.GetVal())
+		if grpcStatusError != nil {
+			return nil, grpcStatusError
+		}
+		results = append(results, res)
+	}
+
+	jsonDump, err := json.Marshal(jsonTree)
+	if err != nil {
+		msg := fmt.Sprintf("error in marshaling IETF JSON tree to bytes: %v", err)
+		log.Error(msg)
+		return nil, status.Error(codes.Internal, msg)
+	}
+	rootStruct, err := s.model.NewConfigStruct(jsonDump)
+	if err != nil {
+		msg := fmt.Sprintf("error in creating config struct from IETF JSON data: %v", err)
+		log.Error(msg)
+		return nil, status.Error(codes.Internal, msg)
+	}
+	s.config = rootStruct
+	return &pb.SetResponse{
+		Prefix:   req.GetPrefix(),
+		Response: results,
+	}, nil
+}
+
+// Subscribe method is not implemented.
+func (s *Server) Subscribe(stream pb.GNMI_SubscribeServer) error {
+	return status.Error(codes.Unimplemented, "Subscribe is not implemented.")
+}
+
+// InternalUpdate is an experimental feature to let the server update its
+// internal states. Use it with your own risk.
+func (s *Server) InternalUpdate(fp func(config ygot.ValidatedGoStruct) error) error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	return fp(s.config)
+}
diff --git a/forks/google/gnmi/server_test.go b/forks/google/gnmi/server_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..68ffea696df150f69e722256389f472a875de107
--- /dev/null
+++ b/forks/google/gnmi/server_test.go
@@ -0,0 +1,1161 @@
+/* Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package gnmi
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/openconfig/gnmi/value"
+	"github.com/openconfig/ygot/ygot"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+
+	"github.com/google/gnxi/gnmi/modeldata"
+	"github.com/google/gnxi/gnmi/modeldata/gostruct"
+)
+
+var (
+	// model is the model for test config server.
+	model = &Model{
+		modelData:       modeldata.ModelData,
+		structRootType:  reflect.TypeOf((*gostruct.Device)(nil)),
+		schemaTreeRoot:  gostruct.SchemaTree["Device"],
+		jsonUnmarshaler: gostruct.Unmarshal,
+		enumData:        gostruct.ΛEnum,
+	}
+)
+
+func TestCapabilities(t *testing.T) {
+	s, err := NewServer(model, nil, nil)
+	if err != nil {
+		t.Fatalf("error in creating server: %v", err)
+	}
+	resp, err := s.Capabilities(nil, &pb.CapabilityRequest{})
+	if err != nil {
+		t.Fatalf("got error %v, want nil", err)
+	}
+	if !reflect.DeepEqual(resp.GetSupportedModels(), model.modelData) {
+		t.Errorf("got supported models %v\nare not the same as\nmodel supported by the server %v", resp.GetSupportedModels(), model.modelData)
+	}
+	if !reflect.DeepEqual(resp.GetSupportedEncodings(), supportedEncodings) {
+		t.Errorf("got supported encodings %v\nare not the same as\nencodings supported by the server %v", resp.GetSupportedEncodings(), supportedEncodings)
+	}
+}
+
+func TestGet(t *testing.T) {
+	jsonConfigRoot := `{
+		"openconfig-system:system": {
+			"openconfig-openflow:openflow": {
+				"agent": {
+					"config": {
+						"failure-mode": "SECURE",
+						"max-backoff": 10
+					}
+				}
+			}
+		},
+	  "openconfig-platform:components": {
+	    "component": [
+	      {
+	        "config": {
+	          "name": "swpri1-1-1"
+	        },
+	        "name": "swpri1-1-1"
+	      }
+	    ]
+	  }
+	}`
+
+	s, err := NewServer(model, []byte(jsonConfigRoot), nil)
+	if err != nil {
+		t.Fatalf("error in creating server: %v", err)
+	}
+
+	tds := []struct {
+		desc        string
+		textPbPath  string
+		modelData   []*pb.ModelData
+		wantRetCode codes.Code
+		wantRespVal interface{}
+	}{{
+		desc: "get valid but non-existing node",
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+		`,
+		wantRetCode: codes.NotFound,
+	}, {
+		desc:        "root node",
+		wantRetCode: codes.OK,
+		wantRespVal: jsonConfigRoot,
+	}, {
+		desc: "get non-enum type",
+		textPbPath: `
+					elem: <name: "system" >
+					elem: <name: "openflow" >
+					elem: <name: "agent" >
+					elem: <name: "config" >
+					elem: <name: "max-backoff" >
+				`,
+		wantRetCode: codes.OK,
+		wantRespVal: uint64(10),
+	}, {
+		desc: "get enum type",
+		textPbPath: `
+					elem: <name: "system" >
+					elem: <name: "openflow" >
+					elem: <name: "agent" >
+					elem: <name: "config" >
+					elem: <name: "failure-mode" >
+				`,
+		wantRetCode: codes.OK,
+		wantRespVal: "SECURE",
+	}, {
+		desc:        "root child node",
+		textPbPath:  `elem: <name: "components" >`,
+		wantRetCode: codes.OK,
+		wantRespVal: `{
+							"openconfig-platform:component": [{
+								"config": {
+						        	"name": "swpri1-1-1"
+								},
+						        "name": "swpri1-1-1"
+							}]}`,
+	}, {
+		desc: "node with attribute",
+		textPbPath: `
+								elem: <name: "components" >
+								elem: <
+									name: "component"
+									key: <key: "name" value: "swpri1-1-1" >
+								>`,
+		wantRetCode: codes.OK,
+		wantRespVal: `{
+								"openconfig-platform:config": {"name": "swpri1-1-1"},
+								"openconfig-platform:name": "swpri1-1-1"
+							}`,
+	}, {
+		desc: "node with attribute in its parent",
+		textPbPath: `
+								elem: <name: "components" >
+								elem: <
+									name: "component"
+									key: <key: "name" value: "swpri1-1-1" >
+								>
+								elem: <name: "config" >`,
+		wantRetCode: codes.OK,
+		wantRespVal: `{"openconfig-platform:name": "swpri1-1-1"}`,
+	}, {
+		desc: "ref leaf node",
+		textPbPath: `
+								elem: <name: "components" >
+								elem: <
+									name: "component"
+									key: <key: "name" value: "swpri1-1-1" >
+								>
+								elem: <name: "name" >`,
+		wantRetCode: codes.OK,
+		wantRespVal: "swpri1-1-1",
+	}, {
+		desc: "regular leaf node",
+		textPbPath: `
+								elem: <name: "components" >
+								elem: <
+									name: "component"
+									key: <key: "name" value: "swpri1-1-1" >
+								>
+								elem: <name: "config" >
+								elem: <name: "name" >`,
+		wantRetCode: codes.OK,
+		wantRespVal: "swpri1-1-1",
+	}, {
+		desc: "non-existing node: wrong path name",
+		textPbPath: `
+								elem: <name: "components" >
+								elem: <
+									name: "component"
+									key: <key: "foo" value: "swpri1-1-1" >
+								>
+								elem: <name: "bar" >`,
+		wantRetCode: codes.NotFound,
+	}, {
+		desc: "non-existing node: wrong path attribute",
+		textPbPath: `
+								elem: <name: "components" >
+								elem: <
+									name: "component"
+									key: <key: "foo" value: "swpri2-2-2" >
+								>
+								elem: <name: "name" >`,
+		wantRetCode: codes.NotFound,
+	}, {
+		desc:        "use of model data not supported",
+		modelData:   []*pb.ModelData{{}},
+		wantRetCode: codes.Unimplemented,
+	}}
+
+	for _, td := range tds {
+		t.Run(td.desc, func(t *testing.T) {
+			runTestGet(t, s, td.textPbPath, td.wantRetCode, td.wantRespVal, td.modelData)
+		})
+	}
+}
+
+// runTestGet requests a path from the server by Get grpc call, and compares if
+// the return code and response value are expected.
+func runTestGet(t *testing.T, s *Server, textPbPath string, wantRetCode codes.Code, wantRespVal interface{}, useModels []*pb.ModelData) {
+	// Send request
+	var pbPath pb.Path
+	if err := proto.UnmarshalText(textPbPath, &pbPath); err != nil {
+		t.Fatalf("error in unmarshaling path: %v", err)
+	}
+	req := &pb.GetRequest{
+		Path:      []*pb.Path{&pbPath},
+		Encoding:  pb.Encoding_JSON_IETF,
+		UseModels: useModels,
+	}
+	resp, err := s.Get(nil, req)
+
+	// Check return code
+	gotRetStatus, ok := status.FromError(err)
+	if !ok {
+		t.Fatal("got a non-grpc error from grpc call")
+	}
+	if gotRetStatus.Code() != wantRetCode {
+		t.Fatalf("got return code %v, want %v", gotRetStatus.Code(), wantRetCode)
+	}
+
+	// Check response value
+	var gotVal interface{}
+	if resp != nil {
+		notifs := resp.GetNotification()
+		if len(notifs) != 1 {
+			t.Fatalf("got %d notifications, want 1", len(notifs))
+		}
+		updates := notifs[0].GetUpdate()
+		if len(updates) != 1 {
+			t.Fatalf("got %d updates in the notification, want 1", len(updates))
+		}
+		val := updates[0].GetVal()
+		if val.GetJsonIetfVal() == nil {
+			gotVal, err = value.ToScalar(val)
+			if err != nil {
+				t.Errorf("got: %v, want a scalar value", gotVal)
+			}
+		} else {
+			// Unmarshal json data to gotVal container for comparison
+			if err := json.Unmarshal(val.GetJsonIetfVal(), &gotVal); err != nil {
+				t.Fatalf("error in unmarshaling IETF JSON data to json container: %v", err)
+			}
+			var wantJSONStruct interface{}
+			if err := json.Unmarshal([]byte(wantRespVal.(string)), &wantJSONStruct); err != nil {
+				t.Fatalf("error in unmarshaling IETF JSON data to json container: %v", err)
+			}
+			wantRespVal = wantJSONStruct
+		}
+	}
+
+	if !reflect.DeepEqual(gotVal, wantRespVal) {
+		t.Errorf("got: %v (%T),\nwant %v (%T)", gotVal, gotVal, wantRespVal, wantRespVal)
+	}
+}
+
+type gnmiSetTestCase struct {
+	desc        string                    // description of test case.
+	initConfig  string                    // config before the operation.
+	op          pb.UpdateResult_Operation // operation type.
+	textPbPath  string                    // text format of gnmi Path proto.
+	val         *pb.TypedValue            // value for UPDATE/REPLACE operations. always nil for DELETE.
+	wantRetCode codes.Code                // grpc return code.
+	wantConfig  string                    // config after the operation.
+}
+
+func TestDelete(t *testing.T) {
+	tests := []gnmiSetTestCase{{
+		desc: "delete leaf node",
+		initConfig: `{
+			"system": {
+				"config": {
+					"hostname": "switch_a",
+					"login-banner": "Hello!"
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "config" >
+			elem: <name: "login-banner" >
+		`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+	}, {
+		desc: "delete sub-tree",
+		initConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				},
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+		`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+	}, {
+		desc: "delete a sub-tree with only one leaf node",
+		initConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				},
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+			elem: <name: "config" >
+		`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+	}, {
+		desc: "delete a leaf node whose parent has only this child",
+		initConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				},
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+			elem: <name: "config" >
+			elem: <name: "timezone-name" >
+		`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+	}, {
+		desc: "delete root",
+		initConfig: `{
+			"system": {
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+		op:          pb.UpdateResult_DELETE,
+		wantRetCode: codes.OK,
+		wantConfig:  `{}`,
+	}, {
+		desc: "delete non-existing node",
+		initConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+			elem: <name: "config" >
+			elem: <name: "foo-bar" >
+		`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				}
+			}
+		}`,
+	}, {
+		desc: "delete node with non-existing precedent path",
+		initConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+			elem: <name: "foo-bar" >
+			elem: <name: "timezone-name" >
+		`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				}
+			}
+		}`,
+	}, {
+		desc: "delete node with non-existing attribute in precedent path",
+		initConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+			elem: <
+				name: "config"
+				key: <key: "name" value: "foo" >
+			>
+			elem: <name: "timezone-name" >`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				}
+			}
+		}`,
+	}, {
+		desc: "delete node with non-existing attribute",
+		initConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+			elem: <name: "config" >
+			elem: <
+				name: "timezone-name"
+				key: <key: "name" value: "foo" >
+			>
+			elem: <name: "timezone-name" >`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "Europe/Stockholm"
+					}
+				}
+			}
+		}`,
+	}, {
+		desc: "delete leaf node with attribute in its precedent path",
+		initConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						},
+						"state": {
+							"name": "swpri1-1-1",
+							"mfg-name": "foo bar inc."
+						}
+					}
+				]
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "components" >
+			elem: <
+				name: "component"
+				key: <key: "name" value: "swpri1-1-1" >
+			>
+			elem: <name: "state" >
+			elem: <name: "mfg-name" >`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						},
+						"state": {
+							"name": "swpri1-1-1"
+						}
+					}
+				]
+			}
+		}`,
+	}, {
+		desc: "delete sub-tree with attribute in its precedent path",
+		initConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						},
+						"state": {
+							"name": "swpri1-1-1",
+							"mfg-name": "foo bar inc."
+						}
+					}
+				]
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "components" >
+			elem: <
+				name: "component"
+				key: <key: "name" value: "swpri1-1-1" >
+			>
+			elem: <name: "state" >`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						}
+					}
+				]
+			}
+		}`,
+	}, {
+		desc: "delete path node with attribute",
+		initConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						}
+					},
+					{
+						"name": "swpri1-1-2",
+						"config": {
+							"name": "swpri1-1-2"
+						}
+					}
+				]
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "components" >
+			elem: <
+				name: "component"
+				key: <key: "name" value: "swpri1-1-1" >
+			>`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-2",
+						"config": {
+							"name": "swpri1-1-2"
+						}
+					}
+				]
+			}
+		}`,
+	}, {
+		desc: "delete path node with int type attribute",
+		initConfig: `{
+			"system": {
+				"openflow": {
+					"controllers": {
+						"controller": [
+							{
+								"config": {
+									"name": "main"
+								},
+								"connections": {
+									"connection": [
+										{
+											"aux-id": 0,
+											"config": {
+												"address": "192.0.2.10",
+												"aux-id": 0
+											}
+										}
+									]
+								},
+								"name": "main"
+							}
+						]
+					}
+				}
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "openflow" >
+			elem: <name: "controllers" >
+			elem: <
+				name: "controller"
+				key: <key: "name" value: "main" >
+			>
+			elem: <name: "connections" >
+			elem: <
+				name: "connection"
+				key: <key: "aux-id" value: "0" >
+			>
+			`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"openflow": {
+					"controllers": {
+						"controller": [
+							{
+								"config": {
+									"name": "main"
+								},
+								"name": "main"
+							}
+						]
+					}
+				}
+			}
+		}`,
+	}, {
+		desc: "delete leaf node with non-existing attribute value",
+		initConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						}
+					}
+				]
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "components" >
+			elem: <
+				name: "component"
+				key: <key: "name" value: "foo" >
+			>`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						}
+					}
+				]
+			}
+		}`,
+	}, {
+		desc: "delete leaf node with non-existing attribute value in precedent path",
+		initConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						},
+						"state": {
+							"name": "swpri1-1-1",
+							"mfg-name": "foo bar inc."
+						}
+					}
+				]
+			}
+		}`,
+		op: pb.UpdateResult_DELETE,
+		textPbPath: `
+			elem: <name: "components" >
+			elem: <
+				name: "component"
+				key: <key: "name" value: "foo" >
+			>
+			elem: <name: "state" >
+			elem: <name: "mfg-name" >
+		`,
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						},
+						"state": {
+							"name": "swpri1-1-1",
+							"mfg-name": "foo bar inc."
+						}
+					}
+				]
+			}
+		}`,
+	}}
+
+	for _, tc := range tests {
+		t.Run(tc.desc, func(t *testing.T) {
+			runTestSet(t, model, tc)
+		})
+	}
+}
+
+func TestReplace(t *testing.T) {
+	systemConfig := `{
+		"system": {
+			"clock": {
+				"config": {
+					"timezone-name": "Europe/Stockholm"
+				}
+			},
+			"config": {
+				"hostname": "switch_a",
+				"login-banner": "Hello!"
+			}
+		}
+	}`
+
+	tests := []gnmiSetTestCase{{
+		desc:       "replace root",
+		initConfig: `{}`,
+		op:         pb.UpdateResult_REPLACE,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{
+				JsonIetfVal: []byte(systemConfig),
+			}},
+		wantRetCode: codes.OK,
+		wantConfig:  systemConfig,
+	}, {
+		desc:       "replace a subtree",
+		initConfig: `{}`,
+		op:         pb.UpdateResult_REPLACE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "clock" >
+		`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{
+				JsonIetfVal: []byte(`{"config": {"timezone-name": "US/New York"}}`),
+			},
+		},
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"clock": {
+					"config": {
+						"timezone-name": "US/New York"
+					}
+				}
+			}
+		}`,
+	}, {
+		desc:       "replace a keyed list subtree",
+		initConfig: `{}`,
+		op:         pb.UpdateResult_REPLACE,
+		textPbPath: `
+			elem: <name: "components" >
+			elem: <
+				name: "component"
+				key: <key: "name" value: "swpri1-1-1" >
+			>`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{
+				JsonIetfVal: []byte(`{"config": {"name": "swpri1-1-1"}}`),
+			},
+		},
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"components": {
+				"component": [
+					{
+						"name": "swpri1-1-1",
+						"config": {
+							"name": "swpri1-1-1"
+						}
+					}
+				]
+			}
+		}`,
+	}, {
+		desc: "replace node with int type attribute in its precedent path",
+		initConfig: `{
+			"system": {
+				"openflow": {
+					"controllers": {
+						"controller": [
+							{
+								"config": {
+									"name": "main"
+								},
+								"name": "main"
+							}
+						]
+					}
+				}
+			}
+		}`,
+		op: pb.UpdateResult_REPLACE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "openflow" >
+			elem: <name: "controllers" >
+			elem: <
+				name: "controller"
+				key: <key: "name" value: "main" >
+			>
+			elem: <name: "connections" >
+			elem: <
+				name: "connection"
+				key: <key: "aux-id" value: "0" >
+			>
+			elem: <name: "config" >
+		`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{
+				JsonIetfVal: []byte(`{"address": "192.0.2.10", "aux-id": 0}`),
+			},
+		},
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"openflow": {
+					"controllers": {
+						"controller": [
+							{
+								"config": {
+									"name": "main"
+								},
+								"connections": {
+									"connection": [
+										{
+											"aux-id": 0,
+											"config": {
+												"address": "192.0.2.10",
+												"aux-id": 0
+											}
+										}
+									]
+								},
+								"name": "main"
+							}
+						]
+					}
+				}
+			}
+		}`,
+	}, {
+		desc:       "replace a leaf node of int type",
+		initConfig: `{}`,
+		op:         pb.UpdateResult_REPLACE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "openflow" >
+			elem: <name: "agent" >
+			elem: <name: "config" >
+			elem: <name: "backoff-interval" >
+		`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_IntVal{IntVal: 5},
+		},
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"openflow": {
+					"agent": {
+						"config": {
+							"backoff-interval": 5
+						}
+					}
+				}
+			}
+		}`,
+	}, {
+		desc:       "replace a leaf node of string type",
+		initConfig: `{}`,
+		op:         pb.UpdateResult_REPLACE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "openflow" >
+			elem: <name: "agent" >
+			elem: <name: "config" >
+			elem: <name: "datapath-id" >
+		`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_StringVal{StringVal: "00:16:3e:00:00:00:00:00"},
+		},
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"openflow": {
+					"agent": {
+						"config": {
+							"datapath-id": "00:16:3e:00:00:00:00:00"
+						}
+					}
+				}
+			}
+		}`,
+	}, {
+		desc:       "replace a leaf node of enum type",
+		initConfig: `{}`,
+		op:         pb.UpdateResult_REPLACE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "openflow" >
+			elem: <name: "agent" >
+			elem: <name: "config" >
+			elem: <name: "failure-mode" >
+		`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_StringVal{StringVal: "SECURE"},
+		},
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"openflow": {
+					"agent": {
+						"config": {
+							"failure-mode": "SECURE"
+						}
+					}
+				}
+			}
+		}`,
+	}, {
+		desc:       "replace an non-existing leaf node",
+		initConfig: `{}`,
+		op:         pb.UpdateResult_REPLACE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "openflow" >
+			elem: <name: "agent" >
+			elem: <name: "config" >
+			elem: <name: "foo-bar" >
+		`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_StringVal{StringVal: "SECURE"},
+		},
+		wantRetCode: codes.NotFound,
+		wantConfig:  `{}`,
+	}}
+
+	for _, tc := range tests {
+		t.Run(tc.desc, func(t *testing.T) {
+			runTestSet(t, model, tc)
+		})
+	}
+}
+
+func TestUpdate(t *testing.T) {
+	tests := []gnmiSetTestCase{{
+		desc: "update leaf node",
+		initConfig: `{
+			"system": {
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+		op: pb.UpdateResult_UPDATE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "config" >
+			elem: <name: "domain-name" >
+		`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_StringVal{StringVal: "foo.bar.com"},
+		},
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"config": {
+					"domain-name": "foo.bar.com",
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+	}, {
+		desc: "update subtree",
+		initConfig: `{
+			"system": {
+				"config": {
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+		op: pb.UpdateResult_UPDATE,
+		textPbPath: `
+			elem: <name: "system" >
+			elem: <name: "config" >
+		`,
+		val: &pb.TypedValue{
+			Value: &pb.TypedValue_JsonIetfVal{
+				JsonIetfVal: []byte(`{"domain-name": "foo.bar.com", "hostname": "switch_a"}`),
+			},
+		},
+		wantRetCode: codes.OK,
+		wantConfig: `{
+			"system": {
+				"config": {
+					"domain-name": "foo.bar.com",
+					"hostname": "switch_a"
+				}
+			}
+		}`,
+	}}
+
+	for _, tc := range tests {
+		t.Run(tc.desc, func(t *testing.T) {
+			runTestSet(t, model, tc)
+		})
+	}
+}
+
+func runTestSet(t *testing.T, m *Model, tc gnmiSetTestCase) {
+	// Create a new server with empty config
+	s, err := NewServer(m, []byte(tc.initConfig), nil)
+	if err != nil {
+		t.Fatalf("error in creating config server: %v", err)
+	}
+
+	// Send request
+	var pbPath pb.Path
+	if err := proto.UnmarshalText(tc.textPbPath, &pbPath); err != nil {
+		t.Fatalf("error in unmarshaling path: %v", err)
+	}
+	var req *pb.SetRequest
+	switch tc.op {
+	case pb.UpdateResult_DELETE:
+		req = &pb.SetRequest{Delete: []*pb.Path{&pbPath}}
+	case pb.UpdateResult_REPLACE:
+		req = &pb.SetRequest{Replace: []*pb.Update{{Path: &pbPath, Val: tc.val}}}
+	case pb.UpdateResult_UPDATE:
+		req = &pb.SetRequest{Update: []*pb.Update{{Path: &pbPath, Val: tc.val}}}
+	default:
+		t.Fatalf("invalid op type: %v", tc.op)
+	}
+	_, err = s.Set(nil, req)
+
+	// Check return code
+	gotRetStatus, ok := status.FromError(err)
+	if !ok {
+		t.Fatal("got a non-grpc error from grpc call")
+	}
+	if gotRetStatus.Code() != tc.wantRetCode {
+		t.Fatalf("got return code %v, want %v\nerror message: %v", gotRetStatus.Code(), tc.wantRetCode, err)
+	}
+
+	// Check server config
+	wantConfigStruct, err := m.NewConfigStruct([]byte(tc.wantConfig))
+	if err != nil {
+		t.Fatalf("wantConfig data cannot be loaded as a config struct: %v", err)
+	}
+	wantConfigJSON, err := ygot.ConstructIETFJSON(wantConfigStruct, &ygot.RFC7951JSONConfig{})
+	if err != nil {
+		t.Fatalf("error in constructing IETF JSON tree from wanted config: %v", err)
+	}
+	gotConfigJSON, err := ygot.ConstructIETFJSON(s.config, &ygot.RFC7951JSONConfig{})
+	if err != nil {
+		t.Fatalf("error in constructing IETF JSON tree from server config: %v", err)
+	}
+	if !reflect.DeepEqual(gotConfigJSON, wantConfigJSON) {
+		t.Fatalf("got server config %v\nwant: %v", gotConfigJSON, wantConfigJSON)
+	}
+}
diff --git a/forks/google/gnmi/util.go b/forks/google/gnmi/util.go
new file mode 100644
index 0000000000000000000000000000000000000000..73d17b49f7cab79a20236681c773b50844060af4
--- /dev/null
+++ b/forks/google/gnmi/util.go
@@ -0,0 +1,102 @@
+package gnmi
+
+import (
+	"fmt"
+	"strconv"
+
+	"github.com/openconfig/goyang/pkg/yang"
+	log "github.com/sirupsen/logrus"
+
+	pb "github.com/openconfig/gnmi/proto/gnmi"
+)
+
+// getChildNode gets a node's child with corresponding schema specified by path
+// element. If not found and createIfNotExist is set as true, an empty node is
+// created and returned.
+func getChildNode(node map[string]interface{}, schema *yang.Entry, elem *pb.PathElem, createIfNotExist bool) (interface{}, *yang.Entry) {
+	var nextSchema *yang.Entry
+	var ok bool
+
+	if nextSchema, ok = schema.Dir[elem.Name]; !ok {
+		return nil, nil
+	}
+
+	var nextNode interface{}
+	if elem.GetKey() == nil {
+		if nextNode, ok = node[elem.Name]; !ok {
+			if createIfNotExist {
+				node[elem.Name] = make(map[string]interface{})
+				nextNode = node[elem.Name]
+			}
+		}
+		return nextNode, nextSchema
+	}
+
+	nextNode = getKeyedListEntry(node, elem, createIfNotExist)
+	return nextNode, nextSchema
+}
+
+// getKeyedListEntry finds the keyed list entry in node by the name and key of
+// path elem. If entry is not found and createIfNotExist is true, an empty entry
+// will be created (the list will be created if necessary).
+func getKeyedListEntry(node map[string]interface{}, elem *pb.PathElem, createIfNotExist bool) map[string]interface{} {
+	curNode, ok := node[elem.Name]
+	if !ok {
+		if !createIfNotExist {
+			return nil
+		}
+
+		// Create a keyed list as node child and initialize an entry.
+		m := make(map[string]interface{})
+		for k, v := range elem.Key {
+			m[k] = v
+			if vAsNum, err := strconv.ParseFloat(v, 64); err == nil {
+				m[k] = vAsNum
+			}
+		}
+		node[elem.Name] = []interface{}{m}
+		return m
+	}
+
+	// Search entry in keyed list.
+	keyedList, ok := curNode.([]interface{})
+	if !ok {
+		return nil
+	}
+	for _, n := range keyedList {
+		m, ok := n.(map[string]interface{})
+		if !ok {
+			log.Errorf("wrong keyed list entry type: %T", n)
+			return nil
+		}
+		keyMatching := true
+		// must be exactly match
+		for k, v := range elem.Key {
+			attrVal, ok := m[k]
+			if !ok {
+				return nil
+			}
+			if v != fmt.Sprintf("%v", attrVal) {
+				keyMatching = false
+				break
+			}
+		}
+		if keyMatching {
+			return m
+		}
+	}
+	if !createIfNotExist {
+		return nil
+	}
+
+	// Create an entry in keyed list.
+	m := make(map[string]interface{})
+	for k, v := range elem.Key {
+		m[k] = v
+		if vAsNum, err := strconv.ParseFloat(v, 64); err == nil {
+			m[k] = vAsNum
+		}
+	}
+	node[elem.Name] = append(keyedList, m)
+	return m
+}
diff --git a/go.mod b/go.mod
index e931734e515cc70bff076fb01fc23b6f3cf89c92..d3bdb8091bef6cf230a54c6305f3f6ab736eeb2f 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,6 @@ module code.fbi.h-da.de/danet/gosdn
 go 1.18
 
 require (
-	code.fbi.h-da.de/danet/forks/goarista v0.0.0-20210709163519-47ee8958ef40
-	code.fbi.h-da.de/danet/forks/google v0.0.0-20210709163519-47ee8958ef40
 	github.com/docker/docker v20.10.11+incompatible
 	github.com/google/go-cmp v0.5.6
 	github.com/google/uuid v1.2.0
@@ -20,7 +18,7 @@ require (
 	github.com/stretchr/testify v1.7.0
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/grpc v1.43.0
-	google.golang.org/protobuf v1.27.1
+	google.golang.org/protobuf v1.28.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
 
@@ -32,7 +30,11 @@ require (
 	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa
 )
 
-require github.com/sethvargo/go-password v0.2.0
+require (
+	github.com/aristanetworks/goarista v0.0.0-20220425175323-05f7c4c5e34c
+	github.com/google/gnxi v0.0.0-20220411075422-cd6b043b7fd0
+	github.com/sethvargo/go-password v0.2.0
+)
 
 require (
 	github.com/Microsoft/go-winio v0.5.1 // indirect
@@ -52,7 +54,7 @@ require (
 	github.com/golang/glog v1.0.0 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2
-	github.com/golang/snappy v0.0.3 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/klauspost/compress v1.13.6 // indirect
@@ -85,8 +87,8 @@ require (
 	github.com/xdg-go/stringprep v1.0.2 // indirect
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 	go.opencensus.io v0.23.0 // indirect
-	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
-	golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
+	golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
 	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
diff --git a/go.sum b/go.sum
index 80a58829b6e9308444cfd2515452d49d9d9d3bab..24ca66a8f470a6a74946140c399fb86180114494 100644
--- a/go.sum
+++ b/go.sum
@@ -48,10 +48,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-code.fbi.h-da.de/danet/forks/goarista v0.0.0-20210709163519-47ee8958ef40 h1:x7rVYGqfJSMWuYBp+JE6JVMcFP03Gx0mnR2ftsgqjVI=
-code.fbi.h-da.de/danet/forks/goarista v0.0.0-20210709163519-47ee8958ef40/go.mod h1:uVe3gCeF2DcIho8K9CIO46uAkHW/lUF+fAaUX1vHrF0=
-code.fbi.h-da.de/danet/forks/google v0.0.0-20210709163519-47ee8958ef40 h1:B45k5tGEdjjdsKK4f+0dQoyReFmsWdwYEzHofA7DPM8=
-code.fbi.h-da.de/danet/forks/google v0.0.0-20210709163519-47ee8958ef40/go.mod h1:Uutdj5aA3jpzfNm3C8gt2wctYE6cRrdyZsILUgJ+tMY=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
 github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -74,7 +70,6 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
@@ -108,11 +103,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
-github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
-github.com/Shopify/sarama v1.28.0/go.mod h1:j/2xTrU39dlzBmsxF1eQ2/DdWrxyBCl6pzz7a81o/ZY=
-github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
-github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
-github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -123,24 +113,15 @@ github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY
 github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
 github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks=
-github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA=
-github.com/aristanetworks/goarista v0.0.0-20210706081233-a582b785a9ce h1:oKfxZ+MdgljqTE33vdUuIUUXQp3VFH9yqqgxCRyB48w=
-github.com/aristanetworks/goarista v0.0.0-20210706081233-a582b785a9ce/go.mod h1:drswc1gdKErwWsW+gV2R5ELcuHehg5pZD2tat4B65Ik=
-github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc=
+github.com/aristanetworks/goarista v0.0.0-20220425175323-05f7c4c5e34c h1:F5PW18QOO08YVJa5lsUpPr/wJR72KleN9QCxxOs7yR8=
+github.com/aristanetworks/goarista v0.0.0-20220425175323-05f7c4c5e34c/go.mod h1:FmFEhIzCEWoI0WuuZDSWCu25zCNW/X4Czla0ls/HSrs=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
-github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
 github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -162,8 +143,6 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq
 github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
 github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI=
 github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY=
-github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
-github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
 github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
@@ -187,7 +166,6 @@ github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX
 github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
 github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
-github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 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=
@@ -201,7 +179,6 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
 github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
 github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
 github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
-github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
 github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
 github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
@@ -362,15 +339,9 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh
 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
-github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
-github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
-github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -387,9 +358,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
-github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -397,7 +365,6 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
 github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
-github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -407,7 +374,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 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=
@@ -434,7 +400,6 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp
 github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
-github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
@@ -444,11 +409,9 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
 github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
@@ -494,16 +457,16 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
-github.com/google/gnxi v0.0.0-20210423111716-4b504ef806a7 h1:cJ62uhbZcclaYm9gq4JNyazqSY7bUEggwZdw0nHTT7o=
-github.com/google/gnxi v0.0.0-20210423111716-4b504ef806a7/go.mod h1:dPTuHPVOqxZ2yGKPjymiMt1vrZa8KHXWKX+Lx1z5d88=
+github.com/google/gnxi v0.0.0-20220411075422-cd6b043b7fd0 h1:Ef2sJA0zQvsviQ13sXiidv+SIkE68t3oy4wfXAV66j0=
+github.com/google/gnxi v0.0.0-20220411075422-cd6b043b7fd0/go.mod h1:dPTuHPVOqxZ2yGKPjymiMt1vrZa8KHXWKX+Lx1z5d88=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -540,7 +503,6 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -555,15 +517,11 @@ github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2c
 github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
-github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
-github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -578,10 +536,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2 h1:I/pwhnUln5wbMnTyRbzswA0/JxpK8sZj0aUfI3TV1So=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.2/go.mod h1:lsuH8kb4GlMdSlI4alNIBBSAt5CHJtg3i+0WuN9J5YM=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
 github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
-github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
 github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -600,8 +556,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -615,7 +569,6 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@@ -625,20 +578,11 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
-github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ=
 github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
 github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw=
-github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
-github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
-github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
-github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
-github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
-github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
@@ -646,7 +590,6 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@@ -662,17 +605,12 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.11.9/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
-github.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
-github.com/klauspost/reedsolomon v1.9.11/go.mod h1:nLvuzNvy1ZDNQW30IuMc2ZWCbiqrJgdLoUS2X8HAUVg=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -689,10 +627,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
-github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
 github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
-github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@@ -778,26 +713,16 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
-github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
-github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
-github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
-github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
-github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
-github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
-github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
-github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -809,7 +734,6 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -817,28 +741,21 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
 github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
-github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 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/go.mod h1:M/EcuapNQgvzxo1DDXHK4tx3QpYM/uG4l591v33jG2A=
-github.com/openconfig/gnmi v0.0.0-20210226144353-8eae1937bf84/go.mod h1:H/20NXlnWbCPFC593nxpiKJ+OU//7mW7s7Qk7uVdg3Q=
-github.com/openconfig/gnmi v0.0.0-20210707145734-c69a5df04b53/go.mod h1:h365Ifq35G6kLZDQlRvrccTt2LKK90VpjZLMNGxJRYc=
 github.com/openconfig/gnmi v0.0.0-20210914185457-51254b657b7d h1:ENKx1I2+/8C70C69qGDw8zfHXFsPnSMtZyf9F2GjN/k=
 github.com/openconfig/gnmi v0.0.0-20210914185457-51254b657b7d/go.mod h1:h365Ifq35G6kLZDQlRvrccTt2LKK90VpjZLMNGxJRYc=
 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/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
-github.com/openconfig/goyang v0.2.5/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
-github.com/openconfig/goyang v0.2.7/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
 github.com/openconfig/goyang v1.0.0 h1:nYaFu7BOAk/eQn4CgAUjgYPfp3J6CdXrBryp32E5CjI=
 github.com/openconfig/goyang v1.0.0/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
 github.com/openconfig/gribi v0.1.1-0.20210423184541-ce37eb4ba92f/go.mod h1:OoH46A2kV42cIXGyviYmAlGmn6cHjGduyC2+I9d/iVs=
 github.com/openconfig/grpctunnel v0.0.0-20210610163803-fde4a9dc048d/go.mod h1:x9tAZ4EwqCQ0jI8D6S8Yhw9Z0ee7/BxWQX0k0Uib5Q8=
-github.com/openconfig/reference v0.0.0-20201210185750-72ca4cfd4abd/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.4/go.mod h1:oCQNdXnv7dWc8scTDgoFkauv1wwplJn5HspHcjlxSAQ=
-github.com/openconfig/ygot v0.11.2/go.mod h1:5q5fz1SDPGUwMyzbm8Ns2Krul+32euNSU89ZmrGrSK8=
 github.com/openconfig/ygot v0.18.1 h1:IcgB9qHoFzXOAvcA+1llUDEYIhm5qLnZ6i1MTyz5BYs=
 github.com/openconfig/ygot v0.18.1/go.mod h1:7ZiBFNc4n/1Hkv2v2dAEpxisqDznp0JVpLR13Toe4AY=
 github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -871,28 +788,15 @@ github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqi
 github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
 github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
 github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
-github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
-github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
-github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
-github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
-github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 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=
-github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
 github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
-github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
-github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pierrec/lz4/v4 v4.1.1/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -900,7 +804,6 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw=
 github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
@@ -911,39 +814,29 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
 github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
 github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
-github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
 github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
-github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
-github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
 github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -956,8 +849,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
-github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -966,7 +857,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
 github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
 github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
 github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
-github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
 github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
@@ -991,7 +881,6 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:s
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
-github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -1024,9 +913,6 @@ github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk=
 github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
 github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
 github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
-github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
-github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -1046,11 +932,8 @@ github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG
 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
-github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
-github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -1079,16 +962,12 @@ github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
 github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
 github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
-github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
-github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-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/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1103,7 +982,6 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
-go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
 go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
 go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
@@ -1115,8 +993,6 @@ go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVd
 go.mongodb.org/mongo-driver v1.8.4 h1:NruvZPPL0PBcRJKmbswoWSrmHeUvzdxA3GCPfD/NEOA=
 go.mongodb.org/mongo-driver v1.8.4/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
 go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
-go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1148,16 +1024,12 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
 go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1175,14 +1047,12 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 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=
@@ -1227,7 +1097,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -1260,7 +1129,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -1277,7 +1145,6 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
@@ -1316,7 +1183,6 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1349,7 +1215,6 @@ golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1394,7 +1259,6 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1445,7 +1309,6 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
 golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/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-20180828015842-6cd1fcedba52/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-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1469,8 +1332,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -1480,7 +1341,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -1522,7 +1382,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
-google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -1551,7 +1410,6 @@ google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00
 google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
 google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
@@ -1565,7 +1423,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
-google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
@@ -1623,13 +1480,10 @@ google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEc
 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
-google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
@@ -1657,7 +1511,6 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
 google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
 google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/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=
@@ -1671,11 +1524,11 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
-gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1687,7 +1540,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -1696,13 +1548,11 @@ gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.64.0 h1:Mj2zXEXcNb5joEiSA0zc3HZpTst/iyjNiR4CN8tDzOg=
 gopkg.in/ini.v1 v1.64.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
-gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1722,7 +1572,6 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
 gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1785,4 +1634,3 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
 sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
-sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=