diff --git a/examples/example01/Dockerfile.debug b/examples/example01/Dockerfile.debug index 4b322e91938bd3b86d6dbd2600c9b536f0ec6dc7..2de98b9b4e38ee9f00ed6e1bb2e56a78663e50a4 100644 --- a/examples/example01/Dockerfile.debug +++ b/examples/example01/Dockerfile.debug @@ -1,4 +1,4 @@ -ARG GOLANG_VERSION=1.20.3 +ARG GOLANG_VERSION=1.22 ARG BUILDARGS FROM golang:$GOLANG_VERSION-buster as builder @@ -6,7 +6,7 @@ WORKDIR /gnmi-target/ COPY . . RUN --mount=type=cache,target=/root/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build -RUN go install github.com/go-delve/delve/cmd/dlv@v1.20.2 +RUN go install github.com/go-delve/delve/cmd/dlv@v1.22.1 RUN make build-debug FROM ubuntu:22.04 as ubuntu diff --git a/examples/example01/cmd/start.go b/examples/example01/cmd/start.go index 9edb84f65afd1b189d35e43c438fdf2b6547e46b..88d5f00f8ca2b578636044f152abeff62cd67855 100644 --- a/examples/example01/cmd/start.go +++ b/examples/example01/cmd/start.go @@ -37,6 +37,7 @@ import ( "code.fbi.h-da.de/danet/gnmi-target/examples/example01/handlers/interfaces" networkinstances "code.fbi.h-da.de/danet/gnmi-target/examples/example01/handlers/network-instances" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/handlers/system" + gnmitargetygot "code.fbi.h-da.de/danet/gnmi-target/examples/example01/model" "code.fbi.h-da.de/danet/gnmi-target/handler" "github.com/sirupsen/logrus" diff --git a/examples/example01/handlers/interfaces/interfacesHandler.go b/examples/example01/handlers/interfaces/interfacesHandler.go index fd5d8a67be5e4c76fcfe311d03b3a066ffc9e7f0..155b558dd71d7629a86e7286522adad9134f5aa7 100644 --- a/examples/example01/handlers/interfaces/interfacesHandler.go +++ b/examples/example01/handlers/interfaces/interfacesHandler.go @@ -7,6 +7,7 @@ import ( gnmitargetygot "code.fbi.h-da.de/danet/gnmi-target/examples/example01/model" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient/additions" + "code.fbi.h-da.de/danet/gnmi-target/handler" "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" log "github.com/sirupsen/logrus" @@ -14,34 +15,25 @@ import ( // InterfacesHandler is the implementation of a gnmitarget.PathHandler. type InterfacesHandler struct { - name string - paths map[string]struct{} + handler.DefaultPathHandler osClient osclient.Osclient } func NewInterfacesHandler() *InterfacesHandler { return &InterfacesHandler{ - name: "openconfig-interfaces-handler", - paths: map[string]struct{}{ - "/interfaces": struct{}{}, + DefaultPathHandler: handler.DefaultPathHandler{ + Name: "openconfig-interfaces-handler", + Paths: map[string]struct{}{ + "/interfaces": {}, + }, }, osClient: osclient.NewOsClient(), } } -func (yh *InterfacesHandler) Name() string { - return yh.name -} - -func (yh *InterfacesHandler) Paths() map[string]struct{} { - return yh.paths -} - -func (yh *InterfacesHandler) Init(c ygot.ValidatedGoStruct) error { - config, ok := c.(*gnmitargetygot.Gnmitarget) - if !ok { - return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) - } +func (yh *InterfacesHandler) Init(config *handler.Config, publishToSubsFunc func([]*gnmi.Notification) error) error { + yh.Config = config + yh.PublishToSubs = publishToSubsFunc // needed for interfaces and network instances localInterfaces, err := yh.osClient.GetInterfaces() @@ -49,10 +41,9 @@ func (yh *InterfacesHandler) Init(c ygot.ValidatedGoStruct) error { return err } - confInterfaces := config.GetOrCreateInterfaces() - for _, localInterface := range localInterfaces { - if err := updateOrCreateInterface(confInterfaces, localInterface); err != nil { + _, err := yh.updateOrCreateInterface(localInterface) + if err != nil { return err } } @@ -66,8 +57,15 @@ func (yh *InterfacesHandler) Init(c ygot.ValidatedGoStruct) error { for { select { case update := <-interfaceChannel: - if err := updateOrCreateInterface(confInterfaces, update); err != nil { - fmt.Println("Error within interface subscription goroutine.") + //lock access for model + diff, err := yh.updateOrCreateInterface(update) + if err != nil { + log.Errorf("Error within interface subscription goroutine; %v", err) + // TODO: check again + break + } + if err := yh.PublishToSubs(diff); err != nil { + log.Errorf("Error within interface subscription goroutine; %v", err) } } } @@ -77,7 +75,7 @@ func (yh *InterfacesHandler) Init(c ygot.ValidatedGoStruct) error { } func (yh *InterfacesHandler) Update(c ygot.ValidatedGoStruct, updates []*gnmi.Update) error { - fmt.Println("Update request received for ", yh.name) + fmt.Println("Update request received for ", yh.GetName()) config, ok := c.(*gnmitargetygot.Gnmitarget) if !ok { return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) @@ -149,7 +147,22 @@ func (yh *InterfacesHandler) Update(c ygot.ValidatedGoStruct, updates []*gnmi.Up return nil } -func updateOrCreateInterface(confInterfaces *gnmitargetygot.OpenconfigInterfaces_Interfaces, localInterface *additions.Interface) error { +func (yh *InterfacesHandler) updateOrCreateInterface(localInterface *additions.Interface) ([]*gnmi.Notification, error) { + yh.Config.Lock() + defer yh.Config.Unlock() + + copyCurrentConfig, err := ygot.DeepCopy(yh.Config.Data) + if err != nil { + return nil, err + } + + newConfig, ok := copyCurrentConfig.(*gnmitargetygot.Gnmitarget) + if !ok { + return nil, fmt.Errorf("Wrong type, exptected: %T, got: %T", (*gnmitargetygot.OpenconfigInterfaces_Interfaces)(nil), copyCurrentConfig) + } + + confInterfaces := newConfig.GetOrCreateInterfaces() + iface := confInterfaces.GetOrCreateInterface(*localInterface.Name) state := iface.GetOrCreateState() config := iface.GetOrCreateConfig() @@ -198,9 +211,16 @@ func updateOrCreateInterface(confInterfaces *gnmitargetygot.OpenconfigInterfaces } //validate struct - if err := confInterfaces.Validate(); err != nil { - return err + if err := newConfig.Validate(); err != nil { + return nil, err } - return nil + notifications, err := ygot.DiffWithAtomic(yh.Config.Data, newConfig) + if err != nil { + return nil, err + } + + yh.Config.Data = newConfig + + return notifications, nil } diff --git a/examples/example01/handlers/network-instances/networkInstanceHandler.go b/examples/example01/handlers/network-instances/networkInstanceHandler.go index b27599a43d83f93943800e144bed29039fafcd48..b37b5eaddf25304d70e34042cb56346bba622c93 100644 --- a/examples/example01/handlers/network-instances/networkInstanceHandler.go +++ b/examples/example01/handlers/network-instances/networkInstanceHandler.go @@ -6,6 +6,7 @@ import ( gnmitargetygot "code.fbi.h-da.de/danet/gnmi-target/examples/example01/model" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient/additions" + "code.fbi.h-da.de/danet/gnmi-target/handler" "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" log "github.com/sirupsen/logrus" @@ -13,34 +14,25 @@ import ( // NetworkInstanceHandler is the implementation of a gnmitarget.PathHandler. type NetworkInstanceHandler struct { - name string - paths map[string]struct{} + handler.DefaultPathHandler osClient osclient.Osclient } func NewNetworkInstanceHandler() *NetworkInstanceHandler { return &NetworkInstanceHandler{ - name: "openconfig-network-instance-handler", - paths: map[string]struct{}{ - "/network-instances": struct{}{}, + DefaultPathHandler: handler.DefaultPathHandler{ + Name: "openconfig-network-instance-handler", + Paths: map[string]struct{}{ + "/network-instances": {}, + }, }, osClient: osclient.NewOsClient(), } } -func (yh *NetworkInstanceHandler) Name() string { - return yh.name -} - -func (yh *NetworkInstanceHandler) Paths() map[string]struct{} { - return yh.paths -} - -func (yh *NetworkInstanceHandler) Init(c ygot.ValidatedGoStruct) error { - config, ok := c.(*gnmitargetygot.Gnmitarget) - if !ok { - return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) - } +func (yh *NetworkInstanceHandler) Init(config *handler.Config, publishToSubsFunc func([]*gnmi.Notification) error) error { + yh.Config = config + yh.PublishToSubs = publishToSubsFunc // needed for interfaces and network instances localInterfaces, err := yh.osClient.GetInterfaces() @@ -48,8 +40,6 @@ func (yh *NetworkInstanceHandler) Init(c ygot.ValidatedGoStruct) error { return err } - confNetworkInstances := config.GetOrCreateNetworkInstances() - for _, localInterface := range localInterfaces { staticRoutes, err := yh.osClient.GetStaticRoutes(*localInterface.Name) if err != nil { @@ -57,7 +47,8 @@ func (yh *NetworkInstanceHandler) Init(c ygot.ValidatedGoStruct) error { } for _, staticRoute := range staticRoutes { - if err := updateOrCreateNetworkInstance(confNetworkInstances, staticRoute); err != nil { + _, err := yh.updateOrCreateNetworkInstance(staticRoute) + if err != nil { return err } } @@ -67,7 +58,7 @@ func (yh *NetworkInstanceHandler) Init(c ygot.ValidatedGoStruct) error { } func (yh *NetworkInstanceHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi.Update) error { - fmt.Println("Update request received for ", yh.name) + fmt.Println("Update request received for ", yh.Name) config, ok := c.(*gnmitargetygot.Gnmitarget) if !ok { return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) @@ -111,8 +102,26 @@ func (yh *NetworkInstanceHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi. return nil } -func updateOrCreateNetworkInstance(confNetworkInstances *gnmitargetygot.OpenconfigNetworkInstance_NetworkInstances, localStaticRoute *additions.StaticRoute) error { +func (yh *NetworkInstanceHandler) updateOrCreateNetworkInstance(localStaticRoute *additions.StaticRoute) ([]*gnmi.Notification, error) { + yh.Config.Lock() + defer yh.Config.Unlock() + + copyCurrentConfig, err := ygot.DeepCopy(yh.Config.Data) + if err != nil { + return nil, err + } + + newConfig, ok := copyCurrentConfig.(*gnmitargetygot.Gnmitarget) + if !ok { + return nil, fmt.Errorf("Wrong type, exptected: %T, got: %T", (*gnmitargetygot.OpenconfigInterfaces_Interfaces)(nil), copyCurrentConfig) + } + + confNetworkInstances := newConfig.GetOrCreateNetworkInstances() + if networkInstances := confNetworkInstances.GetOrCreateNetworkInstance("DEFAULT"); networkInstances != nil && localStaticRoute != nil { + if config := networkInstances.GetOrCreateConfig(); config != nil { + config.Name = ygot.String("DEFAULT") + } if protocols := networkInstances.GetOrCreateProtocols(); protocols != nil { staticProtocol := protocols.GetOrCreateProtocol(gnmitargetygot.OpenconfigPolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, "STATIC") @@ -145,5 +154,18 @@ func updateOrCreateNetworkInstance(confNetworkInstances *gnmitargetygot.Openconf } } } - return nil + + //validate struct + if err := newConfig.Validate(); err != nil { + return nil, err + } + + notifications, err := ygot.DiffWithAtomic(yh.Config.Data, newConfig) + if err != nil { + return nil, err + } + + yh.Config.Data = newConfig + + return notifications, nil } diff --git a/examples/example01/handlers/system/hostnameHandler.go b/examples/example01/handlers/system/hostnameHandler.go index 9ca31617948f3aed87fd876ef2f03d09cdc73b56..00deb0b364db120ad596d15ebf70725f94081587 100644 --- a/examples/example01/handlers/system/hostnameHandler.go +++ b/examples/example01/handlers/system/hostnameHandler.go @@ -5,6 +5,7 @@ import ( gnmitargetygot "code.fbi.h-da.de/danet/gnmi-target/examples/example01/model" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient" + "code.fbi.h-da.de/danet/gnmi-target/handler" "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" "github.com/sirupsen/logrus" @@ -12,45 +13,36 @@ import ( // HostnameHandler is the implementation of a gnmitarget.PathHandler. type HostnameHandler struct { - name string - paths map[string]struct{} + handler.DefaultPathHandler osClient osclient.Osclient weight int } func NewHostnameHandler() *HostnameHandler { return &HostnameHandler{ - name: "openconfig-hostname-handler", - paths: map[string]struct{}{ - "/system/config/hostname": struct{}{}, + DefaultPathHandler: handler.DefaultPathHandler{ + Name: "openconfig-hostname-handler", + Paths: map[string]struct{}{ + "/system/config/hostname": {}, + }, }, osClient: osclient.NewOsClient(), } } -func (yh *HostnameHandler) Name() string { - return yh.name -} - -func (yh *HostnameHandler) Paths() map[string]struct{} { - return yh.paths -} - -func (yh *HostnameHandler) Init(c ygot.ValidatedGoStruct) error { - config, ok := c.(*gnmitargetygot.Gnmitarget) - if !ok { - return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) - } +func (yh *HostnameHandler) Init(config *handler.Config, publishToSubsFunc func([]*gnmi.Notification) error) error { + yh.Config = config + yh.PublishToSubs = publishToSubsFunc - confSystem := config.GetOrCreateSystem() - if err := updateOrCreateHostname(confSystem, yh.osClient); err != nil { + _, err := yh.updateOrCreateHostname(yh.osClient) + if err != nil { return err } return nil } func (yh *HostnameHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi.Update) error { - fmt.Println("Update request received for ", yh.name) + fmt.Println("Update request received for ", yh.Name) config, ok := c.(*gnmitargetygot.Gnmitarget) if !ok { return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) @@ -72,19 +64,41 @@ func (yh *HostnameHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi.Update) return nil } -func updateOrCreateHostname(confSystem *gnmitargetygot.OpenconfigSystem_System, os osclient.Osclient) error { +func (yh *HostnameHandler) updateOrCreateHostname(os osclient.Osclient) ([]*gnmi.Notification, error) { + yh.Config.Lock() + defer yh.Config.Unlock() + + copyCurrentConfig, err := ygot.DeepCopy(yh.Config.Data) + if err != nil { + return nil, err + } + + newConfig, ok := copyCurrentConfig.(*gnmitargetygot.Gnmitarget) + if !ok { + return nil, fmt.Errorf("Wrong type, exptected: %T, got: %T", (*gnmitargetygot.OpenconfigInterfaces_Interfaces)(nil), copyCurrentConfig) + } + + confSystem := newConfig.GetOrCreateSystem() + if config := confSystem.GetOrCreateConfig(); config != nil { h, err := os.GetHostname() if err != nil { - return err + return nil, err } config.Hostname = &h } //validate struct - if err := confSystem.Validate(); err != nil { - return err + if err := newConfig.Validate(); err != nil { + return nil, err } - return nil + notifications, err := ygot.DiffWithAtomic(yh.Config.Data, newConfig) + if err != nil { + return nil, err + } + + yh.Config.Data = newConfig + + return notifications, nil } diff --git a/examples/example01/handlers/system/memoryHandler.go b/examples/example01/handlers/system/memoryHandler.go index b99115a0946c10fab9088f78758c59f5d590aec0..dfd9873698df8fc71c26be974ef9fc1d2469ef74 100644 --- a/examples/example01/handlers/system/memoryHandler.go +++ b/examples/example01/handlers/system/memoryHandler.go @@ -5,54 +5,61 @@ import ( gnmitargetygot "code.fbi.h-da.de/danet/gnmi-target/examples/example01/model" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient" + "code.fbi.h-da.de/danet/gnmi-target/handler" "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" ) // MemoryHandler is the implementation of a gnmitarget.PathHandler. type MemoryHandler struct { - name string - paths map[string]struct{} + handler.DefaultPathHandler osClient osclient.Osclient } func NewMemoryHandler() *MemoryHandler { return &MemoryHandler{ - name: "openconfig-memory-handler", - paths: map[string]struct{}{ - "/system/memory/state": struct{}{}, + DefaultPathHandler: handler.DefaultPathHandler{ + Name: "openconfig-memory-handler", + Paths: map[string]struct{}{ + "/system/memory/state": {}, + }, }, osClient: osclient.NewOsClient(), } } -func (yh *MemoryHandler) Name() string { - return yh.name -} - -func (yh *MemoryHandler) Paths() map[string]struct{} { - return yh.paths -} +func (yh *MemoryHandler) Init(config *handler.Config, publishToSubsFunc func([]*gnmi.Notification) error) error { + yh.Config = config + yh.PublishToSubs = publishToSubsFunc -func (yh *MemoryHandler) Init(c ygot.ValidatedGoStruct) error { - config, ok := c.(*gnmitargetygot.Gnmitarget) - if !ok { - return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) - } - - confSystem := config.GetOrCreateSystem() - if err := updateOrCreateMemory(confSystem, yh.osClient); err != nil { + _, err := yh.updateOrCreateMemory(yh.osClient) + if err != nil { return err } return nil } func (yh *MemoryHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi.Update) error { - fmt.Println("Update request received for ", yh.name) + fmt.Println("Update request received for ", yh.Name) return nil } -func updateOrCreateMemory(confSystem *gnmitargetygot.OpenconfigSystem_System, os osclient.Osclient) error { +func (yh *MemoryHandler) updateOrCreateMemory(os osclient.Osclient) ([]*gnmi.Notification, error) { + yh.Config.Lock() + defer yh.Config.Unlock() + + copyCurrentConfig, err := ygot.DeepCopy(yh.Config.Data) + if err != nil { + return nil, err + } + + newConfig, ok := copyCurrentConfig.(*gnmitargetygot.Gnmitarget) + if !ok { + return nil, fmt.Errorf("Wrong type, exptected: %T, got: %T", (*gnmitargetygot.OpenconfigInterfaces_Interfaces)(nil), copyCurrentConfig) + } + + confSystem := newConfig.GetOrCreateSystem() + if memory := confSystem.GetOrCreateMemory(); memory != nil { memory.GetOrCreateState().Physical = ygot.Uint64(os.GetTotalMemory()) memory.GetOrCreateState().Free = ygot.Uint64(os.GetFreeMemory()) @@ -60,9 +67,16 @@ func updateOrCreateMemory(confSystem *gnmitargetygot.OpenconfigSystem_System, os } //validate struct - if err := confSystem.Validate(); err != nil { - return err + if err := newConfig.Validate(); err != nil { + return nil, err } - return nil + notifications, err := ygot.DiffWithAtomic(yh.Config.Data, newConfig) + if err != nil { + return nil, err + } + + yh.Config.Data = newConfig + + return notifications, nil } diff --git a/examples/example01/handlers/system/motdHandler.go b/examples/example01/handlers/system/motdHandler.go index a9ebcd9fe304efd1681bff2384578e2a7fe6d3c3..19964a2dd33125303c1899783d566d817b24441a 100644 --- a/examples/example01/handlers/system/motdHandler.go +++ b/examples/example01/handlers/system/motdHandler.go @@ -5,6 +5,7 @@ import ( gnmitargetygot "code.fbi.h-da.de/danet/gnmi-target/examples/example01/model" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient" + "code.fbi.h-da.de/danet/gnmi-target/handler" "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" "github.com/sirupsen/logrus" @@ -12,44 +13,35 @@ import ( // MotdHandler is the implementation of a gnmitarget.PathHandler. type MotdHandler struct { - name string - paths map[string]struct{} + handler.DefaultPathHandler osClient osclient.Osclient } func NewMotdHandler() *MotdHandler { return &MotdHandler{ - name: "openconfig-motd-handler", - paths: map[string]struct{}{ - "/system/config/motd-banner": struct{}{}, + DefaultPathHandler: handler.DefaultPathHandler{ + Name: "openconfig-motd-handler", + Paths: map[string]struct{}{ + "/system/config/motd-banner": {}, + }, }, osClient: osclient.NewOsClient(), } } -func (yh *MotdHandler) Name() string { - return yh.name -} - -func (yh *MotdHandler) Paths() map[string]struct{} { - return yh.paths -} - -func (yh *MotdHandler) Init(c ygot.ValidatedGoStruct) error { - config, ok := c.(*gnmitargetygot.Gnmitarget) - if !ok { - return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) - } +func (yh *MotdHandler) Init(config *handler.Config, publishToSubsFunc func([]*gnmi.Notification) error) error { + yh.Config = config + yh.PublishToSubs = publishToSubsFunc - confSystem := config.GetOrCreateSystem() - if err := updateOrCreateMotd(confSystem, yh.osClient); err != nil { + _, err := yh.updateOrCreateMotd(yh.osClient) + if err != nil { return err } return nil } func (yh *MotdHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi.Update) error { - fmt.Println("Update request received for ", yh.name) + fmt.Println("Update request received for ", yh.Name) config, ok := c.(*gnmitargetygot.Gnmitarget) if !ok { return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) @@ -71,19 +63,40 @@ func (yh *MotdHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi.Update) err return nil } -func updateOrCreateMotd(confSystem *gnmitargetygot.OpenconfigSystem_System, os osclient.Osclient) error { +func (yh *MotdHandler) updateOrCreateMotd(os osclient.Osclient) ([]*gnmi.Notification, error) { + yh.Config.Lock() + defer yh.Config.Unlock() + + copyCurrentConfig, err := ygot.DeepCopy(yh.Config.Data) + if err != nil { + return nil, err + } + + newConfig, ok := copyCurrentConfig.(*gnmitargetygot.Gnmitarget) + if !ok { + return nil, fmt.Errorf("Wrong type, exptected: %T, got: %T", (*gnmitargetygot.OpenconfigInterfaces_Interfaces)(nil), copyCurrentConfig) + } + + confSystem := newConfig.GetOrCreateSystem() if config := confSystem.GetOrCreateConfig(); config != nil { motd, err := os.GetMotd() if err != nil { - return err + return nil, err } config.MotdBanner = ygot.String(motd) } //validate struct - if err := confSystem.Validate(); err != nil { - return err + if err := newConfig.Validate(); err != nil { + return nil, err } - return nil + notifications, err := ygot.DiffWithAtomic(yh.Config.Data, newConfig) + if err != nil { + return nil, err + } + + yh.Config.Data = newConfig + + return notifications, nil } diff --git a/examples/example01/handlers/system/stateHandler.go b/examples/example01/handlers/system/stateHandler.go index 46f6f9cf2ba738906341eae2fa5474bfa84abade..bd43316efc5986c508526eeda1aaae1b1d6313e6 100644 --- a/examples/example01/handlers/system/stateHandler.go +++ b/examples/example01/handlers/system/stateHandler.go @@ -6,6 +6,7 @@ import ( gnmitargetygot "code.fbi.h-da.de/danet/gnmi-target/examples/example01/model" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient" + "code.fbi.h-da.de/danet/gnmi-target/handler" "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" gopshost "github.com/shirou/gopsutil/host" @@ -13,53 +14,59 @@ import ( // StateHandler is the implementation of a gnmitarget.PathHandler. type StateHandler struct { - name string - paths map[string]struct{} + handler.DefaultPathHandler osClient osclient.Osclient } func NewStateHandler() *StateHandler { return &StateHandler{ - name: "openconfig-memory-handler", - paths: map[string]struct{}{ - "/system/state": struct{}{}, + DefaultPathHandler: handler.DefaultPathHandler{ + Name: "openconfig-memory-handler", + Paths: map[string]struct{}{ + "/system/state": {}, + }, }, osClient: osclient.NewOsClient(), } } -func (yh *StateHandler) Name() string { - return yh.name -} - -func (yh *StateHandler) Paths() map[string]struct{} { - return yh.paths -} +func (yh *StateHandler) Init(config *handler.Config, publishToSubsFunc func([]*gnmi.Notification) error) error { + yh.Config = config + yh.PublishToSubs = publishToSubsFunc -func (yh *StateHandler) Init(c ygot.ValidatedGoStruct) error { - config, ok := c.(*gnmitargetygot.Gnmitarget) - if !ok { - return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) - } - - confSystem := config.GetOrCreateSystem() - if err := updateOrCreateState(confSystem, yh.osClient); err != nil { + _, err := yh.updateOrCreateState(yh.osClient) + if err != nil { return err } return nil } func (yh *StateHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi.Update) error { - fmt.Println("Update request received for ", yh.name) + fmt.Println("Update request received for ", yh.Name) return nil } -func updateOrCreateState(confSystem *gnmitargetygot.OpenconfigSystem_System, os osclient.Osclient) error { +func (yh *StateHandler) updateOrCreateState(os osclient.Osclient) ([]*gnmi.Notification, error) { + yh.Config.Lock() + defer yh.Config.Unlock() + + copyCurrentConfig, err := ygot.DeepCopy(yh.Config.Data) + if err != nil { + return nil, err + } + + newConfig, ok := copyCurrentConfig.(*gnmitargetygot.Gnmitarget) + if !ok { + return nil, fmt.Errorf("Wrong type, exptected: %T, got: %T", (*gnmitargetygot.OpenconfigInterfaces_Interfaces)(nil), copyCurrentConfig) + } + + confSystem := newConfig.GetOrCreateSystem() + if state := confSystem.GetOrCreateState(); state != nil { state.CurrentDatetime = ygot.String(time.Now().Format(time.RFC3339)) bootTime, err := gopshost.BootTime() if err != nil { - return err + return nil, err } state.BootTime = ygot.Uint64(bootTime) @@ -70,9 +77,16 @@ func updateOrCreateState(confSystem *gnmitargetygot.OpenconfigSystem_System, os } //validate struct - if err := confSystem.Validate(); err != nil { - return err + if err := newConfig.Validate(); err != nil { + return nil, err } - return nil + notifications, err := ygot.DiffWithAtomic(yh.Config.Data, newConfig) + if err != nil { + return nil, err + } + + yh.Config.Data = newConfig + + return notifications, nil } diff --git a/examples/example01/handlers/system/systemHandler.go b/examples/example01/handlers/system/systemHandler.go index 5609ffcdc1951f2845de32443d795bf1bb07cb04..3a32b24947a1333bdcb4a88a2b86e31676d70241 100644 --- a/examples/example01/handlers/system/systemHandler.go +++ b/examples/example01/handlers/system/systemHandler.go @@ -6,58 +6,65 @@ import ( gnmitargetygot "code.fbi.h-da.de/danet/gnmi-target/examples/example01/model" "code.fbi.h-da.de/danet/gnmi-target/examples/example01/osclient" + "code.fbi.h-da.de/danet/gnmi-target/handler" "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" ) // SystemHandler is the implementation of a gnmitarget.PathHandler. type SystemHandler struct { - name string - paths map[string]struct{} + handler.DefaultPathHandler osClient osclient.Osclient } func NewSystemHandler() *SystemHandler { return &SystemHandler{ - name: "openconfig-system-handler", - paths: map[string]struct{}{ - "/system": struct{}{}, + DefaultPathHandler: handler.DefaultPathHandler{ + Name: "openconfig-system-handler", + Paths: map[string]struct{}{ + "/system": {}, + }, }, osClient: osclient.NewOsClient(), } } -func (yh *SystemHandler) Name() string { - return yh.name -} - -func (yh *SystemHandler) Paths() map[string]struct{} { - return yh.paths -} +func (yh *SystemHandler) Init(config *handler.Config, publishToSubsFunc func([]*gnmi.Notification) error) error { + yh.Config = config + yh.PublishToSubs = publishToSubsFunc -func (yh *SystemHandler) Init(c ygot.ValidatedGoStruct) error { - config, ok := c.(*gnmitargetygot.Gnmitarget) - if !ok { - return fmt.Errorf("failed type assertion for config %T", (*gnmitargetygot.Gnmitarget)(nil)) - } - - confSystem := config.GetOrCreateSystem() - if err := updateOrCreateSystem(confSystem, yh.osClient); err != nil { + _, err := yh.updateOrCreateSystem(yh.osClient) + if err != nil { return err } return nil } func (yh *SystemHandler) Update(c ygot.ValidatedGoStruct, jobs []*gnmi.Update) error { - fmt.Println("Update request received for ", yh.name) + fmt.Println("Update request received for ", yh.Name) return nil } -func updateOrCreateSystem(confSystem *gnmitargetygot.OpenconfigSystem_System, os osclient.Osclient) error { +func (yh *SystemHandler) updateOrCreateSystem(os osclient.Osclient) ([]*gnmi.Notification, error) { + yh.Config.Lock() + defer yh.Config.Unlock() + + copyCurrentConfig, err := ygot.DeepCopy(yh.Config.Data) + if err != nil { + return nil, err + } + + newConfig, ok := copyCurrentConfig.(*gnmitargetygot.Gnmitarget) + if !ok { + return nil, fmt.Errorf("Wrong type, exptected: %T, got: %T", (*gnmitargetygot.OpenconfigInterfaces_Interfaces)(nil), copyCurrentConfig) + } + + confSystem := newConfig.GetOrCreateSystem() + if config := confSystem.GetOrCreateConfig(); config != nil { domain, err := os.GetDomainName() if err != nil { - return err + return nil, err } config.DomainName = ygot.String(domain) } @@ -69,9 +76,16 @@ func updateOrCreateSystem(confSystem *gnmitargetygot.OpenconfigSystem_System, os } //validate struct - if err := confSystem.Validate(); err != nil { - return err + if err := newConfig.Validate(); err != nil { + return nil, err } - return nil + notifications, err := ygot.DiffWithAtomic(yh.Config.Data, newConfig) + if err != nil { + return nil, err + } + + yh.Config.Data = newConfig + + return notifications, nil } diff --git a/examples/example01/target.Dockerfile b/examples/example01/target.Dockerfile index a3be6b8a65f7408370c16e1e99304ec54e61b288..b4112efd204057c92b8739ec07d09f142abe39e9 100644 --- a/examples/example01/target.Dockerfile +++ b/examples/example01/target.Dockerfile @@ -1,4 +1,4 @@ -ARG GOLANG_VERSION=1.21 +ARG GOLANG_VERSION=1.22 ARG GITLAB_PROXY ARG BUILDARGS diff --git a/go.mod b/go.mod index 31bbcec33913513d55ecafe512681f5b000fb148..84e3122e9cc4df4245ae027d677a3dd9cb2d40a2 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module code.fbi.h-da.de/danet/gnmi-target -go 1.21 - -toolchain go1.22.1 +go 1.22 require ( github.com/golang/glog v1.2.0 @@ -19,6 +17,7 @@ require ( golang.org/x/net v0.22.0 golang.org/x/sys v0.18.0 google.golang.org/grpc v1.61.1 + google.golang.org/protobuf v1.33.0 ) require ( @@ -47,7 +46,6 @@ require ( golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3ecddbaedf3ff3d20ef7c01cc9ac384d60fa3a0c..f83fbebb444d688be20fd0c9ea3fcfc4392ec16a 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -21,6 +22,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -43,8 +45,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -63,7 +63,9 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -71,7 +73,6 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/openconfig/gnmi v0.10.0 h1:kQEZ/9ek3Vp2Y5IVuV2L/ba8/77TgjdXg505QXvYmg8= github.com/openconfig/gnmi v0.10.0/go.mod h1:Y9os75GmSkhHw2wX8sMsxfI7qRGAEcDh8NTa5a8vj6E= github.com/openconfig/gnmi v0.11.0 h1:H7pLIb/o3xObu3+x0Fv9DCK7TH3FUh7mNwbYe+34hFw= github.com/openconfig/gnmi v0.11.0/go.mod h1:9oJSQPPCpNvfMRj8e4ZoLVAw4wL8HyxXbiDlyuexCGU= @@ -87,14 +88,14 @@ github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6 github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= github.com/protocolbuffers/txtpbfmt v0.0.0-20220608084003-fc78c767cd6a/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -167,8 +168,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -191,8 +190,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -248,12 +245,11 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/handler/handler.go b/handler/handler.go index e2eb191935164ee197d11d15ccd9cae20430b9b9..7ed5fdd6935a81416dac652989ebef4ac394140f 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -1,10 +1,38 @@ package handler import ( + "fmt" + "sync" + "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" ) +// Config holds the current configuration of the device. +// Config is used by the gnmiserver to address GET/SET requests. +// Handlers can use config to e.g. react to internal changes and adjust the +// configuration data accordingly. +type Config struct { + Data ygot.ValidatedGoStruct + mu sync.RWMutex +} + +func (c *Config) Lock() { + c.mu.Lock() +} + +func (c *Config) Unlock() { + c.mu.Unlock() +} + +func (c *Config) RLock() { + c.mu.RLock() +} + +func (c *Config) RUnlock() { + c.mu.RUnlock() +} + // PathHandler handles configuration changes and adjusts the targets config // model accordingly. // @@ -24,9 +52,9 @@ import ( // Therefore the configuration data (containing the new changes) is provided, // aswell as the single changes as gnmi.Update. type PathHandler interface { - Name() string - Paths() map[string]struct{} - Init(ygot.ValidatedGoStruct) error + GetName() string + GetPaths() map[string]struct{} + Init(*Config, func([]*gnmi.Notification) error) error // NOTE: Processing order as defined in the gNMI Spec 3.4: Delete, // Replace, Update // TODO: Add Delete and Replace @@ -41,3 +69,31 @@ type HandlerJob struct { Updates []*gnmi.Update Deletes []*gnmi.Path } + +// DefaultPathHandler should be embeded by all PathHandlers to provide a default +// implementation of the PathHandler interface. Config and the PublishToSubs +// function are provided through the Init() function of the PathHandler. +// Init is called for each handler when the gnmitargets `Start()` +// method is called. +type DefaultPathHandler struct { + Name string + Paths map[string]struct{} + Config *Config + PublishToSubs func([]*gnmi.Notification) error +} + +func (dph *DefaultPathHandler) GetName() string { + return dph.Name +} + +func (dph *DefaultPathHandler) GetPaths() map[string]struct{} { + return dph.Paths +} + +func (dpb *DefaultPathHandler) Init(*Config, func([]*gnmi.Notification) error) error { + return fmt.Errorf("not implemented") +} + +func (dph *DefaultPathHandler) Update(ygot.ValidatedGoStruct, []*gnmi.Update) error { + return fmt.Errorf("not implemented") +} diff --git a/internal/gnmiserver/server.go b/internal/gnmiserver/server.go index e9af0be4178e275aeef533f0e2a97ee9656da1dd..e74732f9a4eb9cf3d796e51c3df72101e1d705b9 100644 --- a/internal/gnmiserver/server.go +++ b/internal/gnmiserver/server.go @@ -21,10 +21,9 @@ import ( "compress/gzip" "encoding/json" "fmt" - "io/ioutil" + "io" "reflect" "strconv" - "sync" "time" "golang.org/x/net/context" @@ -32,15 +31,16 @@ import ( "google.golang.org/grpc/status" log "github.com/golang/glog" - "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" + "google.golang.org/protobuf/proto" "code.fbi.h-da.de/danet/gnmi-target/handler" not "code.fbi.h-da.de/danet/gnmi-target/internal/notifications" dpb "github.com/golang/protobuf/protoc-gen-go/descriptor" + "github.com/openconfig/gnmi/proto/gnmi" pb "github.com/openconfig/gnmi/proto/gnmi" ) @@ -72,17 +72,17 @@ var ( // // // // Do something ... // } + type Server struct { model *Model - config ygot.ValidatedGoStruct - mu sync.RWMutex // mu is the RW lock to protect the access to config + config *handler.Config YangModelChangeDispatcher *not.Dispatcher handlers []handler.PathHandler } // NewServer creates an instance of Server with given json config. -func NewServer(model *Model, config ygot.ValidatedGoStruct, notifications *not.Dispatcher, handlers ...handler.PathHandler) (*Server, error) { +func NewServer(model *Model, config *handler.Config, notifications *not.Dispatcher, handlers ...handler.PathHandler) (*Server, error) { /*rootStruct, err := model.NewConfigStruct(config) if err != nil { return nil, err @@ -104,7 +104,7 @@ func NewServer(model *Model, config ygot.ValidatedGoStruct, notifications *not.D // callback is used to apply a validated new config to the physical device. The // changed values are then sent to YangHandlers to apply the new config values // to the physical device. -func (s *Server) callback(newConfig ygot.ValidatedGoStruct, existingConf ygot.ValidatedGoStruct) error { +func (s *Server) callback(newConfig ygot.ValidatedGoStruct, existingConf ygot.ValidatedGoStruct) ([]*gnmi.Notification, error) { // All applied successfully, so time for finding the diff and report this // Generate gnmi notifications for subscribe configDiff, err := ygot.DiffWithAtomic(existingConf, newConfig) @@ -123,7 +123,7 @@ func (s *Server) callback(newConfig ygot.ValidatedGoStruct, existingConf ygot.Va for _, handler := range s.handlers { handlerJobs, err := checkHandlerPaths(handler, configDiff) if err != nil { - return err + return nil, err } if len(handlerJobs) != 0 { for _, handlerJob := range handlerJobs { @@ -132,20 +132,26 @@ func (s *Server) callback(newConfig ygot.ValidatedGoStruct, existingConf ygot.Va // return err //} if err := handler.Update(newConfig, handlerJob.Updates); err != nil { - return err + return nil, err } } } } - //NOTE: There shoud be a better place for this. - // Run through configDiff and generate Publish events... - for _, specificDiff := range configDiff { + return configDiff, nil +} + +// TODO: This will be moved +func (s *Server) PublishNotificationsToSubscribers(notifications []*gnmi.Notification) error { + for _, specificDiff := range notifications { // First for gnmi Updates updates := specificDiff.GetUpdate() for _, specificUpdate := range updates { - pathString, _ := ygot.PathToString(specificUpdate.Path) + pathString, err := ygot.PathToString(specificUpdate.Path) + if err != nil { + return err + } log.Infof("specificDiff update %s with value of %s", pathString, specificUpdate.Val.String()) // Wrap Update into a notification and ship it off updateNotification := createUpdateNotification(specificUpdate) @@ -156,7 +162,10 @@ func (s *Server) callback(newConfig ygot.ValidatedGoStruct, existingConf ygot.Va deletes := specificDiff.GetDelete() for _, specificDelete := range deletes { - pathString, _ := ygot.PathToString(specificDelete) + pathString, err := ygot.PathToString(specificDelete) + if err != nil { + return err + } log.Infof("specificDiff delete %s ", pathString) // Wrap Update into a notification and ship it off updateNotification := createDeleteNotification(specificDelete) @@ -405,7 +414,7 @@ func getGNMIServiceVersion() (*string, error) { return nil, fmt.Errorf("error in initializing gzip reader: %v", err) } defer r.Close() - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) if err != nil { return nil, fmt.Errorf("error in reading gzip data: %v", err) } @@ -413,10 +422,7 @@ func getGNMIServiceVersion() (*string, error) { 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) - } + ver := proto.GetExtension(desc.Options, pb.E_GnmiService) return ver.(*string), nil } @@ -570,8 +576,8 @@ func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, paths := req.GetPath() notifications := make([]*pb.Notification, len(paths)) - s.mu.RLock() - defer s.mu.RUnlock() + s.config.RLock() + defer s.config.RUnlock() for i, path := range paths { // Get schema node for path from config struct. @@ -582,7 +588,7 @@ func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, if fullPath.GetElem() == nil && fullPath.GetElement() != nil { return nil, status.Error(codes.Unimplemented, "deprecated path element type is unsupported") } - nodes, err := ytypes.GetNode(s.model.schemaTreeRoot, s.config, fullPath) + nodes, err := ytypes.GetNode(s.model.schemaTreeRoot, s.config.Data, fullPath) if len(nodes) == 0 || err != nil || util.IsValueNil(nodes[0].Data) { return nil, status.Errorf(codes.NotFound, "path %v not found: %v", fullPath, err) } @@ -676,11 +682,11 @@ func (s *Server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, // 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() + s.config.Lock() + defer s.config.Unlock() // Obtain current configuration as JSON - jsonTree, err := ygot.ConstructIETFJSON(s.config, &ygot.RFC7951JSONConfig{}) + jsonTree, err := ygot.ConstructIETFJSON(s.config.Data, &ygot.RFC7951JSONConfig{}) if err != nil { msg := fmt.Sprintf("error in constructing IETF JSON tree from config struct: %v", err) log.Error(msg) @@ -717,19 +723,18 @@ func (s *Server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, return nil, status.Error(codes.Internal, err.Error()) } - currentConfig, err := ygot.DeepCopy(s.config) + currentConfig, err := ygot.DeepCopy(s.config.Data) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } // Apply the validated operations to the device. - if s.callback != nil { - if applyErr := s.callback(newConfig, currentConfig.(ygot.ValidatedGoStruct)); applyErr != nil { - if rollbackErr := s.callback(currentConfig.(ygot.ValidatedGoStruct), 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) + diff, applyErr := s.callback(newConfig, currentConfig.(ygot.ValidatedGoStruct)) + if applyErr != nil { + if _, rollbackErr := s.callback(currentConfig.(ygot.ValidatedGoStruct), s.config.Data); 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) } jsonDump, err := json.Marshal(jsonTree) @@ -744,7 +749,16 @@ func (s *Server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, log.Error(msg) return nil, status.Error(codes.Internal, msg) } - s.config = rootStruct + s.config.Data = rootStruct + + // notify subscribers about the changes + err = s.PublishNotificationsToSubscribers(diff) + if err != nil { + msg := fmt.Sprintf("error while publishing config changes to subscribers: %v", err) + log.Error(msg) + return nil, status.Error(codes.Internal, msg) + } + return &pb.SetResponse{ Prefix: req.GetPrefix(), Response: results, @@ -755,7 +769,7 @@ func (s *Server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, // 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) + s.config.Lock() + defer s.config.Unlock() + return fp(&s.config.Data) } diff --git a/internal/gnmiserver/subscribe.go b/internal/gnmiserver/subscribe.go index 4bc3f421a9c3a84cf61f88f9c7b592864caf998d..9a15c9a70edcbb5ced10aaa478daa418fe9be960 100644 --- a/internal/gnmiserver/subscribe.go +++ b/internal/gnmiserver/subscribe.go @@ -37,7 +37,7 @@ func (s *Server) Subscribe(stream gnmi.GNMI_SubscribeServer) error { // NOTE: Using ytypes.GetNode might not be the best solution to // check if a given path exists. Since we do not really care about // the node at all in this case. - if _, err := ytypes.GetNode(s.model.schemaTreeRoot, s.config, sub.GetPath()); err != nil { + if _, err := ytypes.GetNode(s.model.schemaTreeRoot, s.config.Data, sub.GetPath()); err != nil { return status.Error(codes.Internal, fmt.Sprintf("The provided path: %s, does not exist, subscribe not possible.", path.String())) } @@ -112,7 +112,7 @@ func (s *Server) streamOnChangeSubscriptionHandler(notificationSubscriber *notif log.Error(err) return } - node, err := ytypes.GetNode(s.model.schemaTreeRoot, s.config, path) + node, err := ytypes.GetNode(s.model.schemaTreeRoot, s.config.Data, path) if err != nil { log.Error(err) return diff --git a/internal/gnmiserver/util.go b/internal/gnmiserver/util.go index c824f72082fc63ecbf0731b43361a191c2e40314..e45a8ad73ce786c0c600478ce7cbdf8bfb4386a3 100644 --- a/internal/gnmiserver/util.go +++ b/internal/gnmiserver/util.go @@ -136,7 +136,7 @@ func createDeleteNotification(deletedPath *pb.Path) *pb.Notification { func checkHandlerPaths(h handler.PathHandler, diffs []*pb.Notification) ([]*handler.HandlerJob, error) { jobs := make([]*handler.HandlerJob, 0) - for topic, _ := range h.Paths() { + for topic := range h.GetPaths() { for _, diff := range diffs { var updates []*gnmi.Update for _, update := range diff.GetUpdate() { diff --git a/internal/notifications/notifications.go b/internal/notifications/notifications.go index d36f9a50419f8b9904302ffe2703be6b4fc71f4b..0d30b7aea7c1f028a6ad758e5db368f45e5c9248 100644 --- a/internal/notifications/notifications.go +++ b/internal/notifications/notifications.go @@ -123,6 +123,7 @@ func (b *Dispatcher) Publish(stringPath string, msg *gnmi.Notification) { //TODO: handle error } topics, err := pathToStringList(path) + log.Debugf("topics: %v", topics) if err != nil { //TODO: handle error } @@ -130,7 +131,7 @@ func (b *Dispatcher) Publish(stringPath string, msg *gnmi.Notification) { b.mu.RLock() interestedSubscribers := b.topics[topic] b.mu.RUnlock() - log.Debug("checking topic: %s, has %d subscribers\n", topic, len(interestedSubscribers)) + log.Debugf("checking topic: %s, has %d subscribers\n", topic, len(interestedSubscribers)) for _, s := range interestedSubscribers { m := NewInfoChangeMessage(msg, stringPath) if !s.active { @@ -145,7 +146,7 @@ func (b *Dispatcher) Publish(stringPath string, msg *gnmi.Notification) { // TODO: rename and add description func pathToStringList(path *gnmi.Path) ([]string, error) { - paths := make([]string, 0) + paths := []string{"/"} for p := path; len(p.Elem) != 0; p.Elem = p.Elem[:len(p.Elem)-1] { pathStrings, err := ygot.PathToStrings(path) if err != nil { @@ -211,7 +212,7 @@ func (s *Subscriber) GetTopics() []string { s.mutex.RLock() defer s.mutex.RUnlock() topics := []string{} - for topic, _ := range s.topics { + for topic := range s.topics { topics = append(topics, topic) } return topics diff --git a/target.go b/target.go index 04f0b66411fb977f22c8845a2aa8da943a638683..02230ecb6bf697abe9145ad9c476f635e317180a 100644 --- a/target.go +++ b/target.go @@ -77,17 +77,21 @@ func (gt *GnmiTarget) Start(bindAddress string, certFile string, keyFile string, } } - for _, handler := range gt.yangHandlers { - if err := handler.Init(gt.model); err != nil { - log.Fatalf("error in initializing GNMI target through handlers: %v", err) - } + config := &handler.Config{ + Data: gt.model, } - gnmiServer, err := server.NewServer(gnmiModel, gt.model, gt.YangModelChangeDispatcher, gt.yangHandlers...) + gnmiServer, err := server.NewServer(gnmiModel, config, gt.YangModelChangeDispatcher, gt.yangHandlers...) if err != nil { log.Fatalf("error in creating GNMI target: %v", err) } + for _, handler := range gt.yangHandlers { + if err := handler.Init(config, gnmiServer.PublishNotificationsToSubscribers); err != nil { + log.Fatalf("error in initializing GNMI target through handlers: %v", err) + } + } + var grpcServer *grpc.Server if insecure == false {