Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions gnmi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,17 @@ func (s *Server) toGoStruct(jsonTree map[string]interface{}) (ygot.ValidatedGoSt
return goStruct, nil
}

// ConfigAsJSON takes the current configuration of the running server and
// returns it in a RFC7951 compliant json string.
func (s *Server) ConfigAsJSON() (string, error) {
return ygot.EmitJSON(s.config, &ygot.EmitJSONConfig{
Format: ygot.RFC7951,
RFC7951Config: &ygot.RFC7951JSONConfig{
AppendModuleName: true,
},
})
}

// 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) {
Expand Down
61 changes: 61 additions & 0 deletions gnmi/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1696,3 +1696,64 @@ func updateLess(a, b *pb.Update) bool {
}
return pathA < pathB
}

// jsonBytesEqual is a helper function to compare two json strings for
// equality.
func jsonBytesEqual(a, b []byte) (bool, error) {
var j, j2 interface{}
if err := json.Unmarshal(a, &j); err != nil {
return false, err
}
if err := json.Unmarshal(b, &j2); err != nil {
return false, err
}
return reflect.DeepEqual(j2, j), nil
}

// TestConfigAsJSON validates that the method `ConfigAsJSON` returns a json
// string that meets the running configuration of the server in a fashion that
// can be loaded back into the server.
func TestConfigToJSON(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)
}

res, err := s.ConfigAsJSON()
if err != nil {
t.Fatalf("error in creating json from model: %v", err)
}

areEqual, err := jsonBytesEqual([]byte(jsonConfigRoot), []byte(res))
if err != nil {
t.Fatalf("error in comparing json bytes: %v", err)
}

if (!areEqual) {
t.Errorf("config mismatch!\n Got: %s\n Wanted: %s", res, jsonConfigRoot)
}

}
29 changes: 28 additions & 1 deletion gnmi_target/gnmi_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import (
"io/ioutil"
"net"
"os"
"os/signal"
"reflect"
"syscall"

log "github.com/golang/glog"
"golang.org/x/net/context"
Expand All @@ -43,6 +45,7 @@ import (
var (
bindAddr = flag.String("bind_address", ":9339", "Bind to address:port or just :port")
configFile = flag.String("config", "", "IETF JSON file for target startup config")
saveOnExit = flag.Bool("save_on_exit", false, "Save the config before exiting the server.")
)

type server struct {
Expand Down Expand Up @@ -79,7 +82,7 @@ func (s *server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse,
return s.Server.Set(ctx, req)
}

// Set overrides the Subscribe func of gnmi.Target to provide user auth.
// Subscribe overrides the Subscribe func of gnmi.Target to provide user auth.
func (s *server) Subscribe(stream pb.GNMI_SubscribeServer) error {
msg, ok := credentials.AuthorizeUser(stream.Context())
if !ok {
Expand All @@ -90,6 +93,24 @@ func (s *server) Subscribe(stream pb.GNMI_SubscribeServer) error {
return s.Server.Subscribe(stream)
}

// shutdownHook saves the running config back out to the config file.
func (s *server) shutdownHook (c chan os.Signal) {
sig := <- c
log.Infof("Gracefully stopping: %s", sig)
cfg, err := s.ConfigAsJSON()
if err != nil {
log.Exitf("Error getting json representation from server: %v", err)
}
if *configFile != "" {
err := os.WriteFile(*configFile, []byte(cfg), 0644)
if err != nil {
log.Exitf("error in writing config file: %v", err)
}
}
log.Infof("Config saved back out to file.")
os.Exit(0)
}

func main() {
model := gnmi.NewModel(modeldata.ModelData,
reflect.TypeOf((*gostruct.Device)(nil)),
Expand Down Expand Up @@ -134,6 +155,12 @@ func main() {
log.Exitf("failed to listen: %v", err)
}

if(*saveOnExit) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go s.shutdownHook(c)
}

log.Info("starting to serve")
if err := g.Serve(listen); err != nil {
log.Exitf("failed to serve: %v", err)
Expand Down